/******************************************
 * Websanova.com
 *
 * Resources for web entrepreneurs
 *
 * @author          Websanova
 * @copyright       Copyright (c) 2012 Websanova.
 * @license         This wPaint jQuery plug-in is dual licensed under the MIT and GPL licenses.
 * @link            http://www.websanova.com
 * @github            http://github.com/websanova/wPaint
 * @version         Version 1.13.1
 *
 ******************************************/
(function ($) {
    $.fn.wPaint = function (option, settings) {
        if (typeof option === 'object') {
            settings = option;
        }
        else if (typeof option == 'string') {
            var values = [];

            var elements = this.each(function () {
                var data = $(this).data('_wPaint');

				if(data)
				{
					if(option == 'clear') { data.clearAll(); }
					else if(option == 'image' && settings === undefined) { values.push(data.getImage()); }
					else if(option == 'image' && settings !== undefined) { data.setImage(settings, true); }
					else if(option == 'imageBg' && settings !== undefined) { data.setBgImage(settings); }
					else if($.fn.wPaint.defaultSettings[option] !== undefined)
					{
						if(settings !== undefined) { data.settings[option] = settings; }
						else { values.push(data.settings[option]); }
                    }
                    }
            });

			if(values.length === 1) { return values[0]; }
			if(values.length > 0) { return values; }
			else { return elements; }
            }

        //clean up some variables
        settings = $.extend({}, $.fn.wPaint.defaultSettings, settings || {});
        settings.lineWidthMin = parseInt(settings.lineWidthMin);
        settings.lineWidthMax = parseInt(settings.lineWidthMax);
        settings.lineWidth = parseInt(settings.lineWidth);
        settings.fontSizeMin = parseInt(settings.fontSizeMin);
        settings.fontSizeMax = parseInt(settings.fontSizeMax);
        settings.fontSize = parseInt(settings.fontSize);

        return this.each(function () {
            var $elem = $(this);
            var _settings = jQuery.extend(true, {}, settings);

            //test for HTML5 canvas
            var test = document.createElement('canvas');
            if (!test.getContext) {
                $elem.html("Browser does not support HTML5 canvas, please upgrade to a more modern browser.");
                return false;
            }

            if ($elem.data('_wPaint')) return false;

            var canvas = new Canvas(_settings, $elem);
            canvas.mainMenu = new MainMenu(canvas);
            canvas.textMenu = new TextMenu(canvas);

            if (_settings.imageBg) $elem.append(canvas.generateBg($elem.width(), $elem.height(), _settings.imageBg));
            $elem.append(canvas.generate($elem.width(), $elem.height()));
            $elem.append(canvas.generateTemp());
            $elem.append(canvas.generateTextInput());

            $elem
                    .append(canvas.mainMenu.generate(canvas, canvas.textMenu))
                    .append(canvas.textMenu.generate(canvas, canvas.mainMenu));

            //init the snap on the text menu
            canvas.mainMenu.moveTextMenu(canvas.mainMenu, canvas.textMenu);

            //init mode
            canvas.mainMenu.set_mode(canvas.mainMenu, canvas, _settings.mode);

            //pull from css so that it is dynamic
            var buttonSize = $("._wPaint_icon").outerHeight(true) - (parseInt($("._wPaint_icon").css('paddingTop').split('px')[0]) + parseInt($("._wPaint_icon").css('paddingBottom').split('px')[0]));

            canvas.mainMenu.menu.find("._wPaint_fillColorPicker").wColorPicker({
                mode: "click",
                initColor: _settings.fillStyle,
                buttonSize: buttonSize,
                showSpeed: 300,
                hideSpeed: 300,
                onSelect: function (color) {
                    canvas.settings.fillStyle = color;
                    canvas.textInput.css({color: color});
                }
            });

            canvas.mainMenu.menu.find("._wPaint_strokeColorPicker").wColorPicker({
                mode: "click",
                initColor: _settings.strokeStyle,
                buttonSize: buttonSize,
                showSpeed: 300,
                hideSpeed: 300,
                onSelect: function (color) {
                    canvas.settings.strokeStyle = color;
                }
            });

            //must set width after append to get proper dimensions
            canvas.mainMenu.setWidth(canvas, canvas.mainMenu.menu);
            canvas.mainMenu.setWidth(canvas, canvas.textMenu.menu);

            if (_settings.image) {
                canvas.setImage(_settings.image, true);
            }
            else {
                canvas.addUndo();
            }

            $elem.data('_wPaint', canvas);
        });
    }

    var shapes = ['Rectangle', 'Ellipse', 'Line', 'Text'];

    $.fn.wPaint.defaultSettings = {
        mode: 'Pencil',			// drawing mode - Rectangle, Ellipse, Line, Pencil, Eraser
        lineWidthMin: '0', 				// line width min for select drop down
        lineWidthMax: '10',				// line widh max for select drop down
        lineWidth: '2', 				// starting line width
        fillStyle: '#FFFFFF',			// starting fill style
        strokeStyle: '#FFFF00',			// start stroke style
        fontSizeMin: '8',					// min font size in px
        fontSizeMax: '20',				// max font size in px
        fontSize: '12',				// current font size for text input
        fontFamilyOptions: ['Arial', 'Courier', 'Times', 'Trebuchet', 'Verdana'], // available font families
        fontFamily: 'Arial',				// active font family for text input
        fontTypeBold: false,				// text input bold enable/disable
        fontTypeItalic: false,				// text input italic enable/disable
        fontTypeUnderline: false,				// text input italic enable/disable
        image: null,				// preload image - base64 encoded data
        imageBg: null,				// preload image bg, cannot be altered but saved with image
        drawDown: null,				// function to call when start a draw
        drawMove: null,				// function to call during a draw
        drawUp: null,				// function to call at end of draw
        menu: ['undo', 'redo', 'clear', 'rectangle', 'ellipse', 'line', 'pencil', 'text', 'eraser', 'fillColor', 'lineWidth', 'strokeColor'], // menu items - appear in order they are set
        menuOrientation: 'horizontal',		// orinetation of menu (horizontal, vertical)
        menuOffsetX: 5,					// offset for menu (left)
        menuOffsetY: 5,					// offset for menu (top)
        menuTitles: {                    // icon titles, replace any of the values to customize
            'undo': 'undo',
            'redo': 'redo',
            'clear': 'clear',
            'rectangle': 'rectangle',
            'ellipse': 'ellipse',
            'line': 'line',
            'pencil': 'pencil',
            'text': 'text',
            'eraser': 'eraser',
            'fillColor': 'fill color',
            'lineWidth': 'line width',
            'strokeColor': 'stroke color',
            'bold': 'bold',
            'italic': 'italic',
            'underline': 'underline',
            'fontSize': 'font size',
            'fontFamily': 'font family'
        },
        disableMobileDefaults: false            	// disable default touchmove events for mobile (will prevent flipping between tabs and scrolling)
    };

    /**
     * Canvas class definition
     */
    function Canvas(settings, elem) {
        this.settings = settings;
        this.$elem = elem;
        this.mainMenu = null;
        this.textMenu = null;

        this.undoArray = [];
        this.undoCurrent = -1;
        this.undoMax = 10;

        this.draw = false;

        this.canvas = null;
        this.ctx = null;

        this.canvasTemp = null;
        this.ctxTemp = null;

        this.canvasBg = null;
        this.ctxBg = null;

        this.canvasTempLeftOriginal = null;
        this.canvasTempTopOriginal = null;

        this.canvasTempLeftNew = null;
        this.canvasTempTopNew = null;

        this.textInput = null;

        return this;
    }

    Canvas.prototype =
    {
        /*******************************************************************************
         * Generate canvases and events
         *******************************************************************************/
        generate: function (width, height) {
            this.canvas = document.createElement('canvas');
            this.ctx = this.canvas.getContext('2d');

            //create local reference
            var _self = this;

            $(this.canvas)
                    .attr('width', width + 'px')
                    .attr('height', height + 'px')
                    .css({position: 'absolute', left: 0, top: 0})
                    .mousedown(function (e) {
                        e.preventDefault();
                        e.stopPropagation();
                        _self.draw = true;
                        _self.callFunc(e, _self, 'Down');
                    });

            $(document)
                    .mousemove(function (e) {
                        if (_self.draw) _self.callFunc(e, _self, 'Move');
                    })
                    .mouseup(function (e) {
                        //make sure we are in draw mode otherwise this will fire on any mouse up.
                        if (_self.draw) {
                            _self.draw = false;
                            _self.callFunc(e, _self, 'Up');
                        }
                    });

            this.bindMobile();

            return $(this.canvas);
        },

        bindMobile: function () {
            $(this.canvas).bind('touchstart touchmove touchend touchcancel', function () {
                var touches = event.changedTouches, first = touches[0], type = "";

                switch (event.type) {
                    case "touchstart":
                        type = "mousedown";
                        break;
                    case "touchmove":
                        type = "mousemove";
                        break;
                    case "touchend":
                        type = "mouseup";
                        break;
                    default:
                        return;
                }

                var simulatedEvent = document.createEvent("MouseEvent");

                simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0/*left*/, null);
                first.target.dispatchEvent(simulatedEvent);
                event.preventDefault();
            });

            //eliminate browser defaults for
            if (this.settings.disableMobileDefaults) $(document).bind('touchmove', function (e) {
                e.preventDefault();
            });
        },

        generateTemp: function () {
            this.canvasTemp = document.createElement('canvas');
            this.ctxTemp = this.canvasTemp.getContext('2d');

            $(this.canvasTemp).css({position: 'absolute'}).hide();

            return $(this.canvasTemp);
        },

        generateBg: function (width, height, data) {
            var _self = this;

            if (!this.canvasBg) {
                this.canvasBg = document.createElement('canvas');
                this.ctxBg = this.canvasBg.getContext('2d');

                $(this.canvasBg).attr('id', 'mofo').css({position: 'absolute', left: 0, top: 0}).attr('width', width).attr('height', height);
            }

            this.setBgImage(data);

            return $(this.canvasBg);
        },

        generateTextInput: function () {
            var _self = this;

            _self.textCalc = $('<div></div>').css({display: 'none', fontSize: this.settings.fontSize, lineHeight: this.settings.fontSize + 'px', fontFamily: this.settings.fontFamily});

            _self.textInput =
                    $('<textarea class="_wPaint_textInput" spellcheck="false"></textarea>')
                            .css({display: 'none', position: 'absolute', color: this.settings.fillStyle, fontSize: this.settings.fontSize, lineHeight: this.settings.fontSize + 'px', fontFamily: this.settings.fontFamily})

            if (_self.settings.fontTypeBold) {
                _self.textInput.css('fontWeight', 'bold');
                _self.textCalc.css('fontWeight', 'bold');
            }
            if (_self.settings.fontTypeItalic) {
                _self.textInput.css('fontStyle', 'italic');
                _self.textCalc.css('fontStyle', 'italic');
            }
            if (_self.settings.fontTypeUnderline) {
                _self.textInput.css('textDecoration', 'underline');
                _self.textCalc.css('textDecoration', 'underline');
            }

            $('body').append(_self.textCalc);

            return _self.textInput;
        },

        callFunc: function (e, _self, event) {
            $e = jQuery.extend(true, {}, e);

            var canvas_offset = $(_self.canvas).offset();

            $e.origPageX = Math.floor($e.pageX - canvas_offset.left);
            $e.origPageY = Math.floor($e.pageY - canvas_offset.top);
            $e.pageX = $e.origPageX * _self.xRatio();
            $e.pageY = $e.origPageY * _self.yRatio();

            var mode = $.inArray(_self.settings.mode, shapes) > -1 ? 'Shape' : _self.settings.mode;
            var func = _self['draw' + mode + '' + event];

            if (func) func($e, _self);

            if (_self.settings['draw' + event]) _self.settings['draw' + event].apply(_self, [e, mode]);

            if (_self.settings.mode !== 'Text' && event === 'Up') {
                this.addUndo();
            }
        },

        /*******************************************************************************
         * draw any shape
         *******************************************************************************/
        drawShapeDown: function (e, _self) {
            if (_self.settings.mode == 'Text') {
                //draw current text before resizing next text box
                if (_self.textInput.val() != '') _self.drawTextUp(e, _self);

                _self.textInput.css({left: e.pageX - 1, top: e.pageY - 1, width: 0, height: 0});
            }

            $(_self.canvasTemp)
                    .css({left: e.origPageX, top: e.origPageY})
                    .attr('width', 0)
                    .attr('height', 0)
                    .show();

            _self.canvasTempLeftOriginal = e.pageX;
            _self.canvasTempTopOriginal = e.pageY;

            var func = _self['draw' + _self.settings.mode + 'Down'];

            if (func) func(e, _self);
        },

        drawShapeMove: function (e, _self) {
            var xo = _self.canvasTempLeftOriginal;
            var yo = _self.canvasTempTopOriginal;

            var half_line_width = _self.settings.lineWidth / 2;

            // Calculations done respective to the "image" (i.e. "larger") values
            var left = (e.pageX < xo ? e.pageX : xo) - (_self.settings.mode == 'Line' ? Math.floor(half_line_width) : 0);
            var top = (e.pageY < yo ? e.pageY : yo) - (_self.settings.mode == 'Line' ? Math.floor(half_line_width) : 0);
            var width = Math.abs(e.pageX - xo) + (_self.settings.mode == 'Line' ? _self.settings.lineWidth : 0);
            var height = Math.abs(e.pageY - yo) + (_self.settings.mode == 'Line' ? _self.settings.lineWidth : 0);

            $(_self.canvasTemp)
                    .css({left:      left   / _self.xRatio(),
                          top:       top    / _self.yRatio(),
                          maxWidth:  width  / _self.xRatio(),
                          maxHeight: height / _self.yRatio()})
                    .attr('width',  width)
                    .attr('height', height);

            if (_self.settings.mode == 'Text') _self.textInput.css({left: left - 1, top: top - 1, width: width, height: height});

            _self.canvasTempLeftNew = left;
            _self.canvasTempTopNew = top;

            var func = _self['draw' + _self.settings.mode + 'Move'];

            if (func) {
                var factor = _self.settings.mode == 'Line' ? 1 : 2;

                e.x = half_line_width * factor;
                e.y = half_line_width * factor;
                e.w = width - _self.settings.lineWidth * factor;
                e.h = height - _self.settings.lineWidth * factor;

                _self.ctxTemp.fillStyle = _self.settings.fillStyle;
                _self.ctxTemp.strokeStyle = _self.settings.strokeStyle;
                _self.ctxTemp.lineWidth = _self.settings.lineWidth * factor;

                func(e, _self);
            }
        },

        drawShapeUp: function (e, _self) {
            if (_self.settings.mode != 'Text') {
                _self.ctx.drawImage(_self.canvasTemp, _self.canvasTempLeftNew, _self.canvasTempTopNew);

                $(_self.canvasTemp).hide();

                var func = _self['draw' + _self.settings.mode + 'Up'];
                if (func) func(e, _self);
            }
        },

        /*******************************************************************************
         * draw rectangle
         *******************************************************************************/
        drawRectangleMove: function (e, _self) {
            _self.ctxTemp.beginPath();
            _self.ctxTemp.rect(e.x, e.y, e.w, e.h)
            _self.ctxTemp.closePath();
            _self.ctxTemp.stroke();
            _self.ctxTemp.fill();
        },

        /*******************************************************************************
         * draw ellipse
         *******************************************************************************/
        drawEllipseMove: function (e, _self) {
            var kappa = .5522848;
            var ox = (e.w / 2) * kappa; 	// control point offset horizontal
            var oy = (e.h / 2) * kappa; 	// control point offset vertical
            var xe = e.x + e.w;           	// x-end
            var ye = e.y + e.h;           	// y-end
            var xm = e.x + e.w / 2;       	// x-middle
            var ym = e.y + e.h / 2;       	// y-middle

            _self.ctxTemp.beginPath();
            _self.ctxTemp.moveTo(e.x, ym);
            _self.ctxTemp.bezierCurveTo(e.x, ym - oy, xm - ox, e.y, xm, e.y);
            _self.ctxTemp.bezierCurveTo(xm + ox, e.y, xe, ym - oy, xe, ym);
            _self.ctxTemp.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
            _self.ctxTemp.bezierCurveTo(xm - ox, ye, e.x, ym + oy, e.x, ym);
            _self.ctxTemp.closePath();
            if (_self.settings.lineWidth > 0)_self.ctxTemp.stroke();
            _self.ctxTemp.fill();
        },

        /*******************************************************************************
         * draw line
         *******************************************************************************/
        drawLineMove: function (e, _self) {
            var xo = _self.canvasTempLeftOriginal;
            var yo = _self.canvasTempTopOriginal;

            if (e.pageX < xo) {
                e.x = e.x + e.w;
                e.w = e.w * -1
            }
            if (e.pageY < yo) {
                e.y = e.y + e.h;
                e.h = e.h * -1
            }

            _self.ctxTemp.lineJoin = "round";
            _self.ctxTemp.beginPath();
            _self.ctxTemp.moveTo(e.x, e.y);
            _self.ctxTemp.lineTo(e.x + e.w, e.y + e.h);
            _self.ctxTemp.closePath();
            _self.ctxTemp.stroke();
        },

        /*******************************************************************************
         * draw pencil
         *******************************************************************************/
        drawPencilDown: function (e, _self) {
            _self.ctx.lineJoin = "round";
            _self.ctx.lineCap = "round";
            _self.ctx.strokeStyle = _self.settings.strokeStyle;
            _self.ctx.fillStyle = _self.settings.strokeStyle;
            _self.ctx.lineWidth = _self.settings.lineWidth;

            //draw single dot in case of a click without a move
            _self.ctx.beginPath();
            _self.ctx.arc(e.pageX, e.pageY, _self.settings.lineWidth / 2, 0, Math.PI * 2, true);
            _self.ctx.closePath();
            _self.ctx.fill();

            //start the path for a drag
            _self.ctx.beginPath();
            _self.ctx.moveTo(e.pageX, e.pageY);
        },

        drawPencilMove: function (e, _self) {
            _self.ctx.lineTo(e.pageX, e.pageY);
            _self.ctx.stroke();
        },

        drawPencilUp: function (e, _self) {
            _self.ctx.closePath();
        },

        /*******************************************************************************
         * draw text
         *******************************************************************************/

        drawTextDown: function (e, _self) {
            _self.textInput.val('').show().focus();
        },

        drawTextUp: function (e, _self) {
            if (e) {
                this.addUndo();
            }

            var fontString = '';
            if (_self.settings.fontTypeItalic) fontString += 'italic ';
            //if(_self.settings.fontTypeUnderline) fontString += 'underline ';
            if (_self.settings.fontTypeBold) fontString += 'bold ';

            fontString += _self.settings.fontSize + 'px ' + _self.settings.fontFamily;

            //setup lines
            var lines = _self.textInput.val().split('\n');
            var linesNew = [];
            var textInputWidth = _self.textInput.width() - 2;

            var width = 0;
            var lastj = 0;

            for (var i = 0, ii = lines.length; i < ii; i++) {
                _self.textCalc.html('');
                lastj = 0;

                for (var j = 0, jj = lines[0].length; j < jj; j++) {
                    width = _self.textCalc.append(lines[i][j]).width();

                    if (width > textInputWidth) {
                        linesNew.push(lines[i].substring(lastj, j));
                        lastj = j;
                        _self.textCalc.html(lines[i][j]);
                    }
                }

                if (lastj != j) linesNew.push(lines[i].substring(lastj, j));
            }

            lines = _self.textInput.val(linesNew.join('\n')).val().split('\n');

            var offset = _self.textInput.position();
            var left = offset.left;
            var top = offset.top;
            var underlineOffset = 0;

            for (var i = 0, ii = lines.length; i < ii; i++) {
                _self.ctx.fillStyle = _self.settings.fillStyle;

                _self.ctx.textBaseline = 'top';
                _self.ctx.font = fontString;
                _self.ctx.fillText(lines[i], left, top);

                top += _self.settings.fontSize;

                if (lines[i] != '' && _self.settings.fontTypeUnderline) {
                    width = _self.textCalc.html(lines[i]).width();

                    //manually set pixels for underline since to avoid antialiasing 1px issue, and lack of support for underline in canvas
                    var imgData = _self.ctx.getImageData(0, top + underlineOffset, width, 1);

                    for (j = 0; j < imgData.width * imgData.height * 4; j += 4) {
                        imgData.data[j] = parseInt(_self.settings.fillStyle.substring(1, 3), 16);
                        imgData.data[j + 1] = parseInt(_self.settings.fillStyle.substring(3, 5), 16);
                        imgData.data[j + 2] = parseInt(_self.settings.fillStyle.substring(5, 7), 16);
                        imgData.data[j + 3] = 255;
                    }

                    _self.ctx.putImageData(imgData, left, top + underlineOffset);
                }
            }
        },

        /*******************************************************************************
         * eraser
         *******************************************************************************/
        drawEraserDown: function (e, _self) {
            _self.ctx.save();
            _self.ctx.globalCompositeOperation = 'destination-out';
            _self.drawPencilDown(e, _self);
        },

        drawEraserMove: function (e, _self) {
            _self.drawPencilMove(e, _self);
        },

        drawEraserUp: function (e, _self) {
            _self.drawPencilUp(e, _self);
            _self.ctx.restore();
        },

        /*******************************************************************************
         * save / load data
         *******************************************************************************/
        getImage: function () {
            this.canvasSave = document.createElement('canvas');
            this.ctxSave = this.canvasSave.getContext('2d');

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

            //if a bg image is set, it will automatically save with the image
            if (this.canvasBg) this.ctxSave.drawImage(this.canvasBg, 0, 0);

            this.ctxSave.drawImage(this.canvas, 0, 0);

            return this.canvasSave.toDataURL();
        },

        setImage: function (data, addUndo) {
            var _self = this;

            var myImage = new Image();
            myImage.src = data.toString();

            _self.ctx.clearRect(0, 0, _self.canvas.width, _self.canvas.height);

            $(myImage).load(function () {
                _self.ctx.drawImage(myImage, 0, 0);
                if (addUndo) {
                    _self.addUndo();
                }
            });
        },

        setBgImage: function (data, addUndo) {
            var _self = this;

            var myImage = new Image();
            myImage.src = data.toString();

            _self.ctxBg.clearRect(0, 0, _self.canvasBg.width, _self.canvasBg.height);

            $(myImage).load(function () {
                _self.ctxBg.drawImage(myImage, 0, 0);
            });
        },

        /*******************************************************************************
         * undo / redo
         *******************************************************************************/

        addUndo: function () {
            //if it's not at the end of the array we need to repalce the current array position
            if (this.undoCurrent < this.undoArray.length - 1) {
                this.undoArray[++this.undoCurrent] = this.getImage();
            }
            else // owtherwise we push normally here
            {
                this.undoArray.push(this.getImage());

                //if we're at the end of the array we need to slice off the front - in increment required
                if (this.undoArray.length > this.undoMax) {
                    this.undoArray = this.undoArray.slice(1, this.undoArray.length);
                }
                //if we're NOT at the end of the array, we just increment
		 		else{ this.undoCurrent++; }
                }

            //for undo's then a new draw we want to remove everything afterwards - in most cases nothing will happen here
            while (this.undoCurrent != this.undoArray.length - 1) {
                this.undoArray.pop();
            }

            this.undoToggleIcons();
        },

        setUndoImage: function () {
            this.setImage(this.undoArray[this.undoCurrent]);
        },

        undoNext: function () {
            if (this.undoArray[this.undoCurrent + 1]) {
                this.undoCurrent++;
                this.setUndoImage();
            }

            this.undoToggleIcons();
        },

        undoPrev: function () {
            if (this.undoArray[this.undoCurrent - 1]) {
                this.undoCurrent--;
                this.setUndoImage();
            }

            this.undoToggleIcons();
        },

        undoToggleIcons: function () {
            var iconUndo = this.mainMenu.menu.find("._wPaint_undo");
            var iconRedo = this.mainMenu.menu.find("._wPaint_redo");

			if(this.undoCurrent > 0 && this.undoArray.length > 1)
			{
				 if(!iconUndo.hasClass('uactive')) { iconUndo.addClass('uactive'); }
                }
			else { iconUndo.removeClass('uactive'); }

			if(this.undoCurrent < this.undoArray.length-1)
			{
				if(!iconRedo.hasClass('uactive')) { iconRedo.addClass('uactive'); }
                }
			else { iconRedo.removeClass('uactive'); }
        },

        /*******************************************************************************
         * Functions
         *******************************************************************************/
        clearAll: function () {
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

            this.addUndo();
        },

        /**
         * Ration between the actual width, and the image width
         * - image width can be larger when the canvas is constraint with a `max-width`
         * - returned ration is >= 1
         */
        xRatio: function () { return this.canvas.width  / this.canvas.clientWidth ;  },
        yRatio: function () { return this.canvas.height / this.canvas.clientHeight; }
	}

    /**
     * Main Menu
     */
    function MainMenu(canvas) {
        this.menu = null;

        return this;
    }

    MainMenu.prototype =
    {
        generate: function (canvas, textMenu) {
            var $canvas = canvas;
            this.textMenu = textMenu;
            var _self = this;

            //setup the line width select
            var options = '';
            for (var i = $canvas.settings.lineWidthMin; i <= $canvas.settings.lineWidthMax; i++) options += '<option value="' + i + '" ' + ($canvas.settings.lineWidth == i ? 'selected="selected"' : '') + '>' + i + '</option>';

            var lineWidth = $('<div class="_wPaint_lineWidth _wPaint_dropDown" title="' + $canvas.settings.menuTitles.lineWidth + '"></div>').append(
                    $('<select>' + options + '</select>')
                            .change(function (e) {
                                $canvas.settings.lineWidth = parseInt($(this).val());
                            })
            )

            //content
            var menuContent = $('<div class="_wPaint_options"></div>');

            $.each($canvas.settings.menu, function (i, item) {
                switch (item) {
                    case 'undo':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_undo" title="' + $canvas.settings.menuTitles.undo + '"></div>').click(function () {
                            $canvas.undoPrev();
                        }));
                        break;
                    case 'redo':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_redo" title="' + $canvas.settings.menuTitles.redo + '"></div>').click(function () {
                            $canvas.undoNext();
                        }));
                        break;
                    case 'clear':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_clear" title="' + $canvas.settings.menuTitles.clear + '"></div>').click(function () {
                            $canvas.clearAll();
                        }));
                        break;
                    case 'rectangle':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_rectangle" title="' + $canvas.settings.menuTitles.rectangle + '"></div>').click(function () {
                            _self.set_mode(_self, $canvas, 'Rectangle');
                        }));
                        break;
                    case 'ellipse':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_ellipse" title="' + $canvas.settings.menuTitles.ellipse + '"></div>').click(function () {
                            _self.set_mode(_self, $canvas, 'Ellipse');
                        }));
                        break;
                    case 'line':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_line" title="' + $canvas.settings.menuTitles.line + '"></div>').click(function () {
                            _self.set_mode(_self, $canvas, 'Line');
                        }));
                        break;
                    case 'pencil':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_pencil" title="' + $canvas.settings.menuTitles.pencil + '"></div>').click(function () {
                            _self.set_mode(_self, $canvas, 'Pencil');
                        }));
                        break;
                    case 'text':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_text" title="' + $canvas.settings.menuTitles.text + '"></div>').click(function () {
                            _self.set_mode(_self, $canvas, 'Text');
                        }));
                        break;
                    case 'eraser':
                        menuContent.append($('<div class="_wPaint_icon _wPaint_eraser" title="' + $canvas.settings.menuTitles.eraser + '"></div>').click(function (e) {
                            _self.set_mode(_self, $canvas, 'Eraser');
                        }));
                        break;
                    case 'fillColor':
                        menuContent.append($('<div class="_wPaint_fillColorPicker _wPaint_colorPicker" title="' + $canvas.settings.menuTitles.fillColor + '"></div>'));
                        break;
                    case 'lineWidth':
                        menuContent.append(lineWidth);
                        break;
                    case 'strokeColor':
                        menuContent.append($('<div class="_wPaint_strokeColorPicker _wPaint_colorPicker" title="' + $canvas.settings.menuTitles.strokeColor + 'r"></div>'));
                        break;
                }
            });

            //handle
            var menuHandle = $('<div class="_wPaint_handle"></div>')

            //get position of canvas
            //var offset = $($canvas.canvas).offset();

            //menu
            return this.menu =
                    $('<div class="_wPaint_menu _wPaint_menu_' + $canvas.settings.menuOrientation + '"></div>')
                            .css({position: 'absolute', left: $canvas.settings.menuOffsetX, top: $canvas.settings.menuOffsetY})
                            .draggable({
                                handle: menuHandle,
                                drag: function () {
                                    _self.moveTextMenu(_self, _self.textMenu)
                                },
                                stop: function () {
                                    _self.moveTextMenu(_self, _self.textMenu)
                                }
                            })
                            .append(menuHandle)
                            .append(menuContent);
        },

        moveTextMenu: function (mainMenu, textMenu) {
            if (textMenu.docked) {
                textMenu.menu.css({left: parseInt(mainMenu.menu.css('left')) + textMenu.dockOffsetLeft, top: parseInt(mainMenu.menu.css('top')) + textMenu.dockOffsetTop});
            }
        },

        set_mode: function (_self, $canvas, mode) {
            $canvas.settings.mode = mode;

            if (mode == 'Text') {
                _self.textMenu.menu.show();
                _self.setWidth($canvas, _self.textMenu.menu);
            }
            else {
                $canvas.drawTextUp(null, $canvas);
                _self.textMenu.menu.hide();
                $canvas.textInput.hide();
            }

            _self.menu.find("._wPaint_icon").removeClass('active');
            _self.menu.find("._wPaint_" + mode.toLowerCase()).addClass('active');
        },

        setWidth: function (canvas, menu) {
            var options = menu.find('._wPaint_options');

            if (canvas.settings.menuOrientation === 'vertical') {
                // set proper width
                var width = menu.find('._wPaint_options > div:first').outerWidth(true);
                width += (options.outerWidth(true) - options.width());

                //set proper height
            }
            else {
                var width = menu.find('._wPaint_handle').outerWidth(true);
                width += menu.outerWidth(true) - menu.width();

                menu.find('._wPaint_options').children().each(function () {
                    width += $(this).outerWidth(true);
                });
            }


            menu.width(width);
        }
    }

    /**
     * Text Helper
     */
    function TextMenu(canvas) {
        this.menu = null;

        this.docked = true;

        this.dockOffsetLeft = canvas.settings.menuOrientation === 'vertical' ? 36 : 0;
        this.dockOffsetTop = canvas.settings.menuOrientation === 'vertical' ? 0 : 36;

        return this;
    }

    TextMenu.prototype =
    {
        generate: function (canvas, mainMenu) {
            var $canvas = canvas;
            var _self = this;

            //setup font sizes
            var options = '';
            for (var i = $canvas.settings.fontSizeMin; i <= $canvas.settings.fontSizeMax; i++) options += '<option value="' + i + '" ' + ($canvas.settings.fontSize == i ? 'selected="selected"' : '') + '>' + i + '</option>';

            var fontSize = $('<div class="_wPaint_fontSize _wPaint_dropDown" title="' + $canvas.settings.menuTitles.fontSize + '"></div>').append(
                    $('<select>' + options + '</select>')
                            .change(function (e) {
                                var fontSize = parseInt($(this).val());
                                $canvas.settings.fontSize = fontSize;
                                $canvas.textInput.css({fontSize: fontSize, lineHeight: fontSize + 'px'});
                                $canvas.textCalc.css({fontSize: fontSize, lineHeight: fontSize + 'px'});
                            })
            )

            //setup font family
            var options = '';
            for (var i = 0, ii = $canvas.settings.fontFamilyOptions.length; i < ii; i++) options += '<option value="' + $canvas.settings.fontFamilyOptions[i] + '" ' + ($canvas.settings.fontFamily == $canvas.settings.fontFamilyOptions[i] ? 'selected="selected"' : '') + '>' + $canvas.settings.fontFamilyOptions[i] + '</option>';

            var fontFamily = $('<div class="_wPaint_fontFamily _wPaint_dropDown" title="' + $canvas.settings.menuTitles.fontFamily + '"><div class="_wPaint_dropDown_cover"></div></div>').append(
                    $('<select>' + options + '</select>')
                            .change(function (e) {
                                var fontFamily = $(this).val();
                                $canvas.settings.fontFamily = fontFamily;
                                $canvas.textInput.css({fontFamily: fontFamily});
                                $canvas.textCalc.css({fontFamily: fontFamily});
                            })
            )

            //content
            var menuContent =
                    $('<div class="_wPaint_options"></div>')
                            .append($('<div class="_wPaint_icon _wPaint_bold ' + ($canvas.settings.fontTypeBold ? 'active' : '') + '" title="' + $canvas.settings.menuTitles.bold + '"></div>').click(function () {
                                _self.setType(_self, $canvas, 'Bold');
                            }))
                            .append($('<div class="_wPaint_icon _wPaint_italic ' + ($canvas.settings.fontTypeItalic ? 'active' : '') + '" title="' + $canvas.settings.menuTitles.italic + '"></div>').click(function () {
                                _self.setType(_self, $canvas, 'Italic');
                            }))
                            .append($('<div class="_wPaint_icon _wPaint_underline ' + ($canvas.settings.fontTypeUnderline ? 'active' : '') + '" title="' + $canvas.settings.menuTitles.underline + '"></div>').click(function () {
                                _self.setType(_self, $canvas, 'Underline');
                            }))
                            .append(fontSize)
                            .append(fontFamily);

            //handle
            var menuHandle = $('<div class="_wPaint_handle"></div>')

            //get position of canvas
            var offset = $($canvas.canvas).offset();

            //menu
            return this.menu =
                    $('<div class="_wPaint_menu _wPaint_menu_' + $canvas.settings.menuOrientation + '""></div>')
                            .css({display: 'none', position: 'absolute'})
                            .draggable({
                                snap: '._wPaint_menu',
                                handle: menuHandle,
                                stop: function () {
                                    $.each($(this).data('draggable').snapElements, function (index, element) {
                                        _self.dockOffsetLeft = _self.menu.offset().left - mainMenu.menu.offset().left;
                                        _self.dockOffsetTop = _self.menu.offset().top - mainMenu.menu.offset().top;
                                        _self.docked = element.snapping;
                                    });
                                }
                            })
                            .append(menuHandle)
                            .append(menuContent);
        },

        setType: function (_self, $canvas, mode) {
            var element = _self.menu.find("._wPaint_" + mode.toLowerCase());
            var isActive = element.hasClass('active')

            $canvas.settings['fontType' + mode] = !isActive;

            isActive ? element.removeClass('active') : element.addClass('active');

            fontTypeBold = $canvas.settings.fontTypeBold ? 'bold' : 'normal';
            fontTypeItalic = $canvas.settings.fontTypeItalic ? 'italic' : 'normal';
            fontTypeUnderline = $canvas.settings.fontTypeUnderline ? 'underline' : 'none';

            $canvas.textInput.css({fontWeight: fontTypeBold});
            $canvas.textCalc.css({fontWeight: fontTypeBold});
            $canvas.textInput.css({fontStyle: fontTypeItalic});
            $canvas.textCalc.css({fontStyle: fontTypeItalic});
            $canvas.textInput.css({textDecoration: fontTypeUnderline});
            $canvas.textCalc.css({textDecoration: fontTypeUnderline});
        }
    }
})(jQuery);