/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright (c) 2015 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, $: true, exports: true, csInterface: true*/

(function () {
    "use strict";

    var Q = require("q"),
        _ = require("underscore"),
        DocinfoUtils = require("../DocinfoUtils");

    var BoundsUtils = function () {};

    /**
     * Updates the bounds of specific layers in docinfo to accurate bounds, if they aren't accurate already.
     *
     * @param {Array} layers
     * @param {Object} docinfo
     * @param {Function} requestAccurateBoundsFunction A function that takes a documentId and layerId and returns a
     *      promise that resolves with the accurate bounds or rejects if there is a failure.
     * @return {Promise} Resolves with the updated docinfo when all accurate bounds requests are done, failed or not.
     */
    BoundsUtils.prototype.ensureAccurateBoundsForLayers = function(layers, docinfo, requestAccurateBoundsFunction) {
        var promises = layers.map(function (layer) {
            // Request accurate bounds only when a layer needs it.
            if (this.isLayerDataBoundsAccurate(layer)) {
                return;
            }

            return requestAccurateBoundsFunction(docinfo.id, layer.id)
                .then(function (accurateBounds) {
                    layer.bounds = accurateBounds;
                });
        }, this);

        return Q.allSettled(promises).thenResolve(docinfo);
    };

    BoundsUtils.prototype.isLayerDataBoundsAccurate = function (rawLayerData) {
        return !(this._isLayerClipped(rawLayerData) ||
                 this._layerHasBitmapMask(rawLayerData) ||
                 this._layerHasVectorMask(rawLayerData) ||
                 this._layerHasLayerEffects(rawLayerData) ||
                 this._layerHasSmartEffects(rawLayerData) ||
                 this.layerHasZeroBounds(rawLayerData));
    };

    BoundsUtils.prototype.layerIsOutsideDocumentBounds = function (rawLayerData, docinfo) {
        if (!rawLayerData.bounds || !docinfo.bounds)
            return false;

        var intersectionBounds = this._intersectBounds(rawLayerData.bounds, docinfo.bounds);
        return this._boundsAreEmpty(intersectionBounds);
    };

    BoundsUtils.prototype.layerHasZeroBounds = function (rawLayerData) {
        return !rawLayerData.bounds || this._boundsAreEmpty(rawLayerData.bounds);
    };

    BoundsUtils.prototype.layerIsClippedByDocumentBounds = function (rawLayerData, docinfo) {
        if (!rawLayerData.bounds || !docinfo.bounds)
            return false;

        var intersectionBounds = this._intersectBounds(rawLayerData.bounds, docinfo.bounds);

        return (!this._boundsAreEmpty(intersectionBounds) && !_.isEqual(intersectionBounds, rawLayerData.bounds));
    };
    
    BoundsUtils.prototype._boundsAreEmpty = function (bounds) {
        var dimensions = this.boundsToDimensions(bounds);
        return dimensions.width <= 0 || dimensions.height <= 0;
    };

    BoundsUtils.prototype._intersectBounds = function (a, b) {
        var intersectionBounds = {
            top: Math.max(a.top, b.top),
            left: Math.max(a.left, b.left),
            bottom: Math.min(a.bottom, b.bottom),
            right: Math.min(a.right, b.right)
        };
        
        if (this._boundsAreEmpty(intersectionBounds)) {
            intersectionBounds = { top: 0, left: 0, bottom: 0, right: 0 };
        }
        return intersectionBounds;
    };
    
    BoundsUtils.prototype._unionBounds = function (a, b) {
        return {
            top: Math.min(a.top, b.top),
            left: Math.min(a.left, b.left),
            bottom: Math.max(a.bottom, b.bottom),
            right: Math.max(a.right, b.right)
        };
    };

    BoundsUtils.prototype._isLayerClipped = function (rawLayerData) {
        return rawLayerData.clipped;
    };

    BoundsUtils.prototype._layerHasBitmapMask = function (rawLayerData) {
        return rawLayerData.mask && rawLayerData.mask.enabled && rawLayerData.mask.bounds;
    };

    BoundsUtils.prototype._layerHasVectorMask = function (rawLayerData) {
        return rawLayerData.path && rawLayerData.path.bounds && rawLayerData.type !== "shapeLayer";
    };

    BoundsUtils.prototype._isLayerEffectEnabled = function (item) {
        return item.enabled;
    }; 

    BoundsUtils.prototype._layerHasLayerEffects = function (rawLayerData) {
        if (!rawLayerData.layerEffects) {
            return false;
        }

        if (rawLayerData.layerEffects.hasOwnProperty("masterFXSwitch")) {
            return rawLayerData.layerEffects.masterFXSwitch;
        }

        var someLayerEffectEnabled = false;
        Object.keys(rawLayerData.layerEffects).forEach( function (property) {
            if (this._isLayerEffectEnabled(rawLayerData.layerEffects[property])) {
                someLayerEffectEnabled = true;
            } else if (rawLayerData.layerEffects[property] instanceof Array) {
                someLayerEffectEnabled = rawLayerData.layerEffects[property].some(this._isLayerEffectEnabled);
            }
        }, this);

        return someLayerEffectEnabled;
    };

    BoundsUtils.prototype._layerHasSmartEffects = function (rawLayerData) {
        return rawLayerData.smartObject && rawLayerData.smartObject.hasOwnProperty("filterFX");
    };
    
    BoundsUtils.prototype._getBitmapMaskBounds = function (rawLayerData) {
        return rawLayerData.mask && 
                (!_.has(rawLayerData.mask, "enabled") || rawLayerData.mask.enabled) && 
                rawLayerData.mask.bounds;
    };
    
    BoundsUtils.prototype._getVectorMaskBounds = function (rawLayerData) {
        if (rawLayerData.type === "shapeLayer") {
            return;
        }
        return rawLayerData.path && rawLayerData.path.bounds;
    };
    
    BoundsUtils.prototype.getTotalMaskBounds = function (rawLayerData) {
        var maskBounds = this._getBitmapMaskBounds(rawLayerData),
            vectorMaskBounds = this._getVectorMaskBounds(rawLayerData);
        if (maskBounds && this._boundsAreEmpty(maskBounds)) {
            maskBounds = undefined;
        }
        if (vectorMaskBounds && this._boundsAreEmpty(vectorMaskBounds)) {
            vectorMaskBounds = undefined;
        }
        if (maskBounds && vectorMaskBounds) {
            return this._unionBounds(maskBounds, vectorMaskBounds);
        }
        
        return maskBounds || vectorMaskBounds;
    };

    BoundsUtils.prototype.boundsToDimensions = function (bounds) {
        return {
            width: bounds.right - bounds.left,
            height: bounds.bottom - bounds.top
        };
    };

    module.exports = new BoundsUtils();
}());
