/*
 * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WI.ContextMenuItem = class ContextMenuItem extends WI.Object
{
    constructor(topLevelMenu, type, label, disabled, checked)
    {
        super();

        this._type = type;
        this._label = label;
        this._disabled = disabled;
        this._checked = checked;
        this._contextMenu = topLevelMenu || this;

        if (type === "item" || type === "checkbox")
            this._id = topLevelMenu.nextId();
    }

    // Public

    id()
    {
        return this._id;
    }

    type()
    {
        return this._type;
    }

    isEnabled()
    {
        return !this._disabled;
    }

    setEnabled(enabled)
    {
        this._disabled = !enabled;
    }

    // Private

    _buildDescriptor()
    {
        switch (this._type) {
        case "item":
            return {type: "item", id: this._id, label: this._label, enabled: !this._disabled};
        case "separator":
            return {type: "separator"};
        case "checkbox":
            return {type: "checkbox", id: this._id, label: this._label, checked: !!this._checked, enabled: !this._disabled};
        }
    }
};

WI.ContextSubMenuItem = class ContextSubMenuItem extends WI.ContextMenuItem
{
    constructor(topLevelMenu, label, disabled)
    {
        super(topLevelMenu, "subMenu", label, disabled);

        this._items = [];
    }

    // Public

    appendItem(label, handler, disabled)
    {
        let item = new WI.ContextMenuItem(this._contextMenu, "item", label, disabled);
        this.pushItem(item);
        this._contextMenu._setHandler(item.id(), handler);
        return item;
    }

    appendSubMenuItem(label, disabled)
    {
        let item = new WI.ContextSubMenuItem(this._contextMenu, label, disabled);
        this.pushItem(item);
        return item;
    }

    appendCheckboxItem(label, handler, checked, disabled)
    {
        let item = new WI.ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked);
        this.pushItem(item);
        this._contextMenu._setHandler(item.id(), handler);
        return item;
    }

    appendHeader(label)
    {
        return this.appendItem(label, () => {
            console.assert(false, "not reached");
        }, true);
    }

    appendSeparator()
    {
        if (this._items.length)
            this._pendingSeparator = true;
    }

    pushItem(item)
    {
        if (this._pendingSeparator) {
            this._items.push(new WI.ContextMenuItem(this._contextMenu, "separator"));
            this._pendingSeparator = null;
        }
        this._items.push(item);
    }

    isEmpty()
    {
        return !this._items.length;
    }

    // Private

    _buildDescriptor()
    {
        if (this.isEmpty())
            return null;

        let subItems = this._items.map((item) => item._buildDescriptor()).filter((item) => !!item);
        return {type: "subMenu", label: this._label, enabled: !this._disabled, subItems};
    }
};

WI.ContextMenu = class ContextMenu extends WI.ContextSubMenuItem
{
    constructor(event)
    {
        super(null, "");

        this._event = event;
        this._handlers = {};
        this._id = 0;

        this._beforeShowCallbacks = [];
    }

    // Static

    static createFromEvent(event, onlyExisting = false)
    {
        if (!event[WI.ContextMenu.ProposedMenuSymbol] && !onlyExisting)
            event[WI.ContextMenu.ProposedMenuSymbol] = new WI.ContextMenu(event);

        return event[WI.ContextMenu.ProposedMenuSymbol] || null;
    }

    static contextMenuItemSelected(id)
    {
        if (WI.ContextMenu._lastContextMenu)
            WI.ContextMenu._lastContextMenu._itemSelected(id);
    }

    static contextMenuCleared()
    {
        // FIXME: Unfortunately, contextMenuCleared is invoked between show and item selected
        // so we can't delete last menu object from WI. Fix the contract.
    }

    // Public

    nextId()
    {
        return this._id++;
    }

    show()
    {
        console.assert(this._event instanceof MouseEvent);

        let menuObject = this._buildDescriptor();
        if (menuObject.length) {
            WI.ContextMenu._lastContextMenu = this;

            if (this._event.type !== "contextmenu" && typeof InspectorFrontendHost.dispatchEventAsContextMenuEvent === "function") {
                console.assert(event.type !== "mousedown" || this._beforeShowCallbacks.length > 0, "Calling show() in a mousedown handler should have a before show callback to enable quick selection.");

                this._menuObject = menuObject;
                this._event.target.addEventListener("contextmenu", this, true);
                InspectorFrontendHost.dispatchEventAsContextMenuEvent(this._event);
            } else
                this._showSoftContextMenu(this._event, menuObject);
        }

        if (this._event)
            this._event.stopImmediatePropagation();
    }

    addBeforeShowCallback(callback)
    {
        this._beforeShowCallbacks.push(callback);
    }

    // Protected

    handleEvent(event)
    {
        console.assert(event.type === "contextmenu");

        for (let callback of this._beforeShowCallbacks)
            callback(this);

        this._event.target.removeEventListener("contextmenu", this, true);
        this._showSoftContextMenu(event, this._menuObject);
        this._menuObject = null;

        event.stopImmediatePropagation();
    }

    // Private

    _setHandler(id, handler)
    {
        if (handler)
            this._handlers[id] = handler;
    }

    _buildDescriptor()
    {
        return this._items.map((item) => item._buildDescriptor()).filter((item) => !!item);
    }

    _itemSelected(id)
    {
        try {
            if (this._handlers[id])
                this._handlers[id].call(this);
        } catch (e) {
            WI.reportInternalError(e);
        }
    }

    _showSoftContextMenu(event, menuObject)
    {
        new WI.SoftContextMenu(menuObject).show(event);
    }
};

WI.ContextMenu.ProposedMenuSymbol = Symbol("context-menu-proposed-menu");
