/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 */

/*jslint vars: true, node: true, plusplus: true, devel: true, nomen: true, indent: 4, bitwise: true */
/*global  */
"use strict";

var PLUGIN_ID = "crema";

var $ = require("jquery"),
    _ = require("underscore"),
    Q = require("q"),
    docSettings = require("./docSettings.js"),
    JSXRunner = require("./JSXRunner"),
    Strings = require("./LocStrings"),
    ServerInterface = require("./serverInterface.js");

var DocRollup = function (docId) {
    this.docId = docId;
    this.defaultID = "";
    this.defaultName = null;
    this.deletedLayers = [];
    this.layers = {};
    this.comps = {};
    this.generatorSettings = docSettings.getOriginalGeneratorSettings();
    this.autoUpdateSetter = null;
};

var ActionRollup = function () {
    
    this.docRollups = {};
    this.defaultLayers = {};
    
    this.reset = function () {
        this.docRollups = {};
        this.defaultLayers = {};
    };
    
    this.isDirty = function () {
        return (Object.keys(this.docRollups).length !== 0);
    };
    
    this._injectModelUpdate = function (updateDoc) {
        return ServerInterface.sendCommand("injectModelUpdate", { changeDoc: updateDoc });
    };
    
    this._currentDocId = function () {
        return docSettings.getCurrentDocId();
    };
    
    this._getDocRollup = function (docId) {
        if (!this.docRollups[docId]) {
            this.docRollups[docId] = new DocRollup(docId);
            if (this.defaultLayers[docId]) {
                this.docRollups[docId].defaultID = this.defaultLayers[docId].id;
            }
        }
        return this.docRollups[docId];
    };
    
    this.serializeDocGeneratorSettings = function (generatorSettings) {
        var rollup = this._getDocRollup(this._currentDocId());
        rollup.generatorSettings = _.extend(rollup.generatorSettings || {}, generatorSettings);
    };
    
    this.setAutoSaveUpdater = function (updateFunction) {
        var rollup = this._getDocRollup(this._currentDocId());
        rollup.autoUpdateSetter = updateFunction;
    };
    
    this.lobotomizeLayer = function (layerId) {
        var rollup = this._getDocRollup(this._currentDocId());
        
        if (rollup.layers[layerId]) {
            delete rollup.layers[layerId];
        }
    };
    
    this.renameLayer = function (layerId, layerName) {
        var rollup = this._getDocRollup(this._currentDocId()),
            docUpdate = {
                id: this._currentDocId(),
                layers: [{ id: layerId, name: layerName }]
            };
                
        rollup.layers[layerId] = _.extend(rollup.layers[layerId] || {}, {name: layerName});
        
        this._injectModelUpdate(docUpdate);
    };
    
    this.updateLayerMetaData = function (layerId, layerSettingsData, isDocumentLayer) {
        var rollup = this._getDocRollup(this._currentDocId()),
            localGeneratorSettings = {crema: {json: JSON.stringify(layerSettingsData)}},
            previewDocUpdate = { id: this._currentDocId() },
            localDocUpdate = { id: this._currentDocId() };
        
        if (!isDocumentLayer) {
            // If we're processing a normal layer, update its metadata.
            localDocUpdate.layers = [{id: layerId, generatorSettings: localGeneratorSettings}];
        } else {
            // If we're processing the "layer" that represents the whole document, update the document level metadata.
            localDocUpdate.generatorSettings = localGeneratorSettings;
        }
        
        if (!isDocumentLayer) {
            rollup.layers[layerId] = _.extend(rollup.layers[layerId] || {}, {json: layerSettingsData});
        } else {
            rollup.generatorSettings = _.extend(rollup.generatorSettings || {}, layerSettingsData);
        }
    };
    this.updateDocumentMetaData = function(layerId, layerSettingsData) {
        this.updateLayerMetaData(layerId, layerSettingsData, true);
    };
  
    this.lobotomizeComp = function (compIndex) {
        var rollup = this._getDocRollup(this._currentDocId());
        
        if (rollup.comps[compIndex]) {
            delete rollup.comps[compIndex];
        }
    };
    
    this.renameComp = function (compIndex, compId, compName) {
        var rollup = this._getDocRollup(this._currentDocId()),
            docUpdate = {
                id: this._currentDocId(),
                comps: [{
                    id: compId,
                    name: compName
                }]
            };
                
        rollup.comps[compIndex] = compName;
        
        this._injectModelUpdate(docUpdate);
    };
    
    this.deleteLayer = function (layerId) {
        var rollup = this._getDocRollup(this._currentDocId()),
            docUpdate = {
                id: this._currentDocId(),
                layers: [{ id: layerId, removed: true}]
            };
        rollup.deletedLayers.push(layerId);
        this._injectModelUpdate(docUpdate);
    };
    
    this.addDefaultLayer = function () {
        var docId = this._currentDocId(),
            rollup = this._getDocRollup(docId),
            addDefaultName = "default 100%";
        
        if (rollup.defaultID) {
            //this is really a rename
            this.renameDefaultLayer(addDefaultName);
        } else {
            rollup.defaultID = "ADDED";
            rollup.defaultName = addDefaultName;
            this.defaultLayers[docId] = {id: 33333, name: rollup.defaultName};
        }
    };
    
    this.setDefaultLayer = function (layerId, layerName) {
        var docId = this._currentDocId();
        this.defaultLayers[docId] = {id: layerId, name: layerName };
        if (this.docRollups[docId]) {
            this.docRollups[docId].defaultID = layerId;
        }
        this.disableTempDefaultLayer();
    };
    
    this.disableTempDefaultLayer = function () {
        var docId = this._currentDocId();
        if (!this.defaultLayers.hasOwnProperty(docId)) {
            return Q.resolve();
        }
        var defaultLayer = this.defaultLayers[docId],
            docUpdate = {
                id: docId,
                layers: [{ id: defaultLayer.id, removed: true}]
            };
        
        return this._injectModelUpdate(docUpdate);
    };
    
    this.enableTempDefaultLayer = function () {
        var docId = this._currentDocId();
        if (!this.defaultLayers.hasOwnProperty(docId)) {
            return Q.resolve();
        }
        var defaultLayer = this.defaultLayers[docId],
            defId = 33333,
            defIndex = docSettings.getLayerCollection() ? docSettings.getLayerCollection().length : 0,
            docUpdate = {
                id: docId,
                layers: [{ id: defId, type: 'layer', index: defIndex, name: defaultLayer.name, added: true }]
            };
        if (!defaultLayer.name) {
            return Q.resolve();
        }
        //lame I know, but we need a chance for generate to get the change events and there isn't a good
        //way to wait for that here
        return this._injectModelUpdate(docUpdate).delay(500);
    };
    
    this.removeDefaultLayer = function () {
        var docId = this._currentDocId(),
            rollup = this._getDocRollup(docId);
        rollup.defaultName = "DELETED";
        if (rollup.defaultID === "ADDED") {
            rollup.defaultID = "";
        }
        if (this.defaultLayers[docId]) {
            this.defaultLayers[docId].name = "";
        }
    };
    
    this.renameDefaultLayer = function (layerName) {
        var docId = this._currentDocId(),
            rollup = this._getDocRollup(docId);
        rollup.defaultName = layerName;
        if (this.defaultLayers[docId]) {
            this.defaultLayers[docId].name = layerName;
        }
    };
    
    this.writeRolledupJSXDefines = function (aJSX) {
        aJSX.push(JSXRunner.getRawJSX('addNewLayer-init'));
        aJSX.push(JSXRunner.getRawJSX('removeLayer-init'));
        aJSX.push(JSXRunner.getRawJSX('renameLayer-init'));
        aJSX.push(JSXRunner.getRawJSX('renameComp-init'));
        aJSX.push(JSXRunner.getRawJSX('setGeneratorSettings-init'));
    };
    
    this.writeRolledupJSX = function (docId) {
        var rollup = this._getDocRollup(docId),
            aJSX = [],
            indent = '    ',
            newline = '\n',
            metaInfo = {"cremaVersion": "1.1"},
            setterJSON;
        
        var wrapInTryCatch = function (statement) {
            return [indent, "try {", newline,
                    indent, indent, statement, newline,
                    indent, "} catch (e) {", newline,
                    indent, indent, "errorStr += 'Exception: ' + e + ', ';", newline,
                    indent, "}", newline, newline].join("");
        };
        
        //build mega-jsx
        this.writeRolledupJSXDefines(aJSX);
        aJSX.push('var errorStr = "SUCCESS";' + newline);
        aJSX.push('var coreUpdate = function () {' + newline);
        if (rollup.defaultID === 'ADDED') {
            //if we are adding a default layer...
            if (rollup.defaultName && rollup.defaultName !== "DELETED") {
                aJSX.push(wrapInTryCatch('addNewLayer("' + rollup.defaultName + '", true);'));
            }

        } else if (rollup.defaultID !== '' && rollup.defaultName !== null) {
            if (rollup.defaultName === 'DELETED' || rollup.defaultName === '') {
                //delete the default layer
                aJSX.push(wrapInTryCatch('removeLayerById(' + rollup.defaultID + ');'));

            } else {
                //rename the default layer
                aJSX.push(wrapInTryCatch('renameLayerById(' + rollup.defaultID + ', "' + rollup.defaultName + '");'));
            }
        }
        
        //delete extra default layers
        rollup.deletedLayers.forEach(function (layerId) {
            aJSX.push(wrapInTryCatch('removeLayerById(' + layerId + ');'));
        });
        
        Object.keys(rollup.layers).forEach(function (layerId) {
            var lyr = rollup.layers[layerId];
            if (_.has(lyr, "name")){
                var lyrName = lyr.name.replace(/"/g, '\\"');
                aJSX.push(wrapInTryCatch('renameLayerById(' + layerId + ', "' + lyrName + '");'));
            }
            if (_.has(lyr, "json")) {
                var lyrJSON = {json: JSON.stringify(lyr.json)};
                aJSX.push(wrapInTryCatch('setGeneratorSettingsForPlugin("' + PLUGIN_ID + '", ' + JSON.stringify(lyrJSON) + ', ' + layerId + ');'));
            }
        });

        Object.keys(rollup.comps).forEach(function (compId) {
            var cmpName = rollup.comps[compId].replace(/"/g, '\\"');
            aJSX.push(wrapInTryCatch('renameLayerComp(' + compId + ', "' + cmpName + '");'));
        });

        if (rollup.generatorSettings) {
            setterJSON = {json: JSON.stringify(_.extend(rollup.generatorSettings, metaInfo))};
        } else {
            setterJSON = {json: JSON.stringify(metaInfo)};
        }
        aJSX.push(wrapInTryCatch('setGeneratorSettingsForPlugin("' + PLUGIN_ID + '", ' + JSON.stringify(setterJSON) + ');'));

        aJSX.push(newline + '};' + newline);
        aJSX.push('coreUpdate();');
        aJSX.push('errorStr');
        return aJSX.join('');
    };

    
    this.apply = function () {
        //go through all the rollups and make the edits to the doc
        var applyDeferred = Q.defer(),
            rollupKeys = Object.keys(this.docRollups);
        
        rollupKeys.forEach(function (docId) {

            var jsxOut = this.writeRolledupJSX(docId),
                rollup = this.docRollups[docId];
            JSXRunner.runRawJSX(jsxOut, function (result) {
                if (rollup.autoUpdateSetter) {
                    rollup.autoUpdateSetter().finally(function () {
                        applyDeferred.resolve(result);
                    });
                } else {
                    applyDeferred.resolve(result);
                }
            });
        }.bind(this));
        
        if (rollupKeys.length === 0) {
            applyDeferred.resolve();
        }
        
        return applyDeferred.promise;
    };
    
};

module.exports = new ActionRollup();