/* <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,
    NJComponent = require("js/lib/nj-base").NJComponent;

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

exports.ElementMediator = Montage.create(NJComponent, {

    deleteDelegate: {
        value: null
    },

    deserializedFromTemplate: {
        value: function () {
            this.eventManager.addEventListener("elementAdding", this, false);
            this.eventManager.addEventListener("deleting", this, false);
        }
    },

    // TODO use the specific controller to be able to subclass the functionality
    handleElementAdding: {
        value: function(event) {
            this.addElement(event.detail.el, event.detail.data);
        }
    },

    handleDeleting: {
        value: function(event) {
            if(this.deleteDelegate && (typeof this.deleteDelegate.handleDelete === 'function')) {
                this.deleteDelegate.handleDelete();
            } else {
                // Add the Undo/Redo
                var els = [],
                    len = this.application.ninja.selectedElements.length;

                if(len) {
                    for(var i = 0; i<len; i++) {
                        els.push(this.application.ninja.selectedElements[i]);
                    }
                    
                    for(i=0; i<len; i++) {
                        this._removeElement(els[i]._element);
                    }

                    NJevent( "deleteSelection", els );
                }
            }
        }
    },

    addElement: {
        value: function(el, rules, noEvent) {
            var command = Montage.create(Command, {
                _el:            { value: el },
                _rules:         { value: rules },
                _noEvent:       { value: noEvent },

                description: { value: "Adding Element"},

                receiver: { value: this},

                execute: {
                    value: function() {
                        this.receiver._addElement(this._el, this._rules, this._noEvent);
                        return this._el;
                    }
                },

                unexecute: {
                    value: function() {
                        this.receiver._removeElement(this._el, this._rules, this._noEvent);
                        return this._el;
                    }
                }
            });

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

    _addElement: {
        value: function(el, rules, noEvent) {
            ElementController.addElement(el, rules);
            var p3d = this.get3DProperties(el);
            if(p3d) {
                el.elementModel.controller["set3DProperties"](el, [p3d], 0, true);
            }
            if(!noEvent) {
                this.application.ninja.documentController.activeDocument.needsSave = true;
                NJevent("elementAdded", el);
            }
        }
    },

    deleteElements: {
         value: function(items) {
            // Add the Undo/Redo
            var len, el;

            len = items.length;

            if(len) {

                for(var i = len - 1; i >= 0; i--) {
                    el = items[i]._element || items[i];
                    this._removeElement(el);
                }

                NJevent( "deleteSelection", items );
            }
         }
    },

    _removeElement: {
        value: function(el, rules) {
            ElementController.removeElement(el, rules);
            this.application.ninja.documentController.activeDocument.needsSave = true;
            NJevent("elementDeleted", el);
        }
    },

    replaceElement: {
        value: function(el, el2) {
            el2.elementModel = el.elementModel;
            this.application.ninja.currentDocument.documentRoot.replaceChild(el2, el);
        }
    },

    getNJProperty: {
        value: function(el, p) {
            if(el.elementModel) {
                if(el.elementModel.hasOwnProperty(p)) {
                    return el.elementModel[p];
                } else {
                    console.log("Element Model does not have ", p);
                }
            } else {
                console.log("Element has no Model -- Create one");
            }
        }
    },

    getProperty: {
        value: function(el, prop, valueMutator) {
            if(!el.elementModel) {
                console.log("Element has no Model -> One should have been created");
                NJUtils.makeElementModel(el, "Div", "block");
            }

            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) {
            if(!el.elementModel) {
                console.log("Element has no Model -> One should have been created");
                NJUtils.makeElementModel(el, "Canvas", "block", true);
            }

            return el.elementModel.controller["getShapeProperty"](el, prop);
        }
    },

    setShapeProperty: {
        value: function(el, prop, value) {
            if(!el.elementModel) {
                console.log("Element has no Model -> One should have been created");
                NJUtils.makeElementModel(el, "Canvas", "block", true);
            }

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

    /**
     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
     */
    setAttribute: {
        value: function(el, att, value, eventType, source, currentValue) {

            if(eventType === "Changing") {
                this._setAttribute(el, att, value, eventType, source);
            } else {
                // Calculate currentValue if not found for each element
                if(currentValue === null) {
                    console.log("Here");
                    var item = el._element || el;
                    currentValue = item.getAttribute(att);
                }

                var command = Montage.create(Command, {
                    _el:                { value: el },
                    _att:               { value: att },
                    _value:             { value: value },
                    _previous:          { value: currentValue },
                    _eventType:         { value: eventType},
                    _source:            { value: "undo-redo"},
                    description:        { value: "Set Attribute"},
                    receiver:           { value: this},

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

                    unexecute: {
                        value: function() {
                            this.receiver._setAttribute(this._el, this._att, this._previous, this._eventType, this._source);
                            return "";
                        }
                    }
                });

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

        }
    },

    _setAttribute: {
        value: function(el, att, value, eventType, source) {
            var item = el._element || el;

            item.elementModel.controller["setAttribute"](item, att, value);

            NJevent("attribute" + eventType, {type : "setAttribute", source: source, data: {"els": el, "prop": att, "value": value}, redraw: null});
        }
    },



    /**
     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, stageRedraw) {
            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._element || 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++) {
                el = item._element || item;
                el.elementModel.controller["setProperty"](el, p, value[i]);
            }

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

    /**
     Set a property change command for an element or array of elements
     @param els: Array of elements. Can contain 1 or more elements
     @param props: Property/ies object containing both the value and property
     @param eventType: Change/Changing. Will be passed to the dispatched event
     @param source: String for the source object making the call
     @param currentProps *OPTIONAL*: current properties objects array. If not found it will be calculated
     @param stageRedraw: *OPTIONAL*: True. If set to false the stage will not redraw the selection/outline
     */
    setProperties: {
        value: function(els, props, eventType, source, currentProps, stageRedraw) {
            if(eventType === "Changing") {
                this._setProperties(els, props, eventType, source);
            } else {
                var command = Montage.create(Command, {
                    _els:               { value: els },
                    _props:             { value: props },
                    _previous:          { value: currentProps },
                    _eventType:         { value: eventType},
                    _source:            { value: "undo-redo"},
                    description:        { value: "Set Properties"},
                    receiver:           { value: this},

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

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

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

    _setProperties: {
        value: function(els, props, eventType, source) {
            var el, propsArray;

            for(var i=0, item; item = els[i]; i++) {
                el = item._element || item;
                el.elementModel.controller["setProperties"](el, props, i);
            }

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

    /**
     Set a property change command for an element or array of elements
     @param els: Array of elements. Can contain 1 or more elements
     @param props: Property/ies object containing both the value and property
     @param eventType: Change/Changing. Will be passed to the dispatched event
     @param source: String for the source object making the call
     @param currentProps *OPTIONAL*: current properties objects array. If not found it will be calculated
     @param stageRedraw: *OPTIONAL*: True. If set to false the stage will not redraw the selection/outline
     */
    set3DProperties: {
        value: function(els, props, eventType, source, currentProps, stageRedraw) {
            if(eventType === "Changing") {
                this._set3DProperties(els, props, eventType, source);
            } else {
                // Calculate currentProps if not found for each element
                if(!currentProps) {
                    var that = this;
                    currentProps = els.map(function(item) {
                        return that.get3DProperties(item);
                    });
                }

                var command = Montage.create(Command, {
                    _els:               { value: els },
                    _props:             { value: props },
                    _previous:          { value: currentProps },
                    _eventType:         { value: eventType},
                    _source:            { value: "undo-redo"},
                    description:        { value: "Set 3D Properties"},
                    receiver:           { value: this},

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

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

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

    _set3DProperties: {
        value: function(els, props, eventType, source) {
            var el,
                update3DModel = false;

            if(eventType === "Change")
            {
                update3DModel = true;
            }
            for(var i=0, item; item = els[i]; i++) {
                el = item._element || item;
                el.elementModel.controller["set3DProperties"](el, props, i, update3DModel);
            }

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


    //--------------------------------------------------------------------------------------------------------
    // Routines to get/set color
    // for now just return the bg/fill color
    getColor: {
        value: function(el, isFill) {
            if(!el.elementModel) {
                NJUtils.makeElementModel2(el);
            }
            return el.elementModel.controller["getColor"](el, isFill);
        }
    },

    /**
     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, stageRedraw) {

            if(eventType === "Changing") {
                this._setColor(els, value, isFill, eventType, source);
            } else {
                // Calculate currentValue if not found for each element
                if(!currentValue) {
                    var that = this;
                    currentValue = els.map(function(item) {
                        return that.getColor(item._element, 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},

                    execute: {
                        value: function(senderObject) {
                            if(senderObject) this._source = senderObject;
                            this.receiver._setColor(this._els, this._value, this._isFill, this._eventType, this._source);
                            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) {
            var el;

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

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



    getStroke: {
        value: function(el) {
            if(!el.elementModel) {
                NJUtils.makeElementModel(el, "Div", "block");
            }
            return el.elementModel.controller["getStroke"](el);
        }
    },


    /**
     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 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
     @param stageRedraw: *OPTIONAL*: True. If set to false the stage will not redraw the selection/outline
     */
    setStroke: {
        value: function(els, value, eventType, source, currentValue, stageRedraw) {

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

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

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

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

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

        }
    },

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

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

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

    getColor2: {
        value: function(el, prop, mutator) {
            if(!el.elementModel) {
                NJUtils.makeElementModel2(el);
            }

            return this.getColor(el, (prop === "background"));
        }
    },

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

    get3DProperties: {
        value: function(el) {
            if(!el.elementModel) {
                NJUtils.makeElementModel2(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) {
            if(!el.elementModel) {
                NJUtils.makeElementModel2(el);
            }
            return el.elementModel.controller["getMatrix"](el);
        }
    },

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

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

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

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

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




    //--------------------------------------------------------------------------------------------------------

});