/*
 * 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, plusplus: true, devel: true, nomen: true, indent: 4, bitwise: true, node: true, unused:true*/
/*global csInterface: false, require: false, module: false */

"use strict";

var Backbone        = require('backbone'),
    _               = require("underscore"),
    Q               = require("q"),
    Path            = require("path"),
    DocinfoUtils    = require("shared/DocinfoUtils"),
    DocumentLayer   = require("./documentLayer.js"),
    ServerInterface = require("./serverInterface.js"),
    Strings = require("./LocStrings"),
    JSXRunner = require("./JSXRunner"),
    BoundsUtils = require("shared/BoundsUtils"),
    LayerNameParser = require("./utils/LayerNameParser.js"),
    GenableCollection = require("./genableCollection.js"),
    GenSettingsModel = require("./genSettingsModel.js"),
    LayerModel = require("./layerModel.js"),
    CompModel = require("./compModel.js"),
    DefaultSettingsModel = require("./defaultSettingsModel.js"),
    DocSettings = require("./docSettings.js"),
    ActionRollup = require("./actionRollup.js"),
    Headlights  = require("./utils/Headlights");

var GENERATOR_PLUGIN_ID = "crema";

var GeneratorModel = Backbone.Model.extend({
    
    initialize: function () {
        this.set("isPerformingDocumentExport", csInterface.getExtensionID() === "com.adobe.WEBPA.crema.saveforwebdocument");
        this.defaultSettingsModel = new DefaultSettingsModel();
        
        this.layerCollection = new GenableCollection([], { model: LayerModel });
        this.compCollection = new GenableCollection([], { model: CompModel, headlightsGroup: Headlights.COMPS_GROUP });

        DocSettings._layerCollection = this.layerCollection;
        DocSettings._compCollection = this.compCollection;

        this.listenTo(this.layerCollection, "background-layer-status-change", this.handleBackgroundStatusChange);
        this.listenTo(this.layerCollection, "change:layerSettingsDisabled", this.trigger.bind(this, "change:layerSettingsDisabled"));
        this.listenTo(this.layerCollection, "change:settingsLoading add remove", this.updateLayersLoading);
        
        this.reset();
        
        this.ensureGeneratorRunning();
        ServerInterface.createWebSocketConnection().then(this.loadGenerator.bind(this));
        ServerInterface.on("asset-rendering-updated", this.handleAssetUpdate.bind(this));
        ServerInterface.on("generator-connected", this.handleGeneratorConnect.bind(this));
        ServerInterface.on("generator-closed", this.handleGeneratorClose.bind(this));
    },

    handleGeneratorClose: function () {
        this.set("generatorClosed", true);
    },

    handleGeneratorConnect: function () {
        this.set("generatorClosed", false);
    },

    handleBackgroundStatusChange: function () {
        var hasBG = this.layerCollection.hasBackgroundLayer();
        DocSettings.setDocHasBackground(hasBG);
    },
    
    ensureGeneratorRunning: function () {
        var localPrefs = require("./utils/localPrefs.js");
        
        //comment the following line out once and run... then forever you can enjoy not having it auto-start generator
        //localPrefs.setPref("DEV-do-not-auto-start-generator", "internal-generator-off");
        
        if (localPrefs.getPref("DEV-do-not-auto-start-generator") !== "internal-generator-off") {
            JSXRunner.runJSX("ensureGenerator");
        } else {
            console.warn("RUNNING IN DEV-MODE - NOT AUTO-STARTING GENERATOR.  CLEAR localStorage prefs to reset.");
        }
    },
    // TODO: Remove code related to comps, since we don't use it.
    handleCompUpdate: function (cmp, docGS) {
        var generateItems,
            comp = this.compCollection.findBy("compId", cmp.id);
        if (comp) {
            if (cmp.removed) {
                comp.destroy();
                this.compCollection.remove(comp);
            } else if (cmp.name && cmp.name !== comp.get("name")) {
                ActionRollup.lobotomizeComp(comp.get("compIndex"));
                comp.set("name", cmp.name);
                generateItems = this.getCompSettings(cmp, docGS);
                comp.resetSettings(generateItems || []);
                comp.get("layerSettings").trigger("refresh");
            }
        } else {
            docGS = this.get("docGeneratorSettings") || {};
            // this.populateCompCollection([cmp], docGS);
        }
    },
    handleAssetUpdate: function (timestamp, documentId, layerId, compId, componentId, assetId, fileSize, scale, errors, invisible, hasZeroBounds, outsideDocumentBounds) {
        var asset,
            errorMessage = '',
            sep = '',
            layer,
            comp,
            documentLayer,
            server,
            sourceId = layerId || compId || DocumentLayer.GetDefaultLayerID(),
            sourceObject,
        
            _isLayerClipped = function (layer, otherBounds) {
                var layerBounds = layer.get("bounds");
                return (layerBounds.top < otherBounds.top ||
                        layerBounds.left < otherBounds.left ||
                        layerBounds.bottom > otherBounds.bottom ||
                        layerBounds.right > otherBounds.right);
            };
        
        if (documentId !== this.get("docId")) {
            return Q.reject(new Error("mismatched doc ids"));
        }
        
        if (layerId) {
            asset = this.findAssetIdInLayer(layerId, assetId);
        } else if (compId) {
            // TODO: Remove code related to comps, since we don't use any of it.
            // asset = this.findAssetPathInComp(compId, assetPath);
        } else {
            // The full document asset is represented by a layer in the layer collection.
            asset = this.findAssetIdInLayer(DocumentLayer.GetDefaultLayerID(), assetId);
        }
        
        if (!asset || asset.length === 0) {
            return Q.reject(new Error("no asset found"));
        }
        
        if (errors && errors.length) {
            errors.forEach(function (err) {
                var posTimestampEnd = err.indexOf("] ");
                if (posTimestampEnd > 0) {
                    err = err.substr(posTimestampEnd + 2);
                }
                errorMessage = errorMessage + sep + err;
                sep = ', ';
            });
        }

        server = ServerInterface.SERVER_HOST + ':' + ServerInterface.getCachedCremaPort();
        if (layerId) {
            layer = this.layerCollection.findBy("layerId", layerId);
            layer.set("outsideDocumentBounds", outsideDocumentBounds);
            layer.set("clippedByArtboardBounds", false);

            if (invisible) {
                if (layer.get("isArtboard")) {
                    layer.set("artboardEmpty", true);
                    Headlights.logEvent(Headlights.CREMA_ACTION, Headlights.ARTBOARD_EMPTY_PREV);
                } else {
                    layer.set("groupEmpty", true);
                    Headlights.logEvent(Headlights.CREMA_ACTION, Headlights.GROUP_EMPTY_PREV);
                }
            } else if (hasZeroBounds) {
                layer.set("layerEmpty", true);
                Headlights.logEvent(Headlights.CREMA_ACTION, Headlights.LAYER_EMPTY_PREV);
            }

            if (outsideDocumentBounds) {
                layer.set("clippedByDocumentBounds", false);
                Headlights.logEvent(Headlights.CREMA_ACTION, Headlights.LAYER_OUTSIDE_DOC_PREV);
            } else {
                // compare layer bounds to doc bounds
                var docBounds = {top: 0,
                                 right: this.get("docWidth"),
                                 bottom: this.get("docHeight"),
                                 left: 0};
                
                if (_isLayerClipped(layer, docBounds)) {
                    layer.set("clippedByDocumentBounds", true);
                    Headlights.logEvent(Headlights.CREMA_ACTION, Headlights.LAYER_CLIPPEDBY_DOC_PREV);
                } else {
                    var parentArtboardBounds = layer.get("parentArtboardBounds");
                    layer.set("clippedByDocumentBounds", false);
                    // if layer is in an artboard, compare layer bounds to artboard bounds
                    if (parentArtboardBounds && _isLayerClipped(layer, parentArtboardBounds)) {
                        layer.set("clippedByArtboardBounds", true);
                        Headlights.logEvent(Headlights.CREMA_ACTION, Headlights.LAYER_CLIPPEDBY_ARTBOARD_PREV);
                    }
                }
            }
            
        } else if (compId) {
            comp = this.compCollection.findBy("compId", compId);
        } else {
            // The full document asset is represented by a layer in the layer collection.
            documentLayer = this.layerCollection.findBy("layerId", DocumentLayer.GetDefaultLayerID());
        }

        _.invoke(asset, "set", {invisible: invisible,
                                hasZeroBounds: hasZeroBounds,
                                outsideDocumentBounds: outsideDocumentBounds,
                                fileSize: fileSize,
                                docId: documentId});

        return Q.allSettled(_.map(asset, function (currentAsset) {
            var previewErrorMessage = this.getPreviewErrorMessage(currentAsset, layer), // Errors that kept us from generating a preview.
                previewErrorOccurred = !!previewErrorMessage,
                assetErrorMessage = previewErrorMessage || errorMessage; // May include warnings from SVG like that text rendering won't be exact.

            currentAsset.set("errorMessage", assetErrorMessage);
            currentAsset.updatePreviewUrl(timestamp, server, documentId, sourceId, componentId, previewErrorOccurred);
        }, this)).then(function() {
            sourceObject = layer || comp || documentLayer;
            if (sourceObject) {
                sourceObject.set("imageURL", asset[0].get("imageURL"));
            }
        });
    },
    
    // TODO: We're not using this right now because we're just rendering previews of cropped images, which have accurate
    // file sizes. However, if in the future we need to support resize handles, we can send "getComponentFileSize"
    // commands to the server and keep the original (uncropped) preview around instead of rendering and receiving
    // previews of cropped images.
    _updateAssetsFileSizes: function (asset, documentId, layerId, previewFileSize) {
        if (asset.hasClippedDimensions()) {
            var component = asset.createExportComponent(documentId, layerId);
            return ServerInterface.sendCommand("getComponentFileSize", {
                component: component
            }).then(function(actualFileSize) {
                asset.set("fileSize", actualFileSize);
            });
        } else {
            asset.set("fileSize", previewFileSize);
            return Q.resolve();
        }
    },

    getPreviewErrorMessage: function (srcModel, layerModel) {
        if (this && this.get("generatorClosed")) {
            return Strings.PREVIEW_UNKNOWN;
        }

        if (srcModel && srcModel.get("invisible")) {
            if (layerModel.get("isArtboard")) {
                return Strings.PREVIEW_EMPTY_ARTBOARD;
            } else {
                return Strings.PREVIEW_EMPTY_GROUP;
            }
                
        }
        if (srcModel && srcModel.get("hasZeroBounds")) {
            return Strings.PREVIEW_EMPTY_IMG;
        }
        if (srcModel && srcModel.get("outsideDocumentBounds")) {
            return Strings.PREVIEW_DOC_CLIPPED;
        }

        if (layerModel && /\/ /.test(layerModel.get("name")) && layerModel.layerNameParseError) {
            return Strings.PREVIEW_CONFLICT;
        }
        if (layerModel && layerModel.get("layerType") === "adjustmentLayer") {
            return Strings.PREVIEW_EMPTY_IMG;
        }

        return "";
    },

    _assetPathMatches: function (assetPath, layerSetting) {
        var relPath = layerSetting.getRelativeFilePath();
        relPath = relPath.replace(/png-\d+$/, "png");
        return Path.normalize(relPath) === Path.normalize(assetPath);
    },

    _assetPathMatchesDerived: function (element, assetPath, layerSetting) {
        //TBD: a better check would be to visit each derived setting, calculate the derived file path and match
        //console.log("looking for " + assetPath + " to include " + comp.calcBaseName(layerSetting.getRelativeFilePath()));
        var normalizedAssetPath = Path.normalize(assetPath),
            normalizedBasePath = Path.normalize(element.calcBaseName(layerSetting.getRelativeFilePath()));

        return (normalizedAssetPath.indexOf(normalizedBasePath) >= 0);
    },
    
    // TODO: Remove when possible.
    findAssetPathInComp: function (compId, assetPath) {
        var comp = this.compCollection.findBy("compId", compId);
        if (!comp) {
            console.log("Looking for a compId that doesn't exist; compId: " + compId + " for assetPath:" + assetPath);
            return;
        }
        
        var layerSettings = comp.get("layerSettings"),
            assetsRet = layerSettings.filter(_.partial(this._assetPathMatches, assetPath));
        
        if (assetsRet.length === 0) {
            assetsRet = layerSettings.filter(_.partial(this._assetPathMatchesDerived, comp, assetPath));
        }

        return assetsRet;
    },
    
    // TODO: Remove when possible.
    findAssetPathInLayer: function (layerId, assetPath) {
        var layer = this.layerCollection.findBy("layerId", layerId);
        if (!layer) {
            console.log("Look for a layerId that doesn't exist; layerId: " + layerId);
            return;
        }
        
        var layerSettings = layer.get("layerSettings"),
            assetsRet = layerSettings.filter(_.partial(this._assetPathMatches, assetPath));
        
        if (assetsRet.length === 0) {
            assetsRet = layerSettings.filter(_.partial(this._assetPathMatchesDerived, layer, assetPath));
        }

        return assetsRet;
    },

    findAssetIdInLayer: function (layerId, assetId) {
        var layer = this.layerCollection.findBy("layerId", layerId);
        if (!layer) {
            console.log("Look for a layerId that doesn't exist; layerId: " + layerId);
            return;
        }

        var layerSettings = layer.get("layerSettings"),
            asset = layerSettings.findWhere({ assetId: assetId });

        return asset ? [asset] : [];
    },

    getMetaDataSettingsForLayer: function (layer, docGeneratorSettings) {
        var generatorSettings;
        if (layer.generatorSettings && layer.generatorSettings[ServerInterface.escapePluginId(GENERATOR_PLUGIN_ID)]) {
            generatorSettings = layer.generatorSettings[ServerInterface.escapePluginId(GENERATOR_PLUGIN_ID)];
        } else if (docGeneratorSettings.layers) {
            generatorSettings = docGeneratorSettings.layers[String(layer.id)];
        }

        if (!generatorSettings) {
            generatorSettings = {
                generateItems: []
            };
        }
        return generatorSettings;
    },
    
    getCompSettings: function (comp) {
        var parsedExportItems;
        try {
            if (comp.name) {
                parsedExportItems = LayerNameParser.parse(comp.name);
            }
        } catch (exLN) {
            console.log("Failed to parse comp " + comp.name + " with " + exLN);
        }

        //parsed export items win over our settings, so overlay them
        if (parsedExportItems && parsedExportItems.length > 0) {
            if (!GenSettingsModel.prototype.extensionSupported(parsedExportItems[0].extension)) {
                parsedExportItems = []; //toss em..
            }
        }
        return parsedExportItems;
    },
    checkForDocumentArtboards: function (layers) {
        if (this.get("isPerformingDocumentExport")) {
            var hasArtboards = layers.some(function (layer) {
                return layer.artboard !== undefined;
            });
            if (hasArtboards) {
                this.unset("isPerformingDocumentExport");
                this.set("isPerformingDocumentArtboardExport", true);
            }
        }
    },
    populateCompCollection: function (comps, docGeneratorSettings) {
        var comp,
            compModel,
            i,
            parsedExportItems = [];
        
        if (!comps || comps.length === 0) {
            return;
        }
        
        for (i = 0; i < comps.length; i++) {
            comp = comps[i];
            
            parsedExportItems = this.getCompSettings(comp, docGeneratorSettings);
            
            compModel = new CompModel({
                compId: comp.id,
                compIndex: i,
                bounds: { left: 0, top: 0, right: parseInt(this.get("docWidth"), 10), bottom: parseInt(this.get("docHeight"), 10) },
                name: comp.name,
                origName: comp.name
            }, { generatorSettings: { generateItems: parsedExportItems}});
            this._initGenableSettingsBaseNames(compModel, parsedExportItems);
            
            this.compCollection.add(compModel);
        }
    },
    _initGenableSettingsBaseNames: function(genableModel, generatorNameSettings) {
        var prevName;
        if (generatorNameSettings && generatorNameSettings.length !== 0) {
            //if the layer has generator style settings then the actual layer name is something like
            //"foobar.png, 200% foobar@2x.png" and we'd rather not use that whole string as the basename.
            //Instead pluck the first parsed setting and that files basename
            var setting = generatorNameSettings[0];
            if (setting.file) {
                prevName = Path.basename(setting.file, Path.extname(setting.file));
            } else if (setting.name) {
                prevName = setting.name;
            }
        }
        genableModel.initLayerSettingsBaseNames(prevName);
    },
    
    _isDefaultSettingsFromLayerName: function(parsedLayerNameSettings) {
        return (parsedLayerNameSettings &&
                parsedLayerNameSettings.length > 0 &&
                parsedLayerNameSettings[0]["default"]) ? true : false;
    },
    _getSettingsFromLayerName: function (layer) {
        var settings =  [];
        
        if (layer && layer.name) {
            try {
                settings = LayerNameParser.parse(layer.name);
            } catch (ex) {
                console.warn("Failed to parse " + JSON.stringify(layer.name) + " with " + ex);
            }
        }
        return settings;
    },
    _getSettingsFromLayerMetaData: function (layerMetaData) {
        var settings = [];
        if (layerMetaData && layerMetaData.json) {
            try {
                settings = JSON.parse(layerMetaData.json).assetSettings;
            } catch (ex) {
                console.warn("Failed to parse layerMetaData.json" +  layerMetaData.json + " with " + ex);
            }
        }
        return settings || [];
    },
    
    getLayerSettings: function (layer, generatorSettings) {
        var nameSettings = this._getSettingsFromLayerName(layer),
            metaDataSettings = this._getSettingsFromLayerMetaData(generatorSettings);
                
        if (metaDataSettings && metaDataSettings.length > 0 &&
            GenSettingsModel.prototype.extensionSupported(metaDataSettings[0].extension)) {
            
            generatorSettings.generateItems = metaDataSettings;
        }
        generatorSettings.layerNameSettings = nameSettings;
    },

    populateLayerCollection: function (layers, docGeneratorSettings, options) {
        if(!this.addDocumentLayerIfNeeded(docGeneratorSettings)) {
            this._populateLayerCollectionRecursively(layers, docGeneratorSettings, undefined, options);
        }
    },
    addDocumentLayerIfNeeded: function (docGeneratorSettings) {
        if (!this.get("isPerformingDocumentExport"))
            return false;

        // Add a "layer" for the whole document, represented by a special layer id.
        var documentLayer = new DocumentLayer({
            docId: this.get("docId"),
            name: this.get("docFileBaseName"),
            bounds: {
                top: 0,
                left: 0,
                bottom: this.get("docHeight"),
                right: this.get("docWidth")
            },
            boundsAreAccurate: true,
            dpi: this.get("dpi")
        }, {
            generatorSettings: {
                generateItems: docGeneratorSettings && docGeneratorSettings.assetSettings
            }
        });
        this.layerCollection.add(documentLayer);
        return true;
    },
   _isGroupInvisible: function (layer) {
        var _isVisible = function (child) {
            if (!child.visible || !child.layers || !child.layers.length) {
                return !child.visible;
            } else {
                return child.layers.every(function (subchild) {
                    return _isVisible(subchild);
                });
            }
        };
        return layer.layers && layer.layers.every(function (child) {
            //only look at children?
            return _isVisible(child);
        });
    },
    // TODO: If we compute parentArtboard(s) in DocInfoUtils.getSelectedLayersForExport, this function doesn't need to
    // be recursive anymore. It can be a flat iteration over layers.
    _populateLayerCollectionRecursively: function (layers, docGeneratorSettings, parentArtboard, options) {
        var layer,
            layerModel,
            generatorSettings,
            i,
            isDefaultLayerNameSettings,
            layerSelected,
            isArtboard,
            totalMaskBounds;

        options = options || {};
        for (i = 0; i < layers.length; i++) {
            generatorSettings = {};
            isDefaultLayerNameSettings = false;
            layer = layers[i];
            isArtboard = !!layer.artboard;
            if (this._canExportLayer(layer, options.exportableLayerIds)) {
                try {
                    generatorSettings = this.getMetaDataSettingsForLayer(layer, docGeneratorSettings);
                } catch (exGS) {
                    console.warn("Exception getting generatorSettings " + exGS);
                }

                layer.name = layer.name || "";
                this.getLayerSettings(layer, generatorSettings);
                isDefaultLayerNameSettings = this._isDefaultSettingsFromLayerName(generatorSettings.layerNameSettings);

                // TODO: Move this logic that selects the first layer into the collection.
                layerSelected = !options.layerSelected;
                options.layerSelected |= layerSelected;
                totalMaskBounds = BoundsUtils.getTotalMaskBounds(layer);

                layerModel = new LayerModel({
                    docId: this.get("docId"),
                    docWidth: this.get("docWidth"),
                    docHeight: this.get("docHeight"),
                    layerId: layer.id,
                    layerIndex: layer.index,
                    layerType: layer.type,
                    name: layer.name,
                    origName: layer.name,
                    invisible: this._isGroupInvisible(layer),
                    selected: layerSelected,
                    isActive: layerSelected,
                    isArtboard: isArtboard,
                    parentArtboardBounds: parentArtboard && parentArtboard.bounds,
                    bounds: layer.bounds,
                    totalMaskBounds: totalMaskBounds,
                    boundsAreAccurate: totalMaskBounds ? true : LayerModel.prototype.isLayerDataBoundsAccurate(layer),
                    dpi: this.get("dpi")
                }, {
                    generatorSettings: generatorSettings,
                    docSettings: docGeneratorSettings.docSettings
                });

                if (isDefaultLayerNameSettings) {
                    //new UI doesn't support default settings so don't initialize or migrate yet
                    //this.defaultSettingsModel.initializeWithLayer(layerModel);
                } else {
                    this._initGenableSettingsBaseNames(layerModel, generatorSettings.layerNameSettings);
                    this.layerCollection.add(layerModel);
                }
            }
            if (layer.layers) {
                var newParentArtboard = isArtboard ? layer : parentArtboard;
                this._populateLayerCollectionRecursively(layer.layers, docGeneratorSettings, newParentArtboard, options);
            }
        }
    },
    
    loadGenerator: function () {
        ActionRollup.reset();
        this.set("layersLoading", true);
        
        return ServerInterface.sendCommand("docinfo").then(function (docinfo) {
            if(!docinfo) {
                console.log("No docinfo provided from server");
                return;
            }
            console.log("docinfo");
            console.log(docinfo);

            var docSettings = {},
                generatorSettings = {
                    docSettings: docSettings
                },
                settings;
            if (docinfo.bounds) {
                this.set("docWidth", docinfo.bounds.right - docinfo.bounds.left);
                this.set("docHeight", docinfo.bounds.bottom - docinfo.bounds.top);
            } else {
                console.warn("Missing: docinfo.bounds");
            }
            this.set("docId", docinfo.id);
            this.set("docFilepath", docinfo.file);
            this.set("docFileBaseName", docinfo._fileBaseName);
            this.set("docFileExtension", docinfo._fileExtension);
            this.set("docFileDirectory", docinfo._fileDirectory);
            this.set("dpi", docinfo.resolution);
            
            this.layerCollection.stopListeningForFileConflicts();

            if (docinfo.generatorSettings) {
                settings = docinfo.generatorSettings[ServerInterface.escapePluginId(GENERATOR_PLUGIN_ID)];
                if (settings && settings.json) {
                    try {
                        generatorSettings = _.extend(generatorSettings, JSON.parse(settings.json));
                        generatorSettings.docSettings = _.defaults(generatorSettings.docSettings, docSettings);

                        // When we overwite the docSettings for the default export settings, make sure we don't lose the
                        // assetSettings for document export.
                        DocSettings._originalGeneratorSettings = {
                            docSettings: generatorSettings.generatorSettings,
                            assetSettings: generatorSettings.assetSettings
                        };
                    } catch (e) {
                        console.log("Error loading settings: " + e.message + "\n\tsettings: " + settings.json);
                    }
                }
            }
            this.set("docGeneratorSettings", generatorSettings);
            
            //applyDocSetting does not trigger model changes since it is only called during initialization 
            this.defaultSettingsModel.applyDocSettings(generatorSettings.docSettings);
    
            this.checkForDocumentArtboards(docinfo.layers);
        
            var exportableLayerIds = this._getExportableLayerIds(docinfo),
                selectionLen = docinfo._selectionById ? docinfo._selectionById.length : 0;
            this.populateLayerCollection(docinfo.layers, generatorSettings, {layerSelected: false,
                                                                             shouldImport: true,
                                                                             exportableLayerIds: exportableLayerIds});

            this.updateLayersLoading();
            //this.populateCompCollection(docinfo.comps, generatorSettings);
            this.layerCollection.renameFileConflicts();
            this.layerCollection.listenForFileConflicts();    

            // TODO: Would be nicer to put this call inside genSettingsModel if we can get it right.
            var assets = this.layerCollection.map(function (x) { return x.layerSettingsCollection.first(); });
            assets.forEach(function (asset) {
                asset.enforceMaxDimensions().then(asset.generatePreview.bind(asset));
            });

            this.trigger("docinfo-loaded", docinfo);
            this.layerCollection.trigger("docinfo-loaded", docinfo);

            if (exportableLayerIds.length < selectionLen &&
                !this.get("isPerformingDocumentExport") &&
                !this.get("isPerformingDocumentArtboardExport")) {

                Headlights.logEvent(Headlights.CREMA_ACTION, Headlights.EXPORTING_FEWER_THAN_SELECTED);
            }
        }.bind(this)).catch(function (error) {
            console.warn(error);
            console.log("stack -> " + error.stack);
        });
    },
    
    _canExportLayer: function(rawLayer, exportableIds) {
        var id = rawLayer && rawLayer.id;
        if (!_.isFinite(id)) {
            return false;
        }
        if (!exportableIds) {
            return true; //if there is no filter, then default to allow
        }
        return _.indexOf(exportableIds, id) !== -1;
    },
    
    _getExportableLayerIds: function (docinfo) {
        if (!docinfo) {
            return [];
        }

        if (this.get("isPerformingDocumentExport")) {
            return [DocumentLayer.GetDefaultLayerID()];
        } else if (this.get("isPerformingDocumentArtboardExport")) {
            // If the doc has artboards, export them; else export the document.
            return _.pluck(DocinfoUtils.getArtboards(docinfo), "id");
        } else {
            return _(DocinfoUtils.getSelectedLayersForExport(docinfo)).pluck("id");
        }
    },
    isExportDisabled: function () {
        // The exportable items are available after the docinfo comes in. Before it comes in, we still want users to be
        // able to hit the Export button if they don't want to wait for docinfo to load. Similarly for previews, we
        // let users export while previews are in flight. Yes, they may all come back as errors, and the export
        // shouldn't have been allowed, but that's the tradeoff we make to be able to export before previews come in.
        return this.layerCollection.hasDisplayableItems() && this.layerCollection.every(function (genableModel) {
            return genableModel.allLayerSettingsDisabled();
        });
    },
    reset: function () {
        var docSettings = {},
            generatorSettings = {
                docSettings: docSettings
            };
        
        this.layerCollection.reset();
        this.compCollection.reset();
        this.defaultSettingsModel.reset();
        DocSettings.reset();
        this.set("docWidth", 0);
        this.set("docHeight", 0);
        this.set("dpi", 72);
        this.set("docFilepath", "");
        this.set("docGeneratorSettings", generatorSettings);
    },
    reloadGenerator: function () {
        this.reset();
        return this.loadGenerator(true);
    },
    updateLayersLoading: function () {
        var loading = !!this.layerCollection.findWhere({settingsLoading: true});
        this.set("layersLoading", loading);
    }
    
    
});

module.exports = GeneratorModel;
