/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Created by Pardo David on 3/01/2017.
 * For this service to work the user must call bootEditor method
 */
angular.module("flowableModeler").factory("editorManager", ["$http", function ($http) {
    var editorManager = Class.create({
        initialize: function () {
            this.treeFilteredElements = ["SubProcess", "CollapsedSubProcess"];
            this.canvasTracker = new Hash();
            this.structualIcons = {
                "SubProcess": "expanded.subprocess.png",
                "CollapsedSubProcess": "subprocess.png",
                "EventSubProcess": "event.subprocess.png"
            };

            this.current = this.modelId;
            this.loading = true;
        },
        getModelId: function () {
            return this.modelId;
        },
        setModelId: function (modelId){
            this.modelId = modelId;
        },
        getCurrentModelId: function () {
        	return this.current;
        },
        setStencilData: function(stencilData){
            //we don't want a references!
            this.stencilData = jQuery.extend(true, {},stencilData);
        },
        getStencilData: function () {
            return this.stencilData;
        },
        getSelection: function () {
            return this.editor.selection;
        },
        getSubSelection: function () {
            return this.editor._subSelection;
        },
        handleEvents: function (events) {
            this.editor.handleEvents(events);
        },
        setSelection: function (selection) {
            this.editor.setSelection(selection);
        },
        registerOnEvent: function (event, callback) {
            this.editor.registerOnEvent(event, callback);
        },
        getChildShapeByResourceId: function (resourceId) {
            return this.editor.getCanvas().getChildShapeByResourceId(resourceId);
        },
        getJSON: function () {
            return this.editor.getJSON();
        },
        getStencilSets: function () {
            return this.editor.getStencilSets();
        },
        getEditor: function () {
            return this.editor; //TODO: find out if we can avoid exposing the editor object to angular.
        },
        executeCommands: function (commands) {
            this.editor.executeCommands(commands);
        },
        getCanvas: function () {
            return this.editor.getCanvas();
        },
        getRules: function () {
            return this.editor.getRules();
        },
        eventCoordinates: function (coordinates) {
            return this.editor.eventCoordinates(coordinates);
        },
        eventCoordinatesXY: function (x, y) {
            return this.editor.eventCoordinatesXY(x, y);
        },
        updateSelection: function () {
            this.editor.updateSelection();
        },
        /**
         * @returns the modeldata as received from the server. This does not represent the current editor data.
         */
        getBaseModelData: function () {
            return this.modelData;
        },
        edit: function (resourceId) {
            //Save the current canvas in the canvastracker if it is the root process.
            this.syncCanvasTracker();

            this.loading = true;

            var shapes = this.getCanvas().getChildren();
            shapes.each(function (shape) {
                this.editor.deleteShape(shape);
            }.bind(this));

            shapes = this.canvasTracker.get(resourceId);
            if(!shapes){
                shapes = JSON.stringify([]);
            }

            this.editor.loadSerialized({
                childShapes: shapes
            });

            this.getCanvas().update();

            this.current = resourceId;

            this.loading = false;
            FLOWABLE.eventBus.dispatch("EDITORMANAGER-EDIT-ACTION", {});
            FLOWABLE.eventBus.dispatch(FLOWABLE.eventBus.EVENT_TYPE_UNDO_REDO_RESET, {});
        },
        getTree: function () {
            //build a tree of all subprocesses and there children.
            var result = new Hash();
            var parent = this.getModel();
            result.set("name", parent.properties["name"] || "No name provided");
            result.set("id", this.modelId);
            result.set("type", "root");
            result.set("current", this.current === this.modelId)
            var childShapes = parent.childShapes;
            var children = this._buildTreeChildren(childShapes);
            result.set("children", children);
            return result.toObject();
        },
        _buildTreeChildren: function (childShapes) {
            var children = [];
            for (var i = 0; i < childShapes.length; i++) {
                var childShape = childShapes[i];
                var stencilId = childShape.stencil.id;
                //we are currently only interested in the expanded subprocess and collapsed processes
                if (stencilId && this.treeFilteredElements.indexOf(stencilId) > -1) {
                    var child = new Hash();
                    child.set("name", childShape.properties.name || "No name provided");
                    child.set("id", childShape.resourceId);
                    child.set("type", stencilId);
                    child.set("current", childShape.resourceId === this.current);
                    
                    //check if childshapes

                    if (stencilId === "CollapsedSubProcess") {
                        //the save function stores the real object as a childshape
                        //it is possible that there is no child element because the user did not open the collapsed subprocess.
                        if (childShape.childShapes.length === 0) {
                            child.set("children", []);
                        } else {
                            child.set("children", this._buildTreeChildren(childShape.childShapes));
                        }
                        child.set("editable", true);
                    } else {
                        child.set("children", this._buildTreeChildren(childShape.childShapes));
                        child.set("editable", false);
                    }
                    child.set("icon", this.structualIcons[stencilId]);
                    children.push(child.toObject());
                }
            }
            return children;
        },
        syncCanvasTracker: function () {
            var shapes = this.getCanvas().getChildren();
            var jsonShapes = [];
            shapes.each(function (shape) {
                //toJson is an summary object but its not a json string.!!!!!
                jsonShapes.push(shape.toJSON());
            });
            this.canvasTracker.set(this.current, JSON.stringify(jsonShapes));
        },
        getModel: function () {
            this.syncCanvasTracker();
            
            var modelMetaData = this.getBaseModelData();
            
            var stencilId = undefined;
            var stencilSetNamespace = undefined;
            var stencilSetUrl = undefined;
            if (modelMetaData.model.stencilset.namespace == 'http://b3mn.org/stencilset/cmmn1.1#') {
                stencilId = 'CMMNDiagram';
                stencilSetNamespace = 'http://b3mn.org/stencilset/cmmn1.1#';
                stencilSetUrl = '../editor/stencilsets/cmmn1.1/cmmn1.1.json';
            } else {
                stencilId = 'BPMNDiagram';
                stencilSetNamespace = 'http://b3mn.org/stencilset/bpmn2.0#';
                stencilSetUrl = '../editor/stencilsets/bpmn2.0/bpmn2.0.json';
            }

            //this is an object.
            var editorConfig = this.editor.getJSON();
            var model = {
                modelId: this.modelId,
                bounds: editorConfig.bounds,
                properties: editorConfig.properties,
                childShapes: JSON.parse(this.canvasTracker.get(this.modelId)),
                stencil: {
                    id: stencilId,
                },
                stencilset: {
                    namespace: stencilSetNamespace,
                    url: stencilSetUrl
                }
            };

            this._mergeCanvasToChild(model);

            return model;
        },
        setModelData: function(response){
            this.modelData = response.data;
        },
        bootEditor: function () {
            //TODO: populate the canvas with correct json sections.
            //resetting the state
            this.canvasTracker = new Hash();
            var config = jQuery.extend(true, {}, this.modelData); //avoid a reference to the original object.
            if(!config.model.childShapes){
                config.model.childShapes = [];
            }
            
            this.findAndRegisterCanvas(config.model.childShapes); //this will remove any childshapes of a collapseable subprocess.
            this.canvasTracker.set(config.modelId, JSON.stringify(config.model.childShapes)); //this will be overwritten almost instantly.

            this.editor = new ORYX.Editor(config);
            this.current = this.editor.id;
            this.loading = false;

            FLOWABLE.eventBus.editor = this.editor;
            FLOWABLE.eventBus.dispatch("ORYX-EDITOR-LOADED", {});
            FLOWABLE.eventBus.dispatch(FLOWABLE.eventBus.EVENT_TYPE_EDITOR_BOOTED, {});
        },
        findAndRegisterCanvas: function (childShapes) {
            for (var i = 0; i < childShapes.length; i++) {
                var childShape = childShapes[i];
                if (childShape.stencil.id === "CollapsedSubProcess") {
                    if (childShape.childShapes.length > 0) {
                        //the canvastracker will auto correct itself with a new canvasmodel see this.edit()...
                        this.findAndRegisterCanvas(childShape.childShapes);
                        //a canvas can't be nested as a child because the editor would crash on redundant information.
                        this.canvasTracker.set(childShape.resourceId, JSON.stringify(childShape.childShapes));
                        //reference to config will clear the value.
                        childShape.childShapes = [];
                    } else {
                        this.canvasTracker.set(childShape.resourceId, '[]');
                    }
                }
            }
        },
        _mergeCanvasToChild: function (parent) {
            for (var i = 0; i < parent.childShapes.length; i++) {
                var childShape = parent.childShapes[i]
                if(childShape.stencil.id === "CollapsedSubProcess"){
                    
                    var elements = this.canvasTracker.get(childShape.resourceId);
                    if(elements){
                        elements = JSON.parse(elements);
                    }else{
                        elements = [];
                    }
                    childShape.childShapes = elements;
                    this._mergeCanvasToChild(childShape);
                }else if(childShape.stencil.id === "SubProcess"){
                    this._mergeCanvasToChild(childShape);
                }else{
                    //do nothing?
                }
            }
        },
        dispatchOryxEvent: function (event) {
            FLOWABLE.eventBus.dispatchOryxEvent(event);
        },
        isLoading: function(){
            return this.loading;
        },
        navigateTo: function(resourceId){
            //TODO: this could be improved by check if the resourceId is not equal to the current tracker...
            this.syncCanvasTracker();
            var found = false;
            this.canvasTracker.each(function(pair){
                var key = pair.key;
                var children = JSON.parse(pair.value);
                var targetable = this._findTarget(children, resourceId);
                if (!found && targetable){
                    this.edit(key);
                    var flowableShape = this.getCanvas().getChildShapeByResourceId(targetable);
                    this.setSelection([flowableShape],[],true);
                    found = true;
                }
            },this);
        },
        _findTarget: function(children,resourceId){
            for(var i =0; i < children.length; i++){
                var child = children[i];
                if(child.resourceId === resourceId){
                    return child.resourceId;
                }else if(child.properties && child.properties["overrideid"] === resourceId){
                    return child.resourceId;
                }else{
                    var result = this._findTarget(child.childShapes,resourceId);
                    if(result){
                        return result;
                    }
                }
            }
            return false;
        }
    });

    return new editorManager();
}]);
