define(['jquery', 'underscore', 'requtils', 'renderer/baserepresentation', 'renderer/shapebuilder'], function ($, _, requtils, BaseRepresentation, ShapeBuilder) {
    'use strict';

    var Utils = requtils.getUtils();

    /* Rkns.Renderer.Node Class */

    /* The representation for the node : A circle, with an image inside and a text label underneath.
     * The circle and the image are drawn on canvas and managed by Paper.js.
     * The text label is an HTML node, managed by jQuery. */

    //var NodeRepr = Renderer.Node = Utils.inherit(Renderer._BaseRepresentation);
    var NodeRepr = Utils.inherit(BaseRepresentation);

    _(NodeRepr.prototype).extend({
        _init: function() {
            this.renderer.node_layer.activate();
            this.type = "Node";
            this.buildShape();
            this.hidden = false;
            this.ghost= false;
            if (this.options.show_node_circles) {
                this.circle.strokeWidth = this.options.node_stroke_width;
                this.h_ratio = 1;
            } else {
                this.h_ratio = 0;
            }
            this.title = $('<div class="Rk-Label">').appendTo(this.renderer.labels_$);

            if (this.options.editor_mode) {
                var Renderer = requtils.getRenderer();
                this.normal_buttons = [
                                       new Renderer.NodeEditButton(this.renderer, null),
                                       new Renderer.NodeRemoveButton(this.renderer, null),
                                       new Renderer.NodeLinkButton(this.renderer, null),
                                       new Renderer.NodeEnlargeButton(this.renderer, null),
                                       new Renderer.NodeShrinkButton(this.renderer, null)
                                       ];
                if (this.options.hide_nodes){
                    this.normal_buttons.push(
                            new Renderer.NodeHideButton(this.renderer, null),
                            new Renderer.NodeShowButton(this.renderer, null)
                            );
                }
                this.pending_delete_buttons = [
                                               new Renderer.NodeRevertButton(this.renderer, null)
                                               ];
                this.all_buttons = this.normal_buttons.concat(this.pending_delete_buttons);

                for (var i = 0; i < this.all_buttons.length; i++) {
                    this.all_buttons[i].source_representation = this;
                }
                this.active_buttons = [];
            } else {
                this.active_buttons = this.all_buttons = [];
            }
            this.last_circle_radius = 1;

            if (this.renderer.minimap) {
                this.renderer.minimap.node_layer.activate();
                this.minimap_circle = new paper.Path.Circle([0, 0], 1);
                this.minimap_circle.__representation = this.renderer.minimap.miniframe.__representation;
                this.renderer.minimap.node_group.addChild(this.minimap_circle);
            }
        },
        _getStrokeWidth: function() {
            var thickness = (this.model.has('style') && this.model.get('style').thickness) || 1;
            return this.options.node_stroke_width + (thickness-1) * (this.options.node_stroke_max_width - this.options.node_stroke_width) / (this.options.node_stroke_witdh_scale-1);
        },
        _getSelectedStrokeWidth: function() {
            var thickness = (this.model.has('style') && this.model.get('style').thickness) || 1;
            return this.options.selected_node_stroke_width + (thickness-1) * (this.options.selected_node_stroke_max_width - this.options.selected_node_stroke_width) / (this.options.node_stroke_witdh_scale-1);
        },
        buildShape: function(){
            if( 'shape' in this.model.changed ) {
                delete this.img;
            }
            if(this.circle){
                this.circle.remove();
                delete this.circle;
            }
            // "circle" "rectangle" "ellipse" "polygon" "star" "diamond"
            this.shapeBuilder = new ShapeBuilder(this.model.get("shape"));
            this.circle = this.shapeBuilder.getShape();
            this.circle.__representation = this;
            this.circle.sendToBack();
            this.last_circle_radius = 1;
        },
        redraw: function(options) {
            if( 'shape' in this.model.changed && 'change' in options && options.change ) {
            //if( 'shape' in this.model.changed ) {
                this.buildShape();
            }
            var _model_coords = new paper.Point(this.model.get("position")),
                _baseRadius = this.options.node_size_base * Math.exp((this.model.get("size") || 0) * Utils._NODE_SIZE_STEP);
            if (!this.is_dragging || !this.paper_coords) {
                this.paper_coords = this.renderer.toPaperCoords(_model_coords);
            }
            this.circle_radius = _baseRadius * this.renderer.view.scale;
            if (this.last_circle_radius !== this.circle_radius) {
                this.all_buttons.forEach(function(b) {
                    b.setSectorSize();
                });
                this.circle.scale(this.circle_radius / this.last_circle_radius);
                if (this.node_image) {
                    this.node_image.scale(this.circle_radius / this.last_circle_radius);
                }
            }
            this.circle.position = this.paper_coords;
            if (this.node_image) {
                this.node_image.position = this.paper_coords.subtract(this.image_delta.multiply(this.circle_radius));
            }
            this.last_circle_radius = this.circle_radius;

            var old_act_btn = this.active_buttons;

            var opacity = 1;
            if (this.model.get("delete_scheduled")) {
                opacity = 0.5;
                this.active_buttons = this.pending_delete_buttons;
                this.circle.dashArray = [2,2];
            } else {
                opacity = 1;
                this.active_buttons = this.normal_buttons;
                this.circle.dashArray = null;
            }
            if (this.selected && this.renderer.isEditable() && !this.ghost) {
                if (old_act_btn !== this.active_buttons) {
                    old_act_btn.forEach(function(b) {
                        b.hide();
                    });
                }
                this.active_buttons.forEach(function(b) {
                    b.show();
                });
            }

            if (this.node_image) {
                this.node_image.opacity = this.highlighted ? opacity * 0.5 : (opacity - 0.01);
            }

            this.circle.fillColor = this.highlighted ? this.options.highlighted_node_fill_color : this.options.node_fill_color;

            this.circle.opacity = this.options.show_node_circles ? opacity : 0.01;


            //strip html from title
            var html = this.model.get("title");
            var div = document.createElement("div");
            div.innerHTML = html;
            var clean_title = div.textContent || div.innerText || "";

            var _text = clean_title || this.renkan.translate(this.options.label_untitled_nodes) || "";
            _text = Utils.shortenText(_text, this.options.node_label_max_length);

            //_formatedText = _text;


            if (typeof this.highlighted === "object") {
                _text = this.highlighted.replace(_(_text).escape(),'<span class="Rk-Highlighted">$1</span>');
            }

            //TODO: default text node color
            var node_style = (this.model.get("style") || {'title_size': 1, 'title-color': '#00000' });
            var text_size = node_style.title_size || 1;
            var text_color = node_style.title_color || this.options.node_title_color;

            var text_style = {
                'line-height': "1em"
            };

            if(text_size !== 1) {
                text_style['font-size'] = text_size.toFixed(1)+"em";
            }
            //TODO: compare with default value
            if(text_color !== "#000000") {
                text_style.color = text_color;
            }

            var text_node = $("<span>").css(text_style).html(_text);

            this.title.empty().append(text_node);

            var _strokeWidth = this._getStrokeWidth();
            this.title.css({
                left: this.paper_coords.x,
                top: this.paper_coords.y + this.circle_radius * this.h_ratio + this.options.node_label_distance + 0.5*_strokeWidth,
                opacity: opacity
            });
            var _color = (this.model.has("style") && this.model.get("style").color) || (this.model.get("created_by") || Utils._USER_PLACEHOLDER(this.renkan)).get("color"),
                _dash = (this.model.has("style") && this.model.get("style").dash) ? this.options.default_dash_array : null;
            this.circle.strokeWidth = _strokeWidth;
            this.circle.strokeColor = _color;
            this.circle.dashArray = _dash;
            var _pc = this.paper_coords;
            this.all_buttons.forEach(function(b) {
                b.moveTo(_pc);
            });
            var lastImage = this.img;
            this.img = this.model.get("image");
            if (this.img && this.img !== lastImage) {
                this.showImage();
                if(this.circle) {
                    this.circle.sendToBack();
                }
            }
            if (this.node_image && !this.img) {
                this.node_image.remove();
                delete this.node_image;
            }

            if (this.renderer.minimap) {
                this.minimap_circle.fillColor = _color;
                var minipos = this.renderer.toMinimapCoords(_model_coords),
                miniradius = this.renderer.minimap.scale * _baseRadius,
                minisize = new paper.Size([miniradius, miniradius]);
                this.minimap_circle.fitBounds(minipos.subtract(minisize), minisize.multiply(2));
            }

            if (typeof options === 'undefined' || !('dontRedrawEdges' in options) || !options.dontRedrawEdges) {
                var _this = this;
                _.each(
                        this.project.get("edges").filter(
                                function (ed) {
                                    return ((ed.get("to") === _this.model) || (ed.get("from") === _this.model));
                                }
                        ),
                        function(edge, index, list) {
                            var repr = _this.renderer.getRepresentationByModel(edge);
                            if (repr && typeof repr.from_representation !== "undefined" && typeof repr.from_representation.paper_coords !== "undefined" && typeof repr.to_representation !== "undefined" && typeof repr.to_representation.paper_coords !== "undefined") {
                                repr.redraw();
                            }
                        }
                );
            }
            if (this.ghost){
                this.show(true);
            } else {
                if (this.hidden) { this.hide(); }
            }
        },
        showImage: function() {
            var _image = null;
            if (typeof this.renderer.image_cache[this.img] === "undefined") {
                _image = new Image();
                this.renderer.image_cache[this.img] = _image;
                _image.src = this.img;
            } else {
                _image = this.renderer.image_cache[this.img];
            }
            var _this = this;
            if (_image.width) {

                if (this.node_image) {
                    this.node_image.remove();
                }
                this.renderer.node_layer.activate();
                var width = _image.width,
                    height = _image.height,
                    clipPath = this.model.get("clip_path"),
                    hasClipPath = (typeof clipPath !== "undefined" && clipPath),
                    _clip = null,
                    baseRadius = null,
                    centerPoint = null;

                if (hasClipPath) {
                    _clip = new paper.Path();
                    var instructions = clipPath.match(/[a-z][^a-z]+/gi) || [],
                    lastCoords = [0,0],
                    minX = Infinity,
                    minY = Infinity,
                    maxX = -Infinity,
                    maxY = -Infinity;

                    var transformCoords = function(tabc, relative) {
                        var newCoords = tabc.slice(1).map(function(v, k) {
                            var res = parseFloat(v),
                            isY = k % 2;
                            if (isY) {
                                res = ( res - 0.5 ) * height;
                            } else {
                                res = ( res - 0.5 ) * width;
                            }
                            if (relative) {
                                res += lastCoords[isY];
                            }
                            if (isY) {
                                minY = Math.min(minY, res);
                                maxY = Math.max(maxY, res);
                            } else {
                                minX = Math.min(minX, res);
                                maxX = Math.max(maxX, res);
                            }
                            return res;
                        });
                        lastCoords = newCoords.slice(-2);
                        return newCoords;
                    };

                    instructions.forEach(function(instr) {
                        var coords = instr.match(/([a-z]|[0-9.-]+)/ig) || [""];
                        switch(coords[0]) {
                        case "M":
                            _clip.moveTo(transformCoords(coords));
                            break;
                        case "m":
                            _clip.moveTo(transformCoords(coords, true));
                            break;
                        case "L":
                            _clip.lineTo(transformCoords(coords));
                            break;
                        case "l":
                            _clip.lineTo(transformCoords(coords, true));
                            break;
                        case "C":
                            _clip.cubicCurveTo(transformCoords(coords));
                            break;
                        case "c":
                            _clip.cubicCurveTo(transformCoords(coords, true));
                            break;
                        case "Q":
                            _clip.quadraticCurveTo(transformCoords(coords));
                            break;
                        case "q":
                            _clip.quadraticCurveTo(transformCoords(coords, true));
                            break;
                        }
                    });

                    baseRadius = Math[this.options.node_images_fill_mode ? "min" : "max"](maxX - minX, maxY - minY) / 2;
                    centerPoint = new paper.Point((maxX + minX) / 2, (maxY + minY) / 2);
                    if (!this.options.show_node_circles) {
                        this.h_ratio = (maxY - minY) / (2 * baseRadius);
                    }
                } else {
                    baseRadius = Math[this.options.node_images_fill_mode ? "min" : "max"](width, height) / 2;
                    centerPoint = new paper.Point(0,0);
                    if (!this.options.show_node_circles) {
                        this.h_ratio = height / (2 * baseRadius);
                    }
                }
                var _raster = new paper.Raster(_image);
                _raster.locked = true; // Disable mouse events on icon
                if (hasClipPath) {
                    _raster = new paper.Group(_clip, _raster);
                    _raster.opacity = 0.99;
                    /* This is a workaround to allow clipping at group level
                     * If opacity was set to 1, paper.js would merge all clipping groups in one (known bug).
                     */
                    _raster.clipped = true;
                    _clip.__representation = this;
                }
                if (this.options.clip_node_images) {
                    var _circleClip = this.shapeBuilder.getImageShape(centerPoint, baseRadius);
                    _raster = new paper.Group(_circleClip, _raster);
                    _raster.opacity = 0.99;
                    _raster.clipped = true;
                    _circleClip.__representation = this;
                }
                this.image_delta = centerPoint.divide(baseRadius);
                this.node_image = _raster;
                this.node_image.__representation = _this;
                this.node_image.scale(this.circle_radius / baseRadius);
                this.node_image.position = this.paper_coords.subtract(this.image_delta.multiply(this.circle_radius));
                this.node_image.insertAbove(this.circle);
            } else {
                $(_image).on("load", function() {
                    _this.showImage();
                });
            }
        },
        paperShift: function(_delta) {
            if (this.options.editor_mode) {
                if (!this.renkan.read_only) {
                    this.is_dragging = true;
                    this.paper_coords = this.paper_coords.add(_delta);
                    this.redraw();
                }
            } else {
                this.renderer.view.paperShift(_delta);
            }
        },
        openEditor: function() {
            this.renderer.removeRepresentationsOfType("editor");
            var _editor = this.renderer.addRepresentation("NodeEditor",null);
            _editor.source_representation = this;
            _editor.draw();
        },
        select: function() {
            clearTimeout(this.hide_timeout);
            this.selected = true;
            this.circle.strokeWidth = this._getSelectedStrokeWidth();
            if (this.renderer.isEditable() && !this.hidden) {
                this.active_buttons.forEach(function(b) {
                    b.show();
                });
            }
            var _uri = this.model.get("uri");
            if (_uri) {
                $('.Rk-Bin-Item').each(function() {
                    var _el = $(this);
                    if (_el.attr("data-uri") === _uri) {
                        _el.addClass("selected");
                    }
                });
            }
            if (!this.options.editor_mode) {
                this.openEditor();
            }

            if (this.renderer.minimap) {
                this.minimap_circle.strokeWidth = this.options.minimap_highlight_weight;
                this.minimap_circle.strokeColor = this.options.minimap_highlight_color;
            }
            //if the node is hidden and the mouse hover it, it appears as a ghost
            if (this.hidden) {
                this.show(true);
            }
            else {
                this.showNeighbors(true);
            }
            this._super("select");
        },
        hideButtons: function() {
            this.all_buttons.forEach(function(b) {
                b.hide();
            });
            delete(this.buttonTimeout);
        },
        unselect: function(_newTarget) {
            if (!_newTarget || _newTarget.source_representation !== this) {
                this.selected = false;
                var _this = this;
                this.buttons_timeout = setTimeout(function() { _this.hideButtons(); }, 200);
                this.circle.strokeWidth = this._getStrokeWidth();
                $('.Rk-Bin-Item').removeClass("selected");
                if (this.renderer.minimap) {
                    this.minimap_circle.strokeColor = undefined;
                }
                //when the mouse don't hover the node anymore, we hide it
                if (this.hidden) {
                    this.renderer.removeRepresentationsOfType("editor");
                    this.hide();
                }
                else {
                    this.hideNeighbors(true);
                }
                this._super("unselect");
            }
        },
        hide_delay: function(){
            var _this = this;
            this.hide_timeout = setTimeout(function(){
                _this.hide();
            }, 1000);
        },
        hide: function(){
            var _this = this;
            this.ghost = false;
            this.hidden = true;
            if (typeof this.node_image !== 'undefined'){
                this.node_image.opacity = 0;
            }
            this.hideButtons();
            this.circle.opacity = 0;
            this.title.css('opacity', 0);
            this.minimap_circle.opacity = 0;


            _.each(
                    this.project.get("edges").filter(
                            function (ed) {
                                return ((ed.get("to") === _this.model) || (ed.get("from") === _this.model));
                            }
                    ),
                    function(edge, index, list) {
                        var repr = _this.renderer.getRepresentationByModel(edge);
                        if (repr && typeof repr.from_representation !== "undefined" && typeof repr.from_representation.paper_coords !== "undefined" && typeof repr.to_representation !== "undefined" && typeof repr.to_representation.paper_coords !== "undefined") {
                            repr.hide();
                        }
                    }
            );
            this.hideNeighbors(false);
        },
        show: function(ghost){
            var _this = this;
            this.ghost = ghost;
            if (this.ghost){
                if (typeof this.node_image !== 'undefined'){
                    this.node_image.opacity = this.options.ghost_opacity;
                }
                this.circle.opacity = this.options.ghost_opacity;
                this.title.css('opacity', this.options.ghost_opacity);
                this.minimap_circle.opacity = this.options.ghost_opacity;
            } else {
                this.minimap_circle.opacity = 1;
                this.hidden = false;
                this.redraw();
            }

            _.each(
                    this.project.get("edges").filter(
                            function (ed) {
                                return ((ed.get("to") === _this.model) || (ed.get("from") === _this.model));
                            }
                    ),
                    function(edge, index, list) {
                        var repr = _this.renderer.getRepresentationByModel(edge);
                        if (repr && typeof repr.from_representation !== "undefined" && typeof repr.from_representation.paper_coords !== "undefined" && typeof repr.to_representation !== "undefined" && typeof repr.to_representation.paper_coords !== "undefined") {
                            repr.show(_this.ghost);
                        }
                    }
            );
        },
        hideNeighbors: function(delay){
            var _this = this;
            _.each(
                    this.project.get("edges").filter(
                            function (ed) {
                                return (ed.get("from") === _this.model);
                            }
                    ),
                    function(edge, index, list) {
                        var repr = _this.renderer.getRepresentationByModel(edge.get("to"));
                        if (repr && repr.ghost) {
                            if (delay){ repr.hide_delay(); } else {repr.hide(); }
                        }
                    }
            );
        },
        showNeighbors: function(ghost){
            var _this = this;
            _.each(
                    this.project.get("edges").filter(
                            function (ed) {
                                return (ed.get("from") === _this.model);
                            }
                    ),
                    function(edge, index, list) {
                        var repr = _this.renderer.getRepresentationByModel(edge.get("to"));
                        if (repr && repr.hidden) {
                            repr.show(ghost);
                            if (!ghost){
                                var indexNode = _this.renderer.view.hiddenNodes.indexOf(repr.model.id);
                                if (indexNode !== -1){
                                    _this.renderer.view.hiddenNodes.splice(indexNode, 1);
                                }
                            }
                        }
                    }
            );
        },
        highlight: function(textToReplace) {
            var hlvalue = textToReplace || true;
            if (this.highlighted === hlvalue) {
                return;
            }
            this.highlighted = hlvalue;
            this.redraw();
            this.renderer.throttledPaperDraw();
        },
        unhighlight: function() {
            if (!this.highlighted) {
                return;
            }
            this.highlighted = false;
            this.redraw();
            this.renderer.throttledPaperDraw();
        },
        saveCoords: function() {
            var _coords = this.renderer.toModelCoords(this.paper_coords),
            _data = {
                position: {
                    x: _coords.x,
                    y: _coords.y
                }
            };
            if (this.renderer.isEditable()) {
                this.model.set(_data);
            }
        },
        mousedown: function(_event, _isTouch) {
            if (_isTouch) {
                this.renderer.unselectAll();
                this.select();
            }
        },
        mouseup: function(_event, _isTouch) {
            if (this.renderer.is_dragging && this.renderer.isEditable()) {
                this.saveCoords();
            } else {
                if (this.hidden) {
                    var index = this.renderer.view.hiddenNodes.indexOf(this.model.id);
                    if (index !== -1){
                        this.renderer.view.hiddenNodes.splice(index, 1);
                    }
                    this.show(false);
                    this.select();
                } else {
                    if (!_isTouch && !this.model.get("delete_scheduled")) {
                        this.openEditor();
                    }
                    this.model.trigger("clicked");
                }
            }
            this.renderer.click_target = null;
            this.renderer.is_dragging = false;
            this.is_dragging = false;
        },
        destroy: function(_event) {
            this._super("destroy");
            this.all_buttons.forEach(function(b) {
                b.destroy();
            });
            this.circle.remove();
            this.title.remove();
            if (this.renderer.minimap) {
                this.minimap_circle.remove();
            }
            if (this.node_image) {
                this.node_image.remove();
            }
        }
    }).value();

    return NodeRepr;

});
