/*
Copyright 2009 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// From http://www.webtoolkit.info/javascript-sprintf.html
sprintfWrapper = {

  init : function() {
    if (typeof arguments == 'undefined') { return null; }
    if (arguments.length < 1) { return null; }
    if (typeof arguments[0] != 'string') { return null; }
    if (typeof RegExp == 'undefined') { return null; }

    var string = arguments[0];
    var exp = new RegExp(
        /(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g);
    var matches = new Array();
    var strings = new Array();
    var convCount = 0;
    var stringPosStart = 0;
    var stringPosEnd = 0;
    var matchPosEnd = 0;
    var newString = '';
    var match = null;

    while (match = exp.exec(string)) {
      if (match[9]) {
          convCount += 1;
      }
      stringPosStart = matchPosEnd;
      stringPosEnd = exp.lastIndex - match[0].length;
      strings[strings.length] = string.substring(stringPosStart, stringPosEnd);

      matchPosEnd = exp.lastIndex;
      matches[matches.length] = {
        match: match[0],
        left: match[3] ? true : false,
        sign: match[4] || '',
        pad: match[5] || ' ',
        min: match[6] || 0,
        precision: match[8],
        code: match[9] || '%',
        negative: parseInt(arguments[convCount]) < 0 ? true : false,
        argument: String(arguments[convCount])
      };
    }
    strings[strings.length] = string.substring(matchPosEnd);

    if (matches.length == 0) { return string; }
    if ((arguments.length - 1) < convCount) { return null; }

    var code = null;
    var match = null;
    var i = null;

    for (i = 0; i < matches.length; i++) {
      if (matches[i].code == '%') { substitution = '%' }
      else if (matches[i].code == 'b') {
        matches[i].argument = String(
            Math.abs(parseInt(matches[i].argument)).toString(2));
        substitution = sprintfWrapper.convert(matches[i], true);
      }
      else if (matches[i].code == 'c') {
        matches[i].argument = String(
            String.fromCharCode(
                parseInt(Math.abs(parseInt(matches[i].argument)))));
        substitution = sprintfWrapper.convert(matches[i], true);
      }
      else if (matches[i].code == 'd') {
        matches[i].argument = String(
            Math.abs(parseInt(matches[i].argument)));
        substitution = sprintfWrapper.convert(matches[i]);
      }
      else if (matches[i].code == 'f') {
        matches[i].argument = String(
            Math.abs(
                parseFloat(matches[i].argument)).toFixed(
                    matches[i].precision ? matches[i].precision : 6));
        substitution = sprintfWrapper.convert(matches[i]);
      }
      else if (matches[i].code == 'o') {
        matches[i].argument = String(
            Math.abs(parseInt(matches[i].argument)).toString(8));
        substitution = sprintfWrapper.convert(matches[i]);
      }
      else if (matches[i].code == 's') {
        matches[i].argument = matches[i].argument.substring(
            0,
            matches[i].precision ?
            matches[i].precision : matches[i].argument.length)
        substitution = sprintfWrapper.convert(matches[i], true);
      }
      else if (matches[i].code == 'x') {
        matches[i].argument = String(
            Math.abs(parseInt(matches[i].argument)).toString(16));
        substitution = sprintfWrapper.convert(matches[i]);
      }
      else if (matches[i].code == 'X') {
        matches[i].argument = String(
            Math.abs(parseInt(matches[i].argument)).toString(16));
        substitution = sprintfWrapper.convert(matches[i]).toUpperCase();
      }
      else {
        substitution = matches[i].match;
      }

      newString += strings[i];
      newString += substitution;

    }
    newString += strings[i];

    return newString;

  },

  convert : function(match, nosign){
    if (nosign) {
      match.sign = '';
    } else {
      match.sign = match.negative ? '-' : match.sign;
    }
    var l = match.min - match.argument.length + 1 - match.sign.length;
    var pad = new Array(l < 0 ? 0 : l).join(match.pad);
    if (!match.left) {
      if (match.pad == '0' || nosign) {
        return match.sign + pad + match.argument;
      } else {
        return pad + match.sign + match.argument;
      }
    } else {
      if (match.pad == '0' || nosign) {
        return match.sign + match.argument + pad.replace(/0/g, ' ');
      } else {
        return match.sign + match.argument + pad;
      }
    }
  }
}

sprintf = sprintfWrapper.init;


/**
 * Detect whether the string starts with the given prefix.
 * @param {string} prefix A prefix to detect.
 * @return {boolean} Whether the prefix matched.
 */
