/*
 * 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 */
/*global define: true, graphite: true, document: true*/

define([
    'jquery',
    'underscore',
    'backbone',
    'ccweb.files.extract/Constants',
    'ccweb.files.extract/controllers/SelectionController',
    'ccweb.files.extract/controllers/DetailsController',
    'ccweb.files.extract/controllers/ZoomController',
    'ccweb.files.extract/controllers/FlattenedAssetController',
    'ccweb.files.extract/models/LayerModelMap',
    'ccweb.files.extract/utils/KeyboardUtils',
    'ccweb.files.extract/utils/ScrollUtils',
    'ccweb.files.extract/views/preview/InspectStyleOverlayView',
    'ccweb.files.extract/views/preview/SelectionOverlayView',
    'ccweb.files.extract/views/preview/MouseListenerOverlayView',
    'ccweb.files.extract/views/preview/ArtboardOverlayView',
    'ccweb.files.extract/views/preview/PopupOverlayView',
    'ccweb.files.extract/utils/MeasurementUtil',
    'ccweb.files.extract/utils/TemplateUtil',
    'text!ccweb.files.extract/views/templates/lensTemplate.html',
    'text!ccweb.files.extract/views/templates/workerProgressTemplate.html',
    'text!ccweb.files.extract/views/templates/psdPreviewTemplate.html',
    'text!ccweb.files.extract/views/templates/psdPreviewDivSpriteSheetTemplate.html',
    'text!ccweb.files.extract/views/templates/psdPreviewLayerTemplate.html',
    'text!ccweb.files.extract/views/templates/errorNotificationTemplate.html',
    'plugin-dependencies',
    'plugin-components/image-loader',
    'DWPSDExtractManager',
    'parfait/PSDPreviewView'
], function ($, _, Backbone, Constants, SelectionController, DetailsController,
    ZoomController, FlattenedAssetController, LayerModelMap, KeyboardUtils, ScrollUtils,
    InspectStyleOverlayView, SelectionOverlayView, MouseListenerOverlayView, ArtboardOverlayView,
    PopupOverlayView, MeasurementUtil, TemplateUtil, LensTemplate, WorkerProgressTemplate, PSDPreviewTemplate,
    PSDPreviewDivSpriteSheetTemplate, PSDPreviewLayerTemplate, ErrorNotificationTemplate,
    deps, imageLoader, DWPSDExtractManager, PSDPreviewView) {
    "use strict";
    
    var DwPSDPreviewView = PSDPreviewView.extend({
        className: "psd-preview-view move-right",
        timeOutId: -1,
        defaultPadding: 14,
        
        events: function () {
            return _.extend({}, PSDPreviewView.prototype.events, {
                'mousedown' :   'handleMouseDownEvent'
            });
        },
        
        remove: function () {
            graphite.events.off(null, null, this);
            this.extractionStartTime = null;
            this.undelegateEvents();
            this.unbind();
            this.stopListening();
            this.extractionStartTime = null;
            PSDPreviewView.prototype.remove.apply(this, arguments);
        },
        
        
        
        renderProgressPane: function () {
            var tips = [
                deps.translate("USE_SHIFT_CLICK_TO_MEASURE_DISTANCE_BETWEEN_ELEMENTS_IN_PSD_VIEW"),
                deps.translate("AFTER_EXTRACTING_ASSETS_FIND_IN_ASSETS_TABS_TO_DOWNLOAD_LOCALLY"),
                deps.translate("SELECT_COLOR_SWATCH_IN_STYLES_TAB_TO_SEE_COLOR_USED_IN_DESIGN"),
                deps.translate("CAN_SELECT_MULTIPLE_LAYESRS_AND_EXPORT_AS_SINGLE_IMAGE"),
                deps.translate("SELECT_LAYER_COMPS_WITH_DROPDOWN_ABOVE_PSD_VIEW"),
                deps.translate("ALT_ZOOM_IN_OUT_PSD_VIEW"),
                deps.translate("USE_ARROW_KEYS_TO_TRAVERSE_LAYERS_IN_DESIGN"),
                deps.translate("HOVER_THUMBNAILS_IN_LAYERS_TAB_TO_PREVIEW_FOR_LAYER_OR_LAYER_GROUP")
            ];

            this.$progressPane = $(TemplateUtil.createTemplate("#worker-progress-template",
                {tip: deps.translate("TIP") + " :" + tips[Math.round(Math.random() * (tips.length - 1))]}));
            this.$progressPane.hide();
            this.$el.append(this.$progressPane);
        },
        
        render: function () {
            this.renderContainer();

            this.inspectStyleOverlay = new InspectStyleOverlayView({model: this.model});
            this.selectionOverlay = new SelectionOverlayView({parentView: this});
            this.mouseListenerOverlay = new MouseListenerOverlayView({model: this.model});
            this.artboardOverlay = new ArtboardOverlayView({model: this.model});
            this.popupOverlay = new PopupOverlayView({model: this.model});
            this.$psdLens = $(TemplateUtil.createTemplate(LensTemplate));

            this.$el.find('.preview-container').append(this.inspectStyleOverlay.el);
            this.$el.find('.preview-container').append(this.mouseListenerOverlay.el);
            this.$el.find('.preview-container').append(this.artboardOverlay.el);
            this.$el.find('.preview-container').append(this.popupOverlay.el);

            this.mouseListenerOverlay.addHandlers();

            graphite.events.trigger('clear-notification'); //Clear any existing notification
            
            return this;
        },
        
        hideTempPreview: function () {
            var artboardsInfo;
            this.$el.addClass('preview-rendered');
            this.tempPreviewHidden = true;

            var psdModel = SelectionController.getPSDModel();
            if (psdModel.get('isArtboardPSD')) {
                this.$el.find('div.preview-container').addClass('artboard');
                this.$el.find('#psdpreview_root').addClass('artboard');
                artboardsInfo = psdModel.get('artboards');
                if (artboardsInfo) {
                    this.$el.find('#psdpreview_root').css('background-color', 'rgb(' + parseInt(artboardsInfo.canvasColor.red, 10) + ', ' +
                                                                                       parseInt(artboardsInfo.canvasColor.green, 10) + ', ' +
                                                                                       parseInt(artboardsInfo.canvasColor.blue, 10) + ')');
                }
                graphite.events.trigger('zoomToDefaultFit');
                graphite.events.trigger('artboardpsd-shown');
            } else {
                graphite.events.trigger('zoomToDefaultFit');
                graphite.events.trigger('nonartboardpsd-shown'); //Send this to help make it clear this is not a PSD with artboards
            }

        },
        
        handleExtractedStylesChanged: function () {
            if (this.model.get('dataType') !== 'complete') {
                this.populateLayerModelMap(this.model);
                DetailsController.triggerSpriteSheetLoadedEvent('JSONPreviewReady');
                return;
            }

            this.addLayerSpritesBackToFront(this.model);
            this.model.set('renderedSprites', true);
            DetailsController.triggerWhenSpriteSheetsLoaded('drawPreviewFinish');
        },
        
        addHandlers: function () {
            PSDPreviewView.prototype.addHandlers.apply(this, arguments);
            graphite.events.on("clearLayerFocus", this.clearLayerFocus, this);
            graphite.events.on("extract-asset-download-success", this.handleAssetDownloadSuccess, this);
            graphite.events.on("extract-asset-download-error", this.handleAssetDownloadFailure, this);
            graphite.events.on("extract-asset-validation-error", this.handleAssetpathValidationFailure, this);
            graphite.events.on("css-copied-in-styles", this.handleStylesCopiedInStylesView, this);
            graphite.events.on("remove-error-notification", this.handleRemoveErrorNotification, this);
        },
        
        _loadPreviewImage: function () {
            if (imageLoader) {
                var loaderConfig = {
                    id : this.model.get('assetId'),
                    modified : this.model.get('modified'),
                    height : this.model.get('width'),
                    width : this.model.get('height'),
                    type: this.model.get('type'),
                    name: this.model.get('fileName'),
                    link: graphite.linkId
                };
                imageLoader(loaderConfig, this._previewImageDataReceived);
            }
        },
        
         // This function should now be able to handle multiple messages
        // in tooltips at any intervals of time. This was needed for downloading
        // in Extract panel workflow. Depending on the size of image, we can get 
        // multiple notifications in a small time or sometimes longer
        showTooltip: function (message) {
            var measurementPopup = this.$el.find("div.measurementPopup");
            var $parent = measurementPopup.parent();
            var self = this;
            
            var positionTooltip = function () {
                // For some reason when we calculate the width with the new message we just set,
                // we don't get an updated width. Changing CSS may trigger some size invalidation
                // After changing CSS, we get the correct value of width
                self.extractTooltip.css({top: 0, left: 0});
                var tipWidth = self.extractTooltip.width();
                var tipHeight = self.extractTooltip.height();
                var hScrollPos =  $("div.psd-preview-view").scrollLeft();
                var tipPosLeft = measurementPopup.position().left + (measurementPopup.outerWidth() / 2) - (tipWidth / 2);
                var previewAreaWidth = $('div.psd-preview-view').width() + hScrollPos - 10;
                //Check to make sure our placement doesn't get cut off by borders
                if (tipPosLeft < hScrollPos + self.defaultPadding) {
                    tipPosLeft = hScrollPos + self.defaultPadding;
                }
                if ((tipPosLeft + tipWidth) > previewAreaWidth) {
                    tipPosLeft = previewAreaWidth - tipWidth;
                    if (tipPosLeft < hScrollPos + self.defaultPadding) {
                        tipPosLeft = hScrollPos + self.defaultPadding;
                        self.extractTooltip.width(tipWidth - self.defaultPadding);
                    }
                }
                var tipPosTop;
                if (measurementPopup.hasClass("bottom")) {
                    //Place the tooltip just below the blue HUD
                    tipPosTop = measurementPopup.position().top + measurementPopup.outerHeight() + 5;
                } else {
                    //Place the tooltip just above the blue HUD
                    tipPosTop = measurementPopup.position().top - tipHeight - 15;
                }
                self.extractTooltip.css({top: tipPosTop, left: tipPosLeft});
            };
            
            this.extractTooltip = $("#notificationTooltip");
            if (this.extractTooltip.is(':visible')) {
                clearTimeout(this.timeOutId);
                this.extractTooltip.remove();
            }
            $parent.append("<div id='notificationTooltip' style='display:none'><span>" + message + "</span></div>");
            this.extractTooltip = $("#notificationTooltip");
            positionTooltip();
            this.extractTooltip.fadeIn(250);
            
            this.timeOutId = setTimeout(function () {
                self.extractTooltip.remove();
            }, Constants.toolTipTimeout);
        },
        
        handleStylesCopiedInStylesView: function (selector, errorMessage, remove) {
            this.showCommonTooltip(selector, errorMessage, remove);
        },
        
        handleRemoveErrorNotification: function () {
            var extractErrorTooltip = $("#notificationTooltip");
            if (extractErrorTooltip && extractErrorTooltip.length && extractErrorTooltip.is(':visible')) {
                // Make sure this is an error tooltip pertaining to the extract popup
                if (extractErrorTooltip.parent().prop("tagName") === "SECTION") {
                    extractErrorTooltip.remove();
                }
            }
        },
        
        // This function shows tooltip around the asset download popup
        showCommonTooltip: function (selector, message, remove) {
            if (remove === undefined) {
                remove = true;
            }
            var extractPopup = $(selector);
            var $parent = extractPopup.parent();
            var self = this;
            
            var positionTooltip = function () {
                // For some reason when we calculate the width with the new message we just set,
                // we don't get an updated width. Changing CSS may trigger some size invalidation
                // After changing CSS, we get the correct value of width
                self.extractTooltip.css({top: 0, left: 0});
                var tipWidth = self.extractTooltip.width();
                var tipHeight = self.extractTooltip.height();
                var hScrollPos =  $parent.scrollLeft();
                var tipPosLeft = extractPopup.position().left + (extractPopup.outerWidth() / 2) - (tipWidth / 2);
                var previewAreaWidth = $parent.width() + hScrollPos - 10;
                //Check to make sure our placement doesn't get cut off by borders
                if (tipPosLeft < hScrollPos + self.defaultPadding) {
                    tipPosLeft = hScrollPos + self.defaultPadding;
                }
                if ((tipPosLeft + tipWidth) > previewAreaWidth) {
                    tipPosLeft = previewAreaWidth - tipWidth;
                    if (tipPosLeft < hScrollPos + self.defaultPadding) {
                        tipPosLeft = hScrollPos + self.defaultPadding;
                        self.extractTooltip.width(tipWidth - self.defaultPadding);
                    }
                }
                var tipPosTop;
                if (extractPopup.hasClass("bottom")) {
                    //Place the tooltip just below the blue HUD
                    tipPosTop = extractPopup.position().top + extractPopup.outerHeight() + 5;
                } else {
                    //Place the tooltip just above the blue HUD
                    tipPosTop = extractPopup.position().top - tipHeight - 20;
                }
                self.extractTooltip.css({top: tipPosTop, left: tipPosLeft});
            };
            
            this.extractTooltip = $("#notificationTooltip");
            if (this.extractTooltip.is(':visible')) {
                clearTimeout(this.timeOutId);
                this.extractTooltip.remove();
            }
            $parent.append("<div id='notificationTooltip' style='display:none'><span>" + message + "</span></div>");
            this.extractTooltip = $("#notificationTooltip");
            positionTooltip();
            this.extractTooltip.fadeIn(250);
            
            if (remove) {
                this.timeOutId = setTimeout(function () {
                    self.extractTooltip.remove();
                }, Constants.toolTipTimeout);
            }
        },
        
        handleAssetExtractInit: function () {
            var date = new Date();
            this.extractionStartTime = date.getTime();
            date = null;
            this.showTooltip(deps.translate("EXTRACTING_ASSET"));
        },
        
        handleAssetDownloadSuccess: function () {
            this.showTooltip(deps.translate("FILE_DOWNLOAD_SUCCESSFUL"));
        },
         
        handleAssetDownloadFailure: function () {
            this.showTooltip(deps.translate("EXTRACTION_FAILED_DUE_TO_UNKNOWN_ERROR"));
        },
        
        handleAssetpathValidationFailure: function (errorMessage, remove) {
            this.showCommonTooltip("div.extract-asset-popup", errorMessage, remove);
        },
        
        handleMouseDownEvent: function (event) {
            if (event.button === 2) {
                this.logCanvasClick("rightClick", event.clientX, event.clientY);
            } else if (event.button === 0) {
                var page = { x: event.pageX, y: event.pageY, clientX: event.clientX, clientY: event.clientY },
                    self = this,
                    $target = $(event.target);
                
                if ($target.parents("#copy-css-groups-container").length === 0) {
                    $(window).on("mousemove.dragDetect", function (event) {
                        self.isClickDrag = true;
                        if (Math.abs(event.pageX - page.x) > 3 || Math.abs(event.pageY - page.y) > 3) {
                            var hitItem = (event.clientX !== -1 && event.clientY !== -1) ? SelectionController.itemAtPoint(page.clientX + $(document).scrollLeft(), page.clientY + $(document).scrollTop()) : null;
                            if (hitItem) {
                                var hitLayerId = hitItem.get("layerId");
                                var layerIds = [hitLayerId];
								var layerName = hitItem.get("layerName");
                                var selectedLayers = SelectionController.getSelectedLayers();
                                // Check and merge with current selection.
                                if (selectedLayers.length > 0) {
                                    var selLayerIds = [];
                                    selectedLayers.forEach(function (layer) {
                                        selLayerIds.push(layer.get("layerId"));
                                    });
                                    // selection exists and hit item is a part of selected items
                                    // so take the multiple selection. 
                                    if (selLayerIds.indexOf(hitLayerId) !== -1) {
                                        layerIds = selLayerIds;
                                    } else if (KeyboardUtils.isMultiSelectKey(event)) {
                                        // Or if the selection key is still pressed, merge the hit item
                                        // with the selected items.
                                        layerIds = layerIds.concat(selLayerIds);
                                    }
                                }
                                DWPSDExtractManager.startDrag("dFEV", JSON.stringify(layerIds), DWPSDExtractManager.normalizeFileName(layerName)); // also pass the current layercomps
                                // ask dreamwever to drag the layer ids
                                graphite.events.trigger("canvasClickDrag");
                                $(window).off(".dragDetect");
                            }
                        }
                    });
                }
                event.stopPropagation();
            }
        },
        
        showProgressPane: function (workerGuid) {
            if (!this.workerProgressShown) {
                $(".psd-preview-view").addClass("worker-progress-shown");
                $('.worker-status').text(deps.translate("PROCESSING") + " 0%...");
                $(".worker-progress-bar").css("width", 0);
                this.$progressPane.show();
                this.workerProgressShown = true;
            }
            this.workerGuid = workerGuid;
        },

        hideProgressPane: function (workerGuid) {
            if (workerGuid === -1 || this.workerGuid === workerGuid) {
                $(".psd-preview-view").removeClass("worker-progress-shown");
                $('.worker-status').text(deps.translate("PROCESSING") + " 100%");
                $(".worker-progress-bar").width("100%");
                this.$progressPane.fadeOut(300);
                this.workerProgressShown = false;
                this.workerGuid = null;
            }
        },

        updateProgressPane: function (workerGuid, status, progress) {
            var msg,
                complete;

            if (!this.workerProgressShown && workerGuid === this.model.get('id')) {
                this.showProgressPane(workerGuid);
            }

            if (this.workerGuid === workerGuid) {
                if (status === "queued") {
                    msg = deps.translate("JOB_QUEUED");
                    $(".worker-progress-bar").width(0);
                } else {
                    complete = (!isNaN(progress) ? Math.round(progress) : 0) + "%";
                    msg = deps.translate("PROCESSING") + " " + complete + "...";
                    $(".worker-progress-bar").width(complete);
                }
                $('.worker-status').text(msg);
            }
        },

        clearLayerFocus: function () {
            var $container = this.$el.find('#psdpreview_root');
            this.$defocused.remove();
            $container.children().removeClass('focused');
        },
        
        addLayerSpritesBackToFront: function (model) {
            var queue = [],
                currentModel = model,
                parentModel,
                childTmplId,
                childElem,
                flattenedElem,
                flattenedSpriteLayers = [],
                bounds,
                previewRoot = this.$el.find('#psdpreview_root'),
                parentContainer,
                zIndex = 0,
                isGroup,
                layerCollection;

            var waitingOnFlattenedParent = [];
            while (currentModel) {
                isGroup = (currentModel.get('type') === Constants.Type.LAYER_GROUP) || (currentModel.get('type') === Constants.Type.LAYER_ARTBOARD);
                parentModel = currentModel.get('parentModel');
                if (parentModel) {
                    childTmplId = isGroup ? PSDPreviewDivSpriteSheetTemplate : PSDPreviewLayerTemplate;
                    childElem = $(TemplateUtil.createTemplate(childTmplId, currentModel.toJSON()));
                    
                    if (currentModel.get('type') === Constants.Type.LAYER_ARTBOARD) {
                        childElem.addClass('artboard');
                    }
                    
                    flattenedElem = null;
                    bounds = currentModel.get('bounds');
                    MeasurementUtil.setLayerBounds(childElem, false, currentModel, parentModel, SelectionController);

                    childElem.attr('id', 'layer' + currentModel.get('layerId'));

                    if (currentModel.isVisibleOnStage()) {
                        childElem.show();
                    } else {
                        childElem.hide();
                    }

                    parentContainer = LayerModelMap.getLayerInfoForId(parentModel.get('layerId')) ?
                            LayerModelMap.getLayerInfoForId(parentModel.get('layerId')).item : null;
                    if (!parentContainer) {
                        parentContainer = previewRoot;
                    }

                    if (currentModel.get('flattenedSprite')) {
                        flattenedElem = $(TemplateUtil.createTemplate(PSDPreviewLayerTemplate, currentModel.toJSON()));
                        flattenedElem.attr('id', 'flattenedLayer' + currentModel.get('layerId'));
                        var curBounds = MeasurementUtil.setLayerBounds(flattenedElem, true, currentModel, parentModel, SelectionController);
                        parentContainer.append(flattenedElem);
                        FlattenedAssetController.applyParentAndPosition(currentModel, curBounds,
                                flattenedElem, currentModel.get('flattenedSprite'));
                        flattenedSpriteLayers.push(currentModel);

                        if (!isGroup) {
                            childElem.css('visibility', 'hidden');
                        }
                        
                        //Hide elems currently waiting on this as their flattenedParent
                        for (var i = waitingOnFlattenedParent.length - 1; i >= 0; --i) {
                            if (this.shouldFlattenedParentObscure(waitingOnFlattenedParent[i].model, currentModel)) {
                                waitingOnFlattenedParent[i].item.css('visibility', 'hidden');
                                waitingOnFlattenedParent.splice(i, 1);
                            }
                        }
                    }
                    
                    if (currentModel.get('flattenedParent')) {
                        //Hide the layer if it's involved in a blend mode, but only if the blend mode is not in an artboard, or if both belong to the same artboard
                        var flattenedParentModel = LayerModelMap.getLayerModelFromId(currentModel.get('flattenedParent'));
                        if (!flattenedParentModel) {
                            waitingOnFlattenedParent.push({item: childElem, model: currentModel});
                        } else if (this.shouldFlattenedParentObscure(currentModel, flattenedParentModel)) {
                            childElem.css('visibility', 'hidden');
                        }
                    }

                    LayerModelMap.addLayerToMap(currentModel.get('layerId'), childElem, flattenedElem, currentModel);
                    
                    // Load our flattened sprite representation.
                    if (flattenedElem) {
                        this.loadSpriteSheet(flattenedElem, currentModel, currentModel.get('flattenedSprite'), true);
                    }

                    // Also load our single sprite representation (if not a layer set)
                    if (!isGroup) {
                        this.loadSpriteSheet(childElem, currentModel, currentModel.get('spriteSheet'));
                    } else {
                        if (currentModel.get('properties') && currentModel.get('properties').get('blendOptions')) {
                            var blendOptions = currentModel.get('properties').get('blendOptions'),
                                opacity = (blendOptions.opacity || 100) / 100,
                                fillOpacity = (blendOptions.fillOpacity || 100) / 100;
                            childElem.css('opacity', opacity * fillOpacity);
                        }
                    }
                    
                    parentContainer.append(childElem);

                    this.addEventHandlers(currentModel, parentContainer, previewRoot);
                }

                layerCollection = currentModel.get('layerCollection');
                if (layerCollection) {
                    /*jshint loopfunc: true */
                    layerCollection.each(function (layerModel) {
                        layerModel.set('parentModel', currentModel);
                        layerModel.set('zIndex', zIndex);
                        zIndex++;
                        queue.push(layerModel);
                    });
                    /*jshint loopfunc: false */
                }
                currentModel = queue.shift();
            }

            // Hide our flattened sprite(s) if none of their members are visible.
            _.each(flattenedSpriteLayers, function (layer) {
                var flattenedSprite = layer.get('flattenedSprite'),
                    curLayerModel,
                    allHidden = true,
                    $flattenedSpriteElem;

                _.each(flattenedSprite.includedLayers, function (layerId) {
                    curLayerModel = LayerModelMap.getLayerModelFromId(layerId);
                    if (curLayerModel.getEffectiveVisibility()) {
                        allHidden = false;
                    }
                });

                if (allHidden) {
                    $flattenedSpriteElem = LayerModelMap.getLayerInfoForId(layer.get('layerId')).flattenedItem;
                    if ($flattenedSpriteElem) {
                        $flattenedSpriteElem.css('visibility', 'hidden');
                    }
                }
            });
        },
        
        handleClickEvent: function (event) {
            if ($(event.target).parents('.popup').length || $(event.target).hasClass('popup')) {
                return;
            }
            var activeTool = DetailsController.get('activeTool');
            if (activeTool === Constants.Tool.SELECT_DIRECT) {
                // Select the item
                if (!event.altKey || Constants.Features.SELECTION_B) {
                    SelectionController.set("shouldScrollLayers", true);
                    SelectionController.selectItemAtPoint(event.clientX + $(document).scrollLeft(),
                                    event.clientY + $(document).scrollTop(),
                                    KeyboardUtils.isMultiSelectKey(event));
                } else {
                    this.handleLayerFocus(event);
                }
                event.stopPropagation();
            }
        },

        logCanvasClick: function (eventName, x, y) {
            var hitItemModel = SelectionController.itemAtPoint(x, y),
                hitType = hitItemModel ? hitItemModel.get('type') : 'nothingUnderCursor';

            graphite.events.trigger('canvasClick', {type: eventName, layerType: hitType });
        },
        
        layerCompChanging: function () {
            $('.preview-loading-spinner').remove();
            this.renderSpinner();
        },

        handleAssetExtracted: function (params) {
            if (this.extractTooltip) {
                var date = new Date();
                var tooltipDuration = Constants.NotificationTooltip.MIN_DURATION - (date.getTime() - this.extractionStartTime);
                date = null;
                var self = this;
                if (tooltipDuration <= 0) {
                    this.extractTooltip.remove();
                    this.extractTooltip = null;
                } else {
                    setTimeout(function () {
                        if (self.extractTooltip) {
                            self.extractTooltip.remove();
                            self.extractTooltip = null;
                        }
                    }, tooltipDuration);
                }
            }
        },

        handleMultiSelectPending: function (pending) {
            var selection = SelectionController.getSelectedLayers();
            $('.extract-plugin').toggleClass('measure-pending', pending && selection.length === 1);
        },

        handleSelectionChanged: function (fromPreviewView) {
            var selection = SelectionController.getSelectedLayers(),
                bounds,
                previewEl = $('div.psd-preview-view'),
                previewAreaBounds;
            
            if (selection.length !== 1) {
                $('.extract-plugin').removeClass('measure-pending');
            }
            
            if (selection.length >= 1) {
                bounds = MeasurementUtil.calcLayerGroupBounds(selection, SelectionController.getPSDModel());
                bounds = ZoomController.zoomRect(bounds);

                previewAreaBounds = previewEl[0].getBoundingClientRect();

                //Translate the bounds of the layer in the PSD into browser coordinates
                bounds.top += previewAreaBounds.top - previewEl.scrollTop();
                bounds.bottom += previewAreaBounds.top - previewEl.scrollTop();
                bounds.left += previewAreaBounds.left - previewEl.scrollLeft();
                bounds.right += previewAreaBounds.left - previewEl.scrollLeft();
                if (!fromPreviewView) {
                    ScrollUtils.scrollRectIntoView(previewEl, bounds);
                }
            }
        },

        showSuccessTooltip: function (message) {
            var measurementPopup = this.$el.find('#measurementPopupInfo');
            var $parent = measurementPopup.parent();
            $parent.append('<div id="notificationTooltip"><span>' + message + '</span></div>');
            this.extractTooltip = $('#notificationTooltip');
            var tipWidth = this.extractTooltip.width();
            var tipHeight = this.extractTooltip.height();
            var hScrollPos =  $('div.psd-preview-view').scrollLeft();
            var tipPosLeft = measurementPopup.position().left + (measurementPopup.outerWidth() / 2) - (tipWidth / 2);
            var previewAreaWidth = $('div.psd-preview-view').width() + hScrollPos - 10;
            //Check to make sure our placement doesn't get cut off by borders
            if (tipPosLeft < hScrollPos) {
                tipPosLeft = hScrollPos;
            }
            if ((tipPosLeft + tipWidth) > previewAreaWidth) {
                tipPosLeft = previewAreaWidth - tipWidth;
            }
            var tipPosTop;
            if (measurementPopup.hasClass('bottom')) {
                //Place the tooltip just below the blue HUD
                tipPosTop = measurementPopup.position().top + measurementPopup.outerHeight() + 5;
            } else {
                //Place the tooltip just above the blue HUD
                tipPosTop = measurementPopup.position().top - tipHeight - 15;
            }
            this.extractTooltip.css({top: tipPosTop, left: tipPosLeft});
            this.extractTooltip.fadeIn(250);
        },
		
		checkModelForIncompatibilities: function (model) {
            var props = model.get("properties"),
                blendOptions = props ? props.get("blendOptions") : null;

            if (!blendOptions) {
                return;
            }
        },
        
        removeEventListeners: function () {
            this.model.off(null, null, this);
            graphite.events.off(null, null, this);
            this.$el.off(".dropper");
            $(window).off(".dropper");
            this.$el.removeData().unbind();
        },
        
        applyCurrentZoomLevel: function (skipTransition, scrollRect) {
            var $previewContainer = this.$el.find('div.preview-container'),
                $previewRoot = this.$el.find('#psdpreview_root'),
                $preview = this.$el.find('#tempPreview'),
                $selectionOverlay = this.$el.find('#selection-overlay'),
                bounds = this.model.get('imgdata').bounds,
                size = {width: bounds.right, height: bounds.bottom};

            if (this.selectionOverlay && this.selectionOverlay.$el) {
                ZoomController.zoomElementSize($selectionOverlay, size);
            }
            if (!this.tempPreviewHidden || skipTransition) {
                //Check the height & width to the view area width & height and determine if we need to add padding to center it
                var viewWidth = this.$el.outerWidth() - Constants.PREVIEW_SCROLLBAR_SPACING,  //Account for scrollbar width
                    viewHeight = this.$el.outerHeight() - Constants.PREVIEW_SCROLLBAR_SPACING,//and scrollbar height
                    scaledSize = ZoomController.zoomSize(size),
                    leftPosPadding = Math.max(0, Math.round((viewWidth - scaledSize.width)/2)),
                    topPosPadding = Math.max(0, Math.round((viewHeight - scaledSize.height)/2));

                if (leftPosPadding > 0 || topPosPadding > 0) {
                    $previewContainer.css({top: topPosPadding, left: leftPosPadding});
                }

                //This is before the PSD is fully loaded when we still show the temp preview, so don't scale using transitions
                ZoomController.zoomElementScale($preview);
                ZoomController.zoomElementSize($previewContainer, size);
                ZoomController.zoomElementScale($previewRoot);
            } else {
                //PSD is fully ready, scale using transitions
                ZoomController.zoomAndScrollElements($previewRoot, this.$el, scrollRect, $previewContainer, size,
                                                     SelectionController.getPSDModel().get('isArtboardPSD'));
            }

            // Ensure when zoomed > 100% that we don't clip to unscaled bounds.
            $previewRoot.css('overflow', ZoomController.getZoomLevel() > 1 ? 'visible' : 'hidden');
        },

        handleDropperStart: function () {
            var $lensGrid = this.$psdLens.find('.lens-grid'),
                miniCanvas = $('<canvas/>')[0],
                miniContext = miniCanvas.getContext('2d'),
                lastColor,
                gridSize = 7,
                halfGrid = 3,
                self = this;

            $('.extract-plugin').append(this.$psdLens);
            this.$psdLens.hide();

            miniCanvas.width = gridSize;
            miniCanvas.height = gridSize;

            this.$el.on('mousedown.dropper', function (event) {
                // Notify everyone that the user has extracted a color.
                if (lastColor) {
                    graphite.events.trigger('commitDropperColor', lastColor);
                }
            });

            $(window).on('click.dropper', function (event) {
                SelectionController.disableDropperTool();
                $('.dropper-item .color-chip').css('background-color', '');
                self.$psdLens.css('border-color', '');
                event.preventDefault();
                event.stopPropagation();
            });

            this.$el.on('mouseleave.dropper', function (event) {
                self.$psdLens.hide();
            });

            this.$el.on('mouseenter.dropper', function (event) {
                self.$psdLens.show();
            });

            this.$el.on('mousemove.dropper', function (event) {

                var topLeft = SelectionController.convertClientPtToPSDPt(event.clientX - halfGrid, event.clientY - halfGrid),
                    lensBounds = {
                        top: Math.round(topLeft.y),
                        left: Math.round(topLeft.x),
                        bottom: Math.round(topLeft.y) + gridSize,
                        right: Math.round(topLeft.x) + gridSize
                    },
                    intersects = SelectionController.findIntersectsBackToFront(lensBounds),
                    i;

                self.$psdLens.show();

                // Clear mini context
                miniContext.clearRect(0, 0, gridSize, gridSize);

                // Composite all sprites that intersected with our lens in
                // our mini 7x7 canvas.
                for (i = intersects.length - 1; i >= 0; i--) {
                    var bits = intersects[i].intersect,
                        item = intersects[i].item,
                        canvasItem,
                        element;

                    if (item.get('flattenedSprite')) {
                        canvasItem = LayerModelMap.getLayerInfoForId(item.get('layerId')).flattenedItem;
                    } else {
                        canvasItem = LayerModelMap.getLayerInfoForId(item.get('layerId')).item;
                    }
                    element = canvasItem[0];
                    miniContext.drawImage(element, bits.left, bits.top, bits.width, bits.height, bits.destLeft, bits.destTop, bits.width, bits.height);
                }

                // Let's note the current color at the center of
                // our lens, and update our swatches for this frame.
                var pixel = miniContext.getImageData(halfGrid, halfGrid, 1, 1);
                lastColor = null;
                if (pixel.data && pixel.data.length >= 4 && pixel.data[3] > 0) {
                    lastColor = {};
                    lastColor.red = pixel.data[0];
                    lastColor.green = pixel.data[1];
                    lastColor.blue = pixel.data[2];
                    lastColor.alpha = pixel.data[3] / 255;
                    var rgb = 'rgb(' + lastColor.red + ',' + lastColor.green + ',' + lastColor.blue + ')';
                    $('.dropper-item .color-chip').css('background-color', rgb);
                    self.$psdLens.css('border-color', rgb);
                } else {
                    $('.dropper-item .color-chip').css('background-color', '');
                    self.$psdLens.css('border-color', '');
                }

                // Now we blit to our larger lens making sure that result is pixellated.
                var lensContext = $lensGrid[0].getContext('2d');
                lensContext.webkitImageSmoothingEnabled = false;
                lensContext.mozImageSmoothingEnabled = false;
                lensContext.msImageSmoothingEnabled = false;
                lensContext.clearRect(0, 0, gridSize * 10, gridSize * 10);
                lensContext.drawImage(miniCanvas, 0, 0, gridSize, gridSize, 0, 0, gridSize * 10, gridSize * 10);

                // Move our updated lens to current cursor position.
                self.$psdLens.css({
                    left: event.clientX + 'px',
                    top: event.clientY - $('.extract-plugin').offset().top +   'px'
                });
            });
        },
        
        handleDropperStop: function () {
            $(window).off('.dropper');
            this.$el.off('.dropper');
            $('.dropper-item .color-chip').css('background-color', '');
            if (this.$psdLens && this.$psdLens.length > 0) {
                this.$psdLens.hide();
            }
        }
        
    });

    return DwPSDPreviewView;
});
