
/*-------------------------------   Global   ------------------------------*/
var $D;
if (window.console && window.console.debug)
  $D = function(object) { window.console.debug(object) };
else
  $D = function() {};


/*---------------------------- SrcNumberSelect ----------------------------*/

var SrcNumberSelect = Class.extend( Folder, {

  initialize: function() {
    Folder.prototype.initialize.apply(this, arguments);
    this.eventOnWindowClick = this.deactivate.bindAsEventListener(this);
    Event.observe(window, "click", this.eventOnWindowClick);
  },

  destroy: function() {
    Folder.prototype.destroy.apply(this, arguments);
    Event.stopObserving(window, "click", this.eventOnWindowClick);
  }
});

InstanceManager.create(SrcNumberSelect);

/*------------------------- ContactsFilter -------------------------*/

ContactsFilter = Class.create({
  
  initialize: function(element, field, label, options) {
    this.element = $(element);
    this.field = $(field);
    this.term = this.field.value.toLowerCase();
    this.options = options || {};
    this.eventOnKeyUp = this.onKeyUp.bindAsEventListener(this);
    Event.observe(this.field, "keyup", this.eventOnKeyUp);
    this.labeledTextField = LabeledTextField.create(this.field, label);
    this.onKeyUp();
  },

  destroy: function() {
    if (this.eventOnKeyUp) Event.stopObserving(this.field, "keyup", this.eventOnKeyUp);
  },
  
  filter: function(term) {
    if (this.options.beforeFilter) this.options.beforeFilter(term);
    
    term = term ? term.toLowerCase() : '';

    if (term == this.term) return;
    this.term = term;
    if (this.labeledTextField)
       this.labeledTextField.setValue(this.term);
    else
     this.field.value = term;
    
    var selectedNodes = [];
    var nodes = this.element.childNodes;
    for (var i=0; i < nodes.length; i++) {

      var node = nodes[i];
      if (node.nodeType != 1 || Element.hasClassName(node, 'details')) continue;
      if (!term || term.length == 0) {
        Element.show(node);
        selectedNodes.push(node);
        continue;
      }
      
      var match = false;
      if (node.nodeName == 'A') {
        var tokens = node.text.split(/[\s-]/);
        if (tokens && tokens.length > 0) {
          for (var n=0; n < tokens.length; n++) {
            if (tokens[n].toLowerCase().indexOf(term) == 0) {            
              match = true;
              break;
            }
          }
        }      
      }
      
      if (match) {
        Element.show(node);
        selectedNodes.push(node);
      } else {
        Element.hide(node);
      }
    }
    if (this.options.afterFilter) this.options.afterFilter(selectedNodes);
  },
  
  onKeyUp: function(event) {
    this.filter((this.labeledTextField ? this.labeledTextField.getValue() : this.field.value).strip());
  }
});

InstanceManager.create(ContactsFilter);

/*------------------------------ Call ------------------------------*/

