if (window.Evri == undefined) window.Evri = {};

Evri.MILLIS_PER_SECOND = 1000;
Evri.MILLIS_PER_MINUTE = Evri.MILLIS_PER_SECOND * 60;
Evri.MILLIS_PER_HOUR = Evri.MILLIS_PER_MINUTE * 60;
Evri.MILLIS_PER_DAY = Evri.MILLIS_PER_HOUR * 24;
Evri.MILLIS_PER_WEEK = Evri.MILLIS_PER_DAY * 7;
Evri.MILLIS_PER_MONTH = Evri.MILLIS_PER_DAY * 30;
Evri.MILLIS_PER_YEAR = Evri.MILLIS_PER_DAY * 365;
Evri.MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];


Evri.Class = {
  create: function(name) {

    this.name = name;

    function klass() {
      this.initialize.apply(this, arguments);
    }
    return klass;
  }
};


Evri.EventDispatcher = {

  buildHandlersMap: function() {
    if (!this.handlers) {
      this.handlers = {};
    }
  },


  addEventHandler: function(type, handler) {
    if (!handler instanceof Function) {
      throw new Error("handler must be a Function");
    }

    this.buildHandlersMap();

    if (!this.handlers[type]) {
      this.handlers[type] = [handler];
    } else {
      this.handlers[type].push(handler);
    }
  },


  dispatchEvent: function(type, args) {
    this.buildHandlersMap();

    if (!this.hasEventHandler(type)) {
      return false;
    }

    var n = this.handlers[type].length;
    for (var i = 0; i < n; i++) {
      this.handlers[type][i].call(null, args);
    }
  },


  hasEventHandler: function(type) {
    return (typeof this.handlers[type] != "undefined");
  },


  removeEventHandler: function(type, handler) {
    this.buildHandlersMap();

    if (!this.hasEventHandler(type)) {
      return false;
    }

    var n = this.handlers[type].length;
    for (var i = 0; i < n; i++) {
      if (this.handlers[type][i] === handler) {
        this.handlers[type].slice(i, 1);
      }
    }
  },


  mixin: function(subject) {
    for (var p in Evri.EventDispatcher) {
      if (p == "mixin") {
        continue;
      }
      subject[p] = Evri.EventDispatcher[p];
    }
  }
};





