/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright (c) 2013 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 */

define(['jquery',
        'underscore',
        'plugin-dependencies',
        'ccweb.files.extract/models/PSDSettingsModel',
        'ccweb.files.extract/models/UserSettingsModel',
        'ccweb.files.extract/Constants'
       ], function ($, _, deps, PSDSettingsModel, UserSettingsModel, Constants) {
    'use strict';
    var CSSUtil = {
        cssPreprocessors: {
            css: {
                displayName: 'CSS',
                formatString: function (property, value) {
                    return property + ': ' + CSSUtil.formatCSSValue(property, value) + ';';
                },
                listItem: function (cssObj) {
                    if (cssObj.spacer) {
                        return CSSUtil.getSpacer(cssObj);
                    }

                    switch (cssObj.property) {
                    case 'background-color':
                    case 'color':
                    case 'border-color':
                        return '<span class="property">' + cssObj.property + ': </span><span class="color-chip" style="background-color: ' + cssObj.value + ';"></span> <span class="value">' + cssObj.value + ';</span>';
                    default:
                        return '<span class="property">' + cssObj.property + ': </span> <span class="value">' + cssObj.value + ';</span>';
                    }
                }
            },

            scssBourbon: {
                displayName: 'SCSS+Bourbon',
                formatString: function (property, value) {
                    switch (property) {
                    case 'background':
                        return '@include ' + property + '(' + value + ');';
                    default:
                        return CSSUtil.cssPreprocessors.css.formatString(property, value);
                    }
                },
                listItem: function (cssObj) {
                    switch (cssObj.property) {
                    case 'background':
                        return '<span class="value">@include</span> <span class="property">' + cssObj.property + '</span> <span class="value">(' + cssObj.value + ');</span>';
                    default:
                        return CSSUtil.cssPreprocessors.css.listItem(cssObj);
                    }
                }
            },

            sassCompass: {
                enabled: !deps.parfait,
                displayName: 'SCSS+Compass',
                formatString: function (property, value) {
                    switch (property) {
                    case 'background':
                        return '@include ' + property + '(' + value + ');';
                    default:
                        return CSSUtil.cssPreprocessors.css.formatString(property, value);
                    }
                },
                listItem: function (cssObj) {
                    if (cssObj.spacer) {
                        return CSSUtil.getSpacer(cssObj);
                    }

                    switch (cssObj.property) {
                    case 'background':
                        return '<span class="value">@include</span> <span class="property">' + cssObj.property + '</span> <span class="value">(' + cssObj.value + ');</span>';
                    case 'background-color':
                    case 'color':
                    case 'border-color':
                        return '<span class="property">' + cssObj.property + ': </span><span class="color-chip" style="background-color: ' + cssObj.value + '"></span> <span class="value">' + cssObj.value + ';</span>';
                    default:
                        return '<span class="property">' + cssObj.property + ': </span> <span class="value">' + cssObj.value + ';</span>';
                    }
                }
            },

            stylusNib: {
                enabled: !deps.parfait,
                displayName: 'Stylus+Nib',
                formatString: function (property, value) {
                    return property + ' ' + CSSUtil.formatCSSValue(property, value);
                },
                listItem: function (cssObj) {
                    if (cssObj.spacer) {
                        return CSSUtil.getSpacer(cssObj);
                    }

                    switch (cssObj.property) {
                    case 'background':
                        return '<span class="property">' + cssObj.property + '</span> <span class="value">' + cssObj.value + '</span>';
                    case 'background-color':
                    case 'color':
                    case 'border-color':
                        return '<span class="property">' + cssObj.property + ' </span><span class="color-chip" style="background-color: ' + cssObj.value + '"></span> <span class="value">' + cssObj.value + '</span>';
                    default:
                        return '<span class="property">' + cssObj.property + ' </span> <span class="value">' + cssObj.value + '</span>';
                    }
                }
            },

            lessHat: {
                enabled: true,
                displayName: 'LESS+Hat',
                formatString: function (property, value) {
                    switch (property) {
                    case 'background':
                        return '.background-image(' + value + ');';
                    default:
                        return CSSUtil.cssPreprocessors.css.formatString(property, value);
                    }
                },
                listItem: function (cssObj) {
                    switch (cssObj.property) {
                    case 'background':
                        return '<span class="property">.background-image</span><span class="value">(' + cssObj.value + ');</span>';
                    default:
                        return CSSUtil.cssPreprocessors.css.listItem(cssObj);
                    }
                }
            }
        },

        getSpacer: function (cssObj) {
            var spacer = '<br/>';
            if (cssObj.label.length) {
                spacer += '<span class="property">' + cssObj.label + '</span>';
            }
            return spacer;
        },

        applyCSS: function (element, model, prefixed) {
            var css = model.getCSS(prefixed);

            if (css) {
                _.each(css, function (style) {
                    $(element).css(style.property, style.value);
                });
            }
        },

        formatCSSString: function (property, value, preProcessor) {
            preProcessor = preProcessor || 'css';
            return this.cssPreprocessors[preProcessor].formatString(property, value);
        },

        formatCSSValue: function (property, value) {
            var retStr = value ? value.toString().trim() : '';
            if (property === 'font-family' && retStr.indexOf(' ') !== -1 && retStr.indexOf('\'') !== 0 && retStr.lastIndexOf('\'') !== retStr.length - 1) {
                retStr = '\'' + value + '\'';
            }
            return retStr;
        },

        getDefaultColorString: function (r, g, b, a) {
            var colorStr;

            if (a !== null && a !== undefined && a !== 1) {
                colorStr = 'rgba(' + Math.round(r) + ', ' + Math.round(g) + ', ' + Math.round(b) + ', ' + a + ')';
            } else {
                colorStr = this.rgbToHEX(r, g, b);
            }

            return colorStr;

        },

        rgbToHEX: function (r, g, b) {
            return '#' + ((1 << 24) + (Math.round(r) << 16) + (Math.round(g) << 8) + Math.round(b)).toString(16).slice(1);
        },

        rgbaToHSL: function (r, g, b, a) {
            if (a === null || a === undefined) {
                a = 1;
            }

            var hsl = '';
            var normR, normG, normB;
            normR = r / 255;
            normG = g / 255;
            normB = b / 255;
            var max = Math.max(normR, normG, normB), min = Math.min(normR, normG, normB);
            var h, s, l = (max + min) / 2;

            if (max === min) {
                h = s = 0; // achromatic
            } else {
                var d = max - min;
                s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
                switch (max) {
                case normR:
                    h = 60 * (normG - normB) / d;
                    break;
                case normG:
                    h = 60 * (normB - normR) / d + 120;
                    break;
                case normB:
                    h = 60 * (normR - normG) / d + 240;
                    break;
                }
            }

            h = Math.round(h + 360) % 360;
            s = Math.round(s * 100);
            l = Math.round(l * 100);

            if (a === 1) {
                hsl = 'hsl(' + h + ', ' + s + '%, ' + l + '%)';
            } else {
                hsl = 'hsla(' + h + ', ' + s + '%, ' + l + '%, ' + parseFloat(a) + ')';
            }
            return hsl;
        },

        // font-weight conversion based on http://help.typekit.com/customer/portal/articles/6855-using-multiple-weights-and-styles
        // and http://www.webtype.com/info/articles/fonts-weights/
        fontStyleNameToCSS: function (fontStyleName) {
            var cssArray = [];
            var styleName = fontStyleName.toLowerCase();
            if (styleName.indexOf('regular') !== -1 || styleName.indexOf('normal') !== -1) {
                cssArray.push({property: 'font-weight', value: '400'});
            } else if (styleName.indexOf('thin') !== -1) {
                cssArray.push({property: 'font-weight', value: '100'});
            } else if (styleName.indexOf('light') !== -1) {
                if (styleName.indexOf('extra light') !== -1 || styleName.indexOf('extralight') !== -1 ||
                        styleName.indexOf('ultra light') !== -1 || styleName.indexOf('ultralight') !== -1) {
                    cssArray.push({property: 'font-weight', value: '200'});
                } else {
                    cssArray.push({property: 'font-weight', value: '300'});
                }
            } else if (styleName.indexOf('book') !== -1 || styleName.indexOf('demi') !== -1) {
                if (styleName.indexOf('demibold') !== -1) {
                    cssArray.push({property: 'font-weight', value: '600'});
                } else {
                    cssArray.push({property: 'font-weight', value: '300'});
                }
            } else if (styleName.indexOf('medium') !== -1) {
                cssArray.push({property: 'font-weight', value: '500'});
            } else if (styleName.indexOf('semibold') !== -1) {
                cssArray.push({property: 'font-weight', value: '600'});
            } else if (styleName.indexOf('bold') !== -1) {
                cssArray.push({property: 'font-weight', value: '700'});
            } else if (styleName.indexOf('heavy') !== -1) {
                cssArray.push({property: 'font-weight', value: '800'});
            } else if (styleName.indexOf('black') !== -1) {
                cssArray.push({property: 'font-weight', value: '900'});
            }

            if (fontStyleName.indexOf('Italic') !== -1) {
                cssArray.push({property: 'font-style', value: 'italic'});
            }

            return cssArray;
        },

        hasWhiteSpace: function (str) {
            return str.indexOf(' ') >= 0;
        },

        _camelCapsToSpaces: function (value) {
            return value.replace(/([A-Z]+)/g, ' $1').replace(/([A-Z][a-z])/g, ' $1').replace(/\s+/g, ' ').trim();
        },

        getFontInfo: function (textStyle) {
            var fontInfo = {},
                postscriptName,
                dashIndex,
                fontFamily = textStyle.fontName,
                friendlyName = fontFamily,
                fontFace = 'Regular';

            fontInfo.coolTypeResolved = textStyle.cooltypeFontResolved;

            if (textStyle.hasOwnProperty('fontStyleName')) {
                fontFace = textStyle.fontStyleName;
            }

            // Resolve friendly name.
            if (!fontInfo.coolTypeResolved) {
                postscriptName = textStyle.postscriptFontName;
                dashIndex = postscriptName.indexOf('-');
                if (dashIndex !== -1) {
                    fontFamily = postscriptName.substring(0, dashIndex);
                    fontFace = postscriptName.substring(dashIndex + 1, postscriptName.length);
                } else {
                    fontFamily = postscriptName;
                }
                friendlyName = this._camelCapsToSpaces(fontFamily);
            } else if (fontFamily) {
                friendlyName = this._camelCapsToSpaces(fontFamily);
            }

            fontInfo.fontName = fontFamily;
            fontInfo.fontFace = fontFace;
            fontInfo.friendlyName = friendlyName;

            var fontSize = this.parseCSSMeasure(textStyle.size);
            fontSize.val = +(parseFloat(fontSize.val).toFixed(2));
            fontInfo.size = fontSize.val + (fontSize.units || '');

            return fontInfo;
        },

        parseCSSMeasure: function (s) {
            var retVal = {val: s},
                defaultUnits = PSDSettingsModel.get('baseFontSizeUnits');

            if (defaultUnits) {
                retVal.units = defaultUnits;
            }

            if (typeof s === 'string') {
                var matches = s.match(/([+\-]?[\d\.]*)\s?([a-zA-Z%]+)/);
                if (matches) {
                    retVal.val = matches[1];
                }
            }

            return retVal;
        },

        strokeToCSS: function (shapeObj, opacity) {
            var cssArray = [];
            var stroke = shapeObj.stroke;
            if (opacity === null || opacity === undefined) {
                opacity = 1;
            }

            if (shapeObj.type !== Constants.Shape.ELLIPSE && shapeObj.type !== Constants.Shape.RECTANGLE) {
                if (shapeObj.bottomLeftRadius || shapeObj.bottomRightRadius ||
                        shapeObj.topLeftRadius || shapeObj.topRightRadius) {
                    cssArray = this.getBorderRadiusCSS(shapeObj.bottomLeftRadius, shapeObj.bottomRightRadius,
                        shapeObj.topLeftRadius, shapeObj.topRightRadius);
                }
            }

            if (stroke) {
                if (stroke.strokeStyleLineWidth) {
                    var metrics = this.parseCSSMeasure(stroke.strokeStyleLineWidth, 'px'); // WAG, same behavior as before we had units
                    if (metrics.units === 'pt' && stroke.strokeStyleResolution) {
                        // Winsha wanted always pixels
                        metrics.units = 'px';
                        metrics.val = metrics.val * stroke.strokeStyleResolution / Constants.DefaultPPI;
                    }
                    cssArray.push({property: 'border-width', value: metrics.val + metrics.units});
                }

                if (stroke.strokeStyleContent) {
                    var strokeContent = stroke.strokeStyleContent;
                    var color = strokeContent.color;
                    var alpha = +(opacity * stroke.strokeStyleOpacity / 100).toFixed(2);

                    if (strokeContent['class'] === Constants.ContentType.SOLID_COLOR && color) {
                        cssArray.push({
                            property: 'border-color',
                            value: this.getDefaultColorString(color.red, color.green, color.blue, alpha)
                        });
                    }
                }

                if (stroke.strokeStyleLineDashSet) {
                    var dashSet = stroke.strokeStyleLineDashSet;

                    if (dashSet.length === 0) {
                        cssArray.push({property: 'border-style', value: 'solid'});
                    } else if (dashSet.length === 2) {
                        if (this.parseCSSMeasure(dashSet[0]).val === 0) {
                            cssArray.push({property: 'border-style', value: 'dotted'});
                        } else {
                            cssArray.push({property: 'border-style', value: 'dashed'});
                        }
                    }
                }
            }

            return cssArray;
        },

        getShadowCSS: function (layerType, shadow) {
            var cssObj = {property: '', value: ''},
                propertyName;

            if (layerType === Constants.Type.LAYER_TEXT) {
                cssObj.property = 'text-shadow';
            } else {
                //currently defaulting to box-shadow
                //we may also want to support filter: drop-shadow() for shapes and images
                cssObj.property = 'box-shadow';
            }

            if (Array.isArray(shadow)) {
                for (var i = 0; i < shadow.length; i++) {
                    if (shadow[i].enabled) {
                        cssObj.value += (cssObj.value !== '' ? ', ' : '') + this.getShadowCSSText(layerType, propertyName, shadow[i]);
                    }
                }
            } else {
                cssObj.value = this.getShadowCSSText(layerType, propertyName, shadow);
            }

            return cssObj;
        },

        getShadowCSSText: function (layerType, propertyName, shadow) {
            var hShadow, vShadow, color, blur, matte, spread, cssText;

            blur = (shadow.blur === undefined || shadow.blur === null) ? 5 : shadow.blur;
            matte = shadow.chokeMatte || 0;
            spread = parseFloat((blur * matte / 100).toFixed(2));
            hShadow = Math.round(-shadow.distance * Math.cos(this.degreesToRadians(shadow.localLightingAngle)));
            vShadow = Math.round(shadow.distance * Math.sin(this.degreesToRadians(shadow.localLightingAngle)));
            color = shadow.color;

            cssText = hShadow + 'px ' + vShadow + 'px';
            cssText += ' ' + (+(blur - spread).toFixed(2)) + 'px';

            if (layerType !== Constants.Type.LAYER_TEXT) {
                cssText += ' ' + spread + 'px';
            }

            if (color) {
                cssText += ' ' + this.getDefaultColorString(color.red, color.green, color.blue, shadow.opacity / 100);
            }
            return cssText;
        },

        degreesToRadians: function (degrees) {
            return (Math.PI / 180) * degrees;
        },

        getBorderRadiusCSS: function (bottomLeft, bottomRight, topLeft, topRight) {
            var cssArray = [];
            if (bottomLeft && bottomLeft === bottomRight && topLeft === topRight && bottomLeft === topLeft) {
                cssArray.push({property: 'border-radius', value: bottomLeft + 'px'});
            } else {
                if (bottomLeft) {
                    cssArray.push({property: 'border-bottom-left-radius', value: bottomLeft + 'px'});
                }
                if (bottomRight) {
                    cssArray.push({property: 'border-bottom-right-radius', value: bottomRight + 'px'});
                }
                if (topLeft) {
                    cssArray.push({property: 'border-top-left-radius', value: topLeft + 'px'});
                }
                if (topRight) {
                    cssArray.push({property: 'border-top-right-radius', value: topRight + 'px'});
                }
            }
            return cssArray;
        },

        addCSS: function (cssArray, cssObj) {
            cssArray.push(cssObj);
        },

        applyPrefixedStyle: function ($elem, styleName, value) {
            var styleObj = {};
            styleObj[styleName] = value;
            styleObj['-moz-' + styleName] = value;
            styleObj['-webkit-' + styleName] = value;
            styleObj['-o-' + styleName] = value;
            styleObj['-ms-' + styleName] = value;
            $elem.css(styleObj);
        },

        getCSSTransform: function (psdTransform) {
            // Check for simple scale first
            if (psdTransform.xy === 0 && psdTransform.yx === 0){
                if (psdTransform.xx === 1) {
                    if (psdTransform.yy === 1) {
                        return '';
                    } else {
                        return 'scaleY(' + (+psdTransform.yy.toFixed(3)) + ')';
                    }
                } else if (psdTransform.yy === 1) {
                    return 'scaleX(' + (+psdTransform.xx.toFixed(3)) + ')';
                } else {
                    return 'scale(' + (+psdTransform.xx.toFixed(3)) + ',' + (+psdTransform.yy.toFixed(3)) + ')';
                }
            }
            return 'matrix(' + [+psdTransform.xx.toFixed(3), +psdTransform.xy.toFixed(3), +psdTransform.yx.toFixed(3), +psdTransform.yy.toFixed(3), 0, 0] + ')';
        },

        findModelResolution: function (model) {
            if (model.get('imgdata')) {
                return model.get('imgdata').pixelsPerInch || Constants.DefaultPPI;
            }

            if (model.get('parentModel')) {
                return this.findModelResolution(model.get('parentModel'));
            }

            return Constants.DefaultPPI;
        },

        /* This function accepts a value to convert to the preferred unit type the user has chosen and
           which is stored in the user settings model.

           If present, the units in the incoming value are ignored because they are assumed to be in points
           because that is what comes from the graphite JSON. We treat those points as either pixels or points
           based on what the user has chosen for the Base Font Size for this PSD and we perform the conversion
           from either points or pixels to the preferred unit type.
         */
        convertPSDUnitsToPreferredUnits: function (value, includeUnitInOutput) {
            var parsedValue,
                parsedNum,
                baseFontSizeValue = PSDSettingsModel.get('baseFontSizeValue'),
                baseFontSizeUnits = PSDSettingsModel.get('baseFontSizeUnits'),
                preferredUnits = UserSettingsModel.get('preferredFontUnits');

            if (typeof value === 'string') {
                parsedValue = this.parseCSSMeasure(value, baseFontSizeUnits);
            } else if (typeof value === 'number') {
                parsedValue = {
                    val: value,
                    units: Constants.FontUnitType.PX
                };
            } else if (value.hasOwnProperty('val')) {
                parsedValue = {
                    val: value.val,
                    units: value.units || baseFontSizeUnits
                };
            } else {
                return '';
            }

            switch (baseFontSizeUnits) {
                case Constants.FontUnitType.PX:
                    if ((preferredUnits === Constants.FontUnitType.EMS) || (preferredUnits === Constants.FontUnitType.REMS)) {
                        //Convert PX to EMS/REMS
                        parsedValue.val = parsedValue.val / baseFontSizeValue;
                    }
                    parsedValue.units = preferredUnits;
                    break;
            }

            parsedNum = +parseFloat(parsedValue.val).toFixed(2);
            return includeUnitInOutput ? parsedNum + parsedValue.units : parsedNum;
        }

    };

    return CSSUtil;

});