var AbstractCall = {
  
  baseInitialize: function(form, maxDstNumbers) {
    this.form = $(form);
    this.maxDstNumbers = maxDstNumbers;
    
    this.dstNumbersLimitElement = $('dst_numbers_limit');
    this.addDstNumberElement    = $('add_dst_number');
    
    this.eventOnChange = this.onChange.bind(this);
    this.eventOnKeyPress = this.onKeyPress.bind(this);
    
    this.createPhoneNumberFields();
  },

  enable: function() {
    Form.enable(this.form);
    Element.removeClassName(this.form, 'disabled');
  },
  
  disable: function() {
    Form.disable(this.form);
    Element.addClassName(this.form, 'disabled');
  },
  
  isConference: function() {
    return this.dstNumberFields.length > 1;
  },
  
  serialize: function() {
    return Form.serialize(this.form);
  },
  
  onChange: function(field, event) {
    var value = field.value();
    if (value.length > 0) {
      var existingField = this.existingField(value, field);
      if (existingField) {
        this.highlight(existingField.element);
        field.setValue('');
        field.focus();
      }
    }
    this.calculate();
  }, 
  
  calculate: function() { },
  
  submit: function(url, options) {
    options = Object.extend(options || {}, { parameters: this.serialize(), evalScripts: true });
    return new Ajax.Request(url || this.form.action, options);  
  },
  
  existingField: function(number, except) {
    var norm = PhoneNumber.normalize(number);
    if (this.srcNumberField != except && this.srcNumberField.normalizedValue() == norm) return this.srcNumberField;
    return this.dstNumberFields.find(function(f) { return f != except && f.normalizedValue() == norm });
  },
  
  focusField: function(field) {
    return window.setTimeout(field.focus.bind(field), 50);
  },

  rejectDuplicateNumber: function(number) {
    var field = null;
    if (typeof(number) != 'string') {
      field = number;
      number = field.value();
    }
    var existingField = this.existingField(number, field);
    if (existingField && existingField != field) {
      this.highlight(existingField.element);
      if (field) {
        field.setValue('');
        this.focusField(field);
      }
      return existingField;
    }
    return false;
  },
    
  highlight: function(element) {
    new Effect.Highlight(element, { duration: 1.0, startcolor: '#FFCC00', endcolor: '#1E1E1E', queue: 'end' });
  }
}

var NewCall = Object.extend(Object.extend({}, AbstractCall), {
  
  initialize: function(form, maxDstNumbers) {

    this.createConferenceElement = $('create_conference');

    this.eventOnEdit = this.calculate.bind(this);

    AbstractCall.baseInitialize.apply(this, arguments);
    window.Call = NewCall;

    this.canAddDstNumber();
    this.focus();


	Element.removeClassName( $('call_button').parentNode, 'busy');
	$('call_button').innerHTML.replace('<img src="'+nb.src+'"/>');

  },

  update: function() {
    this.destroyPhoneNumberFields();
    this.createPhoneNumberFields();
    this.canAddDstNumber();
    //this.setTimeout();
  },

	//we submit anyway, but if there's an empty field we don't replace the button yet because it's unlikely the call will work
  submit: function(url, options) {
	if( url == '/call/connect' ) {
		fempty = this.firstEmptyDstNumberField();
		if( !fempty ) {
			$('call_button').innerHTML.replace('<img src="'+cb.src+'"/>' );
		}
		Element.addClassName( $('call_button').parentNode, 'busy');
	}
	options = Object.extend(options || {}, { parameters: this.serialize(), evalScripts: true });
	return new Ajax.Request(url || this.form.action, options);  
  },

  destroyPhoneNumberFields: function() {
    this.srcNumberField.destroy();
    this.dstNumberFields.each(function(field) { field.destroy() });
  },

  createPhoneNumberFields: function() {
    var fields = this.form.childrenWithClassName('field');
    this.srcNumberField = this.createDstNumberField(fields.shift());
    this.dstNumberFieldTemplate = fields.shift();
    Element.remove(this.dstNumberFieldTemplate);
    this.dstNumberFields = fields.map(function(field) {
      return this.createDstNumberField(field);
    }.bind(this));
  },

  onKeyPress: function(field, event) {
    if ((event.keyCode == Event.KEY_TAB || event.keyCode == Event.KEY_RETURN) && !event.shiftKey &&
        field == this.dstNumberFields[this.dstNumberFields.length - 1] &&
        !this.rejectDuplicateNumber(field) &&
        this.isConference()) {
      var emptyField = this.firstEmptyDstNumberField();
      if (!emptyField && field.value().length > 0) emptyField = this.insertDstNumberField();
      if (emptyField) this.focusField(emptyField);
    }
  },
  
  calculate: function() {
    this.submit("/call/update_route_costs");
  },

  focus: function() {
    var field = this.srcNumberField;
    if (!field.isEmpty()) field = this.firstEmptyDstNumberField();
    if (field) field.focus();
  },

  firstEmptyDstNumberField: function() {
    return this.dstNumberFields.find(function(f) { return f.isEmpty() });
  },
  
  insertDstNumberField: function() {
    if (!this.canAddDstNumber()) {
      this.highlight(this.dstNumbersLimitElement);
      return false;
    }
    var element = this.dstNumberFieldTemplate.cloneNode(true);
    var field = this.createDstNumberField(element);

	var errors = field.element.childrenWithClassName('error');
	for( var x=0; x<errors.length; x++ ) {
		Element.remove(errors[x]);
	}

    field.setValue('');
    this.dstNumberFields.push(field);
    this.dstNumbersLimitElement.parentNode.insertBefore(element, this.dstNumbersLimitElement);
    this.canAddDstNumber();
    return field;
  },

  createDstNumberField: function(element) {
    return new PhoneNumber.Field(element, {
      onEdit: this.eventOnEdit,
      onChange: this.eventOnChange,
      onKeyPress: this.eventOnKeyPress,
      timeout: 1000 });
  },

  setSrcNumber: function(number) {
    this.srcNumberField.setValue(number);
    if (!this.rejectDuplicateNumber(this.srcNumberField)) {
      this.calculate();
      this.focus();
    }
  },
  
  addDstNumber: function(number) {
    var field;
    if (number) {
      if (this.rejectDuplicateNumber(number)) return;
      if (this.isConference()) {
        field = this.firstEmptyDstNumberField() || this.insertDstNumberField();
      } else {
        field = this.dstNumberFields[0];
      }
      if (field) {
        field.setValue(number);
        this.calculate();
	var errors = field.element.childrenWithClassName('error');
	for( var x=0; x<errors.length; x++ ) {
		Element.remove(errors[x]);
	}
      }
    } else {
      field = this.insertDstNumberField();
      this.focus();
    }
  },
    
  removeDstNumber: function(field) {
    if (!field.element) field = this.dstNumberFields.find(function(f) {
      return f.element == field;
    });
    if (this.isConference()) {    
      Element.remove(field.element);
      this.dstNumberFields = this.dstNumberFields.without(field);
      field.destroy();
    } else {
      field.setValue('');
    }
    this.canAddDstNumber();
    this.calculate();
    this.focus();
  },
  
  canAddDstNumber: function() {
    if (this.dstNumberFields.length < this.maxDstNumbers) {
      this.dstNumbersLimitElement.hide();
      if (this.isConference()) {
        this.addDstNumberElement.show();
        this.createConferenceElement.hide();
      } else {
        this.addDstNumberElement.hide();
        this.createConferenceElement.show();        
      }
      return true;
    } else {
      this.dstNumbersLimitElement.show();
      this.addDstNumberElement.hide();
      this.createConferenceElement.hide();
      return false;
    }    
  }  
});