Evri.Util = {

  /**
   * Converts a Date object into MM, D, YYYY string.
   *
   * @param date The date to format
   */
  dateToString: function(date) {
    return Evri.MONTH_NAMES[date.getMonth()] + " " + date.getDate() + ", " + date.getFullYear();
  },


  /**
   * Returns a string depicting how from from now the date is.
   *
   * @param date The Date to inspect.
   */
  dateAgoToString: function(date) {
    if (date.constructor != Date) {
      throw new Error("Date object is required");
    }
    var string = "some";
    var now = new Date();
    var delta = Math.round(now.getTime() - date.getTime());
    var isPast = delta > 0;
    delta = Math.abs(delta);

    if (delta == 0) {
      return "now";
    } else if (delta > 0 && delta < 5 * Evri.MILLIS_PER_SECOND) {
      return isPast ? "less than 5 seconds ago" : "less than 5 seconds from now";
    } else if (delta >= 5 * Evri.MILLIS_PER_SECOND && delta < 10 * Evri.MILLIS_PER_SECOND) {
      return isPast ? "less than 10 seconds ago" : "less than 10 seconds from now";
    } else if (delta >= 10 * Evri.MILLIS_PER_SECOND && delta < 20 * Evri.MILLIS_PER_SECOND) {
      return isPast ? "less than 20 seconds ago" : "less than 20 seconds from now";
    } else if (delta >= 20 * Evri.MILLIS_PER_SECOND && delta < 40 * Evri.MILLIS_PER_SECOND) {
      return isPast ? "half a minute ago" : "half a minute from now";
    } else if (delta >= 40 * Evri.MILLIS_PER_SECOND && delta < Evri.MILLIS_PER_MINUTE) {
      return isPast ? "less than a minute ago" : "less than a minute from now";
    } else if (delta == Evri.MILLIS_PER_MINUTE) {
      return isPast ? "1 minute ago" : "1 minute from now";
    } else if (delta >= Evri.MILLIS_PER_MINUTE && delta < 45 * Evri.MILLIS_PER_MINUTE) {
      string = Math.round(delta / Evri.MILLIS_PER_MINUTE).toString();
      return  isPast ? string + " minutes ago" : string + " minutes from now";
    } else if (delta >= 45 * Evri.MILLIS_PER_MINUTE && delta < 90 * Evri.MILLIS_PER_MINUTE) {
      return isPast ? "about an hour ago" : "about an hour from now";
    } else if (delta >= 90 * Evri.MILLIS_PER_MINUTE && delta < Evri.MILLIS_PER_DAY) {
      string = Math.round(delta / Evri.MILLIS_PER_HOUR).toString();
      return  isPast ? string + " hours ago" : string + " hours from now";
    } else if (delta >= Evri.MILLIS_PER_DAY && delta < 2 * Evri.MILLIS_PER_DAY) {
      return isPast ? "1 day ago" : "1 day from now";
    } else if (delta >= 2 * Evri.MILLIS_PER_DAY && delta < Evri.MILLIS_PER_MONTH) {
      string = Math.round(delta / Evri.MILLIS_PER_DAY).toString();
      return isPast ? string + " days ago" : string + " days from now";
    } else if (delta >= Evri.MILLIS_PER_MONTH && delta < 2 * Evri.MILLIS_PER_MONTH) {
      return isPast ? "about 1 month ago" : "about 1 month from now";
    } else if (delta >= 2 * Evri.MILLIS_PER_MONTH && delta < Evri.MILLIS_PER_YEAR) {
      string = Math.round(delta / Evri.MILLIS_PER_MONTH).toString();
      return  isPast ? string + " months ago" : string + " months from now";
    } else if (delta >= Evri.MILLIS_PER_HOUR && delta < 2 * Evri.MILLIS_PER_YEAR) {
      return isPast ? "about 1 year ago" : "about 1 year from now";
    } else {
      string = Math.round(delta / Evri.MILLIS_PER_YEAR).toString();
      return isPast ? string + " years ago" : string + " years from now";
    }
  },


  /**
   * Converts a global point to local coordinates.
   *
   * @param point A point (x, y) in the global space
   * @param element The coordinate system to convert to
   */
  globalToLocal: function(point, element) {
    var localPoint = {x: 0, y: 0};
    var offset = jQuery(element).offset();
    localPoint.x = point.x - offset.left;
    localPoint.y = point.y - offset.top;

    return localPoint;
  },



  /**
   * Converts a local point to global coordinates.
   *
   * @param point A point (x, y) in the local space
   * @param element The coordinate system to convert from
   */
  localToGlobal: function(point, element) {
    var globalPoint = {x: 0, y: 0};
    var offset = jQuery(element).offset();
    globalPoint.x = point.x + offset.left;
    globalPoint.y = point.y + offset.top;

    return globalPoint;
  },


  /**
   * Escapes a string, converting brackets.
   *
   * @param string The HTML string to escape
   */
  htmlEscape: function(string) {
    return string.toString().replace(/</,'&lt;').replace(/>/,'&gt;');
  },


  /**
   * Converts a query string (or any such name/value pairs) into an
   * object map.
   *
   * @param queryString The string to convert (?example=true&doc=true)
   */
  paramsToMap: function(queryString) {
    var string = queryString.replace(/^\?/, "");
    var pairs = string.split("&");
    var params = {};
    for (var i = 0; i < pairs.length; i++) {
      var param = pairs[i].split("=", 2);
      params[param[0]] = param[1];
    }

    return params;
  },


  /**
   * Converts the given array to a serial string (separated by commas).
   *
   * @param array The array to iterate over
   * @param property A property to call on each element of the array
   */
  toSentence: function(array, property) {
    var sentence = "";
    var items = [];

    if (property) {
      var n = array.length;
      for (var i = 0; i < n; i++) {
        items.push(array[i][property]);
      }
    } else {
      items = array;
    }

    if (items.length == 1) {
      sentence = items[0];
    } else if (items.length == 2) {
      sentence = items.join(" and ");
    } else if (items.length > 2) {
      var last = items.pop();
      sentence = items.join(", ") + ", and " + last;
    }

    return sentence;
  }
};
