'use strict';

const eventDefinitionsStamp = require('../event-handlers/event-definitions-stamp');
const ruleStamp = require('./rule');
const ruleSetStamp = require('./rule-set');
const validityChecksStamp = require('../utilities/validity-checks');

const stampit = require('stampit');
const $ = require('jquery');

const textDefaultRulesStamp = stampit()
  .methods({

    /**
     * When a field is required, it's trimmed value must not be empty.
     *
     * @access      public
     * @memberOf    {textDefaultRulesStamp}
     * @since       0.1.0
     *
     * @param       {jQuery}        $element        The element upon which to apply the rule. Required.
     * @param       {String}        attribute       The data attribute which may contain additional data. Required.
     *
     * @returns     {Boolean}
     */
    dataFvRequired($element, attribute) {
      return $element.val().trim() !== '';
    },

    /**
     * When a field has a minimum length, its trimmed value must be greater than
     * or equal to the specified attribute value.
     *
     * @access      public
     * @memberOf    {textDefaultRulesStamp}
     * @since       0.1.0
     *
     * @param       {jQuery}        $element        The element upon which to apply the rule. Required.
     * @param       {String}        attribute       The data attribute which may contain additional data. Required.
     *
     * @returns     {Boolean}
     */
    dataFvMinLength($element, attribute) {
      const minChars = $element.attr(attribute);

      return (
        $element.val().trim().length >= parseInt(minChars)
      );
    },

    /**
     * When a field has a maximum length, its trimmed value must be less than
     * or equal to the specified attribute value.
     *
     * @access      public
     * @memberOf    {textDefaultRulesStamp}
     * @since       0.1.0
     *
     * @param       {jQuery}        $element        The element upon which to apply the rule. Required.
     * @param       {String}        attribute       The data attribute which may contain additional data. Required.
     *
     * @returns     {Boolean}
     */
    dataFvMaxLength($element, attribute) {
      const maxChars = $element.attr(attribute);

      return (
        $element.val().trim().length <= parseInt(maxChars)
      );
    },

    /**
     * When a field has a format specified, the value will be checked against
     * that specific format. Some predefined formats include `zip5`, `zip4`,
     * `zip`, `email`, `phone`. You can specify a Regular Expression string
     * if you prefer.
     *
     * @access      public
     * @memberOf    {textDefaultRulesStamp}
     * @since       0.1.0
     *
     * @param       {jQuery}        $element        The element upon which to apply the rule. Required.
     * @param       {String}        attribute       The data attribute which may contain additional data. Required.
     *
     * @returns     {Boolean}       valid
     */
    dataFvFormat($element, attribute) {
      const format = $element.attr(attribute);
      let valid = true;
      const trimmedVal = $element.val().trim();
      switch (format) {
        case 'zip5' :
        case 'zip4' :
        case 'zip' :
          valid = this.isValidNumeric(trimmedVal);
          break;
        case 'email' :
          valid = this.isValidEmail(trimmedVal);
          break;
        case 'phone' :
          valid = this.isValidPhone(trimmedVal);
          break;
        default:
          // If it looks like a regex, test the value against it.
          const formatMatch = format.match(/^\/(.*)\/$/);
          if (formatMatch !== null) {
            const formatAsRegex = new RegExp(formatMatch[1]);
            valid = formatAsRegex.test(trimmedVal);
          }
      }

      return valid;
    },

    /**
     * When a field must match another field, check that the value of
     * both fields is identical.
     *
     * @throws      Error           iff the other field to match is not in the DOM
     * @access      public
     * @memberOf    {textDefaultRulesStamp}
     * @since       0.1.0
     *
     * @param       {jQuery}        $element        The element upon which to apply the rule. Required.
     * @param       {String}        attribute       The data attribute which may contain additional data. Required.
     *
     * @returns     {Boolean}       valid
     */
    dataFvMatchField($element, attribute) {
      const matchOtherFieldID = $element.attr(attribute);
      const $otherField = $(`#${matchOtherFieldID}`);
      if ($otherField.length === 0) {
        throw new Error(
          `Expected an element with an ID equal to "${matchOtherFieldID}" on this form.`
        );
      }

      const trimmedVal = $element.val().trim();
      const otherFieldTrimmedVal = $otherField.val().trim();
      let valid = (trimmedVal === otherFieldTrimmedVal) ? 1 : 0;

      if (parseInt($otherField.attr('data-fv-required')) === 1) {
        // Set the valid flag on the matched field - TODO - trigger an event that $otherField handles
        $otherField.trigger(
          this.getSetValidationFlagEventName(), {elementIsValid : valid}
        );
      }

      return valid;
    }
  })
  .init(function() {

    /**
     * The default rules Formation against which elements will be checked
     * during validation.
     *
     * @private
     * @access      private
     * @const
     * @type        Array
     * @memberOf    {textDefaultRulesStamp}
     * @since       0.1.0
     */
    const __rules = [
      ruleStamp({
        name : 'required',
        callback : this.dataFvRequired
      }),
      ruleStamp({
        name : 'min-length',
        callback : this.dataFvMinLength
      }),
      ruleStamp({
        name : 'max-length',
        callback : this.dataFvMaxLength
      }),
      ruleStamp({
        name : 'format',
        callback : this.dataFvFormat
      }),
      ruleStamp({
        name : 'match-field',
        callback : this.dataFvMatchField
      })
    ];

    /**
     * Return the value of the private `__rules` object.
     *
     * @access      public
     * @memberOf    {textDefaultRulesStamp}
     * @since       0.1.0
     *
     * @returns     {Array}     __rules     The default rules we've defined.
     */
    this.getRules = () => {
      return __rules;
    };
  });

module.exports = ruleSetStamp.compose(textDefaultRulesStamp);

@@ -382,33 +382,65 @@
   * 
-When a field is required,
+The default text-based input elements rule is that%0A     *
  it'
@@ -860,24 +860,23 @@
   dataFv
-Required
+Default
 ($elemen
@@ -5272,24 +5272,37 @@
  The default
+, pre-defined
  rules Forma
@@ -5310,53 +5310,18 @@
 ion 
-against which elements 
 will 
-be 
 check
-ed%0A     *
  dur
@@ -5334,16 +5334,52 @@
 lidation
+%0A     * of text-based input elements
 .%0A     *
@@ -5597,24 +5597,23 @@
 name : '
-required
+default
 ',%0A     
@@ -5637,24 +5637,23 @@
 s.dataFv
-Required
+Default
 %0A      %7D

Change the `required` rule of the `text-default-rules` rule set to `default` with the intent that it will always be called without needing to be added to the DOM. Other element-type rule sets will also use this pattern.