/* <copyright>
 This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
 No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
 (c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
 </copyright> */

var Montage = require("montage/core/core").Montage,
    Component = require("montage/ui/component").Component;

var ElementController = require("js/controllers/elements/element-controller").ElementController,
    Command = require("js/controllers/undo-controller").Command,
    NJUtils = require("js/lib/NJUtils").NJUtils;

exports.ElementMediator = Montage.create(Component, {

    addDelegate:{
        enumerable:false,
        value:null
    },

    deleteDelegate:{
        enumerable:false,
        value:null
    },

    addElements:{
        value:function (elements, rules, notify) {
            if (Array.isArray(elements)) {
                elements.forEach(function (element) {
                    ElementController.addElement(element, rules);
                    element.elementModel.props3D.init(element, false);
                });
            } else {
                ElementController.addElement(elements, rules);
                elements.elementModel.props3D.init(elements, false);

            }

            if (this.addDelegate && typeof (this.addDelegate['onAddElements']) === "function") {
                this.addDelegate['onAddElements'].call(this.addDelegate, elements);
            }

            var undoLabel = "add element";

            document.application.undoManager.add(undoLabel, this.removeElements, this, elements, notify);

            this.application.ninja.currentDocument.model.needsSave = true;

            if (notify || notify === undefined) {
                NJevent("elementAdded", elements);
            }
        }
    },

    removeElements:{
        value:function (elements, notify /* Used for the add undo */) {

            if (this.deleteDelegate && (typeof this.deleteDelegate.handleDelete === 'function')) {
                return this.deleteDelegate.handleDelete();
                // this.handleDelete.call(deleteDelegate);
            }

            if (Array.isArray(elements)) {
                elements = Array.prototype.slice.call(elements, 0);
                elements.forEach(function (element) {
                    ElementController.removeElement(element);
                });
            } else {
                ElementController.removeElement(elements);
            }

            var undoLabel = "add element";

            document.application.undoManager.add(undoLabel, this.addElements, this, elements, null, notify);

            this.application.ninja.currentDocument.model.needsSave = true;

            NJevent("elementsRemoved", elements);
        }
    },

    replaceElement:{
        value:function (newChild, oldChild, notify) {

            this.application.ninja.currentDocument.model.documentRoot.replaceChild(newChild, oldChild);
            if (newChild.getAttribute) newChild.setAttribute('data-ninja-node', 'true');

            var undoLabel = "replace element";

            document.application.undoManager.add(undoLabel, this.replaceElement, this, oldChild, newChild);

            this.application.ninja.currentDocument.model.needsSave = true;

            if (notify || notify === undefined) {
                NJevent("elementReplaced", {type:"replaceElement", data:{"newChild":newChild, "oldChild":oldChild}});
            }
        }
    },

    getProperty:{
        value:function (el, prop, valueMutator) {
            if (valueMutator && typeof valueMutator === "function") {
                return valueMutator(el.elementModel.controller["getProperty"](el, prop));
            } else {
                return el.elementModel.controller["getProperty"](el, prop, valueMutator);
            }
        }
    },

    getShapeProperty:{
        value:function (el, prop) {
            return el.elementModel.controller["getShapeProperty"](el, prop);
        }
    },

    setShapeProperty:{
        value:function (el, prop, value) {
            return el.elementModel.controller["setShapeProperty"](el, prop, value);
        }
    },

    /**
     Set a property change command for an element or array of elements
     @param element: Element
     @param attribute: Attribute to set
     @param value: Value to be set.
     @param currentValue: current value
     @param source: String for the source object making the call
     */
    setAttribute:{
        value:function (element, attribute, value, currentValue, source) {
            element.elementModel.controller["setAttribute"](element, attribute, value);

            // Add to the undo
            var undoLabel = "Attribute change";
            document.application.undoManager.add(undoLabel, this.setAttribute, this, element, attribute, currentValue, value, source);

            NJevent("attributeChange");
        }
    },

    /**
     Set a property change command for an element or array of elements
     @param els: Array of elements. Can contain 1 or more elements
     @param p: Property to set
     @param value: Value to be set. This is an array of values corresponding to the array of elements
     @param eventType: Change/Changing. Will be passed to the dispatched event
     @param source: String for the source object making the call
     @param currentValue *OPTIONAL*: current value array. If not found the current value is calculated
     @param stageRedraw: *OPTIONAL*: True. If set to false the stage will not redraw the selection/outline
     */
    setProperty:{
        value:function (els, p, value, eventType, source, currentValue) {
            if (eventType === "Changing") {
                this._setProperty(els, p, value, eventType, source);
            } else {
                // Calculate currentValue if not found for each element
                if (!currentValue) {
                    var that = this;
                    currentValue = els.map(function (item) {
                        return that.getProperty((item), p);
                    });
                }

                var command = Montage.create(Command, {
                    _els:{ value:els },
                    _p:{ value:p },
                    _value:{ value:value },
                    _previous:{ value:currentValue },
                    _eventType:{ value:eventType},
                    _source:{ value:"undo-redo"},
                    description:{ value:"Set Property"},
                    receiver:{ value:this},

                    execute:{
                        value:function (senderObject) {
                            if (senderObject) this._source = senderObject;
                            this.receiver._setProperty(this._els, this._p, this._value, this._eventType, this._source);
                            this._source = "undo-redo";
                            return "";
                        }
                    },

                    unexecute:{
                        value:function () {
                            this.receiver._setProperty(this._els, this._p, this._previous, this._eventType, this._source);
                            return "";
                        }
                    }
                });

                NJevent("sendToUndo", command);
                command.execute(source);
            }

        }
    },

    _setProperty:{
        value:function (els, p, value, eventType, source) {
            var el;

            for (var i = 0, item; item = els[i]; i++) {
                item.elementModel.controller["setProperty"](item, p, value[i], eventType, source);
            }

            NJevent("element" + eventType, {type:"setProperty", source:source, data:{"els":els, "prop":p, "value":value}, redraw:null});
        }
    },

    /**
     Sets a property object for an element or array of elements. The same properties object gets applied to all the elements
     @param elements: Array of elements objects: element, properties and previousProperties
     @param eventType: Change/Changing. Will be passed to the dispatched event
     @param source: String for the source object making the call
     */
    setProperties:{
        value:function (elements, eventType, source) {

            elements.forEach(function (elementObject) {
                elementObject.element.elementModel.controller["setProperties"](elementObject.element, elementObject.properties);
            });

            if (eventType !== "Changing") {
                var undoLabel = "Properties change";
                elements.forEach(function (elementObject) {
                    var swap = elementObject.properties;
                    elementObject.properties = elementObject.previousProperties;
                    elementObject.previousProperties = swap;
                });
                document.application.undoManager.add(undoLabel, this.setProperties, this, elements, eventType, source);
            }

            // Map the elements for the event data
            // TODO: Clean this up
            var els = elements.map(function (element) {
                return element.element;
            });

            // Dispatch the element change/changing event.
            NJevent("element" + eventType, {type:"setProperties", source:source, data:{"els":els, "prop":elements[0].properties, "value":elements}, redraw:null});
        }
    },

    set3DProperties:{
        value:function (elements, eventType, source) {
            var update3DModel = false;

            if (eventType === "Change") {
                update3DModel = true;
            }

            for (var i = 0, item; item = elements[i]; i++) {
                item.element.elementModel.controller["set3DProperties"](item.element, item.properties, update3DModel);
            }

            /*
             if(eventType === "Change") {
             var undoLabel = "3D Properties change";
             elements.forEach(function(elementObject) {
             var swap = elementObject.properties;
             elementObject.properties = elementObject.previousProperties;
             elementObject.previousProperties = swap;
             });
             document.application.undoManager.add(undoLabel, this.set3DProperties, this, elements, eventType, source);
             }
             */

            var els = elements.map(function (element) {
                return element.element;
            });

            NJevent("element" + eventType, {type:"set3DProperties", source:source, data:{"els":els, "prop":"matrix", "value":elements}, redraw:null});
        }
    },


    //--------------------------------------------------------------------------------------------------------
    // Routines to get/set color
    getColor:{
        value:function (el, isFill, borderSide) {
            return el.elementModel.controller["getColor"](el, isFill, borderSide);
        }
    },

    /**
     Set a property change command for an element or array of elements
     @param els: Array of elements. Can contain 1 or more elements
     @param value: Value to be set. This is the color
     @param isFill: Specifies if setting fill (background) or stroke (border)
     @param eventType: Change/Changing. Will be passed to the dispatched event
     @param source: String for the source object making the call
     @param currentValue *OPTIONAL*: current value array. If not found the current value is calculated
     @param stageRedraw: *OPTIONAL*: True. If set to false the stage will not redraw the selection/outline
     */
    setColor:{
        value:function (els, value, isFill, eventType, source, currentValue, borderSide) {

            if (eventType === "Changing") {
                this._setColor(els, value, isFill, eventType, source, borderSide);
            } else {
                // Calculate currentValue if not found for each element
                if (!currentValue) {
                    var that = this;
                    currentValue = els.map(function (item) {
                        return that.getColor(item, isFill);
                    });
                }

                var command = Montage.create(Command, {
                    _els:{ value:els },
                    _value:{ value:value },
                    _isFill:{ value:isFill },
                    _previous:{ value:currentValue },
                    _eventType:{ value:eventType},
                    _source:{ value:"undo-redo"},
                    description:{ value:"Set Color"},
                    receiver:{ value:this},
                    _borderSide:{ value:borderSide},

                    execute:{
                        value:function (senderObject) {
                            if (senderObject) this._source = senderObject;
                            this.receiver._setColor(this._els, this._value, this._isFill, this._eventType, this._source, this._borderSide);
                            this._source = "undo-redo";
                            return "";
                        }
                    },

                    unexecute:{
                        value:function () {
                            this.receiver._setColor(this._els, this._previous, this._isFill, this._eventType, this._source);
                            return "";
                        }
                    }
                });

                NJevent("sendToUndo", command);
                command.execute(source);
            }

        }
    },

    _setColor:{
        value:function (els, value, isFill, eventType, source, borderSide) {
            for (var i = 0, item; item = els[i]; i++) {
                item.elementModel.controller["setColor"](item, value, isFill, borderSide);
            }

            NJevent("element" + eventType, {type:"setColor", source:source, data:{"els":els, "prop":"color", "value":value, "isFill":isFill, "borderSide":borderSide}, redraw:null});
        }
    },

    getStroke:{
        value:function (el, strokeProperties) {
            return el.elementModel.controller["getStroke"](el, strokeProperties);
        }
    },

    /**
     Set stroke/border properties on an element or array of elements
     @param els: Array of elements. Can contain 1 or more elements
     @param value: Value to be set. This is the stroke info
     @param eventType: Change/Changing. Will be passed to the dispatched event
     @param source: String for the source object making the call
     @param currentValue *OPTIONAL*: current value array. If not found the current value is calculated
     */
    setStroke:{
        value:function (els, value, eventType, source, currentValue) {

            if (eventType !== "Changing") {
                // Calculate currentValue if not found for each element
                if (!currentValue) {
                    var that = this,
                        val = value;
                    currentValue = els.map(function (item) {
                        return that.getStroke(item, val);
                    });
                }
                document.application.undoManager.add("Set stroke", this.setStroke, this, els, currentValue, eventType, source, value);
            }

            for (var i = 0, item; item = els[i]; i++) {
                item.elementModel.controller["setStroke"](item, (value[i] || value), eventType, source);
            }

            NJevent("element" + eventType, {type:"setStroke", source:source, data:{"els":els, "prop":"stroke", "value":value}, redraw:null});
        }
    },

    getFill:{
        value:function (el, fillProperties) {
            return el.elementModel.controller["getFill"](el, fillProperties);
        }
    },

    /**
     Set fill/background properties for an element or array of elements
     @param els: Array of elements. Can contain 1 or more elements
     @param value: Value to be set. This is the fill info
     @param eventType: Change/Changing. Will be passed to the dispatched event
     @param source: String for the source object making the call
     @param currentValue *OPTIONAL*: current value array. If not found the current value is calculated
     */
    setFill:{
        value:function (els, value, eventType, source, currentValue) {

            if (eventType !== "Changing") {
                // Calculate currentValue if not found for each element
                if (!currentValue) {
                    var that = this,
                        val = value;
                    currentValue = els.map(function (item) {
                        return that.getFill(item, val);
                    });
                }
                document.application.undoManager.add("Set fill", this.setFill, this, els, currentValue, eventType, source, value);
            }

            for (var i = 0, item; item = els[i]; i++) {
                item.elementModel.controller["setFill"](item, (value[i] || value));
            }

            NJevent("element" + eventType, {type:"setFill", source:source, data:{"els":els, "prop":"fill", "value":value}, redraw:null});
        }
    },

    //--------------------------------------------------------------------------------------------------------
    // Routines to get/set 3D properties
    get3DProperty:{
        value:function (el, prop) {
            return el.elementModel.controller["get3DProperty"](el, prop);
        }
    },

    get3DProperties:{
        value:function (el) {
//            var mat = this.getMatrix(el);
//            var dist = this.getPerspectiveDist(el);
            var mat = el.elementModel.controller["getMatrix"](el);
            var dist = el.elementModel.controller["getPerspectiveDist"](el);
            return {mat:mat, dist:dist};
        }
    },

    getMatrix:{
        value:function (el) {
            return el.elementModel.controller["getMatrix"](el);
        }
    },

    getPerspectiveDist:{
        value:function (el) {
            return el.elementModel.controller["getPerspectiveDist"](el);
        }
    },

    getPerspectiveMode:{
        value:function (el) {
            return this.getProperty(el, "-webkit-transform-style");
        }
    },

    setMatrix:{
        value:function (el, mat, isChanging, source) {
            var dist = el.elementModel.controller["getPerspectiveDist"](el);
            el.elementModel.controller["set3DProperties"](el, {mat:mat, dist:dist}, !isChanging);

            if (isChanging) {
                NJevent("elementChanging", {type:"setMatrix", source:source, data:{"els":[el], "prop":"matrix", "value":mat}, redraw:null});
            } else {
                NJevent("elementChange", {type:"setMatrix", source:source, data:{"els":[el], "prop":"matrix", "value":mat}, redraw:null});
            }
        }
    },

    has3D:{
        value:function (el) {
            var str = this.getProperty(el, "-webkit-transform");
            return str && str.length;
        }
    },

    reArrangeDOM:{
        value:function (arrLayersDragged, dropTargetElement) {
            var i = 0,
                arrLayersDraggedLength = arrLayersDragged.length,
                targetParentNode,
                targetElement;

            if (arrLayersDraggedLength === 0) {
                // Nothing was dragged, so return.
                return;
            }

            // Get the target parent node (this will be the parentNode of any of the dragging items)
            targetParentNode = arrLayersDragged[0].parentNode;


            // Loop through arrLayersDragged and insertBefore the drop target element
            for (i = 0; i < arrLayersDraggedLength; i++) {
                targetParentNode.insertBefore(arrLayersDragged[i], dropTargetElement);
            }
        }
    }
});