function convertPHPRegex(pattern) {
  var begin = pattern.substr(0,1);
  var regexp = pattern.substring(begin.length, pattern.lastIndexOf(begin))
  var opt = pattern.substr(pattern.lastIndexOf(begin)+1);
  //checking for extended syntax (not handled in javascript
  if (opt.indexOf('x') > -1) {
    regexp = regexp.replace(/#.*?$/mg, '');
    regexp = regexp.replace(/\s+(?![^\[]*])/g, '');
    opt = opt.replace('x', '');
  }
  return new RegExp(regexp,opt);
}

//validators
function sfValidatorError(validator, code, arguments) {
  this.validator = validator;
  this.code = code;
  this.arguments = arguments || {};
  this.name = 'sfValidatorError';
  var _messageFormat = this.validator.getMessage(this.code);
  for (var i in this.arguments) {
    _messageFormat = _messageFormat.replace(new RegExp("%"+i+"%", "g"), this.arguments[i]);
  }
  this.message = _messageFormat;
}

sfValidatorError.prototype = new Error;

function sfValidatorBase(options, message) {
  this.init(options, message);
}
sfValidatorBase.prototype.init = function(options, messages) {
  this.options = options || {};
  this.messages = messages || {};
  this.empty = ['', null, undefined];
};
  
sfValidatorBase.prototype.validate = function(value) {
  if (this.options['trim'] && typeof value == 'string') {
    value = jQuery.trim(value);
  }
  if (this.isEmpty(value)) {
    if (this.options['required']) {
      throw new sfValidatorError(this, 'required'); 
    } else {
      return this.options['empty_value'];
    }
  }
  return this.doValidate(value);
};
  
sfValidatorBase.prototype.doValidate = function(value) {
  return value;
};

sfValidatorBase.prototype.isEmpty = function(value) {
  if (value === undefined) {
    return true;
  }
  for (var i in this.empty) {
    if (value === this.empty[i]) {
      return true;
    } 
  }
  return false;
};
  
sfValidatorBase.prototype.getMessage = function(type) {
  return this.messages[type];
};
  
sfValidatorBase.prototype.merge = function() {
  var target = arguments[0] || {}, i = 1, length = arguments.length;
  
  for ( ; i < length; i++ ) {
    var options = arguments[i];
    for ( var name in options ) {
      target[name] = options[name];
    }
  }
  return target;
};

function sfValidatorAnd(options, messages) {
  sfValidatorBase.call(this, options, messages);
  this.validators = [];
}
sfValidatorAnd.prototype = new sfValidatorBase;

sfValidatorAnd.prototype.addValidator = function(validator) {
  this.validators.push(validator);
}
sfValidatorAnd.prototype.doValidate = function(value) {
  var errors = [];
  var clean = value;
  for (var i in this.validators) {
    try {
      clean = this.validators[i].validate(clean);
    } catch (e) {
      errors.push(e);
    }
  }
  if (errors.length > 0) {
    if (this.messages['invalid']) {
      throw new sfValidatorError(this, 'invalid', {value: value});
    } else {
      throw errors[0];
    }
  } 
  return clean;
}

function sfValidatorString(options, messages) {
  sfValidatorBase.call(this, options, messages);
}
sfValidatorString.prototype = new sfValidatorBase;

sfValidatorString.prototype.doValidate = function(value) {
  clean = value.toString();
  if (this.options['max_length'] && clean.length > this.options['max_length']) {
    throw new sfValidatorError(this, 'max_length', {value: clean, max_length: this.options['max_length']});
  }
  if (this.options['min_length'] && clean.length < this.options['min_length']) {
    throw new sfValidatorError(this, 'min_length', {value: clean, min_length: this.options['min_length']});
  }
  return clean;
};

function sfValidatorRegex(options, messages) {
  sfValidatorBase.call(this, options, messages);
  if (this.options['pattern']) {
    this.options['regexp'] = convertPHPRegex(this.options['pattern']);
  }
}

sfValidatorRegex.prototype = new sfValidatorBase;

sfValidatorRegex.prototype.doValidate = function(value) {
  clean = value.toString();
  if (clean.match(this.options['regexp']) == null) {
    throw new sfValidatorError(this, 'invalid', {value: clean});
  }
  return clean;
};

function sfValidatorEmail(options, messages) {
  sfValidatorRegex.call(this, options, messages);
}
sfValidatorEmail.prototype = new sfValidatorRegex;

function sfValidatorUrl(options, messages) {
  sfValidatorRegex.call(this, options, messages);
}
sfValidatorUrl.prototype = new sfValidatorRegex;

function sfValidatorTime(options, messages) {
  sfValidatorBase.call(this, options, messages);
}
sfValidatorTime.prototype = new sfValidatorBase;

sfValidatorTime.prototype.doValidate = function(value) {
  if (typeof value == "object") {
    var empty = false;
    var invalid = false;
    if (this.isEmpty(value[0])) {
      if (this.options['required']) {
        throw new sfValidatorError(this, 'required'); 
      } 
      empty = true;
    }
    if (this.isEmpty(value[1])) {
      if (this.options['required']) {
        throw new sfValidatorError(this, 'required'); 
      }
      empty = true;
    } else if (empty) {
      invalid = true; 
    }
    if (value[2] !== null) { 
      if (this.isEmpty(value[2])) {
        if (this.options['required']) {
          throw new sfValidatorError(this, 'required'); 
        }
        empty = true;
      } else if (empty) {
        invalid = true; 
      }
    }
    
    if (invalid) {
      throw new sfValidatorError(this, 'invalid', {value: value.join(':')});
    }
    if (value[0] != "" && value[0].match(/1?[0-9]|2[0-3]/) == null) {
      throw new sfValidatorError(this, 'invalid', {value: value.join(':')});
    }
    if (value[1] != "" && value[1].match(/[1-5]?[0-9]/) == null) {
      throw new sfValidatorError(this, 'invalid', {value: value.join(':')});
    }
  } 
  return value
}
//widget wrappers
function kreoJQueryWidgetWrapper(validator, options) {
  this.validator = validator;
  this.options = options;
  this.element = undefined;
}

kreoJQueryWidgetWrapper.prototype.validate = function(value) {
  return this.validator.validate(value);
}

kreoJQueryWidgetWrapper.prototype.initElement = function() {
  this.element = jQuery('#' + this.options.id);
}

kreoJQueryWidgetWrapper.prototype.getValue = function() {
  if (this.element == undefined) {  
    this.initElement(); 
  }
  return this.element.val();
}

kreoJQueryWidgetWrapper.prototype.setValue = function(value) {
  if (this.element == undefined) {  
    this.initElement(); 
  }
  this.element.val(value);
}

kreoJQueryWidgetWrapper.prototype.onError = function(e) {
  if (this.element == undefined) {  
    this.initElement(); 
  }
  alert(e.message);
  this.element.focus();
  return false;
}

function kreoJQueryTimeWidgetWrapper(validator, options) {
  kreoJQueryWidgetWrapper.call(this, validator, options);
}

kreoJQueryTimeWidgetWrapper.prototype = new kreoJQueryWidgetWrapper;
kreoJQueryTimeWidgetWrapper.prototype.initElement = function() {
  this.element = jQuery('#' + this.options.id + '_hour');
  this.element_minute = jQuery('#' + this.options.id + '_minute');
  this.element_second = jQuery('#' + this.options.id + '_second');
}

kreoJQueryTimeWidgetWrapper.prototype.getValue = function() {
  if (this.element == undefined) {  
    this.initElement(); 
  }
  return [this.element.val(), this.element_minute.val(), this.element_second.val ? this.element_second.val() : null];
}

kreoJQueryTimeWidgetWrapper.prototype.setValue = function(value) {
  if (this.element == undefined) {
    this.initElement(); 
  }
  this.element.val(value[0]);
  this.element_minute.val(value[1]);
  if (this.element_second.val) {
    this.element_second.val(value[2]);
  }
}

function kreoJQueryCheckboxWidgetWrapper(validator, options) {
  kreoJQueryWidgetWrapper.call(this, validator, options);
}

kreoJQueryCheckboxWidgetWrapper.prototype = new kreoJQueryWidgetWrapper;
kreoJQueryCheckboxWidgetWrapper.prototype.getValue = function() {
  if (this.element == undefined) {  
    this.initElement(); 
  }
  if (this.element.attr("checked")) {
    return this.element.val();
  }
  return null;
}

kreoJQueryCheckboxWidgetWrapper.prototype.setValue = function(value) {
  if (this.element == undefined) {
    this.initElement(); 
  }
  if (value) {
    this.element.attr("checked", true);
  }
}

function kreoJQueryFileWidgetWrapper(validator, options) {
  kreoJQueryWidgetWrapper.call(this, validator, options);
}
kreoJQueryFileWidgetWrapper.prototype = new kreoJQueryWidgetWrapper;

kreoJQueryFileWidgetWrapper.prototype.setValue = function(value) {
  //cannot set value to file input
}

//validator setup
function kreoValidatorSetup() {
  this.validators = [];
} 

kreoValidatorSetup.prototype.addValidator = function(validator) {
  if (validator.validator == undefined) throw new Error('validator property undefined');
  this.validators[this.validators.length] = validator;
};

kreoValidatorSetup.prototype.validate = function() {
  for (var i in this.validators) {
    try {
      var value = this.validators[i].getValue();
      var clean = this.validators[i].validate(value);
    } catch(e) {
      if (e.name == 'sfValidatorError') {
        return this.validators[i].onError(e);
      }
      throw e;
    }
    this.validators[i].setValue(clean);
  }
  return true;
}

function kreoTinyMceWidgetWrapper(validator, options) {
  kreoJQueryWidgetWrapper.call(this, validator, options);
}

kreoTinyMceWidgetWrapper.prototype = new kreoJQueryWidgetWrapper;

kreoTinyMceWidgetWrapper.prototype.initElement = function() {
  tinymce.EditorManager.triggerSave();
  this.element = tinymce.EditorManager.get(this.options.id);
}

kreoTinyMceWidgetWrapper.prototype.getValue = function() {
  if (this.element == undefined) {  
    this.initElement(); 
  }
  return this.element.getContent();
}

kreoTinyMceWidgetWrapper.prototype.setValue = function(value) {
//leaving cleanup to tinyMCE
}
