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

"use strict";

var Backbone = require("backbone"),
    FilePathSanitizer = require("shared/FilePathSanitizer"),
    MaxPixels = require("shared/MaxPixels"),
    Path = require("path"),
    ServerInterface = require("./serverInterface");

/*
 * Settings for a single generated asset
*/

var _supportedExtensions,
    _maxPixels,
    _maxPixelsPromise,
    _currentId = 0;

var GenSettingsModel = Backbone.Model.extend({
    
    assetRoute: "extractasset",
    
    defaults: function () {
        return {
            // TODO: If it's true that layerSettingsCollections always have one asset, consider
            // removing them and assetId.
            assetId: GenSettingsModel.prototype.createNewAssetId(),
            documentId: null,
            sourceObjectId: null,
            file: "",
            name: "",
            extension: "png",
            quality: "100",
            scale: 1,
            disabled: false,
            selected: false,
            settingsVisible: false,
            interpolationType: "bicubic",
            imageURL: "",
            imageURLRevision: 0,
            fileSize: 0,
            loading: true,
            originalDimensions: {width: -1, height: -1},
            importedGeneratorKeys: [],
            lastPreviewComponent: null,
            lastReceivedPreviewTimestamp: 0
        };
    },

    createNewAssetId: function () {
        _currentId += 1;
        return _currentId;
    },
    
    generatorSettingsKeys: ["file", "name", "folders", "extension", "quality", "interpolationType",
                            "scale", "height", "heightUnit", "width", "widthUnit", "canvasWidth", "canvasHeight"],
    
    resetBaselineSupportedExtensions: function () {
        _supportedExtensions = ['png', 'png-8', 'png-24', 'png-32', 'gif', 'jpg', 'svg'];
    },
    
    isSupported: function () {
        var ext = this.get("extension");
        return this.extensionSupported(ext);
    },
    extensionSupported: function (ext) {
        return (_supportedExtensions.indexOf(ext) >= 0);
    },
    getMaxPixels: function () {
        return _maxPixels;
    },
    // For testing.
    resetMaxPixels: function () {
        _maxPixels = undefined;
        _maxPixelsPromise = undefined;
    },
    initialize: function () {
        if (this.get("baseName")) {
            this.setBaseName(this.get("baseName"));
            this.unset("baseName");
        }

        if (!_maxPixels) {
            // Set a conservative default value, then try to get actual value from generator, which can be larger.
            _maxPixels = 25 * 1000 * 1000;
            _maxPixelsPromise = ServerInterface.sendCommand("getMaxPixels")
                .then(function (genMaxPixels) {
                    _maxPixels = genMaxPixels;
                    return _maxPixels;
                }.bind(this));
        }

        var rerenderEvents = [
            "change:extension",
            "change:quality",
            "change:interpolationType",
            "change:scale",
            "change:canvasWidth",
            "change:canvasHeight"
        ];
        this.listenTo(this, rerenderEvents.join(" "), this.generatePreview);
    },
    setBaseName: function (baseName) {
        var fileName = FilePathSanitizer.sanitize(baseName),
            ext = this.get("extension");
        if (ext && fileName.indexOf("." + ext) === -1) {
            fileName = fileName + "." + ext;
        }
        this.set("file", fileName);
    },
    getBaseName: function () {
        var file = this.get("file"),
            ext = this.get("extension");
        return Path.basename(file, "." + ext);
    },
    getFileExtension: function () {
        var ext = this.get("extension");
        return ext.replace(/png-\d+$/, "png");
    },
    getFileName: function () {
        var file = this.get("file");
        return file.replace(/png-\d+$/, "png");
    },
    serializeSize: function (w, h, unitW, unitH) {
        if (!this.isSupported()) {
            return "";
        }
        var wNum = parseFloat(w),
            hNum = parseFloat(h);
        var output = "";
        if (!isNaN(wNum) || !isNaN(hNum)) {
            if (isNaN(wNum)) {
                w = "?";
            }
            if (isNaN(hNum)) {
                h = "?";
            }
            if (unitW && unitW !== "" && unitW !== "px") {
                w += unitW;
            }
            if (unitH && unitH !== "" && unitH !== "px") {
                h += unitH;
            }
            if (unitW === "%") {
                output += w + " ";
            } else {
                output += w + "x" + h + " ";
            }
        }
        return output;
    },
    
    getValidQuality: function (ext, quality) {
        var qualityInt = parseInt(quality, 10);
        if (isNaN(qualityInt)) {
            return;
        }
        if (ext === "png") {
            //valid png quality numbers are 8,24,32, all others will use default "png"
            if (qualityInt === 8 || qualityInt === 24 || qualityInt === 32) {
                return qualityInt;
            }
        } else if (ext === "png-8") {
            return 8;
        } else if (ext === "png-24") {
            return 24;
        } else if (ext === "png-32") {
            return 32;
        } else if (ext === "jpg" || ext === "webp") {
            // valid quality is a percent between 1-100 but 100 is the default so we don't serialize that
            if (qualityInt > 0 && qualityInt < 100) {
                return qualityInt;
            }
        }
        return;
    },
    
    serializeQuality: function (ext, quality) {
        var output = "",
            qualityInt = this.getValidQuality(ext, quality);
        if (isNaN(qualityInt)) {
            return output;
        }
        if (ext === "png") {
            output += "-" + qualityInt;
        } else if (ext === "jpg" || ext === "webp") {
            output += "-" + qualityInt + "%";
        }
        return output;
    },
    
    // TODO: Remove this eventually.
    getRelativeFilePath: function () {
        var folders = this.get("folder"),
            fileName = this.getFileName();
        
        if (folders) {
            return folders.concat(fileName).join("/");
        }
        
        return fileName;
    },

    // Encode characters in the given path that generator would choke on.
    // Currently, '%' is the only one; no other characters seem to be a problem.
    encode: function (path) {
        return path.replace(/\%/g, "%25");
    },
    
    getNaturalImageDimensions: function () {
        var dim = this.get("originalDimensions");
        if (!dim) {
            return;
        }
        return _.extend({}, dim, {width: Math.ceil(dim.width), height: Math.ceil(dim.height)});
    },
    
    getMaxImageDimensions: function () {
        var dim = this.get("originalDimensions");
        if (!dim) {
            return;
        }
        return _.extend({}, dim, MaxPixels.getMaxDimensions(dim, this.getMaxPixels()));
    },
    
    getScaledImageDimensions: function (percent) {
        if (!percent) {
            percent = (this.get("scale") || 1) * 100;
        }
        
        var origDim = this.getNaturalImageDimensions();
        
        return {
            height: Math.ceil(origDim.height * percent / 100),
            width: Math.ceil(origDim.width * percent / 100)
        };
    },
    
    getMaxScale: function () {
        if (!this.maxScale) {
            var dim = this.get("originalDimensions");
            if (!dim) {
                return;
            }
            this.maxScale = MaxPixels.getMaxScaleFactor(dim, this.getMaxPixels()) * 100;
        }
        return this.maxScale;
    },
    
    getCanvasDimensions: function () {
        var imgDim = this.getScaledImageDimensions(),
            w = this.get("canvasWidth"),
            h = this.get("canvasHeight");
        
        return {
            height: Math.ceil(h || imgDim.height),
            width: Math.ceil(w || imgDim.width)
        };
    },
    
    hasClippedDimensions: function () {
        var imgDim = this.getScaledImageDimensions(),
            w = this.get("canvasWidth"),
            h = this.get("canvasHeight");
        
        return (w && imgDim.width > w) ||
                (h && imgDim.height > h) ? true : false;
    },

    serializeToLayerName: function () {
        
        var output,
            w = this.get("width"),
            h = this.get("height"),
            unitW = this.get("widthUnit"),
            unitH = this.get("heightUnit"),
            quality = this.get("quality"),
            ext = this.get("extension"),
            fileName = this.get("file");
        
        output = this.serializeSize(w, h, unitW, unitH) + fileName + this.serializeQuality(ext, quality);
        
        return output;
    },

    toJSON: function (options) {
        var json = Backbone.Model.prototype.toJSON.call(this, options);
        if (options && options.generatorSettings) {
            var keys = this.generatorSettingsKeys.concat(this.get("importedGeneratorKeys") || []);
            json = _.pick(json, keys);
            
            json.file = this.getFileName();
            json.extension = this.getFileExtension();
            this._updateQualityForGeneratorSettingsJson(json);
            if (options && options.includeCanvasMins) {
                this._updateCanvasSizesForGeneratorSettingsJson(json);
            }
            
        }
        return json;
    },
    
    _updateQualityForGeneratorSettingsJson: function (json) {
        var qualityInt = this.getValidQuality(this.get("extension"), this.get("quality"));
        if (_.isFinite(qualityInt)) {
            if (json.extension === "jpg" || json.extension === "webp") {
                json.quality = qualityInt + "%";
            } else {
                json.quality = qualityInt;
            }
        } else {
            delete json.quality;
        }
    },
    
    _updateCanvasSizesForGeneratorSettingsJson: function (json) {
        var curDim = this.getScaledImageDimensions();
        if (this.has("canvasWidth") && curDim && curDim.width) {
            json.canvasWidth = Math.max(this.get("canvasWidth"), curDim.width);
        }
        if (this.has("canvasHeight") && curDim && curDim.height) {
            json.canvasHeight = Math.max(this.get("canvasHeight"), curDim.height);
        }
    },
    
    // TODO: Remove this eventually.
    incrementPreviewUrlRevision: function () {
        var cur = this.get("imageURLRevision") || 0;
        cur++;
        this.set("imageURLRevision", cur);
        return cur;
    },
    
    updatePreviewUrl: function (timestamp, server, documentId, layerId, componentId, errorOccurred) {
        var changes = {
            loading: false
        };

        // Ensure the preview that just came in is not older than the one we already have.
        if (timestamp >= this.get("lastReceivedPreviewTimestamp")) {
            // TODO: We can simplify this to just componentId (aka previewId) once we change the generator plugin.
            var cleanRelPath = this.encode(this.getRelativeFilePath()),
                urlParts = [
                    server,
                    this.assetRoute,
                    documentId,
                    layerId,
                    componentId,
                    cleanRelPath + "?v=" + this.incrementPreviewUrlRevision()
                ];
            changes.imageURL = urlParts.join("/");
            changes.disabled = errorOccurred;
            this.set("lastReceivedPreviewTimestamp", timestamp);
        }

        this.set(changes);
    },

    cleanupFileName: function (newSettings, docSettings) {
        var bn;
        if (docSettings.extension && (newSettings.get("extension") !== docSettings.extension)) {
            bn = newSettings.get("desiredBaseName");
            if (bn) {
                newSettings.set("file", bn + "." + docSettings.extension);
            }
        }
    },
    
    createExportComponent: function (documentId, layerId, filePath) {
        var json = this.toJSON({generatorSettings:true}),
            quickExportSpecific = {path: filePath, documentId: documentId, layerId: layerId, basename: this.getBaseName()},
            quickExportComponent = _.extend({}, json, quickExportSpecific);
        return quickExportComponent;
    },

    _createPreviewComponent: function () {
        var component = this.createExportComponent(this.get("documentId"), this.get("sourceObjectId"));
        component.assetId = this.get("assetId");
        component.timestamp = new Date().getTime();
        return component;
    },

    /**
     * Ignores the components' timestamps.
     */
    _previewComponentsEqual: function (a, b) {
        return _.isEqual(_.omit(a, "timestamp"), _.omit(b, "timestamp"));
    },

    /**
     * Add a debounced function for generating previews, so that multiple change events in the same browser event
     * loop iteration don't cause multiple renderings.
     */
    generatePreview: function () {
        if (!this._debouncedGeneratePreview) {
            this._debouncedGeneratePreview = _.debounce(this.generatePreviewImmediately, 0);
        }
        this._debouncedGeneratePreview();
    },

    /**
     * Ensure that our scale, canvasWidth, or canvasHeight don't crash generator. Note that if the doc is huge, and we
     * are trying to load the first preview, even a scale of 1 can be too large.
     *
     * return {Promise} Resolved when the component dimensions are clamped.
     */
    enforceMaxDimensions: function() {
        // Make sure we have the actual max dimensions from the server before doing the clamping.
        return _maxPixelsPromise.then(function () {
            var maxScaleFactor = this.getMaxScale() / 100;
            this.set("scale", Math.min(this.get("scale") || 1, maxScaleFactor));

            if (this.get("canvasWidth")) {
                this.set("canvasWidth", Math.min(this.get("canvasWidth"), this.getMaxImageDimensions().width));
            }

            if (this.get("canvasHeight")) {
                this.set("canvasHeight", Math.min(this.get("canvasHeight"), this.getMaxImageDimensions().height));
            }
        }.bind(this));
    },

    // TODO: Response is currently handled by GeneratorModel.handleAssetUpdate. Eventually, we can stream assets back
    // have the view receive them directly. Streaming should also avoid managing temp assets and hitting disk for perf.
    generatePreviewImmediately: function () {
        var component = this._createPreviewComponent();

        // Only request a new preview if we're sending different parameters than before.
        if (!this._previewComponentsEqual(component, this.get("lastPreviewComponent"))) {
            this.set("loading", true);
            ServerInterface.sendCommand("generatePreview", { component: component });
        }

        this.set("lastPreviewComponent", component);
    }
});

//setup the defaults once
GenSettingsModel.prototype.resetBaselineSupportedExtensions();

module.exports = GenSettingsModel;
