# Source: ui/overlay.reel/overlay.js

    /*global require, exports, console, MontageElement */

    /**
     * @module "ui/overlay.reel"
     */
    var Component = require("../component").Component,
        KeyComposer = require("../../composer/key-composer").KeyComposer,
        PressComposer = require("../../composer/press-composer").PressComposer,
        defaultEventManager = require("../../core/event/event-manager").defaultEventManager;

    var CLASS_PREFIX = "montage-Overlay",
        VISIBLE_CLASS_NAME = CLASS_PREFIX + "--visible";

    /**
     *
     * @class Overlay
     * @extends Component
     */
    var Overlay = exports.Overlay = Component.specialize( /** @lends Overlay.prototype # */ {

        /**
         * Dispatched when the user dismiss the overlay by clicking outside of it.
         * @event dismiss
         * @memberof Overlay
         * @param {Event} event
         */
        __pressComposer: {
            value: null
        },

        _pressComposer: {
            get: function () {
                if (!this.__pressComposer) {
                    this.__pressComposer = new PressComposer();
                    this.__pressComposer.delegate = this;
                    this.addComposerForElement(this._pressComposer, this.element.ownerDocument);
                }

                return this.__pressComposer;
            }
        },

        __keyComposer: {
            value: null
        },

        _keyComposer: {
            get: function () {
                if (!this.__keyComposer) {
                    this.__keyComposer = new KeyComposer();
                    this.__keyComposer.keys = "escape";
                    this.__keyComposer.identifier = "escape";
                    this.addComposerForElement(this.__keyComposer, this.element.ownerDocument.defaultView);
                }

                return this.__keyComposer;
            }
        },

        _anchor: {
            value: null
        },

        /**
         * The anchor element or component to display the overlay next to.
         */
        anchor: {
            set: function (value) {
                this._anchor = value;
                this.needsDraw = true;
            },
            get: function () {
                return this._anchor;
            }
        },

        _position: {
            value: null
        },

        /**
         * Where to position the overlay in the screen.
         * @type {{left: number, top: number}}
         */
        position: {
            set: function (value) {
                this._position = value;
                this.needsDraw = true;
            },
            get: function () {
                return this._position;
            }
        },

        // Value used to store the position where the overlay will be drawn.
        // This position is calculated at willDraw time and used at draw.
        _drawPosition: {
            value: null
        },

        _isShown: {
            value: false
        },

        isShown: {
            get: function () {
                return this._isShown;
            }
        },

        _resizeHandlerTimeout: {
            value: null
        },

        _previousActiveTarget: {
            value: null
        },

        /**
         * A delegate that can implement `willPositionOverlay` and/or
         * `shouldDismissOverlay`.
         *
         * - `willPositionOverlay(overlay, calculatedPosition)` is called when the
         *   overlay is being shown, and should return an object with `top` and
         *   `left` properties.
         * - `shouldDismissOverlay(overlay, target, event)` is called when the user
         *   clicks outside of the overlay or presses escape inside the overlay.
         *   Usually this will hide the overlay. Return `true` to hide the overlay,
         *   or `false` to leave the overlay visible.
         * @type {Object}
         */
        delegate: {
            value: null
        },

        _dismissOnExternalInteraction: {
            value: true
        },

        dismissOnExternalInteraction: {
            set: function (value) {
                if (value !== this._dismissOnExternalInteraction) {
                    this._dismissOnExternalInteraction = value;

                    if (value) {
                        this._pressComposer.addEventListener("pressStart", this, false);
                    } else {
                        this._pressComposer.removeEventListener("pressStart", this, false);
                    }
                }
            },
            get: function () {
                return this._dismissOnExternalInteraction;
            }
        },

        enterDocument: {
            value: function (firstTime) {
                if (firstTime) {
                    // Need to move the element to be a child of the document to
                    // escape possible offset parent container.
                    this.element.ownerDocument.body.appendChild(this.element);
                    this.attachToParentComponent();
                }
            }
        },

        /**
         * Show the overlay. The overlay is displayed at the position determined by
         * the following conditions:
         *
         * 1. If a delegate is provided and the willPositionOverlay function is
         *    implemented, the position is always determined by the delegate.
         * 2. If "position" is set, the overlay is always displayed at this
         *    location.
         * 3. If an anchor is set, the overlay is displayed below the anchor.
         * 4. If no positional hints are provided, the overlay is displayed at the
         *    center of the screen.
         *
         * FIXME: We have to add key events on both this component and the keyComposer
         * because of a bug in KeyComposer.
         */
        show: {
            value: function () {
                if (!this._isShown) {
                    if (this.isModal) {
                        this._previousActiveTarget = defaultEventManager.activeTarget;
                        defaultEventManager.activeTarget = this;
                        if (defaultEventManager.activeTarget !== this) {
                            console.warn("Overlay " + this.identifier + " can't become the active target because ", defaultEventManager.activeTarget, " didn't surrender it.");
                            return;
                        }
                    }

                    this.attachToParentComponent();
                    this.classList.add(VISIBLE_CLASS_NAME);
                    this.loadComposer(this._pressComposer);
                    this.loadComposer(this._keyComposer);
                    this._isShown = true;
                    this.needsDraw = true;

                    this._keyComposer.addEventListener("keyPress", this, false);
                    this.element.ownerDocument.defaultView.addEventListener("resize", this);

                    if (this._dismissOnExternalInteraction) {
                        this._pressComposer.addEventListener("pressStart", this, false);
                    }
                }
            }
        },

        hide: {
            value: function () {
                if (this._isShown) {
                    // detachFromParentComponent happens at didDraw
                    this.classList.remove(VISIBLE_CLASS_NAME);
                    this.unloadComposer(this._pressComposer);
                    this.unloadComposer(this._keyComposer);
                    this._isShown = false;
                    this.needsDraw = true;

                    if (this.isModal) {
                        defaultEventManager.activeTarget = this._previousActiveTarget;
                    }

                    this._keyComposer.removeEventListener("keyPress", this, false);
                    this.element.ownerDocument.defaultView.removeEventListener("resize", this);

                    if (this._dismissOnExternalInteraction) {
                        this._pressComposer.removeEventListener("pressStart", this, false);
                    }
                }
            }
        },

        isModal: {
            value: true
        },

        shouldComposerSurrenderPointerToComponent: {
            value: function (composer, pointer, component) {
                if (component && component.element && !this.element.contains(component.element)) {
                    this.hide();
                }

                return true;
            }
        },

        /**
         * The overlay should only surrender focus if it is hidden, non-modal, or
         * if the other component is one of its descendants.
         */
        surrendersActiveTarget: {
            value: function (candidateActiveTarget) {
                var response = !(this.isModal && this.isShown),
                    delegateResponse;

                if (!response && candidateActiveTarget && candidateActiveTarget.element) {
                    response = this.element.contains(candidateActiveTarget.element);
                }

                delegateResponse = this.callDelegateMethod(
                    "overlayShouldDismissOnSurrenderActiveTarget", this, candidateActiveTarget, response
                );

                return delegateResponse !== void 0 ? delegateResponse : response;
            }
        },

        // Event handlers

        handleResize: {
            value: function () {
                if (this.isShown) {
                    this.needsDraw = true;
                }
            }
        },

        handlePressStart: {
            value: function (event) {
                if (!this.element.contains(event.targetElement)) {
                    this.dismissOverlay(event);
                }
            }
        },

        handleKeyPress: {
            value: function (event) {
                if (event.identifier === "escape") {
                    this.dismissOverlay(event);
                }
            }
        },

        /**
         * User event has requested that we dismiss the overlay. Give the delegate
         * an opportunity to prevent it. Returns whether the overlay was hidden.
         */
        dismissOverlay: {
            value: function (event) {
                var shouldDismissOverlay = false;
                if (this._isShown) {
                    shouldDismissOverlay = this.callDelegateMethod("shouldDismissOverlay", this, event.targetElement, event.type);

                    if (shouldDismissOverlay === void 0 || shouldDismissOverlay) {
                        this.hide();
                        this._dispatchDismissEvent();
                    }
                }

                return shouldDismissOverlay;
            }
        },

        // Draw

        willDraw: {
            value: function () {
                if (this._isShown) {
                    this._calculatePosition();

                } else {
                    this.callDelegateMethod("didHideOverlay", this);
                }
            }
        },

        draw: {
            value: function () {
                if (this._isShown) {
                    var position = this._drawPosition;

                    this.element.style.top = position.top + "px";
                    this.element.style.left = position.left + "px";
                    this.element.style.visibility = "visible";

                    this.callDelegateMethod("didShowOverlay", this);

                } else {
                    this.element.style.visibility = "hidden";
                }
            }
        },

        didDraw: {
            value: function () {
                if (!this._isShown) {
                    this.detachFromParentComponent();
                }
            }
        },

        _calculatePosition: {
            value: function () {
                var position,
                    delegatePosition;

                if (this.position) {
                    position = this.position;
                } else if (this.anchor) {
                    position = this._calculateAnchorPosition();
                } else {
                    position = this._calculateCenteredPosition();
                }

                delegatePosition = this.callDelegateMethod("willPositionOverlay", this, position);

                if (delegatePosition) {
                    position = delegatePosition;
                }

                this._drawPosition = position;
            }
        },

        _calculateAnchorPosition: {
            value: function () {
                var anchor = this.anchor,
                    width = this.element.offsetWidth,
                    anchorPosition = anchor.getBoundingClientRect(),
                    anchorHeight = anchor.offsetHeight || 0,
                    anchorWidth = anchor.offsetWidth || 0,
                    position;

                position = {
                    top: anchorPosition.top + anchorHeight,
                    left: anchorPosition.left + (anchorWidth / 2) - (width / 2)
                };

                if (position.left < 0) {
                    position.left = 0;
                }

                return position;
            }
        },

        _calculateCenteredPosition: {
            value: function () {
                var defaultView = this.element.ownerDocument.defaultView,
                    viewportHeight = defaultView.innerHeight,
                    viewportWidth = defaultView.innerWidth,
                    height = this.element.offsetHeight,
                    width = this.element.offsetWidth;

                return {
                    top: (viewportHeight / 2 - (height / 2)),
                    left: (viewportWidth / 2 - (width / 2))
                };
            }
        },

        _dispatchDismissEvent: {
            value: function () {
                var dismissEvent = document.createEvent("CustomEvent");

                dismissEvent.initCustomEvent("dismiss", true, true, null);

                this.dispatchEvent(dismissEvent);
            }
        }

    });

    if (window.MontageElement) {
        MontageElement.define("montage-overlay", Overlay);
    }