;(function($, window, document, undefined) {

  'use strict';

  // Create the defaults once
  var pluginName = 'popSelect';
  var defaults = {
    position: 'top',
    showTitle: true,
    autoIncrease: true,
    title: 'Select Multiple Options',
    debug: false,
    maxAllowed: 0,
    placeholderText: 'Click to Add Values',
    autofocus: false
  };

  var classNames = {
    tag: 'tag',
    arrow: 'arrow',
    selectWrapper: 'popover-select-wrapper',
    tagWrapper: 'popover-tag-wrapper',
    popoverSelect: 'popover-select',
    popoverBody: 'popover-select-body',
    selectTextarea: 'popover-select-textarea',
    selectTags: 'popover-select-tags',
    popoverClose: 'popSelect-close',
    selectList: 'popover-select-list',
    popoverDisabled: 'disabled',
    placeholder: 'placeholder',
    placeholderInput: 'placeholder input',
    placeholderText: 'placeholder-text',
    selectTitle: 'popover-select-title',
    top: 'top'
  };

  var logs = {
    popoverGenerated: 'PopSelect Code Generated',
    closeClicked: 'Close button clicked',
    noElem: 'No element to be removed',
    unSupported: 'Not Supported',
    posChanged: 'Position changed'
  };

  var constants = {
    option: 'option',
    blur: 'blur',
    click: 'click',
    mousedown: 'mousedown',
    li: 'li',
    attrVal: 'data-value',
    attrText: 'data-text',
    body: 'BODY'
  };

  // The actual plugin constructor
  function Plugin(element, options) {
    this.element = element;
    // jQuery has an extend method which merges the contents of two or
    // more objects, storing the result in the first object. The first object
    // is generally empty as we don't want to alter the default options for
    // future instances of the plugin
    this.settings = $.extend({}, defaults, options);
    this._defaults = defaults;
    this._name = pluginName;
    this.init();
  }

  // Avoid Plugin.prototype conflicts
  $.extend(Plugin.prototype, {
    init: function() {
      var $this = this;
      this.$elem = $(this.element);

      // Get all the options in an array
      this.$options = this.$elem.children(constants.option).map(function(i, option) {
       return {
         val: $(option).val(),
         text: $(option).text(),
         selected: $(option).attr('selected')
       };
      });

      // Wrap the whole input box in your own popover
      this.$elem.wrap(template(createEmptyDiv(), {
        wrapper: classNames.selectWrapper
      }));

      var elemPos = this.getPosition(this.$elem);
      this.elemPos = elemPos;

      // Also Add the required css Properties
      this.$elem
      .parent(addDot(classNames.selectWrapper))
      .css({width: this.settings.width || elemPos.width, height: elemPos.height});

      // Append the popover to $elem
      var popUpCode = this.generatePopover(this.$options);
      $this.log(logs.popoverGenerated, popUpCode);
      this.$elem.after(popUpCode);

      // Assign the $popover to the new $elem
      this.$popover = this.$elem.next(addDot(classNames.popoverSelect));
      this.$popover.css({top: 0, left: 0});

      // Append Tagging System to it
      this.$elem.after(createTaggingStr(this.settings.placeholderText, this.$options));

      // Get the Tag Wrapper for later use
      this.$tagWrapper = this.$elem.next(addDot(classNames.tagWrapper));
      this.baseHeight = this.$tagWrapper.height();

      // Get the input
      this.$inputTagField = this.$tagWrapper.find(addDot(classNames.selectTextarea));

      // Hide the popover when blurring the inputTagField
      this.$inputTagField.on(constants.blur, function() {
        $this.$popover.hide();
      });

      // Get the tags in the wrapper
      this.$tags = this.$tagWrapper.find(addDot(classNames.selectTags));

      // Show Popover on click of tags
      this.$tags
        .on(constants.click, this.initializePopover.bind(this));

      // Also Attach to placeHolder Text
      this.$tags.next(addDot(classNames.placeholderText))
        .on(constants.click, this.initializePopover.bind(this));

      // Attach Event Listener to ul list
      this.$tags.on(constants.click, addDot(classNames.popoverClose), function() {
        $this.inputToPopover($(this));
      });

      // Attach List Event Handlers to Li
      this.$popover.find(addDot(classNames.selectList)).on(constants.mousedown, function(e) {
        e.preventDefault();
      }).on(constants.click, constants.li, function() {
        $this.popoverToInput($(this));
      });

      // Finally Hide the Element
      this.$elem.hide();

      // Required for placeholdertext and pre-selected values
      this.checkNumberOfTags();

      // If pre-selected are higher than normal
      this.changeSize();

      // Trigger init event
      this.$elem.trigger('popselect:init');

      if (this.settings.autofocus) {
        this.initializePopover();
      }
    },
    inputToPopover: function($elem) {
      var $li = $elem.parent();
      this.log(logs.closeClicked, $li);
      var val = $li.attr(constants.attrVal);
      var text = $li.attr(constants.attrText);

      // Remove them from input and add it to popover
      this.appendToPopup(val, text);
      $li.remove();

      // Standard Reset Calls
      this.setPlaceholder();
      this.focus();

      // Whether to increase/decrease width
      this.changeSize();

      // Whether to enable / disable popover and Placeholder Text
      this.checkNumberOfTags();

      // Trigger remove event, passing value and text of removed tag
      this.$elem.trigger('popselect:remove', [val, text]);
    },
    enablePopover: function() {
      this.$popover.find(addDot(classNames.selectList) + ' li')
        .removeClass(classNames.popoverDisabled);
    },
    disablePopover: function() {
      this.$popover.find(addDot(classNames.selectList) + ' li')
        .addClass(classNames.popoverDisabled);
    },
    checkNumberOfTags: function() {
      var currentNo = this.$tags.find(addDot(classNames.tag)).length;
      if (currentNo === 0) {
        this.enablePlaceHolderText();
      } else {
        this.disablePlaceHolderText();
      }

      if (this.settings.maxAllowed !== 0) {
        if (this.settings.maxAllowed > currentNo) {
          this.enablePopover();
        } else {
          this.disablePopover();
        }
      }

      this.syncWithSelect();
    },
    popoverToInput: function($elem) {
      var val = $elem.attr(constants.attrVal);
      var text = $elem.text();
      var li = createTagStr(val, text);

      // Remove them from popover and it to input
      this.$tags.append(li);
      $elem.remove();

      // Standard Reset Calls
      this.setPlaceholder();
      this.focus();
      this.popoverShow();
      this.changePosition();

      // Whether to increase/decrease width
      this.changeSize();

      // Enable / Disable Popover
      this.checkNumberOfTags();

      // Trigger add event, passing value and text of added tag
      this.$elem.trigger('popselect:add', [val, text]);
    },
    popoverShow: function() {
      // Change Position as well show popover
      if (this.$popover.find(addDot(classNames.selectList) + ' li').length) {
        this.$popover.show();
      } else {
        this.$popover.hide();
      }
    },
    initializePopover: function() {
      this.popoverShow();
      this.changePosition();
      this.setPlaceholder();
      this.focus();
    },
    enablePlaceHolderText: function() {
      this.$tags.next(addDot(classNames.placeholderText)).show();
    },
    disablePlaceHolderText: function() {
      this.$tags.next(addDot(classNames.placeholderText)).hide();
    },
    focus: function() {
      var $this = this;
      this.$tags.find(addDot(classNames.placeholderInput)).focus();
      this.$tags.find(addDot(classNames.placeholderInput)).on(constants.blur, function() {
        $this.$popover.hide();
      });
    },
    setPlaceholder: function() {
      if (this.$tags.children(addDot(classNames.placeholder)).length) {
        this.$tags.children(addDot(classNames.placeholder)).remove();
      }
      this.$tags.append(createPlaceholderInput());
      this.disableInput();
    },
    disableInput: function() {
      var $this = this;
      this.$tags.find(addDot(classNames.placeholderInput)).keyup(function(e) {
        // Empty the input always
        $(this).val('');

        // For delete key, backspace and Ctrl + x Key
        if (e.which === 8 || e.which === 46 || e.ctrlKey && e.which === 88) {
          $this.removeLastElem();
        }
      });
    },
    changeSize: function() {
      if (this.settings.autoIncrease) {
        var tagWidth = 0;
        var textWidth = this.settings.width || this.elemPos.width;
        this.$tags.find(addDot(classNames.tag)).each(function(i, elem) {
          tagWidth += $(elem).outerWidth() + 20;
        });
        var mHeight = Math.floor(tagWidth / textWidth);
        this.$tags.height((mHeight + 1) * this.baseHeight);
      }
    },
    removeLastElem: function() {
      // Delete the last selected li if present
      var tags = this.$tags.find(addDot(classNames.tag));
      if (tags.length) {
        var $li = $(tags[tags.length - 1]);
        var val = $li.attr(constants.attrVal);
        var text = $li.attr(constants.attrText);

        // Remove them from input and add it to popover
        this.appendToPopup(val, text);
        $li.remove();

        // Standard Reset Calls
        this.changePosition();
        this.setPlaceholder();
        this.focus();

        // Whether to increase/decrease width
        this.changeSize();

        // Enable / Disable Popover
        this.checkNumberOfTags();
      } else {
        this.log(logs.noElem);
      }
    },
    setTitle: function(title) {
      if (this.settings.showTitle) {
        this.$popover.find(addDot(classNames.selectTitle)).text(title);
      }
    },
    getPosition: function($element) {
      $element = $element || this.$element;
      var el = $element[0];
      var isBody = el.tagName === constants.body;

      var elRect = el.getBoundingClientRect();
      if (elRect.width == null) {
        var w = elRect.right - elRect.left;
        var h = elRect.bottom - elRect.top;
        elRect = $.extend({}, elRect, {width: w, height: h});
      }
      var elOffset = isBody ? {top: 0, left: 0} : $element.offset();
      /* jshint ignore:start */
      var scroll = {scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
      /* jshint ignore:end */
      var outerDims = isBody ? {width: $(window).width(), height: $(window).height()} : null;

      return $.extend({}, elRect, scroll, outerDims, elOffset);
    },
    syncWithSelect: function() {
      var arrValues = this.$tags.find(addDot(classNames.tag)).map(function(i, elem) {
        return $(elem).attr('data-value');
      }).toArray();
      this.$elem.children(constants.option).each(function(i, option) {
        if (arrValues.indexOf($(option).val()) < 0) {
          $(option).removeAttr('selected');
        } else {
          $(option).attr('selected', 'selected');
        }
      });
    },
    appendToPopup: function(val, text) {
      var li = createLiTag(val, text);
      this.$popover.find(addDot(classNames.selectList)).append(li);
    },
    generatePopover: function(options) {
      var list = '';
      for (var i = 0; i < options.length; i++) {
        if (!options[i].selected) {
          list += createLiTag(options[i].val, options[i].text);
        }
      }
      var popoverStr = createPopoverStr(list, this.settings);
      return popoverStr;
    },
    changePosition: function() {
      // It first needs to be placed
      var popPos = this.getPosition(this.$popover);
      var tagPos = this.getPosition(this.$tagWrapper);

      var leftOffset = ((this.settings.width || this.elemPos.width) / 2) - (popPos.width / 2);
      var topOffset;
      if (this.settings.position === 'top') {
        topOffset = -(popPos.height);
      } else {
        topOffset = tagPos.height;
      }

      this.log('popPos.width', popPos.width);
      this.log(logs.posChanged, topOffset, leftOffset);
      this.$popover.css({top: topOffset, left: leftOffset});
    },
    log: function() {
      if (this.settings.debug) {
        console.log.apply(console, arguments);
      }
    }
  });

  /**
   * A quick helper function for creating templates
   * @param  {string} s Template String
   * @param  {object} d Values to replace for
   * @return {string}   Populated template string
   */
  function template(s, d) {
    for (var p in d) {
      s = s.replace(new RegExp('{' + p + '}', 'g'), d[p]);
    }
    return s;
  }

  /**
   * Just adds a dot for easy class selection
   * @param {string} str DOM className
   * @return {string} jQuery selector
   */
  function addDot(str) {
    return '.' + str;
  }

  function createEmptyDiv(x) {
    if (x) {
      return '<div class="{' + x + '}"></div>';
    } else {
      return '<div class="{wrapper}"></div>';
    }
  }

  function createTagsLi(options) {
    var str = '';
    for (var i = 0; i < options.length; i++) {
      if (options[i].selected) {
        str += createTagStr(options[i].val, options[i].text);
      }
    }
    return str;
  }

  function createTaggingStr(text, options) {
    return template('<div class="{tagWrapper}">' +
                '<textarea class="{selectTextarea}"></textarea>' +
                '<ul class="{selectTags}">' +
                  '{tags}' +
                '</ul>' +
                  '<div class="{placeholderText}">' +
                    '{text}' +
                    '</div>' +
                  '</div>', {
                     tags: createTagsLi(options),
                     text: text,
                     placeholderText: classNames.placeholderText,
                     tagWrapper: classNames.tagWrapper,
                     selectTextarea: classNames.selectTextarea,
                     selectTags: classNames.selectTags
                   });
  }

  function createTagStr(val, text) {
    return template('<li class="{tag}" data-value="{val}" data-text="{text}">' +
               '<span class="{popoverClose}">&times;</span>{text}' +
               '</li>', {
                 text: text,
                 val: val,
                 tag: classNames.tag,
                 popoverClose: classNames.popoverClose
               });
  }

  function createLiTag(val, text) {
    return template('<li data-value="{val}" data-text="{text}">{text}</li>', {
      val: val,
      text: text
    });
  }

  function createPlaceholderInput() {
    return template('<li class="{placeholder}">' +
             '<div>' +
              '<input type="text" readonly="true">' +
             '</div>' +
              '</li>', {
                placeholder: classNames.placeholder
              });
  }

  function createPopoverStr(list, settings) {
    return template('<div class="{popoverSelect} {top}">' +
             (settings.showTitle ? '<h3 class="{selectTitle}">{title}</h3>' : '') +
             '<div class="{popoverBody}">' +
              '<ul class="{selectList}">' +
               '{list}' +
              '</ul>' +
             '</div>' +
             '<div class="{arrow}"></div>' +
           '</div>', {
             title: settings.title,
             list: list,
             arrow: classNames.arrow,
             popoverSelect: classNames.popoverSelect,
             popoverBody: classNames.popoverBody,
             selectList: classNames.selectList,
             top: settings.position,
             selectTitle: classNames.selectTitle
           });
  }

  // A really lightweight plugin wrapper around the constructor,
  // preventing against multiple instantiations
  $.fn.popSelect = function(options) {
    if (typeof(options) === 'string') {
      if (options === 'value') {
        return this.next(addDot(classNames.tagWrapper))
             .find(addDot(classNames.tag)).map(function(i, $elem) {
               return $($elem).attr(constants.attrVal);
             });
      } else {
        console.warn(logs.unSupported);
      }
    } else {
      return this.each(function() {
       if (!$.data(this, 'plugin_' + pluginName)) {
         $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
       }
     });
    }
  };

})(jQuery, window, document);
