(function ($) {
  'use strict';

  /************************************************************************
   * Paint class
   ************************************************************************/
  function Paint(el, options) {
    this.$el = $(el);
    this.options = options;
    this.init = false;

    this.menus = {primary: null, active: null, all: {}};
    this.previousMode = null;
    this.width = this.$el.width();
    this.height = this.$el.height();

    this.ctxBgResize = false;
    this.ctxResize = false;

    this.generate();
    this._init();
  }
  
  Paint.prototype = {
    generate: function () {
      if (this.init) { return this; }

      var _this = this;

      // automatically appends each canvas
      // also returns the jQuery object so we can chain events right off the function call.
      // for the tempCanvas we will be setting some extra attributes but don't won't matter
      // as they will be reset on mousedown anyway.
      function createCanvas(name) {
        var newName = (name ? name.capitalize() : ''),
            canvasName = 'canvas' + newName,
            ctxName = 'ctx' + newName;

        _this[canvasName] = document.createElement('canvas');
        _this[ctxName] = _this[canvasName].getContext('2d');
        _this['$' + canvasName] = $(_this[canvasName]);
        
        _this['$' + canvasName]
        .attr('class', 'wPaint-canvas' + (name ? '-' + name : ''))
        .attr('width', _this.width + 'px')
        .attr('height', _this.height + 'px')
        .css({position: 'absolute', left: 0, top: 0});

        _this.$el.append(_this['$' + canvasName]);

        return _this['$' + canvasName];
      }

      // event functions
      function canvasMousedown(e) {
        e.preventDefault();
        e.stopPropagation();
        _this.draw = true;
        e.canvasEvent = 'down';
        _this._closeSelectBoxes();
        _this._callShapeFunc.apply(_this, [e]);
      }

      function documentMousemove(e) {
        if (_this.draw) {
          e.canvasEvent = 'move';
          _this._callShapeFunc.apply(_this, [e]);
        }
      }

      function documentMouseup(e) {

        //make sure we are in draw mode otherwise this will fire on any mouse up.
        if (_this.draw) {
          _this.draw = false;
          e.canvasEvent = 'up';
          _this._callShapeFunc.apply(_this, [e]);
        }
      }

      // create bg canvases
      createCanvas('bg');
      
      // create drawing canvas
      createCanvas('')
      .on('mousedown', canvasMousedown)
      .bindMobileEvents();
      
      // create temp canvas for drawing shapes temporarily
      // before transfering to main canvas
      createCanvas('temp').hide();
      
      // event handlers for drawing
      $(document)
      .on('mousemove', documentMousemove)
      .on('mousedown', $.proxy(this._closeSelectBoxes, this))
      .on('mouseup', documentMouseup);

      // we will need to preset theme to get proper dimensions
      // when creating menus which will be appended after this
      this.setTheme(this.options.theme);
    },

    _init: function () {
      var index = null,
          setFuncName = null;

      this.init = true;

      // run any set functions if they exist
      for (index in this.options) {
        setFuncName = 'set' + index.capitalize();
        if (this[setFuncName]) { this[setFuncName](this.options[index]); }
      }

      // fix menus
      this._fixMenus();

      // initialize active menu button
      this.menus.primary._getIcon(this.options.mode).trigger('click');      
    },

    resize: function () {
      var bg = this.getBg(),
          image = this.getImage();

      this.width = this.$el.width();
      this.height = this.$el.height();

      this.canvasBg.width = this.width;
      this.canvasBg.height = this.height;
      this.canvas.width = this.width;
      this.canvas.height = this.height;

      if (this.ctxBgResize === false) {
        this.ctxBgResize = true;
        this.setBg(bg, true);
      }

      if (this.ctxResize === false) {
        this.ctxResize = true;
        this.setImage(image, '', true, true);
      }
    },

    /************************************
     * setters
     ************************************/
    setTheme: function (theme) {
      var i, ii;

      theme = theme.split(' ');

      // remove anything beginning with "wPaint-theme-" first
      this.$el.attr('class', (this.$el.attr('class') || '').replace(/wPaint-theme-.+\s|wPaint-theme-.+$/, ''));
      
      // add each theme
      for (i = 0, ii = theme.length; i < ii; i++) {
        this.$el.addClass('wPaint-theme-' + theme[i]);
      }
    },

    setMode: function (mode) {
      this.setCursor(mode);
      this.previousMode = this.options.mode;
      this.options.mode = mode;
    },

    setImage: function (img, ctxType, resize, notUndo) {
      if (!img) { return true; }

      var _this = this,
          myImage = null,
          ctx = '';

      function loadImage() {
        var ratio = 1, xR = 0, yR = 0, x = 0, y = 0, w = myImage.width, h = myImage.height;

        if (!resize) {
          // get width/height
          if (myImage.width > _this.width || myImage.height > _this.height || _this.options.imageStretch) {
            xR = _this.width / myImage.width;
            yR = _this.height / myImage.height;

            ratio = xR < yR ? xR : yR;

            w = myImage.width * ratio;
            h = myImage.height * ratio;
          }

          // get left/top (centering)
          x = (_this.width - w) / 2;
          y = (_this.height - h) / 2;
        }

        ctx.clearRect(0, 0, _this.width, _this.height);
        ctx.drawImage(myImage, x, y, w, h);

        _this[ctxType + 'Resize'] = false;

        // Default is to run the undo.
        // If it's not to be run set it the flag to true.
        if (!notUndo) {
          _this._addUndo();
        }
      }
      
      ctxType = 'ctx' + (ctxType || '').capitalize();
      ctx = this[ctxType];
      
      if (window.rgbHex(img)) {
        ctx.clearRect(0, 0, this.width, this.height);
        ctx.fillStyle = img;
        ctx.rect(0, 0, this.width, this.height);
        ctx.fill();
      }
      else {
        myImage = new Image();
        myImage.src = img.toString();
        $(myImage).load(loadImage);
      }
    },

    setBg: function (img, resize) {
      if (!img) { return true; }
      
      this.setImage(img, 'bg', resize, true);
    },

    setCursor: function (cursor) {
      cursor = $.fn.wPaint.cursors[cursor] || $.fn.wPaint.cursors['default'];

      this.$el.css('cursor', 'url("' + this.options.path + cursor.path + '") ' + cursor.left + ' ' + cursor.top + ', default');
    },

    setMenuOrientation: function (orientation) {
      $.each(this.menus.all, function (i, menu) {
        menu.options.aligment = orientation;
        menu.setAlignment(orientation);
      });
    },

    getImage: function (withBg) {
      var canvasSave = document.createElement('canvas'),
          ctxSave = canvasSave.getContext('2d');

      withBg = withBg === false ? false : true;

      $(canvasSave)
      .css({display: 'none', position: 'absolute', left: 0, top: 0})
      .attr('width', this.width)
      .attr('height', this.height);

      if (withBg) { ctxSave.drawImage(this.canvasBg, 0, 0); }
      ctxSave.drawImage(this.canvas, 0, 0);

      return canvasSave.toDataURL();
    },

    getBg: function () {
      return this.canvasBg.toDataURL();
    },

    /************************************
     * prompts
     ************************************/
    _displayStatus: function (msg) {
      var _this = this;

      if (!this.$status) {
        this.$status = $('<div class="wPaint-status"></div>');
        this.$el.append(this.$status);
      }

      this.$status.html(msg);
      clearTimeout(this.displayStatusTimer);

      this.$status.fadeIn(500, function () {
        _this.displayStatusTimer = setTimeout(function () { _this.$status.fadeOut(500); }, 1500);
      });
    },

    _showModal: function ($content) {
      var _this = this,
          $bg = this.$el.children('.wPaint-modal-bg'),
          $modal = this.$el.children('.wPaint-modal');

      function modalFadeOut() {
          $bg.remove();
          $modal.remove();
          _this._createModal($content);
        }

      if ($bg.length) {
        $modal.fadeOut(500, modalFadeOut);
      }
      else {
        this._createModal($content);
      }
    },

    _createModal: function ($content) {
      $content = $('<div class="wPaint-modal-content"></div>').append($content.children());

      var $bg = $('<div class="wPaint-modal-bg"></div>'),
          $modal = $('<div class="wPaint-modal"></div>'),
          $holder = $('<div class="wPaint-modal-holder"></div>'),
          $close = $('<div class="wPaint-modal-close">X</div>');

      function modalClick() {
        $modal.fadeOut(500, modalFadeOut);
      }

      function modalFadeOut() {
        $bg.remove();
        $modal.remove();
      }

      $close.on('click', modalClick);
      $modal.append($holder.append($content)).append($close);
      this.$el.append($bg).append($modal);

      $modal.css({
        left: (this.$el.outerWidth() / 2) - ($modal.outerWidth(true) / 2),
        top: (this.$el.outerHeight() / 2) - ($modal.outerHeight(true) / 2)
      });

      $modal.fadeIn(500);
    },

    /************************************
     * menu helpers
     ************************************/
    _createMenu: function (name, options) {
      options = options || {};
      options.alignment = this.options.menuOrientation;
      options.handle = this.options.menuHandle;
      
      return new Menu(this, name, options);
    },

    _fixMenus: function () {
      var _this = this,
          $selectHolder = null;

      function selectEach(i, el) {
        var $el = $(el),
            $select = $el.clone();

        $select.appendTo(_this.$el);

        if ($select.outerHeight() === $select.get(0).scrollHeight) {
          $el.css({overflowY: 'auto'});
        }

        $select.remove();
      }

      // TODO: would be nice to do this better way
      // for some reason when setting overflowY:auto with dynamic content makes the width act up
      for (var key in this.menus.all) {
        $selectHolder = _this.menus.all[key].$menu.find('.wPaint-menu-select-holder');
        if ($selectHolder.length) { $selectHolder.children().each(selectEach); }
      }
    },

    _closeSelectBoxes: function (item) {
      var key, $selectBoxes;

      for (key in this.menus.all) {
        $selectBoxes = this.menus.all[key].$menuHolder.children('.wPaint-menu-icon-select');

        // hide any open select menus excluding the current menu
        // this is to avoid the double toggle since there are some
        // other events running here
        if (item) { $selectBoxes = $selectBoxes.not('.wPaint-menu-icon-name-' + item.name); }

        $selectBoxes.children('.wPaint-menu-select-holder').hide();
      }
    },

    /************************************
     * events
     ************************************/
    //_imageOnload: function () {
    //  /* a blank helper function for post image load calls on canvas - can be extended by other plugins using the setImage called */
    //},

    _callShapeFunc: function (e) {

      // TODO: this is where issues with mobile offsets are probably off
      var canvasOffset = this.$canvas.offset(),
          canvasEvent = e.canvasEvent.capitalize(),
          func = '_draw' + this.options.mode.capitalize() + canvasEvent;

      // update offsets here since we are detecting mouseup on $(document) not on the canvas
      e.pageX = Math.floor(e.pageX - canvasOffset.left);
      e.pageY = Math.floor(e.pageY - canvasOffset.top);

      // call drawing func
      if (this[func]) { this[func].apply(this, [e]); }

      // run callback if set
      if (this.options['draw' + canvasEvent]) { this.options['_draw' + canvasEvent].apply(this, [e]); }

      // run options (user) callback if set
      if (canvasEvent === 'Down' && this.options.onShapeDown) { this.options.onShapeDown.apply(this, [e]); }
      else if (canvasEvent === 'Move' && this.options.onShapeMove) { this.options.onShapeMove.apply(this, [e]); }
      else if (canvasEvent === 'Up' && this.options.onShapeUp) { this.options.onShapeUp.apply(this, [e]); }
    },

    _stopPropagation: function (e) {
      e.stopPropagation();
    },

    /************************************
     * shape helpers
     ************************************/
    _drawShapeDown: function (e) {
      this.$canvasTemp
      .css({left: e.PageX, top: e.PageY})
      .attr('width', 0)
      .attr('height', 0)
      .show();

      this.canvasTempLeftOriginal = e.pageX;
      this.canvasTempTopOriginal = e.pageY;
    },
    
    _drawShapeMove: function (e, factor) {
      var xo = this.canvasTempLeftOriginal,
          yo = this.canvasTempTopOriginal;

      // we may need these in other funcs, so we'll just pass them along with the event
      factor = factor || 2;
      e.left = (e.pageX < xo ? e.pageX : xo);
      e.top = (e.pageY < yo ? e.pageY : yo);
      e.width = Math.abs(e.pageX - xo);
      e.height = Math.abs(e.pageY - yo);
      e.x = this.options.lineWidth / 2 * factor;
      e.y = this.options.lineWidth / 2 * factor;
      e.w = e.width - this.options.lineWidth * factor;
      e.h = e.height - this.options.lineWidth * factor;

      $(this.canvasTemp)
      .css({left: e.left, top: e.top})
      .attr('width', e.width)
      .attr('height', e.height);
      
      // store these for later to use in our "up" call
      this.canvasTempLeftNew = e.left;
      this.canvasTempTopNew = e.top;

      factor = factor || 2;

      // TODO: set this globally in _drawShapeDown (for some reason colors are being reset due to canvas resize - is there way to permanently set it)
      this.ctxTemp.fillStyle = this.options.fillStyle;
      this.ctxTemp.strokeStyle = this.options.strokeStyle;
      this.ctxTemp.lineWidth = this.options.lineWidth * factor;
    },
    
    _drawShapeUp: function () {
      this.ctx.drawImage(this.canvasTemp, this.canvasTempLeftNew, this.canvasTempTopNew);
      this.$canvasTemp.hide();
    },

    /****************************************
     * dropper
     ****************************************/
    _drawDropperDown: function (e) {
      var pos = {x: e.pageX, y: e.pageY},
          pixel = this._getPixel(this.ctx, pos),
          color = null;

      // if we get no color try getting from the background
      //if(pixel.r === 0 && pixel.g === 0 && pixel.b === 0 && pixel.a === 0) {
      //  imageData = this.ctxBg.getImageData(0, 0, this.width, this.height)
      //  pixel = this._getPixel(imageData, pos);
      //}

      color = 'rgba(' + [ pixel.r, pixel.g, pixel.b, pixel.a ].join(',') + ')';

      // set color from dropper here
      this.options[this.dropper] = color;
      this.menus.active._getIcon(this.dropper).wColorPicker('color', color);
    },

    _drawDropperUp: function () {
      this.setMode(this.previousMode);
    },

    // get pixel data represented as RGBa color from pixel array.
    _getPixel: function (ctx, pos) {
      var imageData = ctx.getImageData(0, 0, this.width, this.height),
          pixelArray = imageData.data,
          base = ((pos.y * imageData.width) + pos.x) * 4;
      
      return {
        r: pixelArray[base],
        g: pixelArray[base + 1],
        b: pixelArray[base + 2],
        a: pixelArray[base + 3]
      };
    }
  };

  /************************************************************************
   * Menu class
   ************************************************************************/
  function Menu(wPaint, name, options) {
    this.wPaint = wPaint;
    this.options = options;
    this.name = name;
    this.type = !wPaint.menus.primary ? 'primary' : 'secondary';
    this.docked = true;
    this.dockOffset = {left: 0, top: 0};

    this.generate();
  }
  
  Menu.prototype = {
    generate: function () {
      this.$menu = $('<div class="wPaint-menu"></div>');
      this.$menuHolder = $('<div class="wPaint-menu-holder wPaint-menu-name-' + this.name + '"></div>');
      
      if (this.options.handle) { this.$menuHandle = this._createHandle(); }
      else { this.$menu.addClass('wPaint-menu-nohandle'); }
      
      if (this.type === 'primary') {

        // store the primary menu in primary object - we will need this reference later
        this.wPaint.menus.primary = this;

        this.setOffsetLeft(this.options.offsetLeft);
        this.setOffsetTop(this.options.offsetTop);
      }
      else if (this.type === 'secondary') {
        this.$menu.hide();
      }

      // append menu items
      this.$menu.append(this.$menuHolder.append(this.$menuHandle));
      this.reset();
      
      // append menu
      this.wPaint.$el.append(this.$menu);

      this.setAlignment(this.options.alignment);
    },

    // create / reset menu - will add new entries in the array
    reset: function () {
      var _this = this,
          menu = $.fn.wPaint.menus[this.name],
          key;

      // self invoking function
      function itemAppend(item) { _this._appendItem(item); }

      for (key in menu.items) {

        // only add unique (new) items (icons)
        if (!this.$menuHolder.children('.wPaint-menu-icon-name-' + key).length) {
          
          // add the item name, we will need this internally
          menu.items[key].name = key;

          // use default img if img not set
          menu.items[key].img = _this.wPaint.options.path + (menu.items[key].img || menu.img);

          // make self invoking to avoid overwrites
          (itemAppend)(menu.items[key]);
        }
      }
    },

    _appendItem: function (item) {
      var $item = this['_createIcon' + item.icon.capitalize()](item);

      if (item.after) {
        this.$menuHolder.children('.wPaint-menu-icon-name-' + item.after).after($item);
      }
      else {
        this.$menuHolder.append($item);
      }
    },

    /************************************
     * setters
     ************************************/
    setOffsetLeft: function (left) {
      this.$menu.css({left: left});
    },

    setOffsetTop: function (top) {
      this.$menu.css({top: top});
    },

    setAlignment: function (alignment) {
      var tempLeft = this.$menu.css('left');

      this.$menu.attr('class', this.$menu.attr('class').replace(/wPaint-menu-alignment-.+\s|wPaint-menu-alignment-.+$/, ''));
      this.$menu.addClass('wPaint-menu-alignment-' + alignment);

      this.$menu.width('auto').css('left', -10000);
      this.$menu.width(this.$menu.width()).css('left', tempLeft);

      // set proper offsets based on alignment
      if (this.type === 'secondary') {
        if (this.options.alignment === 'horizontal') {
          this.dockOffset.top = this.wPaint.menus.primary.$menu.outerHeight(true);
        }
        else {
          this.dockOffset.left = this.wPaint.menus.primary.$menu.outerWidth(true);
        }
      }   
    },

    /************************************
     * handle
     ************************************/
    _createHandle: function () {
      var _this = this,
          $handle = $('<div class="wPaint-menu-handle"></div>');

      // draggable functions
      function draggableStart() {
        _this.docked = false;
        _this._setDrag();
      }

      function draggableStop() {
        $.each(_this.$menu.data('ui-draggable').snapElements, function (i, el) {
          var offset = _this.$menu.offset(),
              offsetPrimary = _this.wPaint.menus.primary.$menu.offset();

          _this.dockOffset.left = offset.left - offsetPrimary.left;
          _this.dockOffset.top = offset.top - offsetPrimary.top;
          _this.docked = el.snapping;
        });

        _this._setDrag();
      }

      function draggableDrag() {
        _this._setIndex();
      }

      // the drag/snap events for menus are tricky
      // init handle for ALL menus, primary menu will drag a secondary menu with it, but that is un/binded in the toggle function
      this.$menu.draggable({handle: $handle});

      // if it's a secondary menu we want to check for snapping
      // on drag we set docked to false, on snap we set it back to true
      if (this.type === 'secondary') {
        this.$menu.draggable('option', 'snap', this.wPaint.menus.primary.$menu);
        this.$menu.draggable('option', 'start', draggableStart);
        this.$menu.draggable('option', 'stop', draggableStop);
        this.$menu.draggable('option', 'drag', draggableDrag);
      }

      $handle.bindMobileEvents();

      return $handle;
    },

    /************************************
     * generic icon
     ************************************/
    _createIconBase: function (item) {
      var _this = this,
          $icon = $('<div class="wPaint-menu-icon wPaint-menu-icon-name-' + item.name + '"></div>'),
          $iconImg = $('<div class="wPaint-menu-icon-img"></div>'),
          width = $iconImg.realWidth(null, null, this.wPaint.$el);

      function mouseenter(e) {
        var $el = $(e.currentTarget);

        $el.siblings('.hover').removeClass('hover');
        if (!$el.hasClass('disabled')) { $el.addClass('hover'); }
      }

      function mouseleave(e) {
        $(e.currentTarget).removeClass('hover');
      }

      function click() {
        _this.wPaint.menus.active = _this;
      }

      $icon
      .attr('title', item.title)
      .on('mousedown', $.proxy(this.wPaint._closeSelectBoxes, this.wPaint, item))
      .on('mouseenter', mouseenter)
      .on('mouseleave', mouseleave)
      .on('click', click);

      // can have index:0 so be careful here
      if ($.isNumeric(item.index)) {
        $iconImg
        .css({
          backgroundImage: 'url(' + item.img + ')',
          backgroundPosition: (-width * item.index) + 'px 0px'
        });
      }

      return $icon.append($iconImg);
    },

    /************************************
     * icon group
     ************************************/
    _createIconGroup: function (item) {
      var _this = this,
          css = {backgroundImage: 'url(' + item.img + ')'},
          $icon = this.$menuHolder.children('.wPaint-menu-icon-group-' + item.group),
          iconExists = $icon.length,
          $selectHolder = null,
          $option = null,
          $item = null,
          width = 0;

      // local functions
      function setIconClick() {

        // only trigger if menu is not visible otherwise it will fire twice
        // from the mousedown to open the menu which we want just to display the menu
        // not fire the button callback
        if (!$icon.children('.wPaint-menu-select-holder').is(':visible')) {
          item.callback.apply(_this.wPaint, []);
        }
      }

      function selectHolderClick() {
        $icon.addClass('active').siblings('.active').removeClass('active');
      }

      function optionClick() {

        // rebind the main icon when we select an option
        $icon
        .attr('title', item.title)
        .off('click.setIcon')
        .on('click.setIcon', setIconClick);
        
        // run the callback right away when we select an option
        $icon.children('.wPaint-menu-icon-img').css(css);
        item.callback.apply(_this.wPaint, []);
      }

      // crate icon if it doesn't exist yet
      if (!iconExists) {
        $icon = this._createIconBase(item)
        .addClass('wPaint-menu-icon-group wPaint-menu-icon-group-' + item.group)
        .on('click.setIcon', setIconClick)
        .on('mousedown', $.proxy(this._iconClick, this));
      }

      // get the proper width here now that we have the icon
      // this is for the select box group not the main icon
      width = $icon.children('.wPaint-menu-icon-img').realWidth(null, null, this.wPaint.$el);
      css.backgroundPosition = (-width * item.index) + 'px center';

      // create selectHolder if it doesn't exist
      $selectHolder = $icon.children('.wPaint-menu-select-holder');
      if (!$selectHolder.length) {
        $selectHolder = this._createSelectBox($icon);
        $selectHolder.children().on('click', selectHolderClick);
      }

      $item = $('<div class="wPaint-menu-icon-select-img"></div>')
      .attr('title', item.title)
      .css(css);

      $option = this._createSelectOption($selectHolder, $item)
      .addClass('wPaint-menu-icon-name-' + item.name)
      .on('click', optionClick);

      // move select option into place if after is set
      if (item.after) {
        $selectHolder.children('.wPaint-menu-select').children('.wPaint-menu-icon-name-' + item.after).after($option);
      }

      // we only want to return an icon to append on the first run of a group
      if (!iconExists) { return $icon; }
    },

    /************************************
     * icon generic
     ************************************/
    _createIconGeneric: function (item) {

      // just a go between for the iconGeneric type
      return this._createIconActivate(item);
    },

    /************************************
     * icon
     ************************************/
    _createIconActivate: function (item) {

      // since we are piggy backing icon with the item.group
      // we'll just do a redirect and keep the code separate for group icons
      if (item.group) { return this._createIconGroup(item); }

      var _this = this,
          $icon = this._createIconBase(item);

      function iconClick(e) {
        if (item.icon !== 'generic') { _this._iconClick(e); }
        item.callback.apply(_this.wPaint, [e]);
      }

      $icon.on('click', iconClick);

      return $icon;
    },

    _isIconDisabled: function (name) {
      return this.$menuHolder.children('.wPaint-menu-icon-name-' + name).hasClass('disabled');
    },

    _setIconDisabled: function (name, disabled) {
      var $icon = this.$menuHolder.children('.wPaint-menu-icon-name-' + name);

      if (disabled) {
        $icon.addClass('disabled').removeClass('hover');
      }
      else {
        $icon.removeClass('disabled');
      }
    },

    _getIcon: function (name) {
      return this.$menuHolder.children('.wPaint-menu-icon-name-' + name);
    },

    _iconClick: function (e) {
      var $el = $(e.currentTarget),
          menus = this.wPaint.menus.all;

      // make sure to loop using parent object - don't use .wPaint-menu-secondary otherwise we would hide menu for all canvases
      for (var menu in menus) {
        if (menus[menu] && menus[menu].type === 'secondary') { menus[menu].$menu.hide(); }  
      }

      $el.siblings('.active').removeClass('active');
      if (!$el.hasClass('disabled')) { $el.addClass('active'); }
    },

    /************************************
     * iconToggle
     ************************************/
    _createIconToggle: function (item) {
      var _this = this,
          $icon = this._createIconBase(item);

      function iconClick() {
        $icon.toggleClass('active');
        item.callback.apply(_this.wPaint, [$icon.hasClass('active')]);
      }

      $icon.on('click', iconClick);

      return $icon;
    },

    /************************************
     * select
     ************************************/
    _createIconSelect: function (item) {
      var _this = this,
          $icon = this._createIconBase(item),
          $selectHolder = this._createSelectBox($icon),
          i, ii, $option;

      function optionClick(e) {
        $icon.children('.wPaint-menu-icon-img').html($(e.currentTarget).html());
        item.callback.apply(_this.wPaint, [$(e.currentTarget).html()]);
      }

      // add values for select
      for (i = 0, ii = item.range.length; i < ii; i++) {
        $option = this._createSelectOption($selectHolder, item.range[i]);
        $option.on('click', optionClick);
        if (item.useRange) { $option.css(item.name, item.range[i]); }
      }

      return $icon;
    },

    _createSelectBox: function ($icon) {
      var $selectHolder = $('<div class="wPaint-menu-select-holder"></div>'),
          $select = $('<div class="wPaint-menu-select"></div>'),
          timer = null;

      function clickSelectHolder(e) {
        e.stopPropagation();
        $selectHolder.hide();
      }

      function iconMousedown() {
        timer = setTimeout(function () { $selectHolder.toggle(); }, 200);
      }

      function iconMouseup() {
        clearTimeout(timer);
      }

      function iconClick() {
        $selectHolder.toggle();
      }

      $selectHolder
      .on('mousedown mouseup', this.wPaint._stopPropagation)
      .on('click', clickSelectHolder)
      .hide();

      // of hozizontal we'll pop below the icon
      if (this.options.alignment === 'horizontal') {
        $selectHolder.css({left: 0, top: $icon.children('.wPaint-menu-icon-img').realHeight('outer', true, this.wPaint.$el)});
      }
      // vertical we'll pop to the right
      else {
        $selectHolder.css({left: $icon.children('.wPaint-menu-icon-img').realWidth('outer', true, this.wPaint.$el), top: 0});
      }

      $icon
      .addClass('wPaint-menu-icon-select')
      .append('<div class="wPaint-menu-icon-group-arrow"></div>')
      .append($selectHolder.append($select));

      // for groups we want to add a delay before the selectBox pops up
      if ($icon.hasClass('wPaint-menu-icon-group')) {
        $icon
        .on('mousedown', iconMousedown)
        .on('mouseup', iconMouseup);
      }
      else { $icon.on('click', iconClick); }

      return $selectHolder;
    },

    _createSelectOption: function ($selectHolder, value) {
      var $select = $selectHolder.children('.wPaint-menu-select'),
          $option = $('<div class="wPaint-menu-select-option"></div>').append(value);

      // set class for first item to remove any undesired styles like borders
      if (!$select.children().length) { $option.addClass('first'); }

      $select.append($option);

      return $option;
    },

    _setSelectValue: function (icon, value) {
      this._getIcon(icon).children('.wPaint-menu-icon-img').html(value);
    },

    /************************************
     * color picker
     ************************************/
    _createIconColorPicker: function (item) {
      var _this = this,
          $icon = this._createIconBase(item);

      function iconClick() {

        // if we happen to click on this while in dropper mode just revert to previous
        if (_this.wPaint.options.mode === 'dropper') { _this.wPaint.setMode(_this.wPaint.previousMode); }
      }

      function iconOnSelect(color) {
        item.callback.apply(_this.wPaint, [color]);
      }

      function iconOnDropper() {
        $icon.trigger('click');
        _this.wPaint.dropper = item.name;
        _this.wPaint.setMode('dropper');
      }

      $icon
      .on('click', iconClick)
      .addClass('wPaint-menu-colorpicker')
      .wColorPicker({
        mode: 'click',
        generateButton: false,
        dropperButton: true,
        onSelect: iconOnSelect,
        onDropper: iconOnDropper
      });

      return $icon;
    },

    _setColorPickerValue: function (icon, value) {
      this._getIcon(icon).children('.wPaint-menu-icon-img').css('backgroundColor', value);
    },

    /************************************
     * menu toggle
     ************************************/
    _createIconMenu: function (item) {
      var _this = this,
          $icon = this._createIconActivate(item);

      function iconClick() {
        _this.wPaint.setCursor(item.name);

        // the items name here will be the menu name
        var menu = _this.wPaint.menus.all[item.name];
        menu.$menu.toggle();
        if (_this.handle) {
          menu._setDrag();
        } else {
          menu._setPosition();
        }
      }

      $icon.on('click', iconClick);

      return $icon;
    },

    // here we specify which menu will be dragged
    _setDrag: function () {
      var $menu = this.$menu,
          drag = null, stop = null;

      if ($menu.is(':visible')) {
        if (this.docked) {

          // make sure we are setting proper menu object here
          drag = stop = $.proxy(this._setPosition, this);
          this._setPosition();
        }

        // register drag/stop events
        this.wPaint.menus.primary.$menu.draggable('option', 'drag', drag);
        this.wPaint.menus.primary.$menu.draggable('option', 'stop', stop);
      }
    },

    _setPosition: function () {
      var offset = this.wPaint.menus.primary.$menu.position();

      this.$menu.css({
        left: offset.left + this.dockOffset.left,
        top: offset.top + this.dockOffset.top
      });
    },

    _setIndex: function () {
      var primaryOffset = this.wPaint.menus.primary.$menu.offset(),
          secondaryOffset = this.$menu.offset();

      if (
        secondaryOffset.top < primaryOffset.top ||
        secondaryOffset.left < primaryOffset.left
      ) {
        this.$menu.addClass('wPaint-menu-behind');
      }
      else {
        this.$menu.removeClass('wPaint-menu-behind');
      }
    }
  };

  /************************************************************************
   * wPaint
   ************************************************************************/
  $.support.canvas = (document.createElement('canvas')).getContext;

  $.fn.wPaint = function (options, value) {

    function create() {
      if (!$.support.canvas) {
        $(this).html('Browser does not support HTML5 canvas, please upgrade to a more modern browser.');
        return false;
      }

      return $.proxy(get, this)();
    }

    function get() {
      var wPaint = $.data(this, 'wPaint');

      if (!wPaint) {
        wPaint = new Paint(this, $.extend(true, {}, options));
        $.data(this, 'wPaint', wPaint);
      }

      return wPaint;
    }

    function runOpts() {
      var wPaint = $.data(this, 'wPaint');

      if (wPaint) {
        if (wPaint[options]) { wPaint[options].apply(wPaint, [value]); }
        else if (value !== undefined) {
          if (wPaint[func]) { wPaint[func].apply(wPaint, [value]); }
          if (wPaint.options[options]) { wPaint.options[options] = value; }
        }
        else {
          if (wPaint[func]) { values.push(wPaint[func].apply(wPaint, [value])); }
          else if (wPaint.options[options]) { values.push(wPaint.options[options]); }
          else { values.push(undefined); }
        }
      }
    }

    if (typeof options === 'string') {
      var values = [],
          func = (value ? 'set' : 'get') + options.charAt(0).toUpperCase() + options.substring(1);

      this.each(runOpts);

      if (values.length) { return values.length === 1 ? values[0] : values; }
      
      return this;
    }

    options = $.extend({}, $.fn.wPaint.defaults, options);
    options.lineWidth = parseInt(options.lineWidth, 10);
    options.fontSize = parseInt(options.fontSize, 10);

    return this.each(create);
  };

  /************************************************************************
   * extend
   ************************************************************************/
  $.fn.wPaint.extend = function (funcs, protoType) {
    var key;
    
    function elEach(func) {
      if (protoType[func]) {
        var tmpFunc = Paint.prototype[func],
            newFunc = funcs[func];
        
        protoType[func] = function () {
          tmpFunc.apply(this, arguments);
          newFunc.apply(this, arguments);
        };
      }
      else {
        protoType[func] = funcs[func];
      }
    }

    protoType = protoType === 'menu' ? Menu.prototype : Paint.prototype;

    for (key in funcs) { (elEach)(key); }
  };

  /************************************************************************
   * Init holders
   ************************************************************************/
  $.fn.wPaint.menus = {};

  $.fn.wPaint.cursors = {};

  $.fn.wPaint.defaults = {
    path:            '/',                // set absolute path for images and cursors
    theme:           'standard classic', // set theme
    autoScaleImage:  true,               // auto scale images to size of canvas (fg and bg)
    autoCenterImage: true,               // auto center images (fg and bg, default is left/top corner)
    menuHandle:      true,               // setting to false will means menus cannot be dragged around
    menuOrientation: 'horizontal',       // menu alignment (horizontal,vertical)
    menuOffsetLeft:  5,                  // left offset of primary menu
    menuOffsetTop:   5,                  // top offset of primary menu
    bg:              null,               // set bg on init
    image:           null,               // set image on init
    imageStretch:    false,              // stretch smaller images to full canvans dimensions
    onShapeDown:     null,               // callback for draw down event
    onShapeMove:     null,               // callback for draw move event
    onShapeUp:       null                // callback for draw up event
  };
})(jQuery);