/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */

String.emailRE = /([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([\u00C0-\u017Fa-z0-9]{1}[\u00C0-\u017Fa-z0-9\-]{0,62}[\u00C0-\u017Fa-z0-9]{1})|[\u00C0-\u017Fa-z])\.)+[a-z]{2,})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)/;

String.prototype.endsWith = function(suffix) {
  return this.indexOf(suffix, this.length - suffix.length) !== -1;
};

String.prototype.startsWith = function(pattern, position) {
  position = angular.isNumber(position) ? position : 0;
  return this.lastIndexOf(pattern, position) === position;
};

// See ngSanitize
String.prototype.encodeEntities = function () {
  // Regular Expressions for parsing tags and attributes
  var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
      // Match everything outside of normal chars and " (quote character)
      NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g;

  return this.
    replace(/&/g, '&amp;').
    replace(SURROGATE_PAIR_REGEXP, function(value) {
      var hi = value.charCodeAt(0);
      var low = value.charCodeAt(1);
      return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
    }).
    replace(NON_ALPHANUMERIC_REGEXP, function(value) {
      return '&#' + value.charCodeAt(0) + ';';
    }).
    replace(/</g, '&lt;').
    replace(/>/g, '&gt;');
};

String.prototype._base64_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
String.prototype.base64encode = function () {
  var output = "";
  var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  var i = 0;
  
  var input = this.utf8encode();

  while (i < input.length) {
    chr1 = input.charCodeAt(i++);
    chr2 = input.charCodeAt(i++);
    chr3 = input.charCodeAt(i++);
    
    enc1 = chr1 >> 2;
    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
    enc4 = chr3 & 63;
    
    if (isNaN(chr2)) {
      enc3 = enc4 = 64;
    } else if (isNaN(chr3)) {
      enc4 = 64;
    }
    
    output = output +
      this._base64_keyStr.charAt(enc1) + this._base64_keyStr.charAt(enc2) +
      this._base64_keyStr.charAt(enc3) + this._base64_keyStr.charAt(enc4);
  }
  
  return output;
};

String.prototype.base64decode = function() { 
  var output = "";
  var chr1, chr2, chr3;
  var enc1, enc2, enc3, enc4;
  var i = 0;
  
  var input = "" + this; // .replace(/[^A-Za-z0-9\+\/\=]/g, "")
  while (i < input.length) {
    enc1 = this._base64_keyStr.indexOf(input.charAt(i++));
    enc2 = this._base64_keyStr.indexOf(input.charAt(i++));
    enc3 = this._base64_keyStr.indexOf(input.charAt(i++));
    enc4 = this._base64_keyStr.indexOf(input.charAt(i++));

    chr1 = (enc1 << 2) | (enc2 >> 4);
    chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
    chr3 = ((enc3 & 3) << 6) | enc4;
    
    output = output + String.fromCharCode(chr1);
    
    if (enc3 != 64) {
      output = output + String.fromCharCode(chr2);
    }
    if (enc4 != 64) {
      output = output + String.fromCharCode(chr3);
    }
  }

  return output;
};

