/*
 * 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 */
/*global require:true, Image: true, _:true, module: true */

"use strict";

var Backbone = require("backbone"),
    BoundsUtils = require("shared/BoundsUtils"),
    ServerInterface = require("./serverInterface.js"),
    LayerNameParser = require("./utils/LayerNameParser.js"),
    DocSettings = require("./docSettings.js"),
    ActionRollup = require("./actionRollup.js"),
    GenSettingsCollection = require("./genSettingsCollection.js"),
    GenSettingsModel = require("./genSettingsModel.js");

/*
 * Base class for layerModel, compModel and any other exportable
*/

var GenableModel = Backbone.Model.extend({
    
    defaults: function () {
        return {
            guid: "",
            compId: "",
            compIndex: "",
            layerId: "",
            name: "",
            //this is used for views
            selected: false,
            //isActive is the model communication
            isActive: false,
            isDefaultSettingsLayer: false,
            isDocument: false,
            layerSettings: new GenSettingsCollection(),
            dimensions: {width: -1, height: -1},
            boundsAreAccurate: false,
            settingsLoading: false,
            dpi: 72
        };
    },
    thumbnailRoute: "layerthumb/",
    initialize: function (attributes, options) {
        options = options || {};
        attributes = attributes || {};
        this.layerSettingsCollection = this.get("layerSettings");
        if (attributes.totalMaskBounds) {
            this.set("dimensions", this._getDimensions(attributes.totalMaskBounds));
        } else if (attributes.bounds) {
            this.set("dimensions", this._getDimensions(attributes.bounds));
        }
        var settings = options.generatorSettings || {},
            assetSettings = settings.generateItems && settings.generateItems.length ?  settings.generateItems : options.docSettings;

        this.resetSettings(assetSettings);
        
        this.listenTo(this, "destroy", this.removeAllSettings);

        this.listenToOnce(this.layerSettingsCollection, "change:settingsVisible change:loading", this.updateToAccurateBounds);
        this.listenTo(this.layerSettingsCollection, "add", this.forceRender);
        this.listenTo(this.layerSettingsCollection, "change", this.persistModelSettings);
        this.listenTo(this.layerSettingsCollection, "add remove reset change:file", this.relayFileChange);
        this.listenTo(this.layerSettingsCollection, "change:disabled", this.trigger.bind(this, "change:layerSettingsDisabled"));
        this.listenTo(this.layerSettingsCollection, "change:loading add remove", this.updateSettingsLoading);

        this.listenTo(this, "change:selected", this.updateSelectedState);
        this.listenTo(this, "change:dimensions", this.updateLayersSettingsOriginalDimensions);
        this.listenTo(this.layerSettingsCollection, "change:selected", this.updateSettingSelectedState);
        
        this.listenTo(this.layerSettingsCollection, "remove", this.removeLayerIfSettingsEmpty);
        
        this.updateSelectedState();
        this.updateSettingsLoading();
    },
    destroy: function () {
        //we don't want to actually delete, just pretend
        //we need to keep it in the collection in case they change their mind
        this.removeAllSettings();
        this.set({
            isActive: false,
            selected: false
        });
        //TBD: drive this on being the default layer, not on collection
        if (this.collection) {
            this.collection.trigger("remove", this, this.collection, {
                index: this.collection.indexOf(this)
            });
        }
    },
    initLayerSettingsBaseNames: function (name) {
        var settings = this.get("layerSettings");
        if (settings) {
            settings.invoke("setBaseName", name || this.get("name"));
        }
        this.persistModelSettings();
    },
    _getDimensions: function (bounds) {
        var clippedBounds = {
                top: Math.max(0, bounds.top),
                left: Math.max(0, bounds.left),
                right: Math.max(0, bounds.right),
                bottom: Math.max(0, bounds.bottom)
            },
            clipWidth = this.get("docWidth"),
            clipHeight = this.get("docHeight"),
            parentArtboardBounds = this.get("parentArtboardBounds");
        
        if(parentArtboardBounds) {
            clippedBounds.left = Math.max(parentArtboardBounds.left, clippedBounds.left);
            clippedBounds.top = Math.max(parentArtboardBounds.top, clippedBounds.top);
            clippedBounds.right = Math.min(parentArtboardBounds.right, clippedBounds.right);
            clippedBounds.bottom = Math.min(parentArtboardBounds.bottom, clippedBounds.bottom);
        }
        
        if (clipWidth) {
            clippedBounds.left = Math.max(0, clippedBounds.left);
            clippedBounds.right = Math.min(clipWidth, clippedBounds.right);
        }
        if (clipHeight) {
            clippedBounds.top = Math.max(0, clippedBounds.top);
            clippedBounds.bottom = Math.min(clipHeight, clippedBounds.bottom);
        }

        var clippedHeight = clippedBounds.bottom - clippedBounds.top,
            clippedWidth = clippedBounds.right - clippedBounds.left;

        if (clippedHeight <= 0 || clippedWidth <= 0) {
            clippedHeight = 0;
            clippedWidth = 0;
        }

        return {
            height: clippedHeight,
            width: clippedWidth
        };
    },
    isLayerDataBoundsAccurate: function (rawLayerData) {
        return BoundsUtils.isLayerDataBoundsAccurate(rawLayerData);
    },
    updateToAccurateBounds: function () {
        if (this.get("boundsAreAccurate")) {
            return;
        }
        this._requestAccurateBounds(this.get("docId"), this.get("layerId"))
            .then(function (exactbounds) {
                this.set({bounds: exactbounds,
                          boundsAreAccurate: true,
                          dimensions: this._getDimensions(exactbounds)});
            }.bind(this));
    },
    _requestAccurateBounds: function (documentId, layerId) {
        return ServerInterface.sendCommand("getExactLayerBounds", {
            docId: documentId,
            layerId: layerId
        })
        .catch(function (e) {
            console.error("Error in ServerInterface.sendCommand(\"getExactLayerBounds\"):", e);
        });
    },
    getBaseName: function () {
        return this.calcBaseName(this.get("name"));
    },
    
    cleanBaseName: function (dirtyName) {
        
        var name = dirtyName.trim(),
            nameRefactor,
            extraTextPos,
            extPos,
            extVal;
        
        //change layer.png XXX -> layer XXX.png
        //for example: layer.png copy -> layer copy.png
        //look for a word and optionally digits following a space and at the end of the name
        //for i18n just assume anything over normal ascii is a word for file name purposes
        extraTextPos = name.search(/\s+([A-Za-z\u00A0-\uFFFF]+)\d*$/);
        if (extraTextPos > 0) {
            nameRefactor = name.substring(0, extraTextPos);
            extPos = nameRefactor.search(/\.\D{3}$/);
            if (extPos > 0) {
                //see if it is a valid extension, if not just eat the space b4 the copy
                extVal = nameRefactor.substring(extPos + 1);
                if (extVal && GenSettingsModel.prototype.extensionSupported(extVal.toLowerCase())) {
                    name = nameRefactor.substring(0, extPos) + name.substring(extraTextPos) + nameRefactor.substring(extPos);
                } else {
                    
                    name = nameRefactor.substring(0, extPos) + "_" + extVal + "_" + name.substring(extraTextPos + 1);
                }
            }
        }
        
        //remove any combination of . that doesn't leave a pattern of .{word} left
        name = name.replace(/(\.)+(?!\w)/g, "_");
        
        //these things can confuse layer name parser because they are delimters
        name = name.replace(/[+,*\\>?!:|<]/g, "_");
        
        //totally ditch quotes...
        name = name.replace(/['"]/g, "");
        
        //ditch any space after a path delimeter
        name = name.replace(/\/\s+/g, "/");
        
        //we can't abide layer names that === well known extensions
        if (GenSettingsModel.prototype.extensionSupported(name.toLowerCase())) {
            name = "_" + name;
        }
        
        //leaving '/' in intentionally since its valid to have folders in the list
        
        return this.calcBaseName(name, true);
    },
    
    calcBaseName: function (name, enoughCleaning) {
        var parsed,
            baseName,
            parseFailed,
            lastParsedItem;
        
        if (name) {
            name = name.trim();
            
            //the goal is to get a baseName that will produce good layer syntax

            try {
                parsed = LayerNameParser.parse(name);
            } catch (err) {
                this.layerNameParseError = true;
                console.log("error parsing layer name", name, err);
            }

            //we really need to check the whole list to see if it got a sane set parsed...
            
            if (parsed) {
                _.any(parsed, function (parsedItem) {
                    lastParsedItem = parsedItem;
                    if (!parsedItem.extension || !parsedItem.file ||
                            parsedItem.file === parsedItem.extension ||
                            !GenSettingsModel.prototype.extensionSupported(parsedItem.extension)) {
                        parseFailed = true;
                        return true;
                    } else if (!baseName) {
                        baseName = parsedItem.file.slice(0, -1 * (parsedItem.extension.length + 1));
                    }
                });
                
                if (!parseFailed && baseName) {
                    return baseName;
                }
            }
            if (!enoughCleaning) {
                name = this.cleanBaseName(name, false);
            }
            return name;
        }
        return "";
    },    
    getLayerNameSerializedSettings: function () {
        var genSettings = this.get("layerSettings"),
            layerName = "";
        if (genSettings.size() > 0) {
            layerName = genSettings.serializeToLayerName();
        } else {
            //in theory, we can only reach this point if we have 0 settings but the psd has a layername with extension
            layerName = this.getBaseName();
        }
        return layerName;
    },
    toJSON: function (options) {
        if (options && options.generatorSettings) {
            var genSettings = this.get("layerSettings");
            return genSettings && genSettings.toJSON(options);
        }
        return Backbone.Model.prototype.toJSON.call(this, options);
    },
    getActiveSelection: function () {
        if (this.get("selected")) {
            return this;
        }
        return this.layerSettingsCollection.findWhere({selected: true});
    },
    getActivePreview: function () {
        var preview = this.layerSettingsCollection.findWhere({selected: true}),
            fakeSetting,
            imgURL;
        if (!preview) {
            preview = this.layerSettingsCollection.at(0);
        }
        if (!preview) {
            //lets make a fake one instead of trying to masquarade...
            if (!this._fakieSetting) {
                this._fakieSetting = new GenSettingsModel();
                fakeSetting = this._fakieSetting;
                imgURL = this.get("imageURL");
                fakeSetting.set("imageURL", imgURL);
                if (!imgURL) {
                    fakeSetting.set("loading", false);
                } else {
                    var img = new Image();
                    img.onload = function () {
                        fakeSetting.set("loading", false);
                    };
                    img.onerror = function () {
                        fakeSetting.set("loading", false);
                    };
                    img.src = this.get("imageURL");
                }
            }
            return this._fakieSetting;
        }
        return preview;
    },
    persistModelSettings: function () {
        this._persistModelSettingsAsMetaData();
    },
    _updateModelMetaData: function(json) {
        ActionRollup.updateLayerMetaData(this.get("layerId"), {assetSettings: json});
    },
    _persistModelSettingsAsMetaData: function () {
        this._updateModelMetaData(this.toJSON({generatorSettings:true}));
    },
    forceRender: function (collection, model, options) {
        this.trigger("change:rendableState", this);
        if (!options || !options.importingGeneratorSettings) {
            this.persistModelSettings();
        }
    },
    isActive: function () {
        return this.get("selected") || this.layerSettingsCollection.some(function (setting) {
            return setting.get("selected");
        });
    },
    removeAllSettings: function () {
        if (this.layerSettingsCollection.size() !== 0) {
            this.layerSettingsCollection.reset();
        }
        this.persistModelSettings();
    },
    updateSelectedState: function () {
        if (this.get("selected")) {
            
            this.deselectAllLayers();
        }
        //we want to always trigger this event
        this.set("isActive", this.isActive(), {silent: true});
        this.trigger("change:isActive", this);
    },
    deselectAll: function () {
        this.set("selected", false);
        this.set("isActive", false);
        this.deselectAllLayers();
    },
    deselectAllLayers: function () {
        this._fakieSetting = undefined;
        this.layerSettingsCollection.invoke("set", { selected: false });
    },
    emptyState: function () {
    },
    removeLayerIfSettingsEmpty: function () {
        this.persistModelSettings();
        if (this.layerSettingsCollection.size() === 0) {
            if (this.canBeEmpty) {
                this.emptyState();
            } else {
                this.destroy();
            }
        }
    },
    resetSettings: function (settings) {
        this.layerSettingsCollection.reset();
        if (settings && settings.length) {
            settings.forEach(_.partial(this.addLayerSettings, _, true), this);
        } else {
            var defaultSettings = _.extend(settings || this.get("layerSettings").model.prototype.defaults, {
                baseName: this.get("name"),
                desiredBaseName: this.get("name")
            });
            this.addLayerSettings(defaultSettings,  {importingGeneratorSettings:true});
        }
    },
    addLayerSettings: function (settings, importingGeneratorSettings) {
        var genKeys = [];
        if(importingGeneratorSettings && settings) {
            genKeys = _.keys(settings);
        }
        settings = _.extend(settings || {}, {
            documentId: this.get("docId"),
            sourceObjectId: this.get("layerId"),
            originalDimensions: _.extend(this.get("dimensions"), {dpi: this.get("dpi")}),
            importedGeneratorKeys: genKeys
        });
        return this.layerSettingsCollection.add(settings, {importingGeneratorSettings:importingGeneratorSettings});
    },
    updateLayersSettingsOriginalDimensions: function () {
        var newDim = _.extend(this.get("dimensions"), {dpi: this.get("dpi")});
        this.layerSettingsCollection.invoke("set", "originalDimensions", newDim);
    },
    updateSettingSelectedState: function (layerSetting) {
        var modelSelected = layerSetting.get("selected"),
            previousSelected,
            selected = this.get("selected");
        
        if (modelSelected) {
            previousSelected = this.layerSettingsCollection.find(function (model) {
                return model.get("selected") && model.cid !== layerSetting.cid;
            });

            if (previousSelected) {
                previousSelected.set("selected", false);
            }
            this.layerSettingsCollection.subSelect(true);
            layerSetting.set("subselected", false);
            selected = false;
        }
        this.set({selected: selected});
        this.set({isActive: this.isActive()}, {silent: true});
        this.trigger("change:isActive", this);
    },
    allLayerSettingsDisabled: function () {
        return this.layerSettingsCollection.allDisabled();
    },
    getGenableId: function () {
        return this.get("layerId");
    },
    
    relayFileChange: function (layerSetting) {
        this.trigger("change:file", layerSetting);
    },
    
    updateModelBacking: function () {
        console.warn("updatemodel backing called, track it down");
        //this.set("imageURL", ServerInterface.SERVER_HOST + ':' + ServerInterface.getCachedCremaPort() + '/' + this.thumbnailRoute + this.getGenableId() + ".png");
    },
    updateSettingsLoading: function () {
        var loading = !!this.layerSettingsCollection.findWhere({loading: true});
        this.set("settingsLoading", loading);
    }
});

module.exports = GenableModel;
