// Touch-spin JS

(function ($) {
  "use strict";
  var _currentSpinnerId = 0;
  function _scopedEventName(name, id) {
    return name + ".touchspin_" + id;
  }
  function _scopeEventNames(names, id) {
    return $.map(names, function (name) {
      return _scopedEventName(name, id);
    });
  }
  $.fn.TouchSpin = function (options) {
    if (options === "destroy") {
      this.each(function () {
        var originalinput = $(this),
          originalinput_data = originalinput.data();
        $(document).off(
          _scopeEventNames(
            [
              "mouseup",
              "touchend",
              "touchcancel",
              "mousemove",
              "touchmove",
              "scroll",
              "scrollstart",
            ],
            originalinput_data.spinnerid
          ).join(" ")
        );
      });
      return;
    }
    var defaults = {
      min: 0,
      max: 100,
      initval: "",
      replacementval: "",
      step: 1,
      decimals: 0,
      stepinterval: 100,
      forcestepdivisibility: "round", // none | floor | round | ceil
      stepintervaldelay: 500,
      verticalbuttons: false,
      verticalupclass: "fa fa-chevron-up",
      verticaldownclass: "fa fa-chevron-down",
      prefix: "",
      postfix: "",
      prefix_extraclass: "",
      postfix_extraclass: "",
      booster: true,
      boostat: 10,
      maxboostedstep: false,
      mousewheel: true,
      buttondown_class: "btn btn-light btn-square",
      buttonup_class: "btn btn-light btn-square",
      buttondown_txt: "-",
      buttonup_txt: "+",
    };
    var attributeMap = {
      min: "min",
      max: "max",
      initval: "init-val",
      replacementval: "replacement-val",
      step: "step",
      decimals: "decimals",
      stepinterval: "step-interval",
      verticalbuttons: "vertical-buttons",
      verticalupclass: "vertical-up-class",
      verticaldownclass: "vertical-down-class",
      forcestepdivisibility: "force-step-divisibility",
      stepintervaldelay: "step-interval-delay",
      prefix: "prefix",
      postfix: "postfix",
      prefix_extraclass: "prefix-extra-class",
      postfix_extraclass: "postfix-extra-class",
      booster: "booster",
      boostat: "boostat",
      maxboostedstep: "max-boosted-step",
      mousewheel: "mouse-wheel",
      buttondown_class: "button-down-class",
      buttonup_class: "button-up-class",
      buttondown_txt: "button-down-txt",
      buttonup_txt: "button-up-txt",
    };
    return this.each(function () {
      var settings,
        originalinput = $(this),
        originalinput_data = originalinput.data(),
        container,
        elements,
        value,
        downSpinTimer,
        upSpinTimer,
        downDelayTimeout,
        upDelayTimeout,
        spincount = 0,
        spinning = false;
      init();
      function init() {
        if (originalinput.data("alreadyinitialized")) {
          return;
        }
        originalinput.data("alreadyinitialized", true);
        _currentSpinnerId += 1;
        originalinput.data("spinnerid", _currentSpinnerId);
        if (!originalinput.is("input")) {
          console.log("Must be an input.");
          return;
        }
        _initSettings();
        _setInitval();
        _checkValue();
        _buildHtml();
        _initElements();
        _hideEmptyPrefixPostfix();
        _bindEvents();
        _bindEventsInterface();
        elements.input.css("display", "block");
      }
      function _setInitval() {
        if (settings.initval !== "" && originalinput.val() === "") {
          originalinput.val(settings.initval);
        }
      }
      function changeSettings(newsettings) {
        _updateSettings(newsettings);
        _checkValue();
        var value = elements.input.val();
        if (value !== "") {
          value = Number(elements.input.val());
          elements.input.val(value.toFixed(settings.decimals));
        }
      }
      function _initSettings() {
        settings = $.extend(
          {},
          defaults,
          originalinput_data,
          _parseAttributes(),
          options
        );
      }
      function _parseAttributes() {
        var data = {};
        $.each(attributeMap, function (key, value) {
          var attrName = "bts-" + value + "";
          if (originalinput.is("[data-" + attrName + "]")) {
            data[key] = originalinput.data(attrName);
          }
        });
        return data;
      }
      function _updateSettings(newsettings) {
        settings = $.extend({}, settings, newsettings);
      }
      function _buildHtml() {
        var initval = originalinput.val(),
          parentelement = originalinput.parent();
        if (initval !== "") {
          initval = Number(initval).toFixed(settings.decimals);
        }
        originalinput.data("initvalue", initval).val(initval);
        originalinput.addClass("form-control");
        if (parentelement.hasClass("input-group")) {
          _advanceInputGroup(parentelement);
        } else {
          _buildInputGroup();
        }
      }
      function _advanceInputGroup(parentelement) {
        parentelement.addClass("bootstrap-touchspin");
        var prev = originalinput.prev(),
          next = originalinput.next();
        var downhtml,
          uphtml,
          prefixhtml =
            '<span class="input-group-text bootstrap-touchspin-prefix">' +
            settings.prefix +
            "</span>",
          postfixhtml =
            '<span class="input-group-text bootstrap-touchspin-postfix">' +
            settings.postfix +
            "</span>";
        if (prev.hasClass("input-group-btn")) {
          downhtml =
            '<button class="' +
            settings.buttondown_class +
            ' bootstrap-touchspin-down" type="button">' +
            settings.buttondown_txt +
            "</button>";
          prev.append(downhtml);
        } else {
          downhtml =
            '<button class="' +
            settings.buttondown_class +
            ' bootstrap-touchspin-down" type="button">' +
            settings.buttondown_txt +
            "</button>";
          $(downhtml).insertBefore(originalinput);
        }
        if (next.hasClass("input-group-btn")) {
          uphtml =
            '<button class="' +
            settings.buttonup_class +
            ' bootstrap-touchspin-up" type="button">' +
            settings.buttonup_txt +
            "</button>";
          next.prepend(uphtml);
        } else {
          uphtml =
            '<button class="' +
            settings.buttonup_class +
            ' bootstrap-touchspin-up" type="button">' +
            settings.buttonup_txt +
            "</button>";
          $(uphtml).insertAfter(originalinput);
        }
        $(prefixhtml).insertBefore(originalinput);
        $(postfixhtml).insertAfter(originalinput);
        container = parentelement;
      }
      function _buildInputGroup() {
        var html;
        if (settings.verticalbuttons) {
          html =
            '<div class="input-group bootstrap-touchspin"><span class="input-group-text bootstrap-touchspin-prefix">' +
            settings.prefix +
            '</span><span class="input-group-text bootstrap-touchspin-postfix">' +
            settings.postfix +
            '</span><span class="input-group-btn-vertical"><button class="' +
            settings.buttondown_class +
            ' bootstrap-touchspin-up" type="button"><i class="' +
            settings.verticalupclass +
            '"></i></button><button class="' +
            settings.buttonup_class +
            ' bootstrap-touchspin-down" type="button"><i class="' +
            settings.verticaldownclass +
            '"></i></button></span></div>';
        } else {
          html =
            '<div class="input-group bootstrap-touchspin"><button class="' +
            settings.buttondown_class +
            ' bootstrap-touchspin-down" type="button">' +
            settings.buttondown_txt +
            '</button><span class="input-group-text bootstrap-touchspin-prefix">' +
            settings.prefix +
            '</span><span class="input-group-text bootstrap-touchspin-postfix">' +
            settings.postfix +
            '</span><button class="' +
            settings.buttonup_class +
            ' bootstrap-touchspin-up" type="button">' +
            settings.buttonup_txt +
            "</button></div>";
        }
        container = $(html).insertBefore(originalinput);
        $(".taiwind-touchspin-prefix", container).after(originalinput);
        if (originalinput.hasClass("input-sm")) {
          container.addClass("input-group-sm");
        } else if (originalinput.hasClass("input-lg")) {
          container.addClass("input-group-lg");
        }
      }
      function _initElements() {
        elements = {
          down: $(".taiwind-touchspin-down", container),
          up: $(".taiwind-touchspin-up", container),
          input: $("input", container),
          prefix: $(".taiwind-touchspin-prefix", container).addClass(
            settings.prefix_extraclass
          ),
          postfix: $(".taiwind-touchspin-postfix", container).addClass(
            settings.postfix_extraclass
          ),
        };
      }
      function _hideEmptyPrefixPostfix() {
        if (settings.prefix === "") {
          elements.prefix.hide();
        }
        if (settings.postfix === "") {
          elements.postfix.hide();
        }
      }
      function _bindEvents() {
        originalinput.on("keydown", function (ev) {
          var code = ev.keyCode || ev.which;
          if (code === 38) {
            if (spinning !== "up") {
              upOnce();
              startUpSpin();
            }
            ev.preventDefault();
          } else if (code === 40) {
            if (spinning !== "down") {
              downOnce();
              startDownSpin();
            }
            ev.preventDefault();
          }
        });
        originalinput.on("keyup", function (ev) {
          var code = ev.keyCode || ev.which;
          if (code === 38) {
            stopSpin();
          } else if (code === 40) {
            stopSpin();
          }
        });
        originalinput.on("blur", function () {
          _checkValue();
        });
        elements.down.on("keydown", function (ev) {
          var code = ev.keyCode || ev.which;
          if (code === 32 || code === 13) {
            if (spinning !== "down") {
              downOnce();
              startDownSpin();
            }
            ev.preventDefault();
          }
        });
        elements.down.on("keyup", function (ev) {
          var code = ev.keyCode || ev.which;

          if (code === 32 || code === 13) {
            stopSpin();
          }
        });
        elements.up.on("keydown", function (ev) {
          var code = ev.keyCode || ev.which;
          if (code === 32 || code === 13) {
            if (spinning !== "up") {
              upOnce();
              startUpSpin();
            }
            ev.preventDefault();
          }
        });
        elements.up.on("keyup", function (ev) {
          var code = ev.keyCode || ev.which;
          if (code === 32 || code === 13) {
            stopSpin();
          }
        });
        elements.down.on("mousedown.touchspin", function (ev) {
          elements.down.off("touchstart.touchspin");
          if (originalinput.is(":disabled")) {
            return;
          }
          downOnce();
          startDownSpin();
          ev.preventDefault();
          ev.stopPropagation();
        });
        elements.down.on("touchstart.touchspin", function (ev) {
          elements.down.off("mousedown.touchspin");
          if (originalinput.is(":disabled")) {
            return;
          }
          downOnce();
          startDownSpin();
          ev.preventDefault();
          ev.stopPropagation();
        });
        elements.up.on("mousedown.touchspin", function (ev) {
          elements.up.off("touchstart.touchspin");
          if (originalinput.is(":disabled")) {
            return;
          }
          upOnce();
          startUpSpin();
          ev.preventDefault();
          ev.stopPropagation();
        });
        elements.up.on("touchstart.touchspin", function (ev) {
          elements.up.off("mousedown.touchspin");
          if (originalinput.is(":disabled")) {
            return;
          }
          upOnce();
          startUpSpin();
          ev.preventDefault();
          ev.stopPropagation();
        });
        elements.up.on(
          "mouseout touchleave touchend touchcancel",
          function (ev) {
            if (!spinning) {
              return;
            }
            ev.stopPropagation();
            stopSpin();
          }
        );
        elements.down.on(
          "mouseout touchleave touchend touchcancel",
          function (ev) {
            if (!spinning) {
              return;
            }
            ev.stopPropagation();
            stopSpin();
          }
        );
        elements.down.on("mousemove touchmove", function (ev) {
          if (!spinning) {
            return;
          }
          ev.stopPropagation();
          ev.preventDefault();
        });
        elements.up.on("mousemove touchmove", function (ev) {
          if (!spinning) {
            return;
          }
          ev.stopPropagation();
          ev.preventDefault();
        });
        $(document).on(
          _scopeEventNames(
            ["mouseup", "touchend", "touchcancel"],
            _currentSpinnerId
          ).join(" "),
          function (ev) {
            if (!spinning) {
              return;
            }
            ev.preventDefault();
            stopSpin();
          }
        );
        $(document).on(
          _scopeEventNames(
            ["mousemove", "touchmove", "scroll", "scrollstart"],
            _currentSpinnerId
          ).join(" "),
          function (ev) {
            if (!spinning) {
              return;
            }
            ev.preventDefault();
            stopSpin();
          }
        );
        originalinput.on("mousewheel DOMMouseScroll", function (ev) {
          if (!settings.mousewheel || !originalinput.is(":focus")) {
            return;
          }
          var delta =
            ev.originalEvent.wheelDelta ||
            -ev.originalEvent.deltaY ||
            -ev.originalEvent.detail;
          ev.stopPropagation();
          ev.preventDefault();
          if (delta < 0) {
            downOnce();
          } else {
            upOnce();
          }
        });
      }
      function _bindEventsInterface() {
        originalinput.on("touchspin.uponce", function () {
          stopSpin();
          upOnce();
        });
        originalinput.on("touchspin.downonce", function () {
          stopSpin();
          downOnce();
        });
        originalinput.on("touchspin.startupspin", function () {
          startUpSpin();
        });
        originalinput.on("touchspin.startdownspin", function () {
          startDownSpin();
        });
        originalinput.on("touchspin.stopspin", function () {
          stopSpin();
        });
        originalinput.on("touchspin.updatesettings", function (e, newsettings) {
          changeSettings(newsettings);
        });
      }
      function _forcestepdivisibility(value) {
        switch (settings.forcestepdivisibility) {
          case "round":
            return (Math.round(value / settings.step) * settings.step).toFixed(
              settings.decimals
            );
          case "floor":
            return (Math.floor(value / settings.step) * settings.step).toFixed(
              settings.decimals
            );
          case "ceil":
            return (Math.ceil(value / settings.step) * settings.step).toFixed(
              settings.decimals
            );
          default:
            return value;
        }
      }
      function _checkValue() {
        var val, parsedval, returnval;
        val = originalinput.val();
        if (val === "") {
          if (settings.replacementval !== "") {
            originalinput.val(settings.replacementval);
            originalinput.trigger("change");
          }
          return;
        }
        if (settings.decimals > 0 && val === ".") {
          return;
        }
        parsedval = parseFloat(val);
        if (isNaN(parsedval)) {
          if (settings.replacementval !== "") {
            parsedval = settings.replacementval;
          } else {
            parsedval = 0;
          }
        }
        returnval = parsedval;
        if (parsedval.toString() !== val) {
          returnval = parsedval;
        }
        if (parsedval < settings.min) {
          returnval = settings.min;
        }
        if (parsedval > settings.max) {
          returnval = settings.max;
        }
        returnval = _forcestepdivisibility(returnval);
        if (Number(val).toString() !== returnval.toString()) {
          originalinput.val(returnval);
          originalinput.trigger("change");
        }
      }
      function _getBoostedStep() {
        if (!settings.booster) {
          return settings.step;
        } else {
          var boosted =
            Math.pow(2, Math.floor(spincount / settings.boostat)) *
            settings.step;
          if (settings.maxboostedstep) {
            if (boosted > settings.maxboostedstep) {
              boosted = settings.maxboostedstep;
              value = Math.round(value / boosted) * boosted;
            }
          }
          return Math.max(settings.step, boosted);
        }
      }
      function upOnce() {
        _checkValue();
        value = parseFloat(elements.input.val());
        if (isNaN(value)) {
          value = 0;
        }
        var initvalue = value,
          boostedstep = _getBoostedStep();
        value = value + boostedstep;
        if (value > settings.max) {
          value = settings.max;
          originalinput.trigger("touchspin.on.max");
          stopSpin();
        }
        elements.input.val(Number(value).toFixed(settings.decimals));
        if (initvalue !== value) {
          originalinput.trigger("change");
        }
      }
      function downOnce() {
        _checkValue();
        value = parseFloat(elements.input.val());
        if (isNaN(value)) {
          value = 0;
        }
        var initvalue = value,
          boostedstep = _getBoostedStep();
        value = value - boostedstep;
        if (value < settings.min) {
          value = settings.min;
          originalinput.trigger("touchspin.on.min");
          stopSpin();
        }
        elements.input.val(value.toFixed(settings.decimals));
        if (initvalue !== value) {
          originalinput.trigger("change");
        }
      }
      function startDownSpin() {
        stopSpin();
        spincount = 0;
        spinning = "down";
        originalinput.trigger("touchspin.on.startspin");
        originalinput.trigger("touchspin.on.startdownspin");
        downDelayTimeout = setTimeout(function () {
          downSpinTimer = setInterval(function () {
            spincount++;
            downOnce();
          }, settings.stepinterval);
        }, settings.stepintervaldelay);
      }
      function startUpSpin() {
        stopSpin();
        spincount = 0;
        spinning = "up";
        originalinput.trigger("touchspin.on.startspin");
        originalinput.trigger("touchspin.on.startupspin");
        upDelayTimeout = setTimeout(function () {
          upSpinTimer = setInterval(function () {
            spincount++;
            upOnce();
          }, settings.stepinterval);
        }, settings.stepintervaldelay);
      }
      function stopSpin() {
        clearTimeout(downDelayTimeout);
        clearTimeout(upDelayTimeout);
        clearInterval(downSpinTimer);
        clearInterval(upSpinTimer);
        switch (spinning) {
          case "up":
            originalinput.trigger("touchspin.on.stopupspin");
            originalinput.trigger("touchspin.on.stopspin");
            break;
          case "down":
            originalinput.trigger("touchspin.on.stopdownspin");
            originalinput.trigger("touchspin.on.stopspin");
            break;
        }
        spincount = 0;
        spinning = false;
      }
    });
  };
})(jQuery);
