jQuery(document).ready(function ($) {

  /* MASKING FUNCTIONALITY */

  /* Formatters */
  var currencyFormatter = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
    minimumFractionDigits: 2
  });

  var percentFormatter = new Intl.NumberFormat('en-US', {
    style: 'percent',
    maximumFractionDigits: 2
  });

  /* Default values for masked inputs */
  var defaults = {
    min: 0,
    max: Infinity,
    allowBlank: false,
    checkOthers: false
  }

  /* Restricts input for the given textbox to the given inputFilter */
  function setInputFilter(element, minimun, formatter, checkOthers, inputFilter) {
    ["input", "keyup", "mouseup", "select", "drop"].forEach(function (event) {
      element.addEventListener(event, function () {
        if (checkOthers) {
          checkOtherInputs(element, formatter);
        }
        if (this.value.length === 0 || (numberFormat(this.value) === 0 &&
          isAllowedValue(this.value))) {
          if (isEmpty(this.value)) {
            this.oldValue = this.value;
          }
          return;
        }
        if (inputFilter(this.value)) {
          this.oldValue = this.value;
          this.oldSelectionStart = this.selectionStart;
          this.oldSelectionEnd = this.selectionEnd;
          if (this.value < minimun) {
            $(element).css('color', 'red');
            return;
          }
          $(element).removeAttr("style");
        } else if (this.hasOwnProperty("oldValue")) {
          this.value = this.oldValue;
          this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
        } else {
          this.value = "";
        }
      });
    });
  }

  /* Get data options for the masked input */
  dataOptions = function ($element) {
    var options = {
      min: $element.data('min'),
      max: $element.data('max'),
      allowBlank: $element.data('allow-blank'),
      checkOthers: $element.data('check-others')
    };

    var keys = Object.keys(options);

    for (var i in keys) {
      var key = keys[i];
      if (typeof (options[key]) === 'undefined') {
        delete options[key];
      }
    }
    return options;
  };

  function checkOtherInputs(element, formatter) {
    var otherElements = $("." + element.className);
    if (otherElements.length > 1) {
      otherElements.each(function (i) {
        if (otherElements[i] != element) {
          otherElements[i].value = formatter(otherElements[i].value);
        }
      });
    }
  }
  
  /* Check string (input value) is empty */
  function isEmpty(str) {
    return !str.replace(/\s+/, '').length;
  }

  /* Set minimun value if current value is less that minimun option */
  function setDefaultMinimum(element, minimum, formatter, allowBlank) {
    var value = numberFormat(element.value);
    if (isEmpty(element.value) && allowBlank) {
      return;
    }
    if (isEmpty(element.value) || value < minimum) {
      element.value = formatter(minimum);
      $(element).removeAttr("style");
    }
  }

  /* Transform string value into float number */
  function toFloatNumber(value) {
    value = value.toString().replace(/[^\d\.]/g, '');
    return value == "" ? 0 : parseFloat(value);
  }

  /* Transform value into number format */
  function numberFormat(value) {
    value = (value == "" || value == null) ? "0" : value;
    return toFloatNumber(value);
  }

  /* Transform value into currency format */
  function currencyFormat(amount) {
    return currencyFormatter.format(toFloatNumber(amount));
  }

  /* Transform value into percentage format */
  function percentFormat(percentage) {
    return percentFormatter.format(toFloatNumber(percentage) / 100);
  }

  /* Check value has percentage format */
  function isPercentFormat(value) {
    return /\d*[.]?\d{0,2}\%?$/.test(value);
  }

  /* Check value has currency format */
  function isCurrencyFormat(value) {
    return /^\$?\d*[.]?\d{0,2}$/.test(value);
  }

  /* Check value is allowed (even for currency or percentage) */
  function isAllowedValue(value) {
    return /^\$?[0-9]*\.?[0-9]*\%?$/.test(value);
  }

  /* Add selected mask to inputs */
  function maskField(elements, formatter, filter, options) {
    return elements.each(function () {
      var input_element = $(this);

      var settings = $.extend({}, defaults, dataOptions(input_element), options);

      /* Call to restricts input for the given textbox to the given inputFilter */
      setInputFilter(this, settings.min, formatter, settings.checkOthers, function (value) {
        return filter(value) && numberFormat(value) <= settings.max;
      });

      /* Call to set minimun value if current value is less that minimun option */
      setDefaultMinimum(this, settings.min, formatter, settings.allowBlank);

      if (!(isEmpty(this.value) && settings.allowBlank)) {
        this.value = formatter(this.value);
      }

      /* Add event to change value into number on focus */
      this.addEventListener("focus", function () {
        if ((isEmpty(this.value) && settings.allowBlank)) {
          return;
        }
        this.value = numberFormat(this.value) === 0 || numberFormat(this.value) === settings.min ? "" : numberFormat(this.value);
      });

      /* Add event to change value into masked value on blur */
      this.addEventListener("blur", function () {
        if ((isEmpty(this.value) && settings.allowBlank)) {
          return;
        }
        this.value = formatter(this.value);
        setDefaultMinimum(this, settings.min, formatter, settings.allowBlank);
      });

      /* Add event to unmask value on submit form related with input field */
      $(this).closest('form').on('submit', function () {
        if ((isEmpty(input_element.val()) && settings.allowBlank)) {
          return;
        }
        input_element.val(numberFormat(input_element.val()));
        return true;
      });
      this.dispatchEvent(new Event('input'));
    });
  }

  /* Call funtion to add percentage mask with options */
  $.fn.maskPercent = function (options) {
    maskField(this, percentFormat, isPercentFormat, options);
    return this;
  };

  /* Call funtion to add currency mask with options */
  $.fn.maskCurrency = function (options) {
    maskField(this, currencyFormat, isCurrencyFormat, options);
    return this;
  };

  /* Call funtion to change masked input into number */
  $.fn.numberFormat = function () {
    return numberFormat(
      this.val());
  };

});