var ActiveCall = Object.extend(Object.extend({}, AbstractCall), {
  
  initialize: function(form, maxDstNumbers, updatePeriod) {
    this.updatePeriod = updatePeriod;
    AbstractCall.baseInitialize.apply(this, arguments);
    window.Call = ActiveCall;
    this.canAddDstNumber();
    this.setTimeout();
  },
  
  update: function() {
    this.destroyPhoneNumberFields();
    this.createPhoneNumberFields();
    this.canAddDstNumber();
    this.setTimeout();
  },

  destroyPhoneNumberFields: function() {
    this.srcNumberField.destroy();
    this.dstNumberFields.each(function(field) { field.destroy() });
  },
    
  createPhoneNumberFields: function() {
    var fields = this.form.childrenWithClassName('field');
    this.addDstNumberField = new PhoneNumber.Field(fields.pop(), {
      onKeyPress: this.eventOnKeyPress,
      timeout: 1000 });
    this.srcNumberField = this.createDstNumberField(fields.shift());
    this.dstNumberFields = fields.map(function(field) {
      return this.createDstNumberField(field);
    }.bind(this));
  },

  createDstNumberField: function(element) {
    return new PhoneNumber.Field(element);
  },
  
  onKeyPress: function(field, event) {
    if (event.keyCode == Event.KEY_RETURN) {
      this.addDstNumber();
    }
  },
  
  setTimeout: function() {
    this.clearTimeout();
    this.timeout = window.setTimeout(function () {
      this.submit('/call/refresh');
      this.timeout = null;
    }.bind(this), this.updatePeriod);
  },
  
  clearTimeout: function() {
    if (this.timeout) {
      window.clearTimeout(this.timeout);
      this.timeout = null;
    }
  },
  
  addDstNumber: function(number) {
    if (!this.isConference()) {
      this.highlight(this.form);
      return;
    }
    if (number) {
      this.addDstNumberField.setValue(number);
    } else {
      number = this.addDstNumberField.value();
    }
    if (this.rejectDuplicateNumber(this.addDstNumberField)) return;
    number = this.addDstNumberField.normalizedValue();
    if (number.length > 6) {
      this.clearTimeout();
      this.submit("/call/connect_conference_call", { parameters: this.addDstNumberField.serialize() });
    } else {
      this.highlight(this.addDstNumberField.element);
    }
  },

  canAddDstNumber: function() {
    if (this.isConference()) {
      if (this.dstNumberFields.length < this.maxDstNumbers) {
        this.addDstNumberElement.show();
        this.dstNumbersLimitElement.hide();
        return true;
      } else {
        this.addDstNumberElement.hide();
        this.dstNumbersLimitElement.show();
      }
    } else {
      this.dstNumbersLimitElement.hide();
      this.addDstNumberElement.hide();
    }    
    return false;
  }
});

/*------------------------------ PhoneNumber ------------------------------*/

var PhoneNumber = {
  
  instances: [],
  
  dialingRule: DialingRule.byCountryPrefix('1'),
  
  normalize: function(number) {
    return PhoneNumber.dialingRule.normalize(number);
  },
  
  byValue: function(value) {
    return PhoneNumber.byNorm(PhoneNumber.normalize(value));
  },
  
  byNorm: function(norm) {
    return PhoneNumber.instances.find(function(p) { return p.norm == norm });
  }
}

/*--------------------------- PhoneNumber.Field ----------------------------*/

PhoneNumber.Field = Class.create({

  initialize: function(element, options) {
    this.element = $(element);
    this.options = options || {};
    
    for (var n=0; n < this.element.childNodes.length; n++) {
      var node = this.element.childNodes[n];
      if (node.nodeType != 1) continue;
      if (Element.hasClassName(node, 'number')) {
        this.field = node;
      } else if (Element.hasClassName(node, 'label')) {
        this.label = node;
      } else if (Element.hasClassName(node, 'flag')) {
        this.image = node;
        this.src = "/images/blank.gif";
      } else if (Element.hasClassName(node, 'error')) {
        this.error = node;
      } else if (Element.hasClassName(node, 'auto_completer')) {
        this.autocompleter = new PhoneNumber.Autocompleter(this.field, node, {
          afterUpdateElement: function(field, element) {
            this.setValue(element.childrenWithClassName('number')[0].innerHTML);
            this.options.onChange(this);
          }.bind(this),
          choices: 7
        });
      }
    }

    this.norm = this.normalizedValue();
    this.initialValue = this.field.value;
    
    this.options.onChange = this.options.onChange || function(field, event) {};
    this.eventOnChange = this.onChange.bindAsEventListener(this);
    Event.observe(this.field, "change", this.eventOnChange);
  
    this.options.onEdit = this.options.onEdit || function(field, event) {};
    this.eventOnKeyUp = this.onKeyUp.bindAsEventListener(this);
    Event.observe(this.field, "keyup", this.eventOnKeyUp);

    this.options.onKeyPress = this.options.onKeyPress || function(field, event) {};
    this.eventOnKeyPress = this.onKeyPress.bindAsEventListener(this);
    Event.observe(this.field, "keypress", this.eventOnKeyPress);

    this.eventOnBlur = this.onBlur.bindAsEventListener(this);
    Event.observe(this.field, "blur", this.eventOnBlur);
    
    this.onEditTimeout = function() {
      this.onEdit();
    }.bind(this);
     
    this.update();
  },

  destroy: function() {
    Event.stopObserving(this.field, "change",   this.eventOnChange);
    Event.stopObserving(this.field, "keyup",    this.eventOnKeyUp);
    Event.stopObserving(this.field, "keypress", this.eventOnKeyPress);
    Event.stopObserving(this.field, "blur",     this.eventOnBlur);
  },
  
  update: function(event) {
    var norm = this.normalizedValue();
    if (norm) {
      var dialingRule = DialingRule.parse(norm);
      if (dialingRule) this.image.src = "/images/flags/flag_" + dialingRule.filename + ".gif";
	else if( norm[0] == '+' || norm.substr(0, 2) == '00' ) this.image.src = "/images/blank.gif";
      else this.image.src = "/images/flags/flag_" + PhoneNumber.user_flag + ".gif";
    } else {
      this.image.src = this.src;
    }
    var phoneNumber = PhoneNumber.byNorm(norm);
    if (phoneNumber) {
      this.label.innerHTML = '<span class="name">'+phoneNumber.name+'</span><span class="type">'+phoneNumber.type+'</span>';
      this.field.value = phoneNumber.value;
    } else if (norm.length > 6) {
      this.label.innerHTML = '<span class="type" style="width:auto;"><a href="#" onclick="new Ajax.Request(\'/address_book/add_phone_number?phone_number[value]='+this.value()+'\', {evalScripts:true})">'+PhoneNumber.Field.addToAddressBookLabel+'</a></span>';
    } else {
      this.label.innerHTML = '<span class="type" style="width:auto;clear:right;">'+PhoneNumber.Field.defaultLabel+'</span>';
    }
  },

  onKeyPress: function(event) {
    this.options.onKeyPress(this, event);
  },
    
  onKeyUp: function(event) {
    //this.options.onKeyUp(this, event);
    this.setTimeout();
    this.update();
  },
  
  onBlur: function(event) {
    this.clearTimeout();
  },
  
  onChange: function(event) {
    var value = this.normalizedValue();
    if (value.length == 0) this.field.value = '';
    if (value != PhoneNumber.normalize(this.initialValue)) {
      this.options.onChange(this, event);
      this.clearError();
    }
  },

  onEdit: function(event) {
    var value = this.normalizedValue();
    if (value != this.norm) {
      this.norm = value;
      this.options.onEdit(this.field, event);
      this.clearError();
    }
    this.clearTimeout();
  },
  
  clearError: function() {
    if (this.error) {
      Element.remove(this.error);
      this.error = false;
    }
  },
  
  setTimeout: function() {
    if (this.options.timeout) {
      this.clearTimeout();
      this.timeout = window.setTimeout(this.onEditTimeout, this.options.timeout);
    }
  },
  
  clearTimeout: function() {
    if (this.timeout) window.clearTimeout(this.timeout);
  },

  value: function() {
    return this.field.value;
  },
  
  normalizedValue: function() {
    return PhoneNumber.normalize(this.field.value);
  },
  
  setValue: function(value) {
    this.field.value = value || '';
    this.update();
    this.clearError();
  },
  
  serialize: function() {
    Form.Element.serialize(this.field);
  },
  
  focus: function() {
    this.field.focus();
  },
  
  isEmpty: function() {
    return this.field.value.strip().length == 0;
  }  
});

InstanceManager.create(PhoneNumber.Field);


/*--------------------    PhoneNumber.Autocompleter    ---------------------*/

