/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *  
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 *  
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *  
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 * 
 */

/* jshint latedef: false */

(function () {
    "use strict";

    var DocumentManager = require("./lib/documentmanager"),
        StateManager = require("./lib/statemanager"),
        RenderManager = require("./lib/rendermanager"),
        AssetManager = require("./lib/assetmanager"),
        Headlights = require("./lib/headlights");
    
    var PLUGIN_ID = require("./package.json").name;

    var _generator,
        _config,
        _logger,
        _documentManager,
        _stateManager,
        _renderManager,
        _SONToCSSConverter;

    var _assetManagers = {};

    var _waitingDocuments = {},
        _canceledDocuments = {};

    /**
     * Disable asset generation for the given Document ID, halting any asset
     * rending in progress.
     * 
     * @private
     * @param {!number} id The document ID for which asset generation should be disabled.
     */
    function _pauseAssetGeneration(id) {
        if (_waitingDocuments.hasOwnProperty(id)) {
            _canceledDocuments[id] = true;
        } else if (_assetManagers.hasOwnProperty(id)) {
            _assetManagers[id].stop();
        }
    }

    /**
     * Completely stop asset generation for the given Document ID and collect
     * its AssetManager instance.
     * 
     * @private
     * @param {!number} id The document ID for which asset generation should be disabled.
     */
    function _stopAssetGeneration(id) {
        _pauseAssetGeneration(id);

        if (_assetManagers.hasOwnProperty(id)) {
            delete _assetManagers[id];
        }
    }

    /**
     * Handler for a the "file" change event fired by Document objects. Disables
     * asset generation after Save As is performed on an an already-saved file.
     * 
     * @private
     * @param {number} id The ID of the Document that changed
     * @param {{previous: string=, previousSaved: boolean=}} change The file
     *      change event emitted by the Document
     */
    function _handleFileChange(id, change) {
        // If the filename changed but the saved state didn't change, then the file must have been renamed
        if (change.previous && !change.hasOwnProperty("previousSaved")) {
            _stopAssetGeneration(id);
            _stateManager.deactivate(id);
        }
    }

    /**
     * Extract generator settings from the "current" or "previous" property of a generatorSettings
     * change event. If the supplied value is not actually a settings object, returns null.
     *
     * @private
     * @param {object} settings Settings json object.
     * @return {object} Settings object from generator or null.
     */
    function _getChangedSettings(settings) {
        if (settings && typeof(settings) === "object") {
            return _generator.extractDocumentSettings({generatorSettings: settings}, PLUGIN_ID);
        }
        return null;
    }

    /**
     * Handler for a the "generatorSettings" change event fired by Document objects. Updates
     * the state manger based on the current doc setting if the enable settings actually 
     * changed
     *
     * @private
     * @param {number} id The ID of the Document that changed
     * @param {{previous: settings=, current: settings=}} change The previous and current
     *      document settings
     */
    function _handleDocGeneratorSettingsChange(id, change) {
        var curSettings = _getChangedSettings(change.current),
            prevSettings = _getChangedSettings(change.previous),
            curEnabled = !!(curSettings && curSettings.enabled),
            prevEnabled = !!(prevSettings && prevSettings.enabled);
        
        if (prevEnabled !== curEnabled) {
            if (curEnabled) {
                _stateManager.activate(id);
            } else {
                _stateManager.deactivate(id);
            }
        }
    }

    /**
     * Handler for a the "openDocumentsChanged" event emitted by the DocumentManager.
     * Registers a generatorSettings change to update the StateManager appropiately
     *
     * @private
     * @param {Array.<number>} all The complete set of open document IDs
     * @param {Array.<number>=} opened The set of newly opened document IDs
     */
    function _handleOpenDocumentsChanged(all, opened) {
        var open = opened || all;

        open.forEach(function (id) {
            _documentManager.getDocument(id).done(function (document) {
                document.on("generatorSettings", _handleDocGeneratorSettingsChange.bind(undefined, id));
            }, function (error) {
                _logger.warning("Error getting document during a document changed event, " +
                    "document was likely closed.", error);
            });
        });
    }

    /**
     * Enable asset generation for the given Document ID, causing all annotated
     * assets in the given document to be regenerated.
     * 
     * @private
     * @param {!number} id The document ID for which asset generation should be enabled.
     */
    function _startAssetGeneration(id) {
        if (_waitingDocuments.hasOwnProperty(id)) {
            return;
        }

        var documentPromise = _documentManager.getDocument(id);
        
        _waitingDocuments[id] = documentPromise;

        documentPromise.done(function (document) {
            delete _waitingDocuments[id];

            if (_canceledDocuments.hasOwnProperty(id)) {
                delete _canceledDocuments[id];
            } else {
                if (!_assetManagers.hasOwnProperty(id)) {
                    _assetManagers[id] = new AssetManager(_generator, _config, _logger, document, _renderManager);

                    document.on("closed", _stopAssetGeneration.bind(undefined, id));
                    document.on("end", _restartAssetGeneration.bind(undefined, id));
                    document.on("file", _handleFileChange.bind(undefined, id));
                }
                _assetManagers[id].start();
            }
        }, function (err) {
            delete _waitingDocuments[id];
            _logger.error("Failed to start asset generation for document", id, err);
        });
    }

    /**
     * Restart asset generation for the given Document ID. This is called when
     * a Document emits an "end" event, indicating that there was an error
     * updating its internal state as from Photoshop's change events.
     * 
     * @private
     * @param {!number} id The document ID for which asset generation should be enabled.
     */
    function _restartAssetGeneration(id) {
        _stopAssetGeneration(id);
        _startAssetGeneration(id);
    }

    /**
     * Get a copy of the plugin's config object. For automated testing only.
     * 
     * @private
     * @return {object}
     */
    function _getConfig() {
        var copy = {},
            property;

        for (property in _config) {
            if (_config.hasOwnProperty(property)) {
                copy[property] = _config[property];
            }
        }
 
        return copy;
    }

    /**
     * Set the plugin's config object. This mutates the referenced object, not the
     * reference. For automated testing only.
     * 
     * @private
     * @param {object} config
     * @param {boolean=} keepExisting if true then don't delete previous configs first. default: false
     */
    function _setConfig(config, keepExisting) {
        var property;

        // optionally clean out the existing properties first
        if (!keepExisting) {
            for (property in _config) {
                if (_config.hasOwnProperty(property)) {
                    delete _config[property];
                }
            }
        }

        // add in the new properties
        for (property in config) {
            if (config.hasOwnProperty(property)) {
                _config[property] = config[property];
            }
        }
    }


    /**
     * Initialize the Assets plugin.
     * 
     * @param {Generator} generator The Generator instance for this plugin.
     * @param {object} config Configuration options for this plugin.
     * @param {Logger} logger The Logger instance for this plugin.
     */
    function init(generator, config, logger) {
        _generator = generator;
        _config = config;
        _logger = logger;

        _documentManager = new DocumentManager(generator, config, logger);
        _stateManager = new StateManager(generator, config, logger, _documentManager);
        _renderManager = new RenderManager(generator, config, logger);

        if (!!_config["css-enabled"]) {
            var SONToCSS = require("./lib/css/sontocss.js");
            _SONToCSSConverter = new SONToCSS(generator, config, logger, _documentManager);
        }

        // For automated tests
        exports._renderManager = _renderManager;
        exports._stateManager = _stateManager;
        exports._assetManagers = _assetManagers;
        exports._layerNameParse = require("./lib/parser").parse;

        _stateManager.on("enabled", _startAssetGeneration);
        _stateManager.on("disabled", _pauseAssetGeneration);
        _documentManager.on("openDocumentsChanged", _handleOpenDocumentsChanged);

        Headlights.init(generator, logger, _stateManager, _renderManager);
    }


    exports.init = init;

    // For automated tests
    exports._getConfig = _getConfig;
    exports._setConfig = _setConfig;
}());
