  (function() {
  // Establish the root mraidbridge object.
  var mraidbridge = window.mraidbridge = {};
  // Listeners for bridge events.
  var listeners = {};
  // Queue to track pending calls to the native SDK.
  var nativeCallQueue = [];
  // Whether a native call is currently in progress.
  var nativeCallInFlight = false;
  //////////////////////////////////////////////////////////////////////////////////////////////////
  mraidbridge.windowLoaded = false;
  window.addEventListener('load', function(e) { mraidbridge.windowLoaded = true });
  mraidbridge.fireReadyEvent = function() {
    mraidbridge.fireEvent('ready');
  };
  mraidbridge.fireChangeEvent = function(properties) {
    mraidbridge.fireEvent('change', properties);
  };
  mraidbridge.fireErrorEvent = function(message, action) {
    mraidbridge.fireEvent('error', message, action);
  };
  mraidbridge.fireEvent = function(type) {
  console.log("mraidBridge: firing event of type: " + type);
    var ls = listeners[type];
    if (ls) {
      var args = Array.prototype.slice.call(arguments);
      args.shift();
        console.log("mraidBridge: firing event of type: " + type + " with args - " + JSON.stringify(args));
      var l = ls.length;
      for (var i = 0; i < l; i++) {
        ls[i].apply(null, args);
      }
    }
  };
  mraidbridge.nativeCallComplete = function(command) {
  console.log("mraidbridge: nativeCallComplete: " + command);
    console.log("native Call complete: " + command);
    if (nativeCallQueue.length === 0) {
      nativeCallInFlight = false;
      return;
    }

    var nextCall = nativeCallQueue.pop();
    window.location = nextCall;
  };
  mraidbridge.executeNativeCall = function(command) {
    if (!mraidbridge.windowLoaded) {
      console.log("rejecting native call, window onload has not been invoked");
      return;
    }
    console.log("mraidbridge: executeNativeCall: " + command);
    //if (iamraidnativecallback !== undefined) {
    //    var payload = {command: command, args: [...arguments]};
    //    iamraidnativecallback.onMraidCommand(JSON.stringify(payload));
    //} else {
        var call = 'mraid://' + command;
        var key, value;
        var isFirstArgument = true;
        for (var i = 1; i < arguments.length; i += 2) {
          key = arguments[i];
          value = arguments[i + 1];
          if (value === null) continue;
          if (isFirstArgument) {
            call += '?';
            isFirstArgument = false;
          } else {
            call += '&';
          }
          call += encodeURIComponent(key) + '=' + encodeURIComponent(value);
        }
        if (nativeCallInFlight) {
          nativeCallQueue.push(call);
        } else {
          nativeCallInFlight = true;
          //setTimeout(function() { window.location = call; }, 125);
          window.location = call;
        }
    //}
  };
  //////////////////////////////////////////////////////////////////////////////////////////////////
  mraidbridge.addEventListener = function(event, listener) {
  console.log("mraidbridge: addEventListener: " + listener);
    var eventListeners;
    listeners[event] = listeners[event] || [];
    eventListeners = listeners[event];
    for (var l in eventListeners) {
      // LoadListener already registered, so no need to add it.
      if (listener === l) return;
    }
    eventListeners.push(listener);
  };
  mraidbridge.removeEventListener = function(event, listener) {
  console.log("mraidbridge: removeEventListener: " + listener);
    if (listeners.hasOwnProperty(event)) {
      var eventListeners = listeners[event];
      if (eventListeners) {
        var idx = eventListeners.indexOf(listener);
        if (idx !== -1) {
          eventListeners.splice(idx, 1);
        }
      }
    }
  };
  }());
  (function() {
    console.log("mraidbridge: initializing mraid");
  var mraid = window.mraid = {};
  var bridge = window.mraidbridge;
              // Constants. ////////////////////////////////////////////////////////////////////////////////////
              var VERSION = mraid.VERSION = '2.0';
              var STATES = mraid.STATES = {
    LOADING: 'loading',     // Initial state.
    DEFAULT: 'default',
    EXPANDED: 'expanded',
    HIDDEN: 'hidden',
    RESIZED: 'resized'
  };
              var EVENTS = mraid.EVENTS = {
    ERROR: 'error',
    INFO: 'info',
    READY: 'ready',
    STATECHANGE: 'stateChange',
    VIEWABLECHANGE: 'viewableChange',
    SIZECHANGE: 'sizeChange'
  };
              var PLACEMENT_TYPES = mraid.PLACEMENT_TYPES = {
    UNKNOWN: 'unknown',
    INLINE: 'inline',
    INTERSTITIAL: 'interstitial'
  };
              // External MRAID state: may be directly or indirectly modified by the ad JS. ////////////////////
              // Properties which define the behavior of an expandable ad.
  var expandProperties = {
    width: -1,
    height: -1,
    useCustomClose: false,
    isModal: true,
    lockOrientation: false
  };
  var hasSetCustomSize = false;
  var hasSetCreativeSize = false;
  var hasSetCreativeOffset = false;
  var hasSetCustomClose = false;
  var listeners = {};
  // Internal MRAID state. Modified by the native SDK. /////////////////////////////////////////////
  var state = STATES.LOADING;
  var isViewable = false;
  var screenSize = { width: -1, height: -1 };
  var maxSize = { width: -1, height: -1 };
  var adSize = { width: -1, height: -1 };
   var currentPosition = {
      x:0,
      y:0,
      width:0,
      height:0
   };
    var mraidDefaultPosition = {
      x:0,
      y:0,
      width:0,
      height:0
   };
  // Properties which define the behavior of an resized ad.
   var resizeProperties = {
       width:-1,
       height:-1,
       customClosePosition:'top-right',
       offsetX:0,
       offsetY:0,
       allowOffscreen:true
   };
  var orientationProperties = {
    allowOrientationChange:true,
    forceOrientation:'none'
  };
  var placementType = PLACEMENT_TYPES.UNKNOWN;
  var supports = {
    sms: false,
    tel: false,
    calendar: false,
    storePicture: false,
    inlineVideo: false
  };
              //////////////////////////////////////////////////////////////////////////////////////////////////
  var EventListeners = function(event) {
    this.event = event;
    this.count = 0;
    var listeners = {};
    this.add = function(func) {
      var id = String(func);
      if (!listeners[id]) {
        listeners[id] = func;
        this.count++;
      }
    };
    this.remove = function(func) {
      var id = String(func);
      if (listeners[id]) {
        listeners[id] = null;
        delete listeners[id];
        this.count--;
        return true;
      } else {
        return false;
      }
    };
    this.removeAll = function() {
      for (var id in listeners) {
        if (listeners.hasOwnProperty(id)) this.remove(listeners[id]);
      }
    };
    this.broadcast = function(args) {
      var localListners = {}
      for (var id in listeners) localListners[id] = listeners[id]
      for (var id in localListners) {
          if (localListners.hasOwnProperty(id)) localListners[id].apply({}, args);
      }
    };
    this.toString = function() {
      var out = [event, ':'];
      for (var id in listeners) {
        if (listeners.hasOwnProperty(id)) out.push('|', id, '|');
      }
      return out.join('');
    };
  };
  var broadcastEvent = function() {
    var args = new Array(arguments.length);
    var l = arguments.length;
    for (var i = 0; i < l; i++) args[i] = arguments[i];
    var event = args.shift();
    console.log(`listeners: ${JSON.stringify(listeners)} event: ${JSON.stringify(event)}`)
    if (listeners[event]) {
        console.log(`broadcastEvent ${event} with args - ${JSON.stringify(args)}`)
        listeners[event].broadcast(args);
     } else {
        console.log(`broadcastEvent not sent`)
     }
  };
  var contains = function(value, array) {
    for (var i in array) {
      if (array[i] === value) return true;
    }
    return false;
  };
  var clone = function(obj) {
    if (obj === null) return null;
    var f = function() {};
    f.prototype = obj;
    return new f();
  };
  var stringify = function(obj) {
    if (typeof obj === 'object') {
      var out = [];
      if (obj.push) {
        // Array.
        for (var p in obj) out.push(obj[p]);
        return '[' + out.join(',') + ']';
      } else {
        // Other object.
        for (var p in obj) out.push("'" + p + "': " + obj[p]);
        return '{' + out.join(',') + '}';
      }
    } else return String(obj);
  };
  // Functions that will be invoked by the native SDK whenever a 'change' event occurs.
  var changeHandlers = {
    state: function(val) {
      if (state === STATES.LOADING) {
        broadcastEvent(EVENTS.INFO, 'Native SDK initialized.');
      }
      state = val;
      broadcastEvent(EVENTS.INFO, 'Set state to ' + stringify(val));
      broadcastEvent(EVENTS.STATECHANGE, state);
    },
    viewable: function(val) {
      isViewable = val;
      broadcastEvent(EVENTS.INFO, 'Set isViewable to ' + stringify(val));
      broadcastEvent(EVENTS.VIEWABLECHANGE, isViewable);
    },
    placementType: function(val) {
      broadcastEvent(EVENTS.INFO, 'Set placementType to ' + stringify(val));
      placementType = val;
    },
    adSize: function(val) {
    console.log(`adSize change to ${JSON.stringify(val)}`);
      for (var key in val) {
        if (val.hasOwnProperty(key)) adSize[key] = val[key];
      }
      if (!hasSetCustomSize) {
        console.log(`sizeChange broadcast with ${JSON.stringify(adSize)}`);
        broadcastEvent(EVENTS.SIZECHANGE, adSize['width'], adSize['height']);
      }
    },
    screenSize: function(val) {
      broadcastEvent(EVENTS.INFO, 'Set screenSize to ' + stringify(val));
      for (var key in val) {
        if (val.hasOwnProperty(key)) screenSize[key] = val[key];
      }
      if (!hasSetCustomSize) {
        expandProperties['width'] = screenSize['width'];
        expandProperties['height'] = screenSize['height'];
      }
    },
    maxSize: function(val) {
      broadcastEvent(EVENTS.INFO, 'Set maxSize to ' + stringify(val));
      for (var key in val) {
        if (val.hasOwnProperty(key)) maxSize[key] = val[key];
      }
    },
    currentPosition: function(val) {
      broadcastEvent(EVENTS.INFO, 'Set currentPosition to ' + stringify(val));
      for (var key in val) {
        if (val.hasOwnProperty(key)) currentPosition[key] = val[key];
      }

      broadcastEvent(EVENTS.SIZECHANGE, val.width, val.height);
    },
    expandProperties: function(val) {
      broadcastEvent(EVENTS.INFO, 'Merging expandProperties with ' + stringify(val));
      for (var key in val) {
        if (val.hasOwnProperty(key)) expandProperties[key] = val[key];
      }
    },
    resizeProperties: function(val) {
      broadcastEvent(EVENTS.INFO, 'Merging resizeProperties with ' + stringify(val));
      for (var key in val) {
        if (val.hasOwnProperty(key)) resizeProperties[key] = val[key];
      }
    },
    orientationProperties: function(val) {
      broadcastEvent(EVENTS.INFO, 'Merging orientationProperties with ' + stringify(val));
      for (var key in val) {
        if (val.hasOwnProperty(key)) orientationProperties[key] = val[key];
      }
    },
    supports: function(val) {
      broadcastEvent(EVENTS.INFO, 'Set supports to ' + stringify(val));
        supports = val;
    }
  };
  var validate = function(obj, validators, action, merge) {
    if (!merge) {
      // Check to see if any required properties are missing.
      if (obj === null) {
        broadcastEvent(EVENTS.ERROR, 'Required object not provided.', action);
        return false;
      } else {
        for (var i in validators) {
          if (validators.hasOwnProperty(i) && obj[i] === undefined) {
            broadcastEvent(EVENTS.ERROR, 'Object is missing required property: ' + i + '.', action);
            return false;
          }
        }
      }
    }
    for (var prop in obj) {
      var validator = validators[prop];
      var value = obj[prop];
      if (validator && !validator(value)) {
        // Failed validation.
        broadcastEvent(EVENTS.ERROR, 'Value of property ' + prop + ' is invalid.',
          action);
        return false;
      }
    }
    return true;
  };
  var expandPropertyValidators = {
    width: function(v) { return !isNaN(v) && v >= 0; },
    height: function(v) { return !isNaN(v) && v >= 0; },
    useCustomClose: function(v) { return (typeof v === 'boolean'); },
    lockOrientation: function(v) { return (typeof v === 'boolean'); }
  };
  var resizePropertyValidators = {
    width: function(v) { return !isNaN(v) && v >= 0; },
    height: function(v) { return !isNaN(v) && v >= 0; },
    offsetX: function(v) { return !isNaN(v); },
    offsetY: function(v) { return !isNaN(v); },
    allowOffscreen: function(v) { return (typeof v === 'boolean'); }
  };
  var orientationPropertyValidators = {
    allowOrientationChange: function(v) { return (typeof v === 'boolean'); },
    forceOrientation: function(v) { return !isNaN(v)}
  };
              //////////////////////////////////////////////////////////////////////////////////////////////////
  bridge.addEventListener('change', function(properties) {
    for (var p in properties) {
      if (properties.hasOwnProperty(p)) {
        var handler = changeHandlers[p];
        handler(properties[p]);
      }
    }
  });
  bridge.addEventListener('error', function(message, action) {
    broadcastEvent(EVENTS.ERROR, message, action);
  });
  bridge.addEventListener('ready', function() {
    broadcastEvent(EVENTS.READY);
  });
              //////////////////////////////////////////////////////////////////////////////////////////////////
  mraid.addEventListener = function(event, listener) {
  console.log(`addEventListener ${event}`)
   if (!event || !listener) {
      broadcastEvent(EVENTS.ERROR, 'Both event and listener are required.', 'addEventListener');
    } else if (!contains(event, EVENTS)) {
      broadcastEvent(EVENTS.ERROR, 'Unknown MRAID event: ' + event, 'addEventListener');
    } else {
      if (!listeners[event]) listeners[event] = new EventListeners(event);
      listeners[event].add(listener);
    }
  };
  mraid.close = function() {
    if (state === STATES.HIDDEN) {
      broadcastEvent(EVENTS.ERROR, 'Ad cannot be closed when it is already hidden.',
        'close');
    } else bridge.executeNativeCall('close');
  };
  mraid.expand = function(URL) {
    if (state !== STATES.DEFAULT) {
      broadcastEvent(EVENTS.ERROR, 'Ad can only be expanded from the default state.', 'expand');
    } else {
      var args = ['expand'];
                  if (mraid.getHasSetCustomClose()) {
        args = args.concat(['shouldUseCustomClose', expandProperties.useCustomClose ? 'true' : 'false']);
      }
                  if (mraid.getHasSetCustomSize()) {
        if (expandProperties.width >= 0 && expandProperties.height >= 0) {
          args = args.concat(['w', expandProperties.width, 'h', expandProperties.height]);
        }
      }
                  if (typeof expandProperties.lockOrientation !== 'undefined') {
        args = args.concat(['lockOrientation', expandProperties.lockOrientation]);
      }
                  if (URL) {
        args = args.concat(['url', URL]);
      }
                  bridge.executeNativeCall.apply(this, args);
    }
  };
  mraid.getHasSetCustomClose = function() {
    console.log("mraidbridge: getHasSetCustomClose");
      return hasSetCustomClose;
  };
  mraid.getHasSetCustomSize = function() {
  console.log("mraidbridge: getHasSetCustomSize");
      return hasSetCustomSize;
  };
  mraid.getExpandProperties = function() {
  console.log("mraidbridge: getExpandProperties");
    var properties = {
      width: expandProperties.width,
      height: expandProperties.height,
      useCustomClose: expandProperties.useCustomClose,
      isModal: expandProperties.isModal
    };
    return properties;
  };
  mraid.getPlacementType = function() {
  console.log("mraidbridge: getPlacementType");
    return placementType;
  };
  mraid.getState = function() {
  console.log("mraidbridge: getState - " + state);
    return state;
  };
  mraid.getVersion = function() {
    return mraid.VERSION;
  };
  mraid.isViewable = function() {
  console.log("mraidbridge: isViewable returns " + isViewable);
    return isViewable;
  };
  mraid.open = function(URL) {
    if (!URL) broadcastEvent(EVENTS.ERROR, 'URL is required.', 'open');
    else bridge.executeNativeCall('open', 'url', URL);
  };
  mraid.removeEventListener = function(event, listener) {
    if (!event) broadcastEvent(EVENTS.ERROR, 'Event is required.', 'removeEventListener');
    else {
      if (listener && (!listeners[event] || !listeners[event].remove(listener))) {
        broadcastEvent(EVENTS.ERROR, 'LoadListener not currently registered for event.',
          'removeEventListener');
        return;
      } else if (listeners[event]) listeners[event].removeAll();
                  if (listeners[event] && listeners[event].count === 0) {
        listeners[event] = null;
        delete listeners[event];
      }
    }
  };
  mraid.setExpandProperties = function(properties) {
    if (validate(properties, expandPropertyValidators, 'setExpandProperties', true)) {
      if (properties.hasOwnProperty('width') || properties.hasOwnProperty('height')) {
        hasSetCustomSize = true;
      }
                  if (properties.hasOwnProperty('useCustomClose')) hasSetCustomClose = true;
                  var desiredProperties = ['width', 'height', 'useCustomClose', 'lockOrientation'];
      var length = desiredProperties.length;
      for (var i = 0; i < length; i++) {
        var propname = desiredProperties[i];
        if (properties.hasOwnProperty(propname)) expandProperties[propname] = properties[propname];
      }
    }
  };
  mraid.useCustomClose = function(shouldUseCustomClose) {
    expandProperties.useCustomClose = shouldUseCustomClose;
    hasSetCustomClose = true;
    bridge.executeNativeCall('usecustomclose', 'shouldUseCustomClose', shouldUseCustomClose);
  };
              // MRAID 2.0 APIs ////////////////////////////////////////////////////////////////////////////////

  mraid.createCalendarEvent = function(parameters) {
    CalendarEventParser.initialize(parameters);
    if (CalendarEventParser.parse()) {
      bridge.executeNativeCall.apply(this, CalendarEventParser.arguments);
    } else {
      broadcastEvent(EVENTS.ERROR, CalendarEventParser.errors[0], 'createCalendarEvent');
    }
  };
  mraid.supports = function(feature) {
    return supports[feature];
  };
  mraid.playVideo = function(uri) {
    if (!mraid.isViewable()) {
      broadcastEvent(EVENTS.ERROR, 'playVideo cannot be called until the ad is viewable', 'playVideo');
      return;
    }
                if (!uri) {
      broadcastEvent(EVENTS.ERROR, 'playVideo must be called with a valid URI', 'playVideo');
    } else {
      bridge.executeNativeCall.apply(this, ['playVideo', 'uri', uri]);
    }
  };
  mraid.storePicture = function(uri) {
    if (!mraid.isViewable()) {
      broadcastEvent(EVENTS.ERROR, 'storePicture cannot be called until the ad is viewable', 'storePicture');
      return;
    }
    if (!uri) {
      broadcastEvent(EVENTS.ERROR, 'storePicture must be called with a valid URI', 'storePicture');
    } else {
      bridge.executeNativeCall.apply(this, ['storePicture', 'uri', uri]);
    }
  };
  mraid.resize = function() {
    if (state !== STATES.DEFAULT) {
      broadcastEvent(EVENTS.ERROR, 'Ad can only be expanded from the default state.', 'resize');
    } else {
      var args = ['resize'];
      if (mraid.getHasSetCreativeSize()) {
        if (resizeProperties.width >= 0 && resizeProperties.height >= 0) {
          args = args.concat(['w', resizeProperties.width, 'h', resizeProperties.height]);
        }
      }
      if (mraid.getHasSetCreativeOffset()) {
        args = args.concat(['offsetX', resizeProperties.offsetX, 'offsetY', resizeProperties.offsetY]);
      }
      if (typeof resizeProperties.allowOffscreen !== 'undefined') {
        args = args.concat(['allowOffscreen', resizeProperties.allowOffscreen]);
      }
      if (typeof resizeProperties.customClosePosition !== 'undefined') {
        args = args.concat(['customClosePosition', resizeProperties.customClosePosition]);
      }
                  bridge.executeNativeCall.apply(this, args);
    }
  };
  mraid.getResizeProperties = function() {

    var properties = {
      width: resizeProperties.width,
      height: resizeProperties.height,
      customClosePosition: resizeProperties.customClosePosition,
      offsetX: resizeProperties.offsetX,
      offsetY: resizeProperties.offsetY,
      allowOffscreen: resizeProperties.allowOffscreen
    };
    return properties;
  };
  mraid.setResizeProperties = function(properties) {
    if (validate(properties, resizePropertyValidators, 'setResizeProperties', true)) {
      if (properties.hasOwnProperty('width') || properties.hasOwnProperty('height')) {
        hasSetCreativeSize = true;
      }
      if (properties.hasOwnProperty('offsetX') || properties.hasOwnProperty('offsetY')) {
        hasSetCreativeOffset = true;
      }
      if (properties.hasOwnProperty('useCustomClose')) hasSetCustomClose = true;
      var desiredProperties = ['width', 'height', 'offsetX', 'offsetY', 'customClosePosition', 'allowOffscreen'];
      var length = desiredProperties.length;
      for (var i = 0; i < length; i++) {
        var propname = desiredProperties[i];
        if (properties.hasOwnProperty(propname)) resizeProperties[propname] = properties[propname];
      }
    }
  };

  mraid.setOrientationProperties = function (properties) {
    var args = ['setOrientationProperties'];
  //  if (validate(properties, orientationPropertyValidators, 'setOrientationProperties', true)) {
      var desiredProperties = ['allowOrientationChange', 'forceOrientation'];
      var length = desiredProperties.length;
      for (var i = 0; i < length; i++) {
        var propname = desiredProperties[i];
        if (properties.hasOwnProperty(propname)) orientationProperties[propname] = properties[propname];
        args = args.concat([propname, orientationProperties[propname]]);
      }
  //  }

    bridge.executeNativeCall.apply(this, args);
  };

  mraid.getHasSetCreativeSize = function() {
      return hasSetCreativeSize;
  };

  mraid.getHasSetCreativeOffset = function() {
      return hasSetCreativeOffset;
  };

  mraid.getOrientationProperties = function () {
      return clone(orientationProperties);
  };

  mraid.getCurrentPosition = function () {
    return clone(currentPosition);
  };

  mraid.getDefaultPosition = function() {
    return clone(mraidDefaultPosition);
  };

  mraid.getMaxSize = function() {
    console.log(`getMaxSize ${JSON.stringify(maxSize)}`)
    return clone(maxSize);
  };

  mraid.getScreenSize = function() {
    return clone(screenSize);
  };

  var CalendarEventParser = {
    initialize: function(parameters) {
      this.parameters = parameters;
      this.errors = [];
      this.arguments = ['createCalendarEvent'];
    },
    parse: function() {
      if (!this.parameters) {
        this.errors.push('The object passed to createCalendarEvent cannot be null.');
      } else {
        this.parseDescription();
        this.parseLocation();
        this.parseSummary();
        this.parseStartAndEndDates();
        this.parseReminder();
        this.parseRecurrence();
        this.parseTransparency();
      }
      var errorCount = this.errors.length;
      if (errorCount) {
        this.arguments.length = 0;
      }
      return (errorCount === 0);
    },
    parseDescription: function() {
      this._processStringValue('description');
    },
    parseLocation: function() {
      this._processStringValue('location');
    },
    parseSummary: function() {
      this._processStringValue('summary');
    },
    parseStartAndEndDates: function() {
      this._processDateValue('start');
      this._processDateValue('end');
    },
    parseReminder: function() {
      var reminder = this._getParameter('reminder');
      if (!reminder) {
        return;
      }
      if (reminder < 0) {
        this.arguments.push('relativeReminder');
        this.arguments.push(parseInt(reminder) / 1000);
      } else {
        this.arguments.push('absoluteReminder');
        this.arguments.push(reminder);
      }
    },
    parseRecurrence: function() {
      var recurrenceDict = this._getParameter('recurrence');
      if (!recurrenceDict) {
        return;
      }
      this.parseRecurrenceInterval(recurrenceDict);
      this.parseRecurrenceFrequency(recurrenceDict);
      this.parseRecurrenceEndDate(recurrenceDict);
      this.parseRecurrenceArrayValue(recurrenceDict, 'daysInWeek');
      this.parseRecurrenceArrayValue(recurrenceDict, 'daysInMonth');
      this.parseRecurrenceArrayValue(recurrenceDict, 'daysInYear');
      this.parseRecurrenceArrayValue(recurrenceDict, 'monthsInYear');
    },
    parseTransparency: function() {
      var validValues = ['opaque', 'transparent'];
                  if (this.parameters.hasOwnProperty('transparency')) {
        var transparency = this.parameters['transparency'];
        if (contains(transparency, validValues)) {
          this.arguments.push('transparency');
          this.arguments.push(transparency);
        } else {
          this.errors.push('transparency must be opaque or transparent');
        }
      }
    },
    parseRecurrenceArrayValue: function(recurrenceDict, kind) {
      if (recurrenceDict.hasOwnProperty(kind)) {
        var array = recurrenceDict[kind];
        if (!array || !(array instanceof Array)) {
          this.errors.push(kind + ' must be an array.');
        } else {
          var arrayStr = array.join(',');
          this.arguments.push(kind);
          this.arguments.push(arrayStr);
        }
      }
    },
    parseRecurrenceInterval: function(recurrenceDict) {
      if (recurrenceDict.hasOwnProperty('interval')) {
        var interval = recurrenceDict['interval'];
        if (!interval) {
          this.errors.push('Recurrence interval cannot be null.');
        } else {
          this.arguments.push('interval');
          this.arguments.push(interval);
        }
      } else {
        // If a recurrence rule was specified without an interval, use a default value of 1.
        this.arguments.push('interval');
        this.arguments.push(1);
      }
    },
    parseRecurrenceFrequency: function(recurrenceDict) {
      if (recurrenceDict.hasOwnProperty('frequency')) {
        var frequency = recurrenceDict['frequency'];
        var validFrequencies = ['daily', 'weekly', 'monthly', 'yearly'];
        if (contains(frequency, validFrequencies)) {
          this.arguments.push('frequency');
          this.arguments.push(frequency);
        } else {
          this.errors.push("Recurrence frequency must be one of: 'daily, 'weekly', 'monthly', 'yearly'.");
        }
      }
    },
    parseRecurrenceEndDate: function(recurrenceDict) {
      var expires = recurrenceDict['expires'];
                  if (!expires) {
        return;
      }
                  this.arguments.push('expires');
      this.arguments.push(expires);
    },
    _getParameter: function(key) {
      if (this.parameters.hasOwnProperty(key)) {
        return this.parameters[key];
      }
                  return null;
    },
    _processStringValue: function(kind) {
      if (this.parameters.hasOwnProperty(kind)) {
        var value = this.parameters[kind];
        this.arguments.push(kind);
        this.arguments.push(value);
      }
    },
    _processDateValue: function(kind) {
      if (this.parameters.hasOwnProperty(kind)) {
        var dateString = this._getParameter(kind);
        this.arguments.push(kind);
        this.arguments.push(dateString);
      }
    }
  };
  }());