String.prototype.md5 = function() {
  if (!this.length) { return; }
  // MD5 (Message-Digest Algorithm) by WebToolkit
  var md5 = function(s){function L(k,d){return(k<<d)|(k>>>(32-d));}function K(G,k){var I,d,F,H,x;F=(G&2147483648);H=(k&2147483648);I=(G&1073741824);d=(k&1073741824);x=(G&1073741823)+(k&1073741823);if(I&d){return(x^2147483648^F^H);}if(I|d){if(x&1073741824){return(x^3221225472^F^H);}else{return(x^1073741824^F^H);}}else{return(x^F^H);}}function r(d,F,k){return(d&F)|((~d)&k);}function q(d,F,k){return(d&k)|(F&(~k));}function p(d,F,k){return(d^F^k);}function n(d,F,k){return(F^(d|(~k)));}function u(G,F,aa,Z,k,H,I){G=K(G,K(K(r(F,aa,Z),k),I));return K(L(G,H),F);}function f(G,F,aa,Z,k,H,I){G=K(G,K(K(q(F,aa,Z),k),I));return K(L(G,H),F);}function D(G,F,aa,Z,k,H,I){G=K(G,K(K(p(F,aa,Z),k),I));return K(L(G,H),F);}function t(G,F,aa,Z,k,H,I){G=K(G,K(K(n(F,aa,Z),k),I));return K(L(G,H),F);}function e(G){var Z;var F=G.length;var x=F+8;var k=(x-(x%64))/64;var I=(k+1)*16;var aa=Array(I-1);var d=0;var H=0;while(H<F){Z=(H-(H%4))/4;d=(H%4)*8;aa[Z]=(aa[Z]|(G.charCodeAt(H)<<d));H++;}Z=(H-(H%4))/4;d=(H%4)*8;aa[Z]=aa[Z]|(128<<d);aa[I-2]=F<<3;aa[I-1]=F>>>29;return aa;}function B(x){var k="",F="",G,d;for(d=0;d<=3;d++){G=(x>>>(d*8))&255;F="0"+G.toString(16);k=k+F.substr(F.length-2,2);}return k;}function J(k){k=k.replace(/rn/g,"n");var d="";for(var F=0;F<k.length;F++){var x=k.charCodeAt(F);if(x<128){d+=String.fromCharCode(x);}else{if((x>127)&&(x<2048)){d+=String.fromCharCode((x>>6)|192);d+=String.fromCharCode((x&63)|128);}else{d+=String.fromCharCode((x>>12)|224);d+=String.fromCharCode(((x>>6)&63)|128);d+=String.fromCharCode((x&63)|128);}}}return d;}var C=Array();var P,h,E,v,g,Y,X,W,V;var S=7,Q=12,N=17,M=22;var A=5,z=9,y=14,w=20;var o=4,m=11,l=16,j=23;var U=6,T=10,R=15,O=21;s=J(s);C=e(s);Y=1732584193;X=4023233417;W=2562383102;V=271733878;for(P=0;P<C.length;P+=16){h=Y;E=X;v=W;g=V;Y=u(Y,X,W,V,C[P+0],S,3614090360);V=u(V,Y,X,W,C[P+1],Q,3905402710);W=u(W,V,Y,X,C[P+2],N,606105819);X=u(X,W,V,Y,C[P+3],M,3250441966);Y=u(Y,X,W,V,C[P+4],S,4118548399);V=u(V,Y,X,W,C[P+5],Q,1200080426);W=u(W,V,Y,X,C[P+6],N,2821735955);X=u(X,W,V,Y,C[P+7],M,4249261313);Y=u(Y,X,W,V,C[P+8],S,1770035416);V=u(V,Y,X,W,C[P+9],Q,2336552879);W=u(W,V,Y,X,C[P+10],N,4294925233);X=u(X,W,V,Y,C[P+11],M,2304563134);Y=u(Y,X,W,V,C[P+12],S,1804603682);V=u(V,Y,X,W,C[P+13],Q,4254626195);W=u(W,V,Y,X,C[P+14],N,2792965006);X=u(X,W,V,Y,C[P+15],M,1236535329);Y=f(Y,X,W,V,C[P+1],A,4129170786);V=f(V,Y,X,W,C[P+6],z,3225465664);W=f(W,V,Y,X,C[P+11],y,643717713);X=f(X,W,V,Y,C[P+0],w,3921069994);Y=f(Y,X,W,V,C[P+5],A,3593408605);V=f(V,Y,X,W,C[P+10],z,38016083);W=f(W,V,Y,X,C[P+15],y,3634488961);X=f(X,W,V,Y,C[P+4],w,3889429448);Y=f(Y,X,W,V,C[P+9],A,568446438);V=f(V,Y,X,W,C[P+14],z,3275163606);W=f(W,V,Y,X,C[P+3],y,4107603335);X=f(X,W,V,Y,C[P+8],w,1163531501);Y=f(Y,X,W,V,C[P+13],A,2850285829);V=f(V,Y,X,W,C[P+2],z,4243563512);W=f(W,V,Y,X,C[P+7],y,1735328473);X=f(X,W,V,Y,C[P+12],w,2368359562);Y=D(Y,X,W,V,C[P+5],o,4294588738);V=D(V,Y,X,W,C[P+8],m,2272392833);W=D(W,V,Y,X,C[P+11],l,1839030562);X=D(X,W,V,Y,C[P+14],j,4259657740);Y=D(Y,X,W,V,C[P+1],o,2763975236);V=D(V,Y,X,W,C[P+4],m,1272893353);W=D(W,V,Y,X,C[P+7],l,4139469664);X=D(X,W,V,Y,C[P+10],j,3200236656);Y=D(Y,X,W,V,C[P+13],o,681279174);V=D(V,Y,X,W,C[P+0],m,3936430074);W=D(W,V,Y,X,C[P+3],l,3572445317);X=D(X,W,V,Y,C[P+6],j,76029189);Y=D(Y,X,W,V,C[P+9],o,3654602809);V=D(V,Y,X,W,C[P+12],m,3873151461);W=D(W,V,Y,X,C[P+15],l,530742520);X=D(X,W,V,Y,C[P+2],j,3299628645);Y=t(Y,X,W,V,C[P+0],U,4096336452);V=t(V,Y,X,W,C[P+7],T,1126891415);W=t(W,V,Y,X,C[P+14],R,2878612391);X=t(X,W,V,Y,C[P+5],O,4237533241);Y=t(Y,X,W,V,C[P+12],U,1700485571);V=t(V,Y,X,W,C[P+3],T,2399980690);W=t(W,V,Y,X,C[P+10],R,4293915773);X=t(X,W,V,Y,C[P+1],O,2240044497);Y=t(Y,X,W,V,C[P+8],U,1873313359);V=t(V,Y,X,W,C[P+15],T,4264355552);W=t(W,V,Y,X,C[P+6],R,2734768916);X=t(X,W,V,Y,C[P+13],O,1309151649);Y=t(Y,X,W,V,C[P+4],U,4149444226);V=t(V,Y,X,W,C[P+11],T,3174756917);W=t(W,V,Y,X,C[P+2],R,718787259);X=t(X,W,V,Y,C[P+9],O,3951481745);Y=K(Y,h);X=K(X,E);W=K(W,v);V=K(V,g);}var i=B(Y)+B(X)+B(W)+B(V);return i.toLowerCase();};
  return md5(this.toLowerCase());
};

