/*
 * Released under BSD License
 * Copyright (c) 2014-2021 hizzgdev@163.com
 * 
 * Project Home:
 *   https://github.com/hizzgdev/jsmind/
 */

(function ($w) {
    'use strict';
    var $d = $w.document;
    var __name__ = 'jsMind';
    var jsMind = $w[__name__];
    if (!jsMind) { return; }
    if (typeof jsMind.draggable != 'undefined') { return; }

    var jdom = jsMind.util.dom;
    var clear_selection = 'getSelection' in $w ? function () {
        $w.getSelection().removeAllRanges();
    } : function () {
        $d.selection.empty();
    };

    var options = {
        line_width: 5,
        lookup_delay: 500,
        lookup_interval: 80
    };

    jsMind.draggable = function (jm) {
        this.jm = jm;
        this.e_canvas = null;
        this.canvas_ctx = null;
        this.shadow = null;
        this.shadow_w = 0;
        this.shadow_h = 0;
        this.active_node = null;
        this.target_node = null;
        this.target_direct = null;
        this.client_w = 0;
        this.client_h = 0;
        this.offset_x = 0;
        this.offset_y = 0;
        this.hlookup_delay = 0;
        this.hlookup_timer = 0;
        this.capture = false;
        this.moved = false;
    };

    jsMind.draggable.prototype = {
        init: function () {
            this._create_canvas();
            this._create_shadow();
            this._event_bind();
        },

        resize: function () {
            this.jm.view.e_nodes.appendChild(this.shadow);
            this.e_canvas.width = this.jm.view.size.w;
            this.e_canvas.height = this.jm.view.size.h;
        },

        _create_canvas: function () {
            var c = $d.createElement('canvas');
            this.jm.view.e_panel.appendChild(c);
            var ctx = c.getContext('2d');
            this.e_canvas = c;
            this.canvas_ctx = ctx;
        },

        _create_shadow: function () {
            var s = $d.createElement('jmnode');
            s.style.visibility = 'hidden';
            s.style.zIndex = '3';
            s.style.cursor = 'move';
            s.style.opacity = '0.7';
            this.shadow = s;
        },

        reset_shadow: function (el) {
            var s = this.shadow.style;
            this.shadow.innerHTML = el.innerHTML;
            s.left = el.style.left;
            s.top = el.style.top;
            s.width = el.style.width;
            s.height = el.style.height;
            s.backgroundImage = el.style.backgroundImage;
            s.backgroundSize = el.style.backgroundSize;
            s.transform = el.style.transform;
            this.shadow_w = this.shadow.clientWidth;
            this.shadow_h = this.shadow.clientHeight;

        },

        show_shadow: function () {
            if (!this.moved) {
                this.shadow.style.visibility = 'visible';
            }
        },

        hide_shadow: function () {
            this.shadow.style.visibility = 'hidden';
        },

        _magnet_shadow: function (node) {
            if (!!node) {
                this.canvas_ctx.lineWidth = options.line_width;
                this.canvas_ctx.strokeStyle = 'rgba(0,0,0,0.3)';
                this.canvas_ctx.lineCap = 'round';
                this._clear_lines();
                this._canvas_lineto(node.sp.x, node.sp.y, node.np.x, node.np.y);
            }
        },

        _clear_lines: function () {
            this.canvas_ctx.clearRect(0, 0, this.jm.view.size.w, this.jm.view.size.h);
        },

        _canvas_lineto: function (x1, y1, x2, y2) {
            this.canvas_ctx.beginPath();
            this.canvas_ctx.moveTo(x1, y1);
            this.canvas_ctx.lineTo(x2, y2);
            this.canvas_ctx.stroke();
        },

        _lookup_close_node: function () {
            var root = this.jm.get_root();
            var root_location = root.get_location();
            var root_size = root.get_size();
            var root_x = root_location.x + root_size.w / 2;

            var sw = this.shadow_w;
            var sh = this.shadow_h;
            var sx = this.shadow.offsetLeft;
            var sy = this.shadow.offsetTop;

            var ns, nl;

            var direct = (sx + sw / 2) >= root_x ?
                jsMind.direction.right : jsMind.direction.left;
            var nodes = this.jm.mind.nodes;
            var node = null;
            var layout = this.jm.layout;
            var min_distance = Number.MAX_VALUE;
            var distance = 0;
            var closest_node = null;
            var closest_p = null;
            var shadow_p = null;
            for (var nodeid in nodes) {
                var np, sp;
                node = nodes[nodeid];
                if (node.isroot || node.direction == direct) {
                    if (node.id == this.active_node.id) {
                        continue;
                    }
                    if (!layout.is_visible(node)) {
                        continue;
                    }
                    ns = node.get_size();
                    nl = node.get_location();
                    if (direct == jsMind.direction.right) {
                        if (sx - nl.x - ns.w <= 0) { continue; }
                        distance = Math.abs(sx - nl.x - ns.w) + Math.abs(sy + sh / 2 - nl.y - ns.h / 2);
                        np = { x: nl.x + ns.w - options.line_width, y: nl.y + ns.h / 2 };
                        sp = { x: sx + options.line_width, y: sy + sh / 2 };
                    } else {
                        if (nl.x - sx - sw <= 0) { continue; }
                        distance = Math.abs(sx + sw - nl.x) + Math.abs(sy + sh / 2 - nl.y - ns.h / 2);
                        np = { x: nl.x + options.line_width, y: nl.y + ns.h / 2 };
                        sp = { x: sx + sw - options.line_width, y: sy + sh / 2 };
                    }
                    if (distance < min_distance) {
                        closest_node = node;
                        closest_p = np;
                        shadow_p = sp;
                        min_distance = distance;
                    }
                }
            }
            var result_node = null;
            if (!!closest_node) {
                result_node = {
                    node: closest_node,
                    direction: direct,
                    sp: shadow_p,
                    np: closest_p
                };
            }
            return result_node;
        },

        lookup_close_node: function () {
            var node_data = this._lookup_close_node();
            if (!!node_data) {
                this._magnet_shadow(node_data);
                this.target_node = node_data.node;
                this.target_direct = node_data.direction;
            }
        },

        _event_bind: function () {
            var jd = this;
            var container = this.jm.view.container;
            jdom.add_event(container, 'mousedown', function (e) {
                var evt = e || event;
                jd.dragstart.call(jd, evt);
            });
            jdom.add_event(container, 'mousemove', function (e) {
                var evt = e || event;
                jd.drag.call(jd, evt);
            });
            jdom.add_event(container, 'mouseup', function (e) {
                var evt = e || event;
                jd.dragend.call(jd, evt);
            });
            jdom.add_event(container, 'touchstart', function (e) {
                var evt = e || event;
                jd.dragstart.call(jd, evt);
            });
            jdom.add_event(container, 'touchmove', function (e) {
                var evt = e || event;
                jd.drag.call(jd, evt);
            });
            jdom.add_event(container, 'touchend', function (e) {
                var evt = e || event;
                jd.dragend.call(jd, evt);
            });
        },

        dragstart: function (e) {
            if (!this.jm.get_editable()) { return; }
            if (this.capture) { return; }
            this.active_node = null;

            var jview = this.jm.view;
            var el = e.target || event.srcElement;
            if (el.tagName.toLowerCase() != 'jmnode') { return; }
            var nodeid = jview.get_binded_nodeid(el);
            if (!!nodeid) {
                var node = this.jm.get_node(nodeid);
                if (!node.isroot) {
                    this.reset_shadow(el);
                    this.active_node = node;
                    this.offset_x = (e.clientX || e.touches[0].clientX) / jview.actualZoom - el.offsetLeft;
                    this.offset_y = (e.clientY || e.touches[0].clientY) / jview.actualZoom - el.offsetTop;
                    this.client_hw = Math.floor(el.clientWidth / 2);
                    this.client_hh = Math.floor(el.clientHeight / 2);
                    if (this.hlookup_delay != 0) {
                        $w.clearTimeout(this.hlookup_delay);
                    }
                    if (this.hlookup_timer != 0) {
                        $w.clearInterval(this.hlookup_timer);
                    }
                    var jd = this;
                    this.hlookup_delay = $w.setTimeout(function () {
                        jd.hlookup_delay = 0;
                        jd.hlookup_timer = $w.setInterval(function () {
                            jd.lookup_close_node.call(jd);
                        }, options.lookup_interval);
                    }, options.lookup_delay);
                    this.capture = true;
                }
            }
        },

        drag: function (e) {
            if (!this.jm.get_editable()) { return; }
            if (this.capture) {
                e.preventDefault();
                this.show_shadow();
                this.moved = true;
                clear_selection();
                var jview = this.jm.view;
                var px = (e.clientX || e.touches[0].clientX) / jview.actualZoom - this.offset_x;
                var py = (e.clientY || e.touches[0].clientY) / jview.actualZoom - this.offset_y;
                this.shadow.style.left = px + 'px';
                this.shadow.style.top = py + 'px';
                clear_selection();
            }
        },

        dragend: function (e) {
            if (!this.jm.get_editable()) { return; }
            if (this.capture) {
                if (this.hlookup_delay != 0) {
                    $w.clearTimeout(this.hlookup_delay);
                    this.hlookup_delay = 0;
                    this._clear_lines();
                }
                if (this.hlookup_timer != 0) {
                    $w.clearInterval(this.hlookup_timer);
                    this.hlookup_timer = 0;
                    this._clear_lines();
                }
                if (this.moved) {
                    var src_node = this.active_node;
                    var target_node = this.target_node;
                    var target_direct = this.target_direct;
                    this.move_node(src_node, target_node, target_direct);
                }
                this.hide_shadow();
            }
            this.moved = false;
            this.capture = false;
        },

        move_node: function (src_node, target_node, target_direct) {
            var shadow_h = this.shadow.offsetTop;
            if (!!target_node && !!src_node && !jsMind.node.inherited(src_node, target_node)) {
                // lookup before_node
                var sibling_nodes = target_node.children;
                var sc = sibling_nodes.length;
                var node = null;
                var delta_y = Number.MAX_VALUE;
                var node_before = null;
                var beforeid = '_last_';
                while (sc--) {
                    node = sibling_nodes[sc];
                    if (node.direction == target_direct && node.id != src_node.id) {
                        var dy = node.get_location().y - shadow_h;
                        if (dy > 0 && dy < delta_y) {
                            delta_y = dy;
                            node_before = node;
                            beforeid = '_first_';
                        }
                    }
                }
                if (!!node_before) { beforeid = node_before.id; }
                this.jm.move_node(src_node.id, beforeid, target_node.id, target_direct);
            }
            this.active_node = null;
            this.target_node = null;
            this.target_direct = null;
        },

        jm_event_handle: function (type, data) {
            if (type === jsMind.event_type.resize) {
                this.resize();
            }
        }
    };

    var draggable_plugin = new jsMind.plugin('draggable', function (jm) {
        var jd = new jsMind.draggable(jm);
        jd.init();
        jm.add_event_listener(function (type, data) {
            jd.jm_event_handle.call(jd, type, data);
        });
    });

    jsMind.register_plugin(draggable_plugin);

})(window);