/** @module dojo/dnd/Selector **/
define([
    "../_base/array",
    "../_base/declare",
    "../_base/kernel",
    "../_base/lang",
    "../dom",
    "../dom-construct",
    "../mouse",
    "../_base/NodeList",
    "../on",
    "../touch",
    "./common",
    "./Container"
], function (array, declare, kernel, lang, dom, domConstruct, mouse, NodeList, on, touch, dnd, Container) {

// module:
//		dojo/dnd/Selector

    /*
     Container item states:
     ""			- an item is not selected
     "Selected"	- an item is selected
     "Anchor"	- an item is selected, and is an anchor for a "shift" selection
     */

    /*=====
     var __SelectorArgs = declare([Container.__ContainerArgs], {
     // singular: Boolean
     //		allows selection of only one element, if true
     singular: false,

     // autoSync: Boolean
     //		autosynchronizes the source with its list of DnD nodes,
     autoSync: false
     });
     =====*/

    /**
     * @class module:dojo/dnd/Selector
     * @extends module:dojo/dnd/Container
     */
    var Selector = declare("dojo.dnd.Selector", Container, {
        // summary:
        //		a Selector object, which knows how to select its children

        /*=====
         // selection: Set<String>
         //		The set of id's that are currently selected, such that this.selection[id] == 1
         //		if the node w/that id is selected.  Can iterate over selected node's id's like:
         //	|		for(var id in this.selection)
         selection: {},
         =====*/
        constructor: function (node, params) {
            // summary:
            //		constructor of the Selector
            // node: Node||String
            //		node or node's id to build the selector on
            // params: __SelectorArgs?
            //		a dictionary of parameters
            if (!params) {
                params = {};
            }
            this.singular = params.singular;
            this.autoSync = params.autoSync;
            // class-specific variables
            this.selection = {};
            this.anchor = null;
            this.simpleSelection = false;
            // set up events
            this.events.push(
                on(this.node, touch.press, lang.hitch(this, "onMouseDown")),
                on(this.node, touch.release, lang.hitch(this, "onMouseUp"))
            );
        },

        // object attributes (for markup)
        singular: false,	// is singular property

        // methods
        getSelectedNodes: function () {
            // summary:
            //		returns a list (an array) of selected nodes
            var t = new NodeList();
            var e = dnd._empty;
            for (var i in this.selection) {
                if (i in e) {
                    continue;
                }
                t.push(dom.byId(i));
            }
            return t;	// NodeList
        },
        selectNone: function () {
            // summary:
            //		unselects all items
            return this._removeSelection()._removeAnchor();	// self
        },
        selectAll: function () {
            // summary:
            //		selects all items
            this.forInItems(function (data, id) {
                this._addItemClass(dom.byId(id), "Selected");
                this.selection[id] = 1;
            }, this);
            return this._removeAnchor();	// self
        },
        deleteSelectedNodes: function () {
            // summary:
            //		deletes all selected items
            var e = dnd._empty;
            for (var i in this.selection) {
                if (i in e) {
                    continue;
                }
                var n = dom.byId(i);
                this.delItem(i);
                domConstruct.destroy(n);
            }
            this.anchor = null;
            this.selection = {};
            return this;	// self
        },
        forInSelectedItems: function (/*Function*/ f, /*Object?*/ o) {
            // summary:
            //		iterates over selected items;
            //		see `dojo/dnd/Container.forInItems()` for details
            o = o || kernel.global;
            var s = this.selection, e = dnd._empty;
            for (var i in s) {
                if (i in e) {
                    continue;
                }
                f.call(o, this.getItem(i), i, this);
            }
        },
        sync: function () {
            // summary:
            //		sync up the node list with the data map

            Selector.superclass.sync.call(this);

            // fix the anchor
            if (this.anchor) {
                if (!this.getItem(this.anchor.id)) {
                    this.anchor = null;
                }
            }

            // fix the selection
            var t = [], e = dnd._empty;
            for (var i in this.selection) {
                if (i in e) {
                    continue;
                }
                if (!this.getItem(i)) {
                    t.push(i);
                }
            }
            array.forEach(t, function (i) {
                delete this.selection[i];
            }, this);

            return this;	// self
        },
        insertNodes: function (addSelected, data, before, anchor) {
            // summary:
            //		inserts new data items (see `dojo/dnd/Container.insertNodes()` method for details)
            // addSelected: Boolean
            //		all new nodes will be added to selected items, if true, no selection change otherwise
            // data: Array
            //		a list of data items, which should be processed by the creator function
            // before: Boolean
            //		insert before the anchor, if true, and after the anchor otherwise
            // anchor: Node
            //		the anchor node to be used as a point of insertion
            var oldCreator = this._normalizedCreator;
            this._normalizedCreator = function (item, hint) {
                var t = oldCreator.call(this, item, hint);
                if (addSelected) {
                    if (!this.anchor) {
                        this.anchor = t.node;
                        this._removeItemClass(t.node, "Selected");
                        this._addItemClass(this.anchor, "Anchor");
                    } else if (this.anchor != t.node) {
                        this._removeItemClass(t.node, "Anchor");
                        this._addItemClass(t.node, "Selected");
                    }
                    this.selection[t.node.id] = 1;
                } else {
                    this._removeItemClass(t.node, "Selected");
                    this._removeItemClass(t.node, "Anchor");
                }
                return t;
            };
            Selector.superclass.insertNodes.call(this, data, before, anchor);
            this._normalizedCreator = oldCreator;
            return this;	// self
        },
        destroy: function () {
            // summary:
            //		prepares the object to be garbage-collected
            Selector.superclass.destroy.call(this);
            this.selection = this.anchor = null;
        },

        // mouse events
        onMouseDown: function (e) {
            // summary:
            //		event processor for onmousedown
            // e: Event
            //		mouse event
            if (this.autoSync) {
                this.sync();
            }
            if (!this.current) {
                return;
            }
            if (!this.singular && !dnd.getCopyKeyState(e) && !e.shiftKey && (this.current.id in this.selection)) {
                this.simpleSelection = true;
                if (mouse.isLeft(e)) {
                    // Accept the left button and stop the event.   Stopping the event prevents text selection while
                    // dragging.   However, don't stop the event on mobile because that prevents a click event,
                    // and also prevents scroll (see #15838).
                    // For IE we don't stop event when multiple buttons are pressed.
                    e.stopPropagation();
                    e.preventDefault();
                }
                return;
            }
            if (!this.singular && e.shiftKey) {
                if (!dnd.getCopyKeyState(e)) {
                    this._removeSelection();
                }
                var c = this.getAllNodes();
                if (c.length) {
                    if (!this.anchor) {
                        this.anchor = c[0];
                        this._addItemClass(this.anchor, "Anchor");
                    }
                    this.selection[this.anchor.id] = 1;
                    if (this.anchor != this.current) {
                        var i = 0, node;
                        for (; i < c.length; ++i) {
                            node = c[i];
                            if (node == this.anchor || node == this.current) {
                                break;
                            }
                        }
                        for (++i; i < c.length; ++i) {
                            node = c[i];
                            if (node == this.anchor || node == this.current) {
                                break;
                            }
                            this._addItemClass(node, "Selected");
                            this.selection[node.id] = 1;
                        }
                        this._addItemClass(this.current, "Selected");
                        this.selection[this.current.id] = 1;
                    }
                }
            } else {
                if (this.singular) {
                    if (this.anchor == this.current) {
                        if (dnd.getCopyKeyState(e)) {
                            this.selectNone();
                        }
                    } else {
                        this.selectNone();
                        this.anchor = this.current;
                        this._addItemClass(this.anchor, "Anchor");
                        this.selection[this.current.id] = 1;
                    }
                } else {
                    if (dnd.getCopyKeyState(e)) {
                        if (this.anchor == this.current) {
                            delete this.selection[this.anchor.id];
                            this._removeAnchor();
                        } else {
                            if (this.current.id in this.selection) {
                                this._removeItemClass(this.current, "Selected");
                                delete this.selection[this.current.id];
                            } else {
                                if (this.anchor) {
                                    this._removeItemClass(this.anchor, "Anchor");
                                    this._addItemClass(this.anchor, "Selected");
                                }
                                this.anchor = this.current;
                                this._addItemClass(this.current, "Anchor");
                                this.selection[this.current.id] = 1;
                            }
                        }
                    } else {
                        if (!(this.current.id in this.selection)) {
                            this.selectNone();
                            this.anchor = this.current;
                            this._addItemClass(this.current, "Anchor");
                            this.selection[this.current.id] = 1;
                        }
                    }
                }
            }
            e.stopPropagation();
            e.preventDefault();
        },
        onMouseUp: function (/*===== e =====*/) {
            // summary:
            //		event processor for onmouseup
            // e: Event
            //		mouse event
            if (!this.simpleSelection) {
                return;
            }
            this.simpleSelection = false;
            this.selectNone();
            if (this.current) {
                this.anchor = this.current;
                this._addItemClass(this.anchor, "Anchor");
                this.selection[this.current.id] = 1;
            }
        },
        onMouseMove: function (/*===== e =====*/) {
            // summary:
            //		event processor for onmousemove
            // e: Event
            //		mouse event
            this.simpleSelection = false;
        },

        // utilities
        onOverEvent: function () {
            // summary:
            //		this function is called once, when mouse is over our container
            this.onmousemoveEvent = on(this.node, touch.move, lang.hitch(this, "onMouseMove"));
        },
        onOutEvent: function () {
            // summary:
            //		this function is called once, when mouse is out of our container
            if (this.onmousemoveEvent) {
                this.onmousemoveEvent.remove();
                delete this.onmousemoveEvent;
            }
        },
        _removeSelection: function () {
            // summary:
            //		unselects all items
            var e = dnd._empty;
            for (var i in this.selection) {
                if (i in e) {
                    continue;
                }
                var node = dom.byId(i);
                if (node) {
                    this._removeItemClass(node, "Selected");
                }
            }
            this.selection = {};
            return this;	// self
        },
        _removeAnchor: function () {
            if (this.anchor) {
                this._removeItemClass(this.anchor, "Anchor");
                this.anchor = null;
            }
            return this;
        }
    });

    return Selector;

});
