"use strict";
(function(factory){
  // AMD
  if(typeof define === 'function' && define.amd) {
    define(['jquery', './jquery.enjoyhint.js', 'jquery.scrollto'], factory);
  }
  // CommonJS
  else if (typeof require === 'function' && typeof exports === 'object') {
    module.exports = factory(require('jquery'), require('./jquery.enjoyhint.js'), require('jquery.scrollto'));
  }
  // Global
  else {
    window.EnjoyHint = factory(jQuery)
  }
})(function($){
  var EnjoyHint = function(configs) {
    var $event_element;
    var that = this;
    var _options = configs || {};
    var BTN_NEXT_TEXT = _options.btnNextText;
    var BTN_SKIP_TEXT = _options.btnSkipText;
  
    var SHAPE_BACKGROUND_COLOR = _options.backgroundColor || "rgba(0,0,0,0.6)";
  
    var body = "body"; // TODO: Is it possible case when we need to define enjoyhint somewhere else?
    var elementAvailableEventName = "enjoyhint-element-available";
  
    var defaults = {
      onStart: function() {},
  
      onEnd: function() {},
  
      onSkip: function() {},

      onNext: function () { },

      elementToScroll: document.body
    };
  
    var options = $.extend(defaults, _options);
    var data = [];
    var current_step = 0;
  
    var $body = $(body);
  
    /********************* PRIVATE METHODS ***************************************/
  
    var init = function() {
      if ($(".enjoyhint")) {
        $(".enjoyhint").remove();
      }
  
      $body.css({ overflow: "hidden" });
  
      $(document).on("touchmove", lockTouch);
  
      $body.enjoyhint({
        onNextClick: function() {
          nextStep();
        },
        onPrevClick: function(){
          prevStep();
        },
        onSkipClick: function() {
          options.onSkip();
          skipAll();
        },
        fill: SHAPE_BACKGROUND_COLOR,
        elementToScroll: options.elementToScroll
      });
    };
  
    var lockTouch = function(e) {
      e.preventDefault();
    };
  
    var destroyEnjoy = function() {
      $(".enjoyhint").remove();
      $body.css({ overflow: "auto" });
      $(document).off("touchmove", lockTouch);
    };
  
    that.clear = function() {
      var $nextBtn = $(".enjoyhint_next_btn");
      var $skipBtn = $(".enjoyhint_skip_btn");
      var $prevBtn = $(".enjoyhint_prev_btn");

      $prevBtn.removeClass(that.prevUserClass);
      $nextBtn.removeClass(that.nextUserClass);
      $nextBtn.text(BTN_NEXT_TEXT);
      $skipBtn.removeClass(that.skipUserClass);
      $skipBtn.text(BTN_SKIP_TEXT);
    };
  
    function hideCurrentHint(){
      $body.enjoyhint("render_circle", []);
      $("#enjoyhint_label").remove();
      $("#enjoyhint_arrpw_line").remove();
      $body.enjoyhint("hide_prev");
      $body.enjoyhint("hide_next");
      $body.enjoyhint("hide_skip");
    };

    var stepAction = function(unpause) {
      if (!(data && data[current_step])) {
        $body.enjoyhint("hide");
        options.onEnd();
        destroyEnjoy();
        return;
      }
      
      options.onNext();
  
      var $enjoyhint = $(".enjoyhint");
  
      $enjoyhint.removeClass("enjoyhint-step-" + current_step);
      $enjoyhint.removeClass("enjoyhint-step-" + (current_step + 1));
      $enjoyhint.removeClass("enjoyhint-step-" + (current_step + 2));
      $enjoyhint.addClass("enjoyhint-step-" + (current_step + 1));

      var step_data = data[current_step];

      $body.off(elementAvailableEventName);

      //loops waiting until specified element becomes visible
      var waitUntilAvailable = function (selector, interval) {
        if (interval == null)
          interval = 150; 
        
        var triggerIfAvailable = function () {
             if ($(selector).is(":visible")) {
                 $body.trigger(elementAvailableEventName);
             }
             else {
                 setTimeout(triggerIfAvailable, interval)
             }
         };
 
         setTimeout(triggerIfAvailable, 0);
      }
 
      //if pausedUntil was specified, hide current overlay and wait until specified event occurs
      if (!unpause && step_data.pausedUntil != null && step_data.pausedUntil.event != null) {
            //hide current overlay during waiting time
            $body.enjoyhint("hide");
            
           //if 'available' event was chosen wait for the custom event, which is triggered when the element becomes visible
            if (step_data.pausedUntil.event === 'available') {
                $body.on(elementAvailableEventName, function () {
                    stepAction(true);      //restart the step without pause
                    $body.off(elementAvailableEventName);
                });

                //check if element is available every 150ms
                waitUntilAvailable(step_data.pausedUntil.selector);
            }
            else {
                //delay the actual action until 'the event' happens on body or selector
                if (step_data.pausedUntil.selector == null) {
                  on(step_data.pausedUntil.event, function () {
                      stepAction(true);     //restart the step without pause
                      off(step_data.pausedUntil.event);
                  });
                }
                else {
                  $(step_data.pausedUntil.selector).on(step_data.pausedUntil.event, function () {
                      stepAction(true);     //restart the step without pause
                      $(step_data.pausedUntil.selector).off(step_data.pausedUntil.event)
                  });
                }
            }

            //the rest of the logic will be executed whenever the step is restarted
            return;
      }

      var scrollSpeed = step_data.scrollAnimationSpeed;
  
      if (
        step_data.onBeforeStart &&
        typeof step_data.onBeforeStart === "function"
      ) {
        step_data.onBeforeStart();
      }
  
      var timeout = step_data.timeout || 0;
  
      setTimeout(function() {
        if (!step_data.selector) {
          for (var prop in step_data) {
            if (step_data.hasOwnProperty(prop) && prop.split(" ")[1]) {
              step_data.selector = prop.split(" ")[1];
              step_data.event = prop.split(" ")[0];
  
              if (
                prop.split(" ")[0] == "next" ||
                prop.split(" ")[0] == "auto" ||
                prop.split(" ")[0] == "custom"
              ) {
                step_data.event_type = prop.split(" ")[0];
              }
  
              step_data.description = step_data[prop];
            }
          }
        }
  
        setTimeout(function() {
          that.clear();
        }, 250);

        var isHintInViewport = $(step_data.selector).get(0).getBoundingClientRect();
        if(isHintInViewport.top < 0 || isHintInViewport.bottom > (window.innerHeight || document.documentElement.clientHeight)){
            hideCurrentHint();
            $(options.elementToScroll).scrollTo(step_data.selector, step_data.scrollAnimationSpeed || 250, {offset: -200});
        }
        else {
          // if previous button has been clicked and element are in viewport to prevent custom step scrollAnimationSpeed set scrollSpeed to default
          scrollSpeed = 250;
        }
  
        setTimeout(function() {
          var $element = $(step_data.selector);
          var event = makeEventName(step_data.event);
          $body.enjoyhint("show");
          $event_element = $element;
  
          if (step_data.event_selector) {
            $event_element = $(step_data.event_selector);
          }
          
          $event_element.off(event)
          $element.off("keydown");

          if (!step_data.event_type && step_data.event == "key") {
            $element.keydown(function(event) {
              if (event.which == step_data.keyCode) {
                current_step++;
                stepAction();
              }
            });
          }
  
          if (step_data.showNext !== true) {
            $body.enjoyhint("hide_next");
          }
          else {
            $body.enjoyhint("show_next");
          }
          
          $body.enjoyhint("hide_prev");

          if(current_step !== 0) {
            $body.enjoyhint("show_prev");
          }

          if (step_data.showPrev == false) {
            $body.enjoyhint("hide_prev");
        }

          if (step_data.showSkip == false) {
            $body.enjoyhint("hide_skip");
          } else {
            $body.enjoyhint("show_skip");
          }
  
  
          if (step_data.nextButton) {
            var $nextBtn = $(".enjoyhint_next_btn");
  
            $nextBtn.addClass(step_data.nextButton.className || "");
            $nextBtn.text(step_data.nextButton.text || "Next");
            that.nextUserClass = step_data.nextButton.className;
          }

          if (step_data.prevButton) {
            var $prevBtn = $(".enjoyhint_prev_btn");

            $prevBtn.addClass(step_data.prevButton.className || "");
            $prevBtn.text(step_data.prevButton.text || "Previous");
            that.prevUserClass = step_data.prevButton.className;
          }

          if (step_data.skipButton) {
            var $skipBtn = $(".enjoyhint_skip_btn");
  
            $skipBtn.addClass(step_data.skipButton.className || "");
            $skipBtn.text(step_data.skipButton.text || "Skip");
            that.skipUserClass = step_data.skipButton.className;
          }
  
          if (step_data.event_type) {
            switch (step_data.event_type) {
              case "auto":
                $element[step_data.event]();
  
                switch (step_data.event) {
                  case "click":
                    break;
                }
                current_step++;
                stepAction();
                return;
  
              case "custom":
                on(step_data.event, function() {
                  current_step++;
                  off(step_data.event);
                  stepAction();
                });
  
                break;
  
              case "next":
                $body.enjoyhint("show_next");
                break;
            }
          } else {
              var alreadyTriggered = false;
              $event_element.one(event, function (e) {    //one should ensure that event is handled only once, but that's not always enough
                  if (alreadyTriggered)                   //make sure that the step is not changed twice handling the same event
                      return;

                  alreadyTriggered = true;                
                  if (step_data.keyCode && e.keyCode != step_data.keyCode) {
                    return;
                  }

                  $event_element.off(event);              //unregister the event
                  
                  current_step++;
                  stepAction();                           //move to the next step
            });
          }

          var max_habarites = Math.max(
            $element.outerWidth(),
            $element.outerHeight()
          );
          var radius = step_data.radius || Math.round(max_habarites / 2) + 5;
          var offset = $element.offset();
          var w = $element.outerWidth();
          var h = $element.outerHeight();
          var shape_margin =
            step_data.margin !== undefined ? step_data.margin : 10;
  
          var coords = {
            x: offset.left + Math.round(w / 2),
            y: offset.top + Math.round(h / 2) - $(document).scrollTop()
          };
  
          var shape_data = {
            enjoyHintElementSelector: step_data.selector,
            center_x: coords.x,
            center_y: coords.y,
            text: step_data.description,
            arrowColor: step_data.arrowColor,
            top: step_data.top,
            bottom: step_data.bottom,
            left: step_data.left,
            right: step_data.right,
            margin: step_data.margin,
            scroll: step_data.scroll,
            preventEvents: step_data.preventEvents
          };

          var customBtnProps = {
              nextButton: step_data.nextButton,
              prevButton: step_data.prevButton
          }

          if (shape_data.center_x === 0 && shape_data.center_y === 0) {
            $body.enjoyhint("hide");
            destroyEnjoy();
            return console.log("Error: Element position couldn't be reached");
          }
  
          if (step_data.shape && step_data.shape == "circle") {
            shape_data.shape = "circle";
            shape_data.radius = radius;
          } else {
            shape_data.radius = 0;
            shape_data.width = w + shape_margin;
            shape_data.height = h + shape_margin;
          }
  
          $body.enjoyhint("render_label_with_shape", shape_data, that.stop, customBtnProps);
          
        }, scrollSpeed + 20 || 270);
      }, timeout);
    };
  
    var nextStep = function() {
      current_step++;
      stepAction();
    };

    var prevStep = function() {
      current_step--;
      stepAction();
    };
  
    var skipAll = function() {
      var step_data = data[current_step];
      var $element = $(step_data.selector);
  
      off(step_data.event);
      $element.off(makeEventName(step_data.event));
  
      destroyEnjoy();
    };
  
    var makeEventName = function(name, is_custom) {
      return name + (is_custom ? "custom" : "") + ".enjoy_hint";
    };
  
    var on = function(event_name, callback) {
      $body.on(makeEventName(event_name, true), callback);
    };
  
    var off = function(event_name) {
      $body.off(makeEventName(event_name, true));
    };
  
    /********************* PUBLIC METHODS ***************************************/
  
    window.addEventListener(
      "resize",
      function() {
        if ($event_element != null) {
          $body.enjoyhint(
            "redo_events_near_rect",
            $event_element[0].getBoundingClientRect()
          );
        }
      },
      false
    );
  
    that.stop = function() {
      skipAll();
    };
  
    that.reRunScript = function(cs) {
      current_step = cs;
      stepAction();
    };
  
    that.runScript = function() {
      current_step = 0;
      options.onStart();
      stepAction();
    };
  
    that.resumeScript = function() {
      stepAction();
    };
  
    that.setCurrentStep = function(cs) {
      current_step = cs;
    };
  
    that.getCurrentStep = function() {
      return current_step;
    };
  
    that.trigger = function(event_name) {
      switch (event_name) {
        case "next":
          nextStep();
          break;
  
        case "skip":
          skipAll();
          options.onSkip();
          break;
  
        default: $body.trigger(makeEventName(event_name, true));
      }
    };
  
    that.setScript = function(_data) {
      if (!(_data instanceof Array) && _data.length < 1) {
        throw new Error("Configurations list isn't correct.");
      }
  
      data = _data;
    };
  
    //support deprecated API methods
    /**
     * Configure data list
     */
    that.set = function(_data) {
      that.setScript(_data);
    };
  
    that.setSteps = function(_data) {
      that.setScript(_data);
    };
  
    that.run = function() {
      that.runScript();
    };
  
    that.resume = function() {
      that.resumeScript();
    };
  
    init();
  };
  return EnjoyHint;
})


