"use strict";

/* globals define */

define(function () {
  /**
   * A class which appears to act like the Date class with customizable timezone
   * offsets.
   *
   * @param {string} iso8601String An ISO-8601 date/time string including a
   *                               timezone offset.
   */
  function MockDate(iso8601String) {
    // Find the timezone offset (Z or ±hhmm) from the ISO-8601 date string, and
    // then convert that into a number of minutes.
    const parse = /\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(Z|[+-]\d{4})/.exec(
      iso8601String
    );
    const tzOffsetStr = parse[1];
    if (tzOffsetStr == "Z") {
      this._tzOffset = 0;
    } else {
      this._tzOffset =
        parseInt(tzOffsetStr.substring(1, 3)) * 60 +
        parseInt(tzOffsetStr.substring(3));
      if (tzOffsetStr[0] == "-") {
        this._tzOffset = -this._tzOffset;
      }
    }

    // To store the offset, we store both the real time in _realDate and a time
    // that is offset by the tzOffset in _shiftedDate. Only the getUTC* methods
    // should be used on these properties, to avoid problems caused by daylight
    // savings time or other timezone effects. This shifting is always legal
    // because ES6 is specified to assume that leap seconds do not exist, so there
    // are always 60 seconds in a minute.
    this._realDate = new Date(iso8601String);
    this._shiftedDate = new Date(
      this._realDate.getTime() + this._tzOffset * 60 * 1000
    );
  }
  MockDate.prototype = {
    getTimezoneOffset() {
      // This property is reversed from how it's defined in ISO 8601, i.e.,
      // UTC +0100 needs to return -60.
      return -this._tzOffset;
    },
    getTime() {
      return this._realDate.getTime();
    },
  };

  // Provide an implementation of Date methods that will be need in JSMime. For
  // the time being, we only need .get* methods.
  for (const name of Object.getOwnPropertyNames(Date.prototype)) {
    // Only copy getters, not setters or x.toString.
    if (!name.startsWith("get")) {
      continue;
    }
    // No redefining any other names on MockDate.
    if (MockDate.prototype.hasOwnProperty(name)) {
      continue;
    }

    if (name.includes("UTC")) {
      // 'name' is already supposed to be freshly bound per newest ES6 drafts, but
      // current ES6 implementations reuse the bindings. Until implementations
      // catch up, use a new let to bind it freshly.
      const boundName = name;
      Object.defineProperty(MockDate.prototype, name, {
        value(...aArgs) {
          return Date.prototype[boundName].call(this._realDate, aArgs);
        },
      });
    } else {
      const newName = "getUTC" + name.substr(3);
      Object.defineProperty(MockDate.prototype, name, {
        value(...aArgs) {
          return Date.prototype[newName].call(this._shiftedDate, aArgs);
        },
      });
    }
  }

  return MockDate;
});