String.prototype.capitalize = function() {
  return this.charAt(0).toUpperCase() + this.slice(1);
};

String.prototype.asDate = function () {
    var newDate;
    var date = this.split("/");
    if (date.length == 3)
        newDate = new Date(date[2], date[1] - 1, date[0]); // dd/mm/yyyy
    else {
        date = this.split("-");
        if (date.length == 3)
            newDate = new Date(date[0], date[1] - 1, date[2]); // yyyy-mm-dd
        else {
            if (this.length == 8) {
                newDate = new Date(this.substring(0, 4),
                                   this.substring(4, 6) - 1,
                                   this.substring(6, 8)); // yyyymmdd
            }
        }
    }

    return newDate;
};

String.prototype.formatted = function() {
  var newString = this;

  for (var i = 0; i < arguments.length; i++) {
    newString = newString.replace("%{" + i + "}", arguments[i], "g");
  }

  return newString;
};

String.prototype.isValidEmail = function(strict) {
  var result = String.emailRE.test(this);

  if (strict && result) {
    result = String.emailRE.exec(this)[0] == this;
  }

  return result;
};

String.prototype.asCSSIdentifier = function() {
  var characters = [ '_'  , '\\.', '#'  , '@'  , '\\*', ':'  , ','   , ' ',    "'",    '&',    '\\+' ];
  var escapeds =   [ '_U_', '_D_', '_H_', '_A_', '_S_', '_C_', '_CO_', '_SP_', '_SQ_', '_AM_', '_P_' ];

  var newString = this;
  for (var i = 0; i < characters.length; i++) {
    var re = new RegExp(characters[i], 'g');
    newString = newString.replace(re, escapeds[i]);
  }

  newString = newString.replace(/[^\x00-\x7F]/g, '');

  if (/^\d+/.test(newString)) {
    newString = '_' + newString;
  }

  return newString;
};

