/**
 * @author Benjamin Baer <benjamin.baer@math.uzh.ch>
 *
 * qfq.fabric.js: Integrates a callable fabric.js html5 canvas.
 * Currently in development for a special case, may be expanded
 * and integrated into qfq.
 *
 * Buttons and color switches are generated on basis of a json.
 * May be expanded to make it easy to create new brushes and stuff.
 *
 * Probably will soon open up "easy" ways for you to create plugins
 * that integrate into the qfq js client.
 *
 */

var QfqNS = QfqNS || {};

$(function (n) {
    n.Fabric = function() {
        this.parentElement = {};
        this.rotation = 0;
        this.controlElement = $('<div>', {
            class: 'qfq-fabric-bar',
            id: 'qfq-fabric-control'
        });
        this.emojiContainer = {};
        this.eventEmitter = new EventEmitter();
        this.qfqPage = {};
        this.changeHistory = true;
        this.scaled = false;
        this.textContainer = {};
        this.userTextInput = {};
        this.outputField = {};
        this.json = {};
        this.canvas = {};
        this.activeColor = {red: 0, green: 68, blue: 255, opacity: 1};
        this.brushSize = 3;
        this.highlightscale = 8;
        this.borderSize = 5;
        this.textSize = 16;
        this.scaling = 1;
        this.panning = false;
        this.userText = "";
        this.moveMode = false;
        this.isZoomMode = false;
        this.drawRectangleMode = false;
        this.drawTextBoxMode = false;
        this.emojiMode = false;
        this.isHighlightMode = false;
        this.isMouseMode = false;
        this.isDrawingMode = true;
        this.isDown = false;
        this.isAddText = false;
        this.origX = 0;
        this.origY = 0;
        this.imageHeight = 0;
        this.imageWidth = 0;
        this.resizeWidth = 0;
        this.userChangePossible = false;
        this.overObject = false;
        this.selectionActive = false;
        this.dragAndDrop = false;
        this.dndData = '';
        this.backgroundImage = false;
        this.fabricJSON = '';
        this.showObject = true;
        this.mouseInsideCanvas = false;
        this.imageOutput = '';
        this.localStore = new n.LocalStorage("fabric");
        this.history = new n.History();
        this.firstLoad = false;
        this.version = 5;

        // Handles button states and generation of said buttons. Should be renamed.
        function ModeSettings() {
            this.qFabric = {};
            this.myButtons = [];
            this.myColors = [];
            this.myModes = {modes: [], currentMode: "", colors: []};
        }

        this.modeSettings = new ModeSettings();

        ModeSettings.prototype.initialize = function(qfqFabric, uri) {
            this.qFabric = qfqFabric;
            // Extra Button html
            this.extraHtml = qfqFabric.parentElement.attr('data-extrabutton') || "";
            $.getJSON(uri, (data) => this.setMyModes(data, this));
        };


        ModeSettings.prototype.setUpButtons = function() {
            var $controlWrapper = this.qFabric.controlElement;
            var $buttonGroup = $("<div>", {class: "btn-group pull-left"});
            var that = this;
            this.myModes.modes.forEach(function(o) {
                var $button = $("<button>", {
                    type: 'button',
                    id: that.qFabric.qfqPage.settings.formId + "-" + o.selector,
                    class: 'btn btn-default'
                });
                var $symbol = $("<i>", {
                    class: 'fa ' + o.icon
                });
                $button.append($symbol);
                if (o.name === that.myModes.currentMode) {
                    $button.removeClass("btn-default");
                    $button.addClass("btn-primary");
                }
                that.myButtons.push($button);
                $buttonGroup.append($button);
                var modePressed = o.name;
                $button.on("click", function() {
                    that.qFabric.buttonPress(modePressed, $button)
                });
                $button.prop("disabled", o.disabled);
                $button.prop("title", o.tooltip)
            });
            $controlWrapper.append($buttonGroup);


            var $colorSelector = $("<div>", {class: "color-picker pull-right"});
            $controlWrapper.append($colorSelector);
            if (this.myModes.colors instanceof Array) {
                this.myModes.colors.forEach(function (o) {
                    var $button = $("<button>", {
                        type: 'button',
                        id: o.selector,
                        class: "text-bg-toggle",
                        style: "background-color: rgba(" + o.red + "," + o.green + "," + o.blue + ",1)"
                    });
                    $colorSelector.append($button);
                    that.myColors.push($button);
                    $button.on("click", function () {
                        that.qFabric.setColor(o, $button);
                    })
                });
            }
            // Extra Buttons
            if (this.extraHtml) {
                const $extraBtns = $($.parseHTML(this.extraHtml));
                $controlWrapper.append($extraBtns);

                // Extra Button Lock
                const $lockBtn = $extraBtns
                    .filter('.extraButtonLock')
                    .add($extraBtns.find('.extraButtonLock'));

                if ($lockBtn.length) {
                    // start locked
                    this.isLocked = true;
                    this.applyLockState();

                    $lockBtn.on('click', () => this.toggleLock($lockBtn));
                }
            }
        };

        ModeSettings.prototype.setMyModes = function (data, modeSettings) {
            this.myModes = data;
            this.setUpButtons();
        };

        ModeSettings.prototype.activateMode = function (modeName, $button) {
            this.myModes.currentMode = modeName;
            var that = this;
            $.each(this.myModes.modes, function(i, o) {
                if (o.name === that.myModes.currentMode) {
                    that.qFabric.canvas.isDrawingMode = !!o.requiresDrawing;
                    that.qFabric.canvas.selection = !!o.requiresSelection;
                    if (o.isToggle) {
                        $button.removeClass("btn-default");
                        $button.addClass("btn-primary");
                        that.qFabric[o.toggle] = true;
                    }
                    if (o.hasToggleElement) {
                        that.qFabric[o.toggleElement].slideToggle();
                    }
                } else {
                    that.deactivateMode(o);
                }
            });
            this.qFabric.canvas.renderAll();
        };

        ModeSettings.prototype.deactivateMode = function (o) {
            if (o.isToggle) {
                var $button = this.getButtonById(o.selector);
                $button.removeClass("btn-primary");
                $button.addClass("btn-default");
                this.qFabric[o.toggle] = false;
            }
            if (o.hasToggleElement) {
                this.qFabric[o.toggleElement].slideUp();
            }
        };

        ModeSettings.prototype.getButtonById = function (needle) {
            var needleInHaystack = false;
            var elementId = this.qFabric.qfqPage.settings.formId + "-" + needle;
            for(i = 0; i < this.myButtons.length; i++) {
                if(this.myButtons[i][0].id === elementId) return this.myButtons[i]
            }
            
            console.error("Button not found, id: " + elementId);
        };

        ModeSettings.prototype.disableButton = function(id, bool) {
            var $button = this.getButtonById(id);
            $button.prop("disabled",bool);
        };

        ModeSettings.prototype.getModeByName = function (string) {
            $.each(this.myModes.modes, function(i, o) {
                if (o.name === string) {
                    return o;
                }
            });
            console.error("Couldn't find mode with name: " + string);
        };

        ModeSettings.prototype.disableControls = function() {
            // Buttons + picker
            this.qFabric.controlElement
                .find('.btn-group button, .color-picker button')
                .prop('disabled', true);

            // Fabric interaction
            const c = this.qFabric.canvas;
            if (c) {
                c.isDrawingMode = false;
                c.selection     = false;
                // block pointer-events on both canvas layers
                $(c.wrapperEl).css('pointer-events','none');
                $(c.lowerCanvasEl).css('pointer-events','none');
            }
        };

        /**
         * Re-enable everything
         */
        ModeSettings.prototype.enableControls = function() {
            this.qFabric.controlElement
                .find('.btn-group button, .color-picker button')
                .prop('disabled', false);

            const c = this.qFabric.canvas;
            if (c) {
                c.selection = true;
                // only re-enable drawing if currently in “draw” mode
                if (this.myModes.currentMode === 'draw') c.isDrawingMode = true;
                $(c.wrapperEl).css('pointer-events','all');
                $(c.lowerCanvasEl).css('pointer-events','all');
            }
        };

        /**
         * Apply the current lock state
         */
        ModeSettings.prototype.applyLockState = function() {
            if (this.isLocked) this.disableControls();
            else               this.enableControls();
        };

        /**
         * Toggle lock state (call from on click handler)
         */
        ModeSettings.prototype.toggleLock = function($btn) {
            this.isLocked = !this.isLocked;
            this.applyLockState();
            $btn.toggleClass('locked', this.isLocked);
        };

        function Emojis() {
            this.qFabric = {};
            this.emojis = [];
        }

        this.emojiHandler = new Emojis();

        Emojis.prototype.initialize = function(qFabric, uri, callback) {
            this.qFabric = qFabric;
            this.callback = callback;
            this.getFromJSON(uri);
        };

        Emojis.prototype.getFromJSON = function(uri) {
            var that = this;
            $.getJSON(uri, function(data) {
                that.setData(data);
            });
        };

        Emojis.prototype.setData = function(data) {
            if (data.emojis instanceof Array) {
                this.emojis = data.emojis;
                this.buildList();
            } else {
                console.error("Couldn't load emojis: data.emojis not an array");
            }
        };

        Emojis.prototype.buildList = function() {
            var that = this;
            var $container = this.qFabric.emojiContainer;
            var $emojiField = $("<div>");
            $container.append($emojiField);
            this.emojis.forEach(function (o) {
                $img = $("<img>", {
                    class: o.class,
                    src: o.url,
                    draggable: true
                });
                $emojiField.append($img);
                $img.on("dragstart", function() {
                    that.qFabric.dragAndDrop = true;
                    that.qFabric.parentElement.css('cursor', 'url("' + o.url + '"), auto');
                    that.qFabric.dndData = o.url;
                    console.log("dragStart");
                });
                $img.on("dragend", function(e) {
                    that.qFabric.dragEndEvent(e);
                    
                })
            })
        };

        fabric.Object.prototype.transparentCorners = false;
    };

    n.Fabric.prototype.initialize = function($fabricElement, qfqPage) {
        var inputField = $fabricElement.data('control-name');
        var imageOutput = $fabricElement.data('image-output');
        var viewOnly = $fabricElement.data('view-only') || false;
        var editImage = $fabricElement.data('edit-image') || false;
        var resizeWidth = $fabricElement.data('image-resize-width') || 0;
        this.qfqPage = qfqPage;
        this.parentElement = $fabricElement;
        this.backgroundImage = $fabricElement.data('background-image') || false;
        this.fabricJSON = $fabricElement.data('fabric-json') || false;
        this.resizeWidth = resizeWidth;
        if(inputField && $("#" + inputField).val().length > 5) {
            this.json = JSON.parse($("#" + inputField).val());
            if(this.json.hasOwnProperty("objects")) this.version = this.json.version || 1;
        } else if (this.fabricJSON) {
            this.json = JSON.parse(this.fabricJSON);
            if(this.json.hasOwnProperty("objects")) this.version = this.json.version || 1;
        }
        var defaultColor = $fabricElement.data('fabric-color') || false;
        if (defaultColor) {
            this.activeColor.red = defaultColor.red;
            this.activeColor.blue = defaultColor.blue;
            this.activeColor.green = defaultColor.green;
        }
        var lastColor = this.localStore.get("color");
        console.log(lastColor);
        if (lastColor) {
            this.activeColor = lastColor;
            this.activeColor.opacity = 1;
        }
        var that = this;

        if (viewOnly || this.version < 5) {
            if (inputField) {
                this.outputField = $("#" + inputField);
            }
            this.getMeta(this.backgroundImage, function(width, height) { that.prepareCanvas(width, height, true); } );
        } else if (editImage) {
            this.outputField = $("#" + inputField);
            this.imageOutput = $("#" + imageOutput);
            this.getMeta(this.backgroundImage, function(width, height) { that.prepareImageEditor(width, height);});
            var jsonButtons = $fabricElement.data('buttons');
            this.modeSettings.initialize(this, jsonButtons);
        } else {
            this.outputField = $("#" + inputField);
            this.generateControls();
            this.getMeta(this.backgroundImage, function(width, height) { that.prepareCanvas(width, height, false);});
        }
        //localStorage.clear();
    };

    n.Fabric.prototype.generateControls = function () {
        var jsonButtons = this.parentElement.data('buttons');
        var jsonEmojis = this.parentElement.data('emojis');
        var emojiContainer = $('<div>', {
            style: 'display: none;'
        });
        var textContainer = $('<div>', {
            style: 'display: none;'
        });
        var textArea = $('<textarea>', {
            class: 'fabric-text',
            placeholder: 'Write a text and then draw a textbox over the image'
        });
        this.modeSettings.initialize(this, jsonButtons);
        this.emojiHandler.initialize(this, jsonEmojis, this.dragEndEvent);
        textContainer.append(textArea);
        this.parentElement.append(this.controlElement);
        this.parentElement.append(emojiContainer);
        this.parentElement.append(textContainer);
        this.emojiContainer = emojiContainer;
    };

    n.Fabric.prototype.prepareCanvas = function (width, height, isReadOnly) {
        this.backgroundImageWidth = width;
        var that = this;
        this.generateCanvas(width, height, isReadOnly);
        if (this.outputField.val()) {
            var fabricJSON = this.prepareJSON(this.outputField.val());
            this.history.put(fabricJSON);
            this.canvas.loadFromJSON(fabricJSON, function() {
                this.firstLoad = true;
                that.setBackground();
                if(this.version < 5) that.resizeCanvas();
                if(!isReadOnly) { that.setBrush(); }
                that.canvas.renderAll();
                that.userChangePossible = true;
            });
        } else {
            that.setBackground();
            if(!isReadOnly) {
	        that.setBrush();
            }
            that.canvas.renderAll();
            that.userChangePossible = true;
            this.history.put(this.canvas.toJSON());
            this.modeSettings.applyLockState();
        }
        var defaultColor = this.parentElement.data('fabric-color') || false;
        if (defaultColor) {
            this.setColor(defaultColor);
        }
    };

    n.Fabric.prototype.prepareJSON = function (jsonString) {
        var object = JSON.parse(jsonString);
        if (this.backgroundImage) {
            object.backgroundImage.src = this.backgroundImage;
        }
        var jsonOutput = JSON.stringify(object);
        return jsonOutput;
    };

    n.Fabric.prototype.prepareImageEditor = function(width, height) {
        this.imageHeight = height;
        this.imageWidth = width;
        var ratio = height / width;
        var canvas = document.createElement('canvas');
        var that = this;
        canvas.width = this.parentElement.innerWidth();
        canvas.height = canvas.width * ratio;
        this.parentElement.append(this.controlElement);
        this.parentElement.append(canvas);
        this.canvas = this.__canvas = new fabric.Canvas(canvas, {
            isDrawingMode: false,
            stateful: true,
            enableRetinaScaling: true,
            backgroundColor: "#1c222b"
        });

        fabric.Image.fromURL(this.backgroundImage, function(oImg) {
            oImg.set({
                width: canvas.width,
                height: canvas.height,
                originX: 'left',
                originY: 'top',
                selectable: false,
                evented: false,
                lockMovementY: true,
                lockMovementX: true,
                lockScalingX: true,
                lockScalingY : true,
                lockUniScaling: true,
                hasControls: false,
                hasBorders: false,
                centeredRotation: true
            });
            that.canvas.add(oImg);
            that.canvas.centerObject(oImg);
            that.canvas.renderAll();
            this.imageToEdit = oImg;
        });
        this.modeSettings.applyLockState();
    };

    n.Fabric.prototype.getMeta = function(url, callback) {
      var img = new Image();
      img.src = url;
      img.onload = function() { callback(this.width, this.height); };
    };

    n.Fabric.prototype.toggleObjects = function() {
        this.showObject = !this.showObject;
        var objects = this.canvas.getObjects();
        for (var i in objects) {
            objects[i].visible = this.showObject;
            //objects[i].opacity = opacity;
        }
        this.canvas.renderAll();
    };

    n.Fabric.prototype.generateCanvas = function(width, height, isReadOnly) {
        var canvas = document.createElement('canvas');
        canvas.setAttribute('draggable', true);
        var that = this;
        var ratio = height / width;
        canvas.width = this.parentElement.innerWidth();
        canvas.height = canvas.width * ratio;
        this.parentElement.append(canvas);
        if(isReadOnly) {
            this.canvas = this.__canvas = new fabric.StaticCanvas(canvas, {
                enableRetinaScaling: false,
                centeredScaling: true,
                stateful: true
            });
        } else {
            this.canvas = this.__canvas = new fabric.Canvas(canvas, {
                isDrawingMode: true,
                stateful: true,
                enableRetinaScaling: false,
                centeredScaling: true
            }); 
        }
        this.canvas.on('mouse:up', function (e) { that.defaultMouseUpEvent(e) });
        this.canvas.on('mouse:down', function (e) { that.defaultMouseDownEvent(e) });
        this.canvas.on('mouse:move', function (e) { that.defaultMouseMoveEvent(e) });
        this.canvas.on('mouse:over', function(e) { that.defaultMouseInEvent(e) });
        this.canvas.on('mouse:out', function (e) { that.defaultMouseOutEvent(e) });
        this.canvas.on('after:render', function(e) { that.defaultRenderHandler(e) });
        this.canvas.on('object:added', function(e) { that.defaultChangeHandler(e) });
        this.canvas.on('object:modified', function(e) { that.defaultChangeHandler(e) });
        this.canvas.on('path:created', function(e) { that.defaultChangeHandler(e) });
        this.canvas.on('object:over', function(e) { that.defaultObjectHoverHandler(e) });
        this.canvas.on('object:out', function(e) { that.defaultObjectOutHandler(e) });
        this.canvas.on('object:selected', function(e) { that.defaultSelectionCreateHandler(e) });
        this.canvas.on('selection:cleared', function(e) { that.defaultSelectionClearHandler(e) });
        window.addEventListener('keydown', function(e) { that.defaultKeyStrokeHandler(e) });
        //document.addEventListener('dragend', function(e) { that.dragEndEvent(e); e.preventDefault(); e.stopImmediatePropagation(); });
        this.parentElement.on('contextmenu', function(e) { that.defaultRightClickHandler(e) });
        $(window).resize(function() { that.resizeCanvas(); });
    };

    n.Fabric.prototype.dragEndEvent = function(e) {
        if (this.dragAndDrop) {
            this.emojiAdd(this.dndData, e);
            this.dndData = '';
            this.dragAndDrop = false;
            console.log("Success dragend", e);
            e.target.addEventListener('click', function(event) { event.stopImmediatePropagation(); }.bind(this), {capture: true, once: true});
            e.preventDefault();
            e.stopImmediatePropagation();
            return false;
        }
    };

    n.Fabric.prototype.defaultMouseInEvent = function(e) {
        this.mouseInsideCanvas = true;
    };

    /*
    *
    * Function is called at initialization and at each window resize.
    * forceJSON is to tell resizeCanvas to ALWAYS use the JSON data as base
    *           Used for static canvas initialization
    * this.scaled - after the first call it sets scaled to true, which prevents
    *               accidental wrong scaling when multiple resizeCanvas get started
    *               in parallel. When scaled is true resize will only use real time
    *               browser values and ignore database / json values.
    *
     */
    n.Fabric.prototype.resizeCanvas = function (forceJSON) {
        var source = false;
        var backgroundSizing = false;
        var oldWidth = this.canvas.getWidth();
        var backgroundImageWidth = 0;
        if (this.canvas.backgroundImage) {
            if (forceJSON) {
                source = this.fabricJSON;
            } else {
                source = this.canvas;
            }
        } else {
            source = this.fabricJSON;
        }
        if (source) {
            backgroundImageWidth = source.backgroundImage.width;
        }
        if (oldWidth !== backgroundImageWidth && backgroundImageWidth !== 0 && !this.scaled) {
            oldWidth = backgroundImageWidth;
            backgroundSizing = true;
        }
        var newWidth = this.parentElement.innerWidth();
        if (newWidth !== oldWidth) {
            var scaleMultiplier = newWidth / oldWidth;
            var oldHeight;
            if (backgroundSizing) {
                if (source) {
                    oldHeight = source.backgroundImage.height;
                }
            } else {
                oldHeight = this.canvas.getHeight();
            }
            this.canvas.setWidth(newWidth);
            this.canvas.setHeight(oldHeight * scaleMultiplier);
            if (this.version < 5) {
                var objects = this.canvas.getObjects();
                scaleMultipler = oldWidth / newWidth;
                console.log("Resize with multiplier: ", scaleMultiplier);
                for (var i in objects) {
                        objects[i].scaleX = objects[i].scaleX * scaleMultiplier;
                        objects[i].scaleY = objects[i].scaleY * scaleMultiplier;
                        objects[i].left = objects[i].left * scaleMultiplier;
                        objects[i].top = objects[i].top * scaleMultiplier;
                        objects[i].setCoords();

                }
                this.setBackground();
            } else {
                var scale = this.canvas.getWidth() / this.imageWidth;
                this.setZoom(scale);
            }
            this.scaled=true;
            this.canvas.renderAll();
        }

    };

    n.Fabric.prototype.zoomCanvas = function(o, zoomCalc) {
        var zoom = this.canvas.getZoom() + zoomCalc;
        var zoomPoint = this.canvas.getPointer(o.e);
        this.canvas.zoomToPoint(zoomPoint, zoom);
        this.canvas.renderAll();
    };

    n.Fabric.prototype.zoomCanvasToCenter = function(zoomCalc) {
        var zoom = this.canvas.getZoom() + zoomCalc;
        this.canvas.setZoom(zoom);
        this.canvas.renderAll();
    };

    n.Fabric.prototype.setBackground = function () {
        var that = this;
        var scaleX = 0;
        var scaleY = 0;
        
        
        console.log("Found Version:", this.version);
       
        var newZoom = (this.version > 2);
        if (this.backgroundImage) {
             fabric.Image.fromURL(this.backgroundImage, function(img) {
                 img.set({
                     lockUniScaling: true,
                     centeredScaling: true,
                     centeredRotation: true
                 });
                 that.imageWidth = img.width
                 if(!newZoom) {
                    // Needed to use an estimate (820) of the initial width, to fit it. This one perfectly fitted one, and seemed near enough to others.
                    var scaleMultiplier = that.canvas.width / 820;
                    img.scaleX = that.canvas.width / img.width;
                    img.scaleY = that.canvas.height / img.height;
                    var objects = that.canvas.getObjects();
                    for (var i in objects) {
                        objects[i].scaleX = objects[i].scaleX * scaleMultiplier;
                        objects[i].scaleY = objects[i].scaleY * scaleMultiplier;
                        objects[i].left = objects[i].left * scaleMultiplier;
                        objects[i].top = objects[i].top * scaleMultiplier;
                        objects[i].setCoords();
                    }
                    console.log("Use old zoom", scaleMultiplier);
                 } else { 
                    scaleX = that.canvas.width / img.width;
                    scaleY = that.canvas.height / img.height;
                    console.log("Set zoom to: ", {x: scaleX, y: scaleY});
                 }
                 that.canvas.setBackgroundImage(img, function() {
                     that.canvas.renderAll.bind(that.canvas);
                     if(newZoom) that.setZoom(scaleX);
                     that.canvas.renderAll();
                 });
             });
        } else {
            var $image = document.getElementsByClassName("qfq-fabric-image")[0];
            $image.onload = function() {
                console.log("Image loaded");
                var img = new fabric.Image(this, {
                    originX: 'left',
                    originY: 'top',
                    lockUniScaling: true,
                    centeredScaling: true,
                    centeredRotation: true
                });
                that.imageWidth = img.width
                if(!newZoom) {
                    img.scaleX = that.canvas.width / img.width;
                    img.scaleY = that.canvas.height / img.height;
                    console.log("Use old zoom");
                 } else { 
                    scaleX = that.canvas.width / img.width;
                    scaleY = that.canvas.height / img.height;
                    console.log("Set zoom to: ", scaleX);
                 }
                img.rotate(that.rotation);
                that.canvas.setBackgroundImage(img, function() {
                    that.canvas.renderAll.bind(that.canvas);
                    that.canvas.renderAll();
                });
            };
            console.log("Set zoom to: ", {x: scaleX, y: scaleY});

            if(newZoom) that.setZoom(scaleX);
            that.canvas.renderAll();

        }
    };

    n.Fabric.prototype.setZoom = function(scale) {
        this.canvas.setZoom(scale)
        this.scaling = (1/scale);
        //this.brushSize = this.brushBaseSize * (1/scale);
        //if (this.canvas.freeDrawingBrush) this.canvas.freeDrawingBrush.width = this.brushSize * this.scaling;
    }

    n.Fabric.prototype.updateBackground = function () {
        if (this.canvas.backgroundImage) {
            this.canvas.backgroundImage.setWidth(this.canvas.getWidth());
            this.canvas.backgroundImage.setHeight(this.canvas.getHeight());
            this.canvas.renderAll();
        } else {
            var that = this;
            setTimeout(function () {
                that.updateBackground();
            }, 200);
        }
    };

    n.Fabric.prototype.deactivatePanning = function () {
        this.panning = false;
    };

    n.Fabric.prototype.emojiAdd = function(uri, o) {
        var that = this;
        var pointer = this.canvas.getPointer(o.e);
        fabric.Image.fromURL(uri, function(img) {
            img.set({
                left: pointer.x - 32,
                top: pointer.y - 32,
                height: 64,
                width: 64
            });
            that.canvas.add(img);
            that.canvas.renderAll();
        });
    };

    n.Fabric.prototype.addText = function(e) {
        var color = this.getActiveRGBA(1);
    
        console.log("Text Event", e);
        var pointer = this.canvas.getPointer(e.e);
        var text = new fabric.IText('Double Click here to write text', {
            left: pointer.x,
            top: pointer.y,
            fontFamily: 'Arial',
            fill: color,
            fontSize: 18 * this.scaling
        });
        this.canvas.add(text);
        this.canvas.renderAll();
    };

    n.Fabric.prototype.deactivateRectangleDrawing = function() {
        //this.drawRectangleMode = false;
        this.drawTextBoxMode = false;
        this.isDown = false;
        var rect = this.canvas.getActiveObject();
        rect.hasControls = true;
        this.canvas.selection = true;
        this.canvas.discardActiveObject();
        this.canvas.remove(rect);
        this.canvas.add(rect);
        this.canvas.renderAll();

    };

    n.Fabric.prototype.panImage = function(e) {
        if (this.panning && e && e.e && !this.overObject && !this.selectionActive) {
            var delta = new fabric.Point(e.e.movementX, e.e.movementY);
            this.canvas.relativePan(delta);
        }
    };

    n.Fabric.prototype.getActiveRGBA = function(changedOpacity) {
        var opacity = this.activeColor.opacity;
        if (changedOpacity) {
            opacity = changedOpacity;
        }
        return "rgba(" + this.activeColor.red + ","
            + this.activeColor.green + ","
            + this.activeColor.blue + ","
            + opacity + ")";
    };

    // Has to be changed for Fabric 2.0! Group selection are deprecated then, still needed
    // for Fabric 1.x and current Fabric 2.0 beta has a major drawing bug.
    n.Fabric.prototype.deleteActiveGroup = function() {
        var that = this;
        if (this.canvas.getActiveObjects()) {
            this.canvas.getActiveObjects().forEach(function(o) { that.canvas.remove(o) });
            this.canvas.discardActiveObject().renderAll();
        } else {
            this.canvas.remove(this.canvas.getActiveObject());
        }
    };

    n.Fabric.prototype.freeDrawRectangleStart = function(o) {
        this.isDown = true;
        var that = this;
        var pointer = this.canvas.getPointer(o.e);
        this.origX = pointer.x;
        this.origY = pointer.y;
        var colorFill = this.getActiveRGBA(0.4);
        var colorBorder = this.getActiveRGBA(1);
        this.pointer = this.canvas.getPointer(o.e);
        var rect = new fabric.Rect({
            left: that.origX,
            top: that.origY,
            originX: 'left',
            originY: 'top',
            width: pointer.x - that.origX,
            height: pointer.y - that.origY,
            angle: 0,
            fill: colorFill,
            strokeWidth: this.borderSize * this.scaling,
            stroke: colorBorder,
            selectable: true,
            borderScaleFactor: 0,
            hasControls: false
        });
        this.canvas.add(rect);
        this.canvas.setActiveObject(rect);

        this.canvas.selection = false;
    };

    n.Fabric.prototype.freeDrawTextBoxStart = function(o) {
        this.isDown = true;
        var that = this;
        var pointer = this.canvas.getPointer(o.e);
        this.origX = pointer.x;
        this.origY = pointer.y;
        var colorFill = this.getActiveRGBA(1);
        pointer = this.canvas.getPointer(o.e);
        var textBox = new fabric.Textbox(this.userText, {
            left: that.origX,
            top: that.origY,
            originX: 'left',
            originY: 'top',
            width: pointer.x - that.origX,
            height: pointer.y - that.origY,
            angle: 0,
            backgroundColor: colorFill,
            padding: 5,
            editable: true
        });
        this.canvas.add(textBox);
        this.canvas.setActiveObject(textBox);
        this.canvas.selection = false;
    };

    // Used by both textbox and rectangle sizing. Maybe fusing those function later, since
    // they reuse code and have significant overlap.
    n.Fabric.prototype.resizeRectangle = function(o) {
        if (!this.isDown) return;
        var rect = this.canvas.getActiveObject();
        var pointer = this.canvas.getPointer(o.e);

        if(this.origX > pointer.x){
            rect.set({
                left: Math.abs(pointer.x)
            });
        }
        if(this.origY > pointer.y){
            rect.set({
                top: Math.abs(pointer.y)
            });
        }

        rect.set({
            width: Math.abs(this.origX - pointer.x)
        });
        rect.set({
            height: Math.abs(this.origY - pointer.y)
        });

        this.canvas.renderAll();
    };

    n.Fabric.prototype.setBrush = function() {
        console.log("trying to set brush");
        this.canvas.freeDrawingBrush.color = this.getActiveRGBA();
        this.canvas.freeDrawingBrush.width = this.brushSize;
    };

    // Default Canvas mouse events are currently strangely implemented.
    n.Fabric.prototype.defaultMouseUpEvent = function(e) {
        if (this.moveMode) {
            this.deactivatePanning();
        }

        if (this.drawRectangleMode || this.drawTextBoxMode) {
            this.deactivateRectangleDrawing();
            this.activateAllObjects();
        }

        if (this.isZoomMode) {
            this.zoomCanvas(e, 0.1);
        }
    };

    n.Fabric.prototype.defaultRightClickHandler = function(e) {
        if (this.isZoomMode) {
            this.zoomCanvas(e, -0.1);
            e.preventDefault();
        }
    };

    n.Fabric.prototype.defaultMouseOutEvent = function() {
        this.mouseInsideCanvas = false;

        if (this.moveMode) {
            this.deactivatePanning();
        }
    };

    n.Fabric.prototype.defaultMouseDownEvent = function(e) {
        if (!this.selectionActive) {
            if (this.moveMode) {
                this.panning = true;
            }
            if (this.drawRectangleMode) {
                this.freeDrawRectangleStart(e);
                this.deactivateAllObjects();
            }
            if (this.drawTextBoxMode) {
                this.freeDrawTextBoxStart(e);
                this.deactivateAllObjects();
            }
            if (this.isAddText) {
                this.addText(e)
                this.isAddText = false;
            }
        }
    };

    n.Fabric.prototype.defaultMouseMoveEvent = function(e) {
        if (this.moveMode) {
            if (this.panning) {
                this.panImage(e);
            }
        }
        if (this.drawRectangleMode || this.drawTextBoxMode) {
            this.resizeRectangle(e);
        }
    };

    // Calls additional functions on button press, could eventually be integrated to
    // the button/mode json. Talk about strange integration.
    n.Fabric.prototype.buttonPress = function(string, $button) {
        this.modeSettings.activateMode(string, $button);
        switch(string) {
            case "draw":
                this.draw();
                break;
            case "highlight":
                this.highlight();
                break;
            case "write":
                this.isAddText = true;
                //this.addText();
                break;
            case "rectangle":
            case "move":
                break;
            case "delete":
                this.delete();
                break;
            case "rotateRight":
                this.rotateImage(90);
                break;
            case "exportImage":
                this.prepareForExport();
                break;
            case "undo":
                this.changeState("undo");
                break;
            case "redo":
                this.changeState("redo");
                break;
            default:
                console.error("unrecognized mode");
        }
    };

    n.Fabric.prototype.prepareForExport = function() {
        var img = this.canvas.item(0);
        var oldHeight = img.height;
        var oldWidth = img.width;
        var newSize = this.calculateImageSize();
        var rotated = false;
        img.height =  newSize.height;
        img.width = newSize.width;
        if (this.rotation === 90 || this.rotation === 270) {
            // width is now height and viceversa
            rotated = true;
            this.canvas.setHeight(img.width);
            this.canvas.setWidth(img.height);
        } else {
            this.canvas.setWidth(img.width);
            this.canvas.setHeight(img.height);
        }
        this.canvas.calcOffset();
        this.canvas.centerObject(img);
        this.canvas.renderAll();

        this.exportToPNG(oldHeight, oldWidth, rotated);
    };

    // Resizes export image if resizing is set. Doesn't resize if original image
    // is smaller than the resize size. Returns an object with width and height.
    n.Fabric.prototype.calculateImageSize = function() {
        var img = { width: this.imageWidth, height: this.imageHeight };
        if (this.resizeWidth !== 0) {
            if (this.rotation === 90 || this.rotation === 270) {
                if (this.resizeWidth < this.imageHeight) {
                    img.height = this.resizeWidth;
                    img.width = this.resizeWidth * this.imageWidth / this.imageHeight;
                }
            } else {
                if (this.resizeWidth < this.imageWidth) {
                    img.width = this.resizeWidth;
                    img.height = this.resizeWidth * this.imageHeight / this.imageWidth;
                }
            }
        }
        return img;
    };

    n.Fabric.prototype.exportToPNG = function(height, width, rotated) {
        var png = this.canvas.toDataURL('png');
        if (this.imageOutput !== "#") {
            this.imageOutput.prop("src", png);
        }
        this.outputField.val("" + png);
        var img = this.canvas.item(0);
        img.width = width;
        img.height = height;
        if (rotated) {
            this.canvas.setWidth(height);
            this.canvas.setHeight(width);
        } else {
            this.canvas.setWidth(width);
            this.canvas.setHeight(height);
        }
        this.canvas.calcOffset();
        this.canvas.centerObject(img);
        this.canvas.renderAll();
    };

    n.Fabric.prototype.rotateImage = function(degrees) {
        var img = this.canvas.item(0);
        this.setRotation(this.rotation + degrees);
        img.rotate(this.rotation);
        this.canvas.renderAll();
        if (this.rotation === 90 || this.rotation === 270) {
            if (img.width > this.canvas.height) {
                img.height = this.canvas.height * img.height / img.width;
                img.width = this.canvas.height;
            }
            if (img.height > this.canvas.width) {
                img.width = this.canvas.width * img.width / img.height;
                img.height = this.canvas.width;
            }
        } else {
            if (img.width < this.canvas.width) {
                img.height = this.canvas.width * img.width / img.height;
                img.width = this.canvas.width;
            }
            if (img.height < this.canvas.height) {
                img.width = this.canvas.height * img.height / img.width;
                img.height = this.canvas.height;
            }
        }
        this.canvas.centerObject(img);
        this.canvas.renderAll();
    };

    n.Fabric.prototype.setRotation = function(degrees) {
        var mirror = false;
        if (degrees < 0) {
            degrees = Math.abs(degrees);
            mirror = true;
        }

        while (degrees > 360) {
            degrees = degrees - 360;
        }

        if (mirror) {
            degrees = 360 - degrees;
        }

        if (degrees >= 0 && degrees <= 360) {
            this.rotation = degrees;
        } else {
            console.error("You broke the laws of mathematics!");
        }
    };

    n.Fabric.prototype.delete = function() {
       this.deleteActiveGroup();
       this.defaultChangeHandler();
    };

    n.Fabric.prototype.draw = function() {
        this.activeColor.opacity = 0.8;
        this.brushSize = 2;
        this.setBrush();
    };

    n.Fabric.prototype.highlight = function() {
        this.activeColor.opacity = 0.4;
        this.brushSize = this.brushSize * this.highlightscale;
        this.setBrush();
    };

    /**
     * Calls state from attached history and moves in defined
     * direction. (undo / redo)
     * @param direction string, undo or redo
     */
    n.Fabric.prototype.changeState = function(direction) {
        var state = {};
        console.log(direction);
        if (direction === "undo") {
            state = this.history.back();
        } else {
            state = this.history.forward();
        }

        if (state) {
            this.changeHistory = false;
            var that = this;
            this.canvas.loadFromJSON(state, function() {
                that.setBackground();
                that.canvas.renderAll();
                that.changeHistory = true;
            });
        }
        this.updateHistoryButtons();
    };

    n.Fabric.prototype.updateHistoryButtons = function() {
        this.modeSettings.disableButton("undo", !this.history.canGoBack());
        this.modeSettings.disableButton("redo", !this.history.canGoForward());
    };


    n.Fabric.prototype.rectangle = function() {
        this.drawRectangleMode = true;
    };

    n.Fabric.prototype.setColor = function(color) {
        this.activeColor.red = color.red;
        this.activeColor.blue = color.blue;
        this.activeColor.green = color.green;
        if(this.userChangePossible) {
            this.localStore.set("color", this.activeColor);
        }
        this.setBrush();
    };

    n.Fabric.prototype.defaultRenderHandler = function () {
        this.canvas.calcOffset();
    };

    n.Fabric.prototype.defaultChangeHandler = function () {
        /* Important! Changes only possible after initialisation */

        if (this.userChangePossible) {
            if (this.changeHistory) {
                this.history.put(this.canvas.toJSON());
                this.updateHistoryButtons();
            }
            var that = this;
            var json = this.canvas.toJSON();
            json['version'] = 5;
            this.outputField.val(JSON.stringify(json));
            
            if (this.qfqPage.qfqForm) {
                this.qfqPage.qfqForm.eventEmitter.emitEvent('form.changed', n.EventEmitter.makePayload(that, null));
                this.qfqPage.qfqForm.changeHandler();
                this.qfqPage.qfqForm.form.formChanged = true;
            } else {
                //throw("Error: Couldn't initialize qfqForm - not possible to send form.changed event");
                console.log("Error: Couldn't initialize qfqForm - not possible to send form.changed event");
            }
        }
    };

    n.Fabric.prototype.defaultKeyStrokeHandler = function(e) {
        // Delete key
        if (e.keyCode === 46) {
            this.delete();
        }

        if (this.mouseInsideCanvas) {

            // Numpad +
            if (e.keyCode === 107) {
                this.zoomCanvasToCenter(0.1);
            }

            // Numpad -
            if (e.keyCode === 109) {
                this.zoomCanvasToCenter(-0.1);
            }

            // Up Arrow
            if (e.keyCode === 38) {
                this.panWithZoom(0, 10);
                e.preventDefault();
            }

            // Down Arrow
            if (e.keyCode === 40) {
                this.panWithZoom(0, -10);
                e.preventDefault();
            }

            // Left Arrow
            if (e.keyCode === 37) {
                this.panWithZoom(-10, 0);
            }

            // Numpad 0
            if (e.keyCode === 96) {
                this.resetZoom();
            }

            // Right Arrow
            if (e.keyCode === 39) {
                this.panWithZoom(10, 0);
            }
        }
    };

    n.Fabric.prototype.defaultObjectHoverHandler = function() {
        this.overObject = true;
    };

    n.Fabric.prototype.defaultObjectOutHandler = function() {
        this.overObject = false;
    };

    n.Fabric.prototype.defaultSelectionCreateHandler = function() {
        this.selectionActive = true;
    };

    n.Fabric.prototype.defaultSelectionClearHandler = function() {
        this.selectionActive = false;
    };

    n.Fabric.prototype.panWithZoom = function(x, y) {
        var zoom = this.canvas.getZoom();
        var delta = new fabric.Point(x * zoom, y * zoom);
        this.canvas.relativePan(delta);
    };

    n.Fabric.prototype.resetZoom = function() {
        var delta = new fabric.Point(0,0);
        this.canvas.absolutePan(delta);
        this.canvas.setZoom(1);
        this.canvas.renderAll();
    };

    n.Fabric.prototype.deactivateAllObjects = function() {
        this.canvas.forEachObject(function(object) {
            object.selectable = false;
        });
    };

    n.Fabric.prototype.activateAllObjects = function() {
        this.canvas.forEachObject(function(object) {
            object.selectable = true;
        });
    };

}(QfqNS));