PhoneNumber.Autocompleter = Class.extend( Autocompleter.Base, {
  initialize: function(element, update, options) {
    this.baseInitialize(element, update, options);
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },
  
  phoneNumberSelector: function(instance) {
    var matches = [];
    var phoneNumber, regexp, norm, field;
  
    regexp = instance.getToken().toLowerCase();
    norm = PhoneNumber.dialingRule.normalize(regexp);

    if (norm.length > 3) {
      field = 'norm';
      regexp = new RegExp('^'+norm);
    } else {
      field = 'name';
      regexp = new RegExp('(^| )'+regexp.split(/\b/).join('.*?\\b'), 'gi');          
    }
    
    for (var i=0; i < PhoneNumber.instances.length; i++) {
      phoneNumber = PhoneNumber.instances[i];
      if (phoneNumber[field].match(regexp)) {
        matches.push('<li><span><span class="name">'+phoneNumber.name+'</span> '+
        '<span class="type">'+phoneNumber.type+'</span></span>'+
        '<span class="number">'+phoneNumber.value+'</span></li>');
      }
      if (matches.length == instance.options.choices) break;
    }

    return "<ul>" + matches.join('') + "</ul>";
  },

  setOptions: function(options) {
    this.options = Object.extend({ choices: 10, selector: this.phoneNumberSelector }, options || {});
  },
  
  redraw: function() {
    this.options.onShow(this.element, this.update);
  }
  
});

/*----------------------------- CountrySelect -----------------------------*/

var CountrySelect = Class.create({

  initialize: function(field, image) {
    this.field = $(field);
    this.image = $(image);
    this.eventOnChange = this.update.bindAsEventListener(this);
    Event.observe(this.field, "change", this.eventOnChange, true);
    Event.observe(this.field, "keyup", this.eventOnChange, true);
    this.update();
  },

  destroy: function() {
    Event.stopObserving(this.field, "change", this.eventOnChange, true);
    Event.stopObserving(this.field, "keyup", this.eventOnChange, true);
  },

  update: function(event) {
    dialingRule = DialingRule.byCountryCode(this.field.value);
    var src = "/images/flags/flag_";
    src += (dialingRule ? dialingRule.filename : "united_nations");
    this.image.src = src + ".gif";
  }

});

InstanceManager.create(CountrySelect);


/*----------------------------- Timer -----------------------------*/

var Timer = Class.create({

  initialize: function(offset) {
    this.start(offset);
  },

  value: function() {
    return new Date(new Date() - this.offset);
  },

  start: function(offset) {
    this.offset = new Date() - (offset || 0);
  },

  toString: function(forceMinutes, forceHours) {
    var value = this.value();
    var string = "";
    var scope = value.getUTCHours();
    if (scope > 0 || forceHours) string += this.pad(scope)+':';
    scope = value.getUTCMinutes();
    if (scope > 0 || forceMinutes) string += this.pad(scope)+':';
    scope = value.getUTCSeconds();
    string += this.pad(scope);
    return string;
  },
  
  pad: function(value) {
    return value < 10 ? '0' + value : value;
  }
});


/*----------------------------- TimerElement -----------------------------*/

var TimerElement = Class.create({

  initialize: function(element, offset, forceMinutes, forceHours) {
    this.element = $(element);
    this.timer = new Timer(offset);
    this.forceMinutes = forceMinutes;
    this.forceHours = forceHours;
    this.enable();
  },

  destroy: function() {
    this.disable();
  },

  start: function(offset) {
    this.offset = new Date() - (offset || 0);
  },

  update: function() {
    this.element.innerHTML = this.timer.toString(this.forceMinutes, this.forceHours);
  },

  enable: function() {
    this.interval = setInterval(this.update.bind(this), 1000)
  },

  disable: function() {
    if (this.interval) clearInterval(this.interval);
  }
});

InstanceManager.create(TimerElement);

/*---------------------------- Application --------------------------------*/

On.load(function() {
  ModalBox.initialize();
});





/**
*
*  Javascript sprintf
*  http://www.webtoolkit.info/
*
*
**/

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;