String.prototype.timeInterval = function () {
  var interval;
  if (this == "once_per_hour")
    interval = 3600;
  else if (this == "every_minute")
    interval = 60;
  else {
    interval = parseInt(this.substr(6)) * 60;
  }

  return interval;
};

String.prototype.parseDate = function(localeProvider, format) {
  var string, formattingTokens, tokens, token, now, date, regexes, i, parsedInput, matchesCount;

  string = '' + this;
  formattingTokens = /%[dembByYHIMp]/g;
  now = new Date();
  date = {
    year: now.getYear() + 1900,
    month: now.getMonth(),
    day: now.getDate(),
    hour: 0,
    minute: 0
  };
  regexes = {
    '%d': [/\d\d/, function(input) {
      date.day = parseInt(input);
      return (date.day < 32);
    }],
    '%e': [/ ?\d?\d/, function(input) {
      date.day = parseInt(input);
      return (date.day < 32);
    }],
    '%m': [/\d\d/, function(input) {
      date.month = parseInt(input) - 1;
      return (date.month < 12);
    }],
    '%b': [/[^\d\s\.\/\-]{2,}/, function(input) {
      var i = _.indexOf(_.map(localeProvider.shortMonths, _.toLower), _.toLower(input));
      if (i >= 0)
        date.month = i;
      return (i >= 0);
    }],
    '%B': [/[^\d\s\.\/\-]{2,}/, function(input) {
      var i = _.indexOf(_.map(localeProvider.months, _.toLower), _.toLower(input));
      if (i >= 0)
        date.month = i;
      return (i >= 0);
    }],
    '%y': [/\d\d/, function(input) {
      var nearFuture = parseInt(now.getFullYear().toString().substring(2)) + 50;
      date.year = parseInt(input);
      if (date.year < nearFuture) date.year += 2000;
      else date.year += 1900;
      return true;
    }],
    '%Y': [/[12]\d\d\d/, function(input) {
      date.year = parseInt(input);
      return true;
    }],
    '%H': [/\d{1,2}/, function(input) {
      date.hour = parseInt(input);
      return (date.hour < 24);
    }],
    '%I': [/\d{1,2}/, function(input) {
      date.hour = parseInt(input);
      return (date.hour <= 12);
    }],
    '%M': [/[0-5]\d/, function(input) {
      date.minute = parseInt(input);
      return (date.minute < 60 );
    }],
    '%p': [/[^\d\s\/\-]+/, function(input) {
      var linput = _.toLower(input), am = _.toLower(l('AM')), pm = _.toLower(l('PM'));
      if (linput == pm)
        date.hour += 12;
      return (linput == am || linput == pm);
    }],
  };
  tokens = format.match(formattingTokens) || [];
  matchesCount = 0;

  for (i = 0; i < tokens.length; i++) {
    token = tokens[i];
    parsedInput = (string.match(regexes[token][0]) || [])[0];
    if (parsedInput) {
      string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
      if (regexes[token][1](parsedInput))
        matchesCount++;
    }
  }

  if (tokens.length === matchesCount) {
    // console.debug(this + ' + ' + format + ' = ' + JSON.stringify(date));
    return new Date(date.year, date.month, date.day, date.hour, date.minute);
  }
  else
    return new Date(NaN);
};

Date.prototype.clone = function() {
  var newDate = new Date();

  newDate.setTime(this.getTime());

  return newDate;
};

Date.prototype.daysUpTo = function(otherDate) {
  var days = [];

  var day1 = this.getTime();
  var day2 = otherDate.getTime();
  if (day1 > day2) {
    var tmp = day1;
    day1 = day2;
    day2 = tmp;
  }

  var DAY_SECS = 25 * 60 * 60 * 1000; // compensate for DST
  var nbrDays = Math.round((day2 - day1) / DAY_SECS) + 1;
  for (var i = 0; i < nbrDays; i++) {
    var newDate = new Date();
    newDate.setTime(day1 + (i * DAY_SECS));
    newDate.setHours(this.getHours());
    newDate.setMinutes(this.getMinutes());
    newDate.setSeconds(this.getSeconds());
    newDate.setMilliseconds(this.getMilliseconds());
    days.push(newDate);
  }

  return days;
};

Date.prototype.minutesTo = function(otherDate) {
  var delta, dstOffset;

  delta = Math.floor(otherDate.valueOf() - this.valueOf())/1000/60;
  dstOffset = otherDate.getTimezoneOffset() - this.getTimezoneOffset();

  return delta - dstOffset;
};

Date.prototype.stringWithSeparator = function(separator) {
    var month = '' + (this.getMonth() + 1);
    var day = '' + this.getDate();
    var year = this.getYear();
    if (year < 1000)
        year = '' + (year + 1900);
    if (month.length == 1)
        month = '0' + month;
    if (day.length == 1)
        day = '0' + day;

    if (separator == '-')
        str = year + '-' + month + '-' + day;
    else
        str = day + '/' + month + '/' + year;

    return str;
};

Date.prototype.addDays = function(nbrDays) {
  var initialDate, milliSeconds, dstOffset;

  milliSeconds = this.getTime();
  initialDate = new Date(milliSeconds);
  milliSeconds += 86400000 * nbrDays;
  this.setTime(milliSeconds);

  dstOffset = this.getTimezoneOffset() - initialDate.getTimezoneOffset();
  if (dstOffset !== 0) {
    milliSeconds = this.getTime() + dstOffset*60*1000;
    this.setTime(milliSeconds);
  }

  return this;
};

Date.prototype.addHours = function(nbrHours) {
  var milliSeconds = this.getTime();
  milliSeconds += 3600000 * nbrHours;
  this.setTime(milliSeconds);
};

Date.prototype.addMinutes = function(nbrMinutes) {
  var milliSeconds = this.getTime();
  milliSeconds += 60000 * nbrMinutes;
  this.setTime(milliSeconds);
};

Date.prototype.beginOfDay = function() {
    var beginOfDay = new Date(this.getTime());
    beginOfDay.setHours(0);
    beginOfDay.setMinutes(0);
    beginOfDay.setSeconds(0);
    beginOfDay.setMilliseconds(0);

    return beginOfDay;
};

/**
 * See [SOGoUser dayOfWeekForDate:]
 */
Date.prototype.dayOfWeek = function(localeProvider) {
  var offset, baseDayOfWeek, dayOfWeek;

  offset = localeProvider.firstDayOfWeek;
  baseDayOfWeek = this.getDay();
  if (offset > baseDayOfWeek)
    baseDayOfWeek += 7;

  dayOfWeek = baseDayOfWeek - offset;

  return dayOfWeek;
};

/**
 * See [SOGoUser firstWeekOfYearForDate:]
 */