String.prototype.startsWith = function(prefix)
{
  return (this.match('^' + prefix) == prefix);
};

/**
 * Detect whether the string ends with the given suffix
 * @param {string} suffix A suffix to detect.
 * @return {boolean} Whether the suffix matched.
 */
String.prototype.endsWith = function(suffix)
{
  return (this.match(prefix + '$') == suffix)
};

/**
 * Prepend a prefix to the given string given number of tiems
 * @param {string} str Original string.
 * @param {string} prefix String to prepend.
 * @param {integer} count Prepend how many times.
 * @return {string} String with prefix(es).
 * @see http://www.mredkj.com/javascript/nfbasic.html.
 */
String.prototype.prepend = function(str, prefix, count)
{
  var current_count = 0;
  var result = '';
  while (current_count < count) {
    result = prefix + result;
    current_count++;
  }
  return result;
};

/**
 * Convenience wrapper.
 * @param {number} val A number to convert to string and commaify.
 * @return {string} Commaified string representing the number.
 */
function numWithCommas(val) {
  return (val + '').numWithCommas();
}


/**
 * Print a number with commas between each three digits of integer part.
 * @return {string} Pretty-printed number.
 * @see http://www.mredkj.com/javascript/nfbasic.html.
 */
String.prototype.numWithCommas = function() {
  var str = this + '';
  x = str.split('.');
  x1 = x[0];
  x2 = x.length > 1 ? '.' + x[1] : '';
  var rgx = /(\d+)(\d{3})/;
  while (rgx.test(x1)) {
    x1 = x1.replace(rgx, '$1' + ',' + '$2');
  }
  return x1 + x2;
};


function arrayMin(array) {
  var min = array[0];
  var len = array.length;
  for (var i = 1; i < len; i++) {
    if (array[i] < min) {
      min = array[i];
    }
  }
  return min;
}


function arrayMax(array) {
  var max = array[0];
  var len = array.length;
  for (var i = 1; i < len; i++) {
    if (array[i] > max) {
      max = array[i];
    }
  }
  return max;
}

function tupleSection(tupleList, tupleKey) {
  result = []
  for (var i = 0; i < tupleList.length; i++) {
    result.push(tupleList[i][tupleKey]);
  }
  return result;
}

sortByField = function(field, a, b) {
  // Push undefined values to the bottom
  if (! (field in a) || !(a[field])) {
    a[field] = 0.00;
  }
  if (! (field in b) || !(b[field])) {
    b[field] = 0.00;
  }
  if (a[field] < b[field]) {
    return -1;
  } else if (a[field] > b[field]) {
    return 1;
  } else {
    return 0;
  }
};

sortByNumericField = function(field, a, b) {
  // Push undefined values to the bottom
  if (! (field in a) || isNaN(a[field]) || (null == a[field])) {
    a[field] = NaN;
  } else if (!(a[field])){
    a[field] = 0.00;
  }
  if (! (field in b) || isNaN(b[field]) || (null == b[field])) {
    b[field] = NaN;
  } else if (!(b[field])){
    b[field] = 0.00;
  }
  var x = a[field];
  var y = b[field];

  if (isNaN(x) && isNaN(y)) {
    return sortByField('name', a, b);
  } else if (isNaN(x)) {
    return -1;
  } else if (isNaN(y)) {
    return 1;
  } else if (x < y) {
    return -1;
  } else if (x > y) {
    return 1;
  } else {
    return 0;
  }
};

/**
 * Parse a GET URL and extract the parameter with the given name, if any.
 * @param {string} name URL parameter name.
 * @return {string} string The parameter value or undefined.
 * @see http://www.netlobo.com/url_query_string_javascript.html
 */
function urlParam(name)
{
  name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
  var regexS = '[\\?&]' + name + '=([^&#]*)';
  var regex = new RegExp(regexS);
  var results = regex.exec(window.location.href);
  if (results == null)
    return undefined;
  else
    return results[1];
}


/**
 * Partially apply arguments to a function.
 * @param {JS arguments} arguments Valid arguments expression.
 * @return {function} Function object with args partially applied.
 */
Function.prototype.curry = function()
 {
   var method = this, args = Array.prototype.slice.call(arguments);
   return function()
   {
     return method.apply(
        this, args.concat(Array.prototype.slice.call(arguments)));
   };
 };

