var Xf = {
  Validate: {
    last: null,
    callback: null
  }
};

Xf.Base = function () {
  this.parameters = {};
};


Xf.Base.prototype = {
  getParameters: function(paramStr) {
    this.parameters = {};

    if (paramStr) {
      // All quoted values are moved to a separate array and replaced
      // with empty quotes ('').

      var quoted = paramStr.match(/\'(\\\'|.)*?\'/g);

      if (quoted) {
	for (var i = 0; i < quoted.length; i++) {
	  // Strip off leading and trailing quotes
	  quoted[i] = quoted[i].replace(/^'|'$/g, '');
	  // Unescape quote characters
	  quoted[i] = quoted[i].replace(/\\\'/g, "'");
	}

	paramStr = paramStr.replace(/\'(\\\'|.)*?\'/g, "''");
      }

      var p = paramStr.split(/\s*,\s*/);
      var m;

      var q = 0;

      for (var i = 0; i < p.length; i++) {
	m = p[i].match(/([\w-]+)\s*(:\s*)?([^,]+)?/);

	if (m && m[1])
	  m[1] = m[1].replace(/_/g, "-");
	  this.parameters[m[1]] = (m[3] != null && m[2] != null && 
				   m[2].length > 0 ? 
				   (m[3] == "''" ? quoted[q++] : m[3]) : true);
      }
    }
  }
};

function XfInitElement(elem)
{
  var value, id;

  if (elem.getAttribute) {
    if (elem.getAttributeNode("xf:validate") ||
	elem.getAttributeNode("xf:effect")) 
    {
      if (value = elem.getAttribute("xf:validate"))
	elem.XfValidate = new XfValidate(elem, value);
      if (value = elem.getAttribute("xf:effect"))
	elem.XfEffect = new XfEffect(elem, value);
    }
  }
}

function DoElement(elem, func)
{
  func(elem);

  if (elem.childNodes) {
    for (var i = 0; i < elem.childNodes.length; i++) {
      DoElement(elem.childNodes[i], func);
    }
  }
}

function ForEachElement(func)
{
  var elem = document.documentElement;

  func(elem);

  if (elem.childNodes) {
    for (var i = 0; i < elem.childNodes.length; i++) {
      DoElement(elem.childNodes[i], func);
    }
  }
}

function XfLoad() 
{
  if (!String.prototype.trim)
    String.prototype.trim = function () {
      return this.replace(/^\s*|\s*$/g, "");
    };

  ForEachElement(XfInitElement);
}

window.onload = XfLoad;


// ---- VALIDATION CALLBACKS -----------------------
Xf.Validate.callback = function (result, message)
{
  var o = this.element.parentNode;
  o.className = (result ? 
	o.className.replace(/alert-red/, "alert-required") :
	o.className.replace(/alert-required/, "alert-red"));

  do {
    o = o.nextSibling;
  } while (o && (!o.tagName || o.tagName.toLowerCase() != 'span'));

  if (o) {
    if (o.firstChild)
      o.removeChild(o.firstChild);

    if (!result && message)
      o.appendChild(document.createTextNode(message));
  }

  return result;
};


// ---- Validation -----------------------

XfTest = function (val) 
{
  this.val = null;
};


XfTest.prototype.initialize = function ()
{
};


XfTestNotEmpty = Class.create();
XfTestNotEmpty.prototype = Object.extend(new XfTest(), {
  message: null,

  initialize: function (val) 
  {
    this.val = val;
  },

  check: function ()
  {
    return (this.val.getData().match(/\S/) != null);
  }
});


XfTestMinLength = Class.create();
XfTestMinLength.prototype = Object.extend(new XfTest(), {
  length: 0,
  message: "Please enter at least #length# characters.",

  initialize: function (val, length)
  {
    this.val = val;
    this.length = length || 1;
    this.message = this.message.replace(/#length#/, this.length);
  },

  check: function ()
  {
    return (this.val.getData().length >= this.length);
  }
});


XfTestMaxLength = Class.create();
XfTestMaxLength.prototype = Object.extend(new XfTest(), {
  length: 0,
  message: "The maximum length of #length# characters " +
	   "for this field has been reached.",

  initialize: function (val, length)
  {
    this.val = val;
    this.length = length || 1;
    this.message = this.message.replace(/#length#/, this.length);
  },

  check: function ()
  {
    return (this.val.getData().length <= this.length);
  }
});


XfTestMinValue = Class.create();
XfTestMinValue.prototype = Object.extend(new XfTest(), {
  value: "0",
  message: "Minimum accepted value is #value#.",

  initialize: function (val, value)
  {
    this.val = val;
    this.value = value || "0";
    this.message = this.message.replace(/#value#/, this.value);
  },

  check: function ()
  {
    var v, min;

    v = parseFloat(this.val.getData().replace(/\$/g, ""));
    min = parseFloat(this.value.replace(/\$/g, ""));

    return (v >= min);
  }
});


XfTestMaxValue = Class.create();
XfTestMaxValue.prototype = Object.extend(new XfTest(), {
  value: "0",
  message: "Maximum accepted value is #value#.",

  initialize: function (val, value)
  {
    this.val = val;
    this.value = value || "0";
    this.message = this.message.replace(/#value#/, this.value);
  },

  check: function ()
  {
    var v, max;

    v = parseFloat(this.val.getData().replace(/\$/g, ""));
    max = parseFloat(this.value.replace(/\$/g, ""));

    return (v <= max);
  }
});


XfTestEmail = Class.create();
XfTestEmail.prototype = Object.extend(new XfTest(), {
  message: "Please enter a valid email.",

  initialize: function (val)
  {
    this.val = val;
  },

  check: function ()
  {
    return (/^[a-zA-Z0-9_\.\-\+]+\@([a-zA-Z0-9\-]+\.)+[a-zA-Z0-9]{2,4}$/.test(this.val.getData()));
  }
});


XfTestEqualTo = Class.create();
XfTestEqualTo.prototype = Object.extend(new XfTest(), {
  other: null,
  message: "Entries do not match.",

  initialize: function (val, other)
  {
    this.val = val;
    this.other = other;
  },

  getOtherData: function ()
  {
    if (this.other.tagName) {
      if (this.other.tagName.toLowerCase() == 'input') {
	if (this.other.type.toLowerCase() == 'text')
	  return this.other.value;
	else if (this.other.type.toLowerCase() == 'password')
	  return this.other.value;
      }
      else if (this.other.tagName.toLowerCase() == 'textarea') {
	return this.other.value;
      }
    }
  },

  check: function ()
  {
    return (this.val.getData() == this.getOtherData());
  }
});

XfTestAjax = Class.create();
XfTestAjax.prototype = Object.extend(new XfTest(), {
  message: "",
  ajax: null,
  validating: false,
  url: null,

  initialize: function (val, url)
  {
    this.val = val;
    this.url = url;
  },

  check: function ()
  {
    var url;
    var timer;

    url = this.url.replace(/#value#/, this.val.getData());

    var u, location, params;

    u = url.split(/\?/);

    location = u[0];
    params = u[1];

    if (this.validating)
      this.ajax.transport.abort();

    this.validating = true;
  
    this.ajax = new Ajax.Request(location,
	{ method: "get",
	  parameters: params,
	  onComplete: (function (req) {
	    var response;

	    response = eval("(" + req.responseText + ")");

	    this.validating = false;

	    if (this.val.onValidate && this.val.valid != false)
	      // FIXME - which message should go first?
	      this.val.onValidate(response.result, response.message 
				  || this.val.message);
	  }).bind(this)
	});
    
    return null;
  }
});


XfValidate = Class.create();
XfValidate.prototype = Object.extend(new Xf.Base(), {
  initialize: function(element, value) {
    this.element = element;
    this.tests = [];
    this.valid = true;
    this.next = null;
    this.message = null;
    
    if (this.element.tagName.toLowerCase() == 'input' ||
	this.element.tagName.toLowerCase() == 'textarea') 
    {
      this.getData = function () { return this.element.value; };
      Event.observe(this.element, 'change', this.Validate.bind(this), false);
      if (this.element.type.toLowerCase() != 'file') {
	Event.observe(this.element, 'blur', this.Validate.bind(this), false);
	Event.observe(this.element, 'keyup', 
		      (function () {
			if (!this.valid)
			  this.Validate();
		      }).bind(this), false);
      }
    }
    
    if (Xf.Validate.last && Xf.Validate.last.element.form == this.element.form)
      Xf.Validate.last.next = this;
    else
      this.element.form.onsubmit = this.ValidateForm.bind(this);
    
    Xf.Validate.last = this;

    if (value != null) {
      this.getParameters(value);

      this.message = this.parameters["message"];
      this.onValidate = this.parameters["onvalidate"];

      for (p in this.parameters) {
	var test = null;

	switch (p) {
	case "not-empty":
	  test = new XfTestNotEmpty(this);
	  break;
	case "min-length":
	  test = new XfTestMinLength(this, parseInt(this.parameters["min-length"]));
	  break;
	case "max-length":
	  test = new XfTestMaxLength(this, parseInt(this.parameters["max-length"]));
	  break;
	case "min-value":
	  test = new XfTestMinValue(this, this.parameters["min-value"]);
	  break;
	case "max-value":
	  test = new XfTestMaxValue(this, this.parameters["max-value"]);
	  break;
	case "email":
	  test = new XfTestEmail(this);
	  break;
	case "equal-to":
	  if ($(this.parameters["equal-to"]))
	    test = new XfTestEqualTo(this, $(this.parameters["equal-to"]));
	  else
	    continue;
	  break;
	case "ajax":
	  test = new XfTestAjax(this, this.parameters["ajax"]);
	  break;
	}

	if (test)
	  this.tests[this.tests.length] = test;
      }
    }
    
    if (this.onValidate)
      this.onValidate = new Function("valid", "message", this.onValidate +
				     "(valid, message)");
    else if (Xf.Validate.callback) {
      // Bind the global callback function
      this.onValidate = Xf.Validate.callback.bind(this);;
    }
  },

		
  Validate: function () {
    for (var i = 0; i < this.tests.length; i++) {
      if ((this.valid = this.tests[i].check()) != null)
        if (this.onValidate)
      //          if (!this.onValidate(this.valid, (this.message || this.tests[i].message)))
      //            return false;
          if (!this.onValidate(this.valid, (this.message || this.tests[i].message)))
            return true;
    }
    
    return true;
  },


  ValidateForm: function () {
    var val = this;
    
    while (val)
      if (!val.Validate() || !val.valid) {
	if (val.element.focus) {
	  Element.scrollTo(val.element);
	  val.element.focus();
	}
	return false;
      }
      else
	val = val.next;

    return true;
  }
});