(function() {
  define(["underscore", "./dom", "./events", "./utils", "./messages", "./fields"], function(_, dom, events, utils, messages) {
    var REGEXP_META, decimal, emailRE, grouping, matches, minus, parseNumber, translate;
    REGEXP_META = "t5:regular-expression";
    minus = messages("decimal-symbols.minus");
    grouping = messages("decimal-symbols.group");
    decimal = messages("decimal-symbols.decimal");
    parseNumber = function(input, isInteger) {
      var accept, acceptDigitOnly, any, canonical, ch, decimalPortion, i, leadingMinus, len, mustBeDigit, ref, state;
      canonical = "";
      accept = function(ch) {
        return canonical += ch;
      };
      acceptDigitOnly = function(ch) {
        if (ch < "0" || ch > "9") {
          throw new Error(messages("core-input-not-numeric"));
        }
        accept(ch);
      };
      mustBeDigit = function(ch) {
        acceptDigitOnly(ch);
        return any;
      };
      decimalPortion = function(ch) {
        acceptDigitOnly(ch);
        return decimalPortion;
      };
      any = function(ch) {
        switch (ch) {
          case grouping:
            return mustBeDigit;
          case decimal:
            if (isInteger) {
              throw new Error(messages("core-input-not-integer"));
            }
            accept(".");
            return decimalPortion;
          default:
            return mustBeDigit(ch);
        }
      };
      leadingMinus = function(ch) {
        if (ch === minus) {
          accept("-");
          return mustBeDigit;
        } else {
          return any(ch);
        }
      };
      state = leadingMinus;
      ref = utils.trim(input);
      for (i = 0, len = ref.length; i < len; i++) {
        ch = ref[i];
        state = state(ch);
      }
      return Number(canonical);
    };
    matches = function(input, re) {
      var groups;
      groups = input.match(re);
      if (groups === null) {
        return false;
      }
      return groups[0] === input;
    };
    emailRE = new RegExp("[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?");
    translate = function(field, memo, isInteger) {
      var e, error, result;
      try {
        result = parseNumber(memo.value, isInteger);
        if (_.isNaN(result)) {
          throw messages("core-input-not-numeric");
        }
        return memo.translated = result;
      } catch (error) {
        e = error;
        memo.error = (field.attr("data-translation-message")) || e.message || "ERROR";
        return false;
      }
    };
    dom.onDocument(events.field.optional, "[data-optionality=required]", function(event, memo) {
      if (utils.isBlank(memo.value)) {
        return memo.error = (this.attr("data-required-message")) || "REQUIRED";
      }
    });
    dom.onDocument(events.field.translate, "[data-translation=numeric]", function(event, memo) {
      return translate(this, memo, false);
    });
    dom.onDocument(events.field.translate, "[data-translation=integer]", function(event, memo) {
      return translate(this, memo, true);
    });
    dom.onDocument(events.field.validate, "[data-validate-min-length]", function(event, memo) {
      var min;
      min = parseInt(this.attr("data-validate-min-length"));
      if (memo.translated.length < min) {
        memo.error = (this.attr("data-min-length-message")) || "TOO SHORT";
        return false;
      }
    });
    dom.onDocument(events.field.validate, "[data-validate-max-length]", function(event, memo) {
      var max;
      max = parseInt(this.attr("data-validate-max-length"));
      if (memo.translated.length > max) {
        memo.error = (this.attr("data-max-length-message")) || "TOO LONG";
        return false;
      }
    });
    dom.onDocument(events.field.validate, "[data-validate-max]", function(event, memo) {
      var max;
      max = parseInt(this.attr("data-validate-max"));
      if (memo.translated > max) {
        memo.error = (this.attr("data-max-message")) || "TOO LARGE";
        return false;
      }
    });
    dom.onDocument(events.field.validate, "[data-validate-min]", function(event, memo) {
      var min;
      min = parseInt(this.attr("data-validate-min"));
      if (memo.translated < min) {
        memo.error = (this.attr("data-min-message")) || "TOO SMALL";
        return false;
      }
    });
    dom.onDocument(events.field.validate, "[data-validate-email]", function(event, memo) {
      if (!(matches(memo.translated, emailRE))) {
        memo.error = (this.attr("data-email-message")) || "INVALID EMAIL";
        return false;
      }
    });
    dom.onDocument(events.field.validate, "[data-validate-regexp]", function(event, memo) {
      var re;
      re = this.meta(REGEXP_META);
      if (!re) {
        re = new RegExp(this.attr("data-validate-regexp"));
        this.meta(REGEXP_META, re);
      }
      if (!(matches(memo.translated, re))) {
        memo.error = (this.attr("data-regexp-message")) || "INVALID";
        return false;
      }
    });
    dom.onDocument(events.field.validate, "[data-expected-status=checked]", function(event, memo) {
      if (!memo.value) {
        return memo.error = (this.attr("data-checked-message")) || "MUST BE CHECKED";
      }
    });
    dom.onDocument(events.field.validate, "[data-expected-status=unchecked]", function(event, memo) {
      if (memo.value) {
        return memo.error = (this.attr("data-checked-message")) || "MUST NOT BE CHECKED";
      }
    });
    return {
      parseNumber: parseNumber
    };
  });

}).call(this);
