var DateFormat = {};

(function($) {
  var daysInWeek          = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  var shortDaysInWeek     = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  var shortMonthsInYear   = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                              'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  var longMonthsInYear    = ['January', 'February', 'March', 'April', 'May', 'June',
                              'July', 'August', 'September', 'October', 'November', 'December'];
  var shortMonthsToNumber = { 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
                              'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12' };

  var YYYYMMDD_MATCHER = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/;

  $.format = (function() {
    function numberToLongDay(value) {
      // 0 to Sunday
      // 1 to Monday
      return daysInWeek[parseInt(value, 10)] || value;
    }

    function numberToShortDay(value) {
      // 0 to Sun
      // 1 to Mon
      return shortDaysInWeek[parseInt(value, 10)] || value;
    }

    function numberToShortMonth(value) {
      // 1 to Jan
      // 2 to Feb
      var monthArrayIndex = parseInt(value, 10) - 1;
      return shortMonthsInYear[monthArrayIndex] || value;
    }

    function numberToLongMonth(value) {
      // 1 to January
      // 2 to February
      var monthArrayIndex = parseInt(value, 10) - 1;
      return longMonthsInYear[monthArrayIndex] || value;
    }

    function shortMonthToNumber(value) {
      // Jan to 01
      // Feb to 02
      return shortMonthsToNumber[value] || value;
    }

    function parseTime(value) {
      // 10:54:50.546
      // => hour: 10, minute: 54, second: 50, millis: 546
      // 10:54:50
      // => hour: 10, minute: 54, second: 50, millis: ''
      var time = value,
          hour,
          minute,
          second,
          millis = '',
          delimited,
          timeArray;

      if(time.indexOf('.') !== -1) {
        delimited = time.split('.');
        // split time and milliseconds
        time   = delimited[0];
        millis = delimited[delimited.length - 1];
      }

      timeArray = time.split(':');

      if(timeArray.length === 3) {
        hour   = timeArray[0];
        minute = timeArray[1];
        // '20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
        // => 20
        // '20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
        // => 20
        second = timeArray[2].replace(/\s.+/, '').replace(/[a-z]/gi, '');
        // '01:10:20 GMT-0200 (BRST)'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
        // => 01:10:20
        // '01:10:20Z'.replace(/\s.+/, '').replace(/[a-z]/gi, '');
        // => 01:10:20
        time = time.replace(/\s.+/, '').replace(/[a-z]/gi, '');
        return {
          time:    time,
          hour:    hour,
          minute:  minute,
          second:  second,
          millis:  millis
        };
      }

      return { time : '', hour : '', minute : '', second : '', millis : '' };
    }


    function padding(value, length) {
      var paddingCount = length - String(value).length;
      for(var i = 0; i < paddingCount; i++) {
        value = '0' + value;
      }
      return value;
    }

    return {

      parseDate: function(value) {
        var values,
            subValues;

        var parsedDate = {
          date:       null,
          year:       null,
          month:      null,
          dayOfMonth: null,
          dayOfWeek:  null,
          time:       null
        };

        if(typeof value == 'number') {
          return this.parseDate(new Date(value));
        } else if(typeof value.getFullYear == 'function') {
          parsedDate.year       = String(value.getFullYear());
          // d = new Date(1900, 1, 1) // 1 for Feb instead of Jan.
          // => Thu Feb 01 1900 00:00:00
          parsedDate.month      = String(value.getMonth() + 1);
          parsedDate.dayOfMonth = String(value.getDate());
          parsedDate.time       = parseTime(value.toTimeString() + '.' + value.getMilliseconds());
        } else if(value.search(YYYYMMDD_MATCHER) != -1) {
          /* 2009-04-19T16:11:05+02:00 || 2009-04-19T16:11:05Z */
          values = value.split(/[T\+-]/);
          parsedDate.year       = values[0];
          parsedDate.month      = values[1];
          parsedDate.dayOfMonth = values[2];
          parsedDate.time       = parseTime(values[3].split('.')[0]);
        } else {
          values = value.split(' ');
          if(values.length === 6 && isNaN(values[5])) {
            // values[5] == year
            /*
             * This change is necessary to make `Mon Apr 28 2014 05:30:00 GMT-0300` work
             * like `case 7`
             * otherwise it will be considered like `Wed Jan 13 10:43:41 CET 2010
             * Fixes: https://github.com/phstc/jquery-dateFormat/issues/64
             */
            values[values.length] = '()';
          }
          switch (values.length) {
            case 6:
              /* Wed Jan 13 10:43:41 CET 2010 */
              parsedDate.year       = values[5];
              parsedDate.month      = shortMonthToNumber(values[1]);
              parsedDate.dayOfMonth = values[2];
              parsedDate.time       = parseTime(values[3]);
              break;
            case 2:
              /* 2009-12-18 10:54:50.546 */
              subValues = values[0].split('-');
              parsedDate.year       = subValues[0];
              parsedDate.month      = subValues[1];
              parsedDate.dayOfMonth = subValues[2];
              parsedDate.time       = parseTime(values[1]);
              break;
            case 7:
              /* Tue Mar 01 2011 12:01:42 GMT-0800 (PST) */
            case 9:
              /* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0800 (China Standard Time) */
            case 10:
              /* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0200 (W. Europe Daylight Time) */
              parsedDate.year       = values[3];
              parsedDate.month      = shortMonthToNumber(values[1]);
              parsedDate.dayOfMonth = values[2];
              parsedDate.time       = parseTime(values[4]);
              break;
            case 1:
              /* added by Jonny, for 2012-02-07CET00:00:00 (Doctrine Entity -> Json Serializer) */
              subValues = values[0].split('');
              parsedDate.year       = subValues[0] + subValues[1] + subValues[2] + subValues[3];
              parsedDate.month      = subValues[5] + subValues[6];
              parsedDate.dayOfMonth = subValues[8] + subValues[9];
              parsedDate.time       = parseTime(subValues[13] + subValues[14] + subValues[15] + subValues[16] + subValues[17] + subValues[18] + subValues[19] + subValues[20]);
              break;
            default:
              return null;
          }
        }

        if(parsedDate.time) {
          parsedDate.date = new Date(parsedDate.year, parsedDate.month - 1, parsedDate.dayOfMonth, parsedDate.time.hour, parsedDate.time.minute, parsedDate.time.second, parsedDate.time.millis);
        } else {
          parsedDate.date = new Date(parsedDate.year, parsedDate.month - 1, parsedDate.dayOfMonth);
        }

        parsedDate.dayOfWeek = String(parsedDate.date.getDay());

        return parsedDate;
      },

      date : function(value, format) {
        try {
          var parsedDate = this.parseDate(value);

          if(parsedDate === null) {
            return value;
          }

          var year       = parsedDate.year,
              month      = parsedDate.month,
              dayOfMonth = parsedDate.dayOfMonth,
              dayOfWeek  = parsedDate.dayOfWeek,
              time       = parsedDate.time;
          var hour;

          var pattern      = '',
              retValue     = '',
              unparsedRest = '',
              inQuote      = false;

          /* Issue 1 - variable scope issue in format.date (Thanks jakemonO) */
          for(var i = 0; i < format.length; i++) {
            var currentPattern = format.charAt(i);
            // Look-Ahead Right (LALR)
            var nextRight      = format.charAt(i + 1);

            if (inQuote) {
              if (currentPattern == "'") {
                retValue += (pattern === '') ? "'" : pattern;
                pattern = '';
                inQuote = false;
              } else {
                pattern += currentPattern;
              }
              continue;
            }
            pattern += currentPattern;
            unparsedRest = '';
            switch (pattern) {
              case 'ddd':
                retValue += numberToLongDay(dayOfWeek);
                pattern = '';
                break;
              case 'dd':
                if(nextRight === 'd') {
                  break;
                }
                retValue += padding(dayOfMonth, 2);
                pattern = '';
                break;
              case 'd':
                if(nextRight === 'd') {
                  break;
                }
                retValue += parseInt(dayOfMonth, 10);
                pattern = '';
                break;
              case 'D':
                if(dayOfMonth == 1 || dayOfMonth == 21 || dayOfMonth == 31) {
                  dayOfMonth = parseInt(dayOfMonth, 10) + 'st';
                } else if(dayOfMonth == 2 || dayOfMonth == 22) {
                  dayOfMonth = parseInt(dayOfMonth, 10) + 'nd';
                } else if(dayOfMonth == 3 || dayOfMonth == 23) {
                  dayOfMonth = parseInt(dayOfMonth, 10) + 'rd';
                } else {
                  dayOfMonth = parseInt(dayOfMonth, 10) + 'th';
                }
                retValue += dayOfMonth;
                pattern = '';
                break;
              case 'MMMM':
                retValue += numberToLongMonth(month);
                pattern = '';
                break;
              case 'MMM':
                if(nextRight === 'M') {
                  break;
                }
                retValue += numberToShortMonth(month);
                pattern = '';
                break;
              case 'MM':
                if(nextRight === 'M') {
                  break;
                }
                retValue += padding(month, 2);
                pattern = '';
                break;
              case 'M':
                if(nextRight === 'M') {
                  break;
                }
                retValue += parseInt(month, 10);
                pattern = '';
                break;
              case 'y':
              case 'yyy':
                if(nextRight === 'y') {
                  break;
                }
                retValue += pattern;
                pattern = '';
                break;
              case 'yy':
                if(nextRight === 'y') {
                  break;
                }
                retValue += String(year).slice(-2);
                pattern = '';
                break;
              case 'yyyy':
                retValue += year;
                pattern = '';
                break;
              case 'HH':
                retValue += padding(time.hour, 2);
                pattern = '';
                break;
              case 'H':
                if(nextRight === 'H') {
                  break;
                }
                retValue += parseInt(time.hour, 10);
                pattern = '';
                break;
              case 'hh':
                /* time.hour is '00' as string == is used instead of === */
                hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
                    : time.hour - 12);
                retValue += padding(hour, 2);
                pattern = '';
                break;
              case 'h':
                if(nextRight === 'h') {
                  break;
                }
                hour = (parseInt(time.hour, 10) === 0 ? 12 : time.hour < 13 ? time.hour
                    : time.hour - 12);
                retValue += parseInt(hour, 10);
                // Fixing issue https://github.com/phstc/jquery-dateFormat/issues/21
                // retValue = parseInt(retValue, 10);
                pattern = '';
                break;
              case 'mm':
                retValue += padding(time.minute, 2);
                pattern = '';
                break;
              case 'm':
                if(nextRight === 'm') {
                  break;
                }
                retValue += time.minute;
                pattern = '';
                break;
              case 'ss':
                /* ensure only seconds are added to the return string */
                retValue += padding(time.second.substring(0, 2), 2);
                pattern = '';
                break;
              case 's':
                if(nextRight === 's') {
                  break;
                }
                retValue += time.second;
                pattern = '';
                break;
              case 'S':
              case 'SS':
                if(nextRight === 'S') {
                  break;
                }
                retValue += pattern;
                pattern = '';
                break;
              case 'SSS':
                retValue +=  padding(time.millis.substring(0, 3), 3);
                pattern = '';
                break;
              case 'a':
                retValue += time.hour >= 12 ? 'PM' : 'AM';
                pattern = '';
                break;
              case 'p':
                retValue += time.hour >= 12 ? 'p.m.' : 'a.m.';
                pattern = '';
                break;
              case 'E':
                retValue += numberToShortDay(dayOfWeek);
                pattern = '';
                break;
              case "'":
                pattern = '';
                inQuote = true;
                break;
              default:
                retValue += currentPattern;
                pattern = '';
                break;
            }
          }
          retValue += unparsedRest;
          return retValue;
        } catch (e) {
          if(console && console.log) {
            console.log(e);
          }
          return value;
        }
      },
      /*
       * JavaScript Pretty Date
       * Copyright (c) 2011 John Resig (ejohn.org)
       * Licensed under the MIT and GPL licenses.
       *
       * Takes an ISO time and returns a string representing how long ago the date
       * represents
       *
       * ('2008-01-28T20:24:17Z') // => '2 hours ago'
       * ('2008-01-27T22:24:17Z') // => 'Yesterday'
       * ('2008-01-26T22:24:17Z') // => '2 days ago'
       * ('2008-01-14T22:24:17Z') // => '2 weeks ago'
       * ('2007-12-15T22:24:17Z') // => 'more than 5 weeks ago'
       *
       */
      prettyDate : function(time) {
        var date;
        var diff;
        var abs_diff;
        var day_diff;
        var abs_day_diff;
        var tense;

        if(typeof time === 'string' || typeof time === 'number') {
          date = new Date(time);
        }

        if(typeof time === 'object') {
          date = new Date(time.toString());
        }

        diff = (((new Date()).getTime() - date.getTime()) / 1000);
  
        abs_diff = Math.abs(diff);
        abs_day_diff = Math.floor(abs_diff / 86400);

        if(isNaN(abs_day_diff)) {
          return;
        }

        tense = diff < 0 ? 'from now' : 'ago';
  
        if(abs_diff < 60) {
          if(diff >= 0)
            return 'just now';
          else
            return 'in a moment';
        } else if(abs_diff < 120) {
          return '1 minute ' + tense;
        } else if(abs_diff < 3600) {
          return Math.floor(abs_diff / 60) + ' minutes ' + tense;
        } else if(abs_diff < 7200) {
          return '1 hour ' + tense;
        } else if(abs_diff < 86400) {
          return Math.floor(abs_diff / 3600) + ' hours ' + tense;
        } else if(abs_day_diff === 1) {
          if(diff >= 0)
            return 'Yesterday';
          else
            return 'Tomorrow';
        } else if(abs_day_diff < 7) {
          return abs_day_diff + ' days ' + tense;
        } else if(abs_day_diff === 7) {
          return '1 week ' + tense;
        } else if(abs_day_diff < 31) {
          return Math.ceil(abs_day_diff / 7) + ' weeks ' + tense;
        } else {
          return 'more than 5 weeks ' + tense;
        }
      },
      toBrowserTimeZone : function(value, format) {
        return this.date(new Date(value), format || 'MM/dd/yyyy HH:mm:ss');
      }
    };
  }());
}(DateFormat));