Date.prototype.firstWeekOfYearForDate = function(localeProvider) {
  var firstWeekRule, dayOfWeek, januaryFirst, firstWeek;

  firstWeekRule = localeProvider.firstWeekOfYear;

  januaryFirst = new Date(this.getTime());
  januaryFirst.setMonth(0);
  januaryFirst.setDate(1);
  dayOfWeek = januaryFirst.dayOfWeek(localeProvider);

  if (firstWeekRule == 'First4DayWeek') {
    if (dayOfWeek < 4)
      firstWeek = januaryFirst.beginOfWeek(localeProvider.firstDayOfWeek);
    else
      firstWeek = januaryFirst.addDays(7).beginOfWeek(localeProvider.firstDayOfWeek);
  }
  else if (firstWeekRule == 'FirstFullWeek') {
    if (dayOfWeek === 0)
      firstWeek = januaryFirst.beginOfWeek(localeProvider.firstDayOfWeek);
    else
      firstWeek = januaryFirst.addDays(7).beginOfWeek(localeProvider.firstDayOfWeek);
  }
  else {
    firstWeek = januaryFirst.beginOfWeek(localeProvider.firstDayOfWeek);
  }

  return firstWeek;
};

/**
 * See [SOGoUser weekNumberForDate:]
 */
Date.prototype.getWeek = function(localeProvider) {
  var firstWeek, previousWeek, weekNumber, clone;

  clone = new Date(this.getTime());
  clone.addDays(6);
  firstWeek = clone.firstWeekOfYearForDate(localeProvider);
  if (firstWeek.getTime() < clone.getTime()) {
    weekNumber = 1 + Math.floor((clone.getTime() - firstWeek.getTime()) / (86400000 * 7));
  }
  else
    {
      // Date is within the last week of the previous year;
      // Compute the previous week number to find the week number of the requested date.
      // The number will either be 52 or 53.
      previousWeek = new Date(clone.getTime());
      previousWeek.addDays(-7);
      firstWeek = previousWeek.firstWeekOfYearForDate(localeProvider);
      weekNumber = 2 + Math.floor((previousWeek.getTime() - firstWeek.getTime()) / (86400000 * 7));
    }

  return weekNumber;
};

Date.prototype.beginOfWeek = function(firstDayOfWeek) {
    var offset = firstDayOfWeek - this.getDay();
    if (offset > 0)
        offset -= 7;

    var beginOfWeek = this.beginOfDay();
    beginOfWeek.setHours(12);
    beginOfWeek.addDays(offset);

    return beginOfWeek;
};

Date.prototype.endOfWeek = function(firstDayOfWeek) {
    var endOfWeek = this.beginOfWeek(firstDayOfWeek);
    endOfWeek.addDays(6);

    endOfWeek.setHours(23);
    endOfWeek.setMinutes(59);
    endOfWeek.setSeconds(59);
    endOfWeek.setMilliseconds(999);

    return endOfWeek;
};

// YYYYMMDD
Date.prototype.getDayString = function() {
    var newString = this.getYear();
    if (newString < 1000) newString += 1900;
    var month = '' + (this.getMonth() + 1);
    if (month.length == 1)
        month = '0' + month;
    newString += month;
    var day = '' + this.getDate();
    if (day.length == 1)
        day = '0' + day;
    newString += day;

    return newString;
};

// HH00
Date.prototype.getHourString = function() {
    var newString = this.getHours() + '00';
    if (newString.length == 3)
        newString = '0' + newString;

    return newString;
};

