/*
 * Copyright (C) 2013, 2015 Apple 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:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.FrameTreeElement = class FrameTreeElement extends WI.ResourceTreeElement
{
    constructor(frame)
    {
        console.assert(frame instanceof WI.Frame);

        super(frame.mainResource, frame);

        this._frame = frame;

        this._updateExpandedSetting();

        frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
        frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
        frame.addEventListener(WI.Frame.Event.ResourceWasRemoved, this._resourceWasRemoved, this);
        frame.addEventListener(WI.Frame.Event.ExtraScriptAdded, this._extraScriptAdded, this);
        frame.addEventListener(WI.Frame.Event.ChildFrameWasAdded, this._childFrameWasAdded, this);
        frame.addEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this);

        this.shouldRefreshChildren = true;
        this.folderSettingsKey = this._frame.url.hash;

        this.registerFolderizeSettings("frames", WI.UIString("Frames"), this._frame.childFrameCollection, WI.FrameTreeElement);
        this.registerFolderizeSettings("extra-scripts", WI.UIString("Extra Scripts"), this._frame.extraScriptCollection, WI.ScriptTreeElement);

        function forwardingConstructor(representedObject, ...extraArguments) {
            if (representedObject instanceof WI.CSSStyleSheet)
                return new WI.CSSStyleSheetTreeElement(representedObject, ...extraArguments);
            return new WI.ResourceTreeElement(representedObject, ...extraArguments);
        }

        for (let [key, value] of Object.entries(WI.Resource.Type)) {
            let folderName = WI.Resource.displayNameForType(value, true);

            let treeElementConstructor = forwardingConstructor;
            if (value === WI.Resource.Type.WebSocket)
                treeElementConstructor = WI.WebSocketResourceTreeElement;

            this.registerFolderizeSettings(key, folderName, this._frame.resourceCollectionForType(value), treeElementConstructor);
        }

        this.updateParentStatus();
    }

    // Public

    get frame()
    {
        return this._frame;
    }

    descendantResourceTreeElementTypeDidChange(resourceTreeElement, oldType)
    {
        // Called by descendant ResourceTreeElements.

        // Add the tree element again, which will move it to the new location
        // based on sorting and possible folder changes.
        this._addTreeElement(resourceTreeElement);
    }

    descendantResourceTreeElementMainTitleDidChange(resourceTreeElement, oldMainTitle)
    {
        // Called by descendant ResourceTreeElements.

        // Add the tree element again, which will move it to the new location
        // based on sorting and possible folder changes.
        this._addTreeElement(resourceTreeElement);
    }

    // Overrides from SourceCodeTreeElement.

    updateSourceMapResources()
    {
        // Frames handle their own SourceMapResources.

        if (!this.treeOutline || !this.treeOutline.includeSourceMapResourceChildren)
            return;

        if (!this._frame)
            return;

        this.updateParentStatus();

        if (this.resource && this.resource.sourceMaps.length)
            this.shouldRefreshChildren = true;
    }

    onattach()
    {
        // Immediate superclasses are skipped, since Frames handle their own SourceMapResources.
        WI.GeneralTreeElement.prototype.onattach.call(this);

        WI.cssStyleManager.addEventListener(WI.CSSStyleManager.Event.StyleSheetAdded, this._styleSheetAdded, this);
    }

    ondetach()
    {
        WI.cssStyleManager.removeEventListener(WI.CSSStyleManager.Event.StyleSheetAdded, this._styleSheetAdded, this);

        super.ondetach();
    }

    // Overrides from FolderizedTreeElement (Protected).

    compareChildTreeElements(a, b)
    {
        if (a === b)
            return 0;

        var aIsResource = a instanceof WI.ResourceTreeElement;
        var bIsResource = b instanceof WI.ResourceTreeElement;

        if (aIsResource && bIsResource)
            return WI.ResourceTreeElement.compareResourceTreeElements(a, b);

        if (!aIsResource && !bIsResource) {
            // When both components are not resources then default to base class comparison.
            return super.compareChildTreeElements(a, b);
        }

        // Non-resources should appear before the resources.
        // FIXME: There should be a better way to group the elements by their type.
        return aIsResource ? 1 : -1;
    }

    // Overrides from TreeElement (Private).

    onpopulate()
    {
        if (this.children.length && !this.shouldRefreshChildren)
            return;
        this.shouldRefreshChildren = false;

        this.removeChildren();
        this.updateParentStatus();
        this.prepareToPopulate();

        for (let frame of this._frame.childFrameCollection)
            this.addChildForRepresentedObject(frame);

        for (let resource of this._frame.resourceCollection)
            this.addChildForRepresentedObject(resource);

        var sourceMaps = this.resource && this.resource.sourceMaps;
        for (var i = 0; i < sourceMaps.length; ++i) {
            var sourceMap = sourceMaps[i];
            for (var j = 0; j < sourceMap.resources.length; ++j)
                this.addChildForRepresentedObject(sourceMap.resources[j]);
        }

        for (let extraScript of this._frame.extraScriptCollection) {
            if (extraScript.sourceURL || extraScript.sourceMappingURL)
                this.addChildForRepresentedObject(extraScript);
        }

        const doNotCreateIfMissing = true;
        WI.cssStyleManager.preferredInspectorStyleSheetForFrame(this._frame, this.addRepresentedObjectToNewChildQueue.bind(this), doNotCreateIfMissing);
    }

    onexpand()
    {
        this._expandedSetting.value = true;
    }

    oncollapse()
    {
        // Only store the setting if we have children, since setting hasChildren to false will cause a collapse,
        // and we only care about user triggered collapses.
        if (this.hasChildren)
            this._expandedSetting.value = false;
    }

    // Private

    _updateExpandedSetting()
    {
        this._expandedSetting = new WI.Setting("frame-expanded-" + this._frame.url.hash, this._frame.isMainFrame() ? true : false);
        if (this._expandedSetting.value)
            this.expand();
        else
            this.collapse();
    }

    _mainResourceDidChange(event)
    {
        this._updateResource(this._frame.mainResource);

        this.updateParentStatus();
        this.removeChildren();

        // Change the expanded setting since the frame URL has changed. Do this before setting shouldRefreshChildren, since
        // shouldRefreshChildren will call onpopulate if expanded is true.
        this._updateExpandedSetting();

        this.shouldRefreshChildren = true;
    }

    _resourceWasAdded(event)
    {
        this.addRepresentedObjectToNewChildQueue(event.data.resource);
    }

    _resourceWasRemoved(event)
    {
        this.removeChildForRepresentedObject(event.data.resource);
    }

    _extraScriptAdded(event)
    {
        let extraScript = event.data.script;
        if (extraScript.sourceURL || extraScript.sourceMappingURL)
            this.addRepresentedObjectToNewChildQueue(extraScript);
    }

    _childFrameWasAdded(event)
    {
        this.addRepresentedObjectToNewChildQueue(event.data.childFrame);
    }

    _childFrameWasRemoved(event)
    {
        this.removeChildForRepresentedObject(event.data.childFrame);
    }

    _styleSheetAdded(event)
    {
        if (!event.data.styleSheet.isInspectorStyleSheet())
            return;

        this.addRepresentedObjectToNewChildQueue(event.data.styleSheet);
    }
};