Date.prototype.format = function(localeProvider, format) {
  var separators, parts, i, max,
      date = [],
      validParts = /%[deaAmbByYUHIMp]/g,
      val = {
        '%d': this.getDate(),                                  // day of month (e.g., 01)
        '%e': this.getDate(),                                  // day of month, space padded
        '%a': localeProvider.shortDays[this.getDay()],         // locale's abbreviated weekday name (e.g., Sun)
        '%A': localeProvider.days[this.getDay()],              // locale's full weekday name (e.g., Sunday)
        '%m': this.getMonth() + 1,                             // month (01..12)
        '%b': localeProvider.shortMonths[this.getMonth()],     // locale's abbreviated month name (e.g., Jan)
        '%B': localeProvider.months[this.getMonth()],          // locale's full month name (e.g., January)
        '%y': this.getFullYear().toString().substring(2),      // last two digits of year (00..99)
        '%Y': this.getFullYear(),                              // year
        '%U': this.getWeek(localeProvider),                    // week of the year
        '%H': this.getHours(),                                 // hour (00..23)
        '%M': this.getMinutes() };                             // minute (00..59)
  val['%I'] = val['%H'] > 12 ? val['%H'] % 12 : val['%H'];     // hour (01..12)
  val['%p'] = val['%H'] < 12 ? l('AM') : l('PM');              // locale's equivalent of either AM or PM

  val['%d'] = (val['%d'] < 10 ? '0' : '') + val['%d'];
  val['%e'] = (val['%e'] < 10 ? ' ' : '') + val['%e'];
  val['%m'] = (val['%m'] < 10 ? '0' : '') + val['%m'];
  val['%H'] = (val['%H'] < 10 ? '0' : '') + val['%H'];
  val['%I'] = (val['%I'] < 10 ? '0' : '') + val['%I'];
  val['%M'] = (val['%M'] < 10 ? '0' : '') + val['%M'];

  separators = format.replace(validParts, '\0').split('\0');
  parts = format.match(validParts);
  for (i = 0, max = parts.length; i <= max; i++){
    if (separators.length)
      date.push(separators.shift());
    date.push(val[parts[i]]);
  }

  return date.join('');
};

Element.prototype.setCaretTo = function(pos) {
  if (this.setSelectionRange) {  // For Mozilla and Safari
    this.focus();
    this.setSelectionRange(pos, pos);
  }
  else if (this.createTextRange) {  // For IE
    var range = this.createTextRange();
    range.move('character', pos);
    range.select();
  }
};

Element.prototype.selectText = function(start, end) {
  if (this.setSelectionRange) {     // For Mozilla and Safari
    this.setSelectionRange(start, end);
  }
  else if (this.createTextRange) {  // For IE
    var textRange = this.createTextRange();
    textRange.moveStart('character', start);
    textRange.moveEnd('character', end-element.value.length);
    textRange.select();
  }
  else {
    this.select();
  }
};

/* Functions */

function l() {
  var key = arguments[0], value = key, args = arguments, i, j;

  // Retrieve translation
  if (labels[key]) {
    value = labels[key];
  }
  else if (clabels[key]) {
    value = clabels[key];
  }

  // Format placeholders %{0}, %{1], %{2}, ...
  for (i = 1, j = 0; i < args.length; i++, j++) {
    value = value.replace('%{' + j + '}', args[i]);
  }

  // Format placeholders %d and %s
  i = 1;
  if (args.length > 1) {
    value = value.replace(/%((%)|s|d)/g, function(m) {
      // m is the matched format, e.g. %s, %d
      var val = null;
      if (m[2]) {
        val = m[2];
      }
      else {
        val = args[i];
        // A switch statement so that the formatter can be extended. Default is %s
        switch (m) {
        case '%d':
          val = parseFloat(val);
          if (isNaN(val))
            val = 0;
          break;
        }
        i++;
      }
      return val;
    });
  }

  return value;
}

function hexToRgb(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16)
  } : null;
}

// Respect contrast ratio recommendation from W3C:
// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
function contrast(hex) {
  var color, c, l = 1;

  color = hexToRgb(hex);
  if (color) {
    c = [color.r / 255, color.g / 255, color.b / 255];

    for (var i = 0; i < c.length; ++i) {
      if (c[i] <= 0.03928) {
	c[i] = c[i] / 12.92;
      }
      else {
	c[i] = Math.pow((c[i] + 0.055) / 1.055, 2.4);
      }
    }

    l = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
  }

  if (l > 0.179) {
    return 'black';
  }
  else {
    return 'white';
  }
}

function guid() {
  function S4() {
    return (((1+Math.random())*0x10000)|0).toString(16).substring(1); 
  }
  
  return (S4() + S4() + "-" + S4() + "-4" + S4().substr(0,3) + "-" + S4() + "-" + S4() + S4() + S4()).toLowerCase();
}
