/*
 * 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.
 */

WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspector.NavigationSidebarPanel
{
    constructor(contentBrowser)
    {
        super("debugger", WebInspector.UIString("Debugger"), true);

        this.contentBrowser = contentBrowser;

        WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
        WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceAdded, this);

        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, this._debuggerCallFramesDidChange, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptAdded, this._scriptAdded, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptRemoved, this._scriptRemoved, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptsCleared, this._scriptsCleared, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this);
        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.WaitingToPause, this._debuggerWaitingToPause, this);

        this._navigationBar = new WebInspector.NavigationBar;
        this.addSubview(this._navigationBar);

        var breakpointsImage = {src: "Images/Breakpoints.svg", width: 15, height: 15};
        var pauseImage = {src: "Images/Pause.svg", width: 15, height: 15};
        var resumeImage = {src: "Images/Resume.svg", width: 15, height: 15};
        var stepOverImage = {src: "Images/StepOver.svg", width: 15, height: 15};
        var stepIntoImage = {src: "Images/StepInto.svg", width: 15, height: 15};
        var stepOutImage = {src: "Images/StepOut.svg", width: 15, height: 15};

        var toolTip = WebInspector.UIString("Enable all breakpoints (%s)").format(WebInspector.toggleBreakpointsKeyboardShortcut.displayName);
        var altToolTip = WebInspector.UIString("Disable all breakpoints (%s)").format(WebInspector.toggleBreakpointsKeyboardShortcut.displayName);

        this._debuggerBreakpointsButtonItem = new WebInspector.ActivateButtonNavigationItem("debugger-breakpoints", toolTip, altToolTip, breakpointsImage.src, breakpointsImage.width, breakpointsImage.height);
        this._debuggerBreakpointsButtonItem.activated = WebInspector.debuggerManager.breakpointsEnabled;
        this._debuggerBreakpointsButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, WebInspector.debuggerToggleBreakpoints, this);
        this._navigationBar.addNavigationItem(this._debuggerBreakpointsButtonItem);

        toolTip = WebInspector.UIString("Pause script execution (%s or %s)").format(WebInspector.pauseOrResumeKeyboardShortcut.displayName, WebInspector.pauseOrResumeAlternateKeyboardShortcut.displayName);
        altToolTip = WebInspector.UIString("Continue script execution (%s or %s)").format(WebInspector.pauseOrResumeKeyboardShortcut.displayName, WebInspector.pauseOrResumeAlternateKeyboardShortcut.displayName);

        this._debuggerPauseResumeButtonItem = new WebInspector.ToggleButtonNavigationItem("debugger-pause-resume", toolTip, altToolTip, pauseImage.src, resumeImage.src, pauseImage.width, pauseImage.height);
        this._debuggerPauseResumeButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, WebInspector.debuggerPauseResumeToggle, this);
        this._navigationBar.addNavigationItem(this._debuggerPauseResumeButtonItem);

        this._debuggerStepOverButtonItem = new WebInspector.ButtonNavigationItem("debugger-step-over", WebInspector.UIString("Step over (%s or %s)").format(WebInspector.stepOverKeyboardShortcut.displayName, WebInspector.stepOverAlternateKeyboardShortcut.displayName), stepOverImage.src, stepOverImage.width, stepOverImage.height);
        this._debuggerStepOverButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, WebInspector.debuggerStepOver, this);
        this._debuggerStepOverButtonItem.enabled = false;
        this._navigationBar.addNavigationItem(this._debuggerStepOverButtonItem);

        this._debuggerStepIntoButtonItem = new WebInspector.ButtonNavigationItem("debugger-step-into", WebInspector.UIString("Step into (%s or %s)").format(WebInspector.stepIntoKeyboardShortcut.displayName, WebInspector.stepIntoAlternateKeyboardShortcut.displayName), stepIntoImage.src, stepIntoImage.width, stepIntoImage.height);
        this._debuggerStepIntoButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, WebInspector.debuggerStepInto, this);
        this._debuggerStepIntoButtonItem.enabled = false;
        this._navigationBar.addNavigationItem(this._debuggerStepIntoButtonItem);

        this._debuggerStepOutButtonItem = new WebInspector.ButtonNavigationItem("debugger-step-out", WebInspector.UIString("Step out (%s or %s)").format(WebInspector.stepOutKeyboardShortcut.displayName, WebInspector.stepOutAlternateKeyboardShortcut.displayName), stepOutImage.src, stepOutImage.width, stepOutImage.height);
        this._debuggerStepOutButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, WebInspector.debuggerStepOut, this);
        this._debuggerStepOutButtonItem.enabled = false;
        this._navigationBar.addNavigationItem(this._debuggerStepOutButtonItem);

        // Add this offset-sections class name so the sticky headers don't overlap the navigation bar.
        this.element.classList.add(WebInspector.DebuggerSidebarPanel.OffsetSectionsStyleClassName);

        this._globalBreakpointsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Global Breakpoints"), null, WebInspector.DebuggerSidebarPanel.GlobalIconStyleClassName);
        this._allExceptionsBreakpointTreeElement = new WebInspector.BreakpointTreeElement(WebInspector.debuggerManager.allExceptionsBreakpoint, WebInspector.DebuggerSidebarPanel.ExceptionIconStyleClassName, WebInspector.UIString("All Exceptions"));
        this._allUncaughtExceptionsBreakpointTreeElement = new WebInspector.BreakpointTreeElement(WebInspector.debuggerManager.allUncaughtExceptionsBreakpoint, WebInspector.DebuggerSidebarPanel.ExceptionIconStyleClassName, WebInspector.UIString("All Uncaught Exceptions"));
        this.suppressFilteringOnTreeElements([this._globalBreakpointsFolderTreeElement, this._allExceptionsBreakpointTreeElement, this._allUncaughtExceptionsBreakpointTreeElement]);

        this.filterBar.placeholder = WebInspector.UIString("Filter Breakpoint List");

        function showResourcesWithIssuesOnlyFilterFunction(treeElement)
        {
            // Keep issues.
            if (treeElement instanceof WebInspector.IssueTreeElement)
                return true;

            // Keep resources with issues.
            if (treeElement.hasChildren) {
                for (let child of treeElement.children) {
                    if (child instanceof WebInspector.IssueTreeElement)
                        return true;
                }
            }
            return false;
        };

        this.filterBar.addFilterBarButton("debugger-show-resources-with-issues-only", showResourcesWithIssuesOnlyFilterFunction, true, WebInspector.UIString("Show only resources with issues."), WebInspector.UIString("Show resources with and without issues."), "Images/Errors.svg", 15, 15);

        this._breakpointsContentTreeOutline = this.contentTreeOutline;

        let breakpointsRow = new WebInspector.DetailsSectionRow;
        breakpointsRow.element.appendChild(this._breakpointsContentTreeOutline.element);

        let breakpointsGroup = new WebInspector.DetailsSectionGroup([breakpointsRow]);
        let breakpointsSection = new WebInspector.DetailsSection("scripts", WebInspector.UIString("Scripts"), [breakpointsGroup]);
        this.contentView.element.appendChild(breakpointsSection.element);

        this._breakpointsContentTreeOutline.element.classList.add("breakpoints");
        this._breakpointsContentTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
        this._breakpointsContentTreeOutline.ondelete = this._breakpointTreeOutlineDeleteTreeElement.bind(this);
        this._breakpointsContentTreeOutline.oncontextmenu = this._breakpointTreeOutlineContextMenuTreeElement.bind(this);

        this._breakpointsContentTreeOutline.appendChild(this._globalBreakpointsFolderTreeElement);
        this._globalBreakpointsFolderTreeElement.appendChild(this._allExceptionsBreakpointTreeElement);
        this._globalBreakpointsFolderTreeElement.appendChild(this._allUncaughtExceptionsBreakpointTreeElement);
        this._globalBreakpointsFolderTreeElement.expand();

        this._callStackContentTreeOutline = this.createContentTreeOutline(true, true);
        this._callStackContentTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);

        this._callStackRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Call Frames"));
        this._callStackRow.showEmptyMessage();

        let callStackGroup = new WebInspector.DetailsSectionGroup([this._callStackRow]);
        this._callStackSection = new WebInspector.DetailsSection("call-stack", WebInspector.UIString("Call Stack"), [callStackGroup]);

        this._pauseReasonTreeOutline = null;

        this._pauseReasonLinkContainerElement = document.createElement("span");
        this._pauseReasonTextRow = new WebInspector.DetailsSectionTextRow;
        this._pauseReasonGroup = new WebInspector.DetailsSectionGroup([this._pauseReasonTextRow]);
        this._pauseReasonSection = new WebInspector.DetailsSection("paused-reason", null, [this._pauseReasonGroup], this._pauseReasonLinkContainerElement);
        this._pauseReasonSection.title = WebInspector.UIString("Pause Reason");

        WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisplayLocationDidChange, this._handleDebuggerObjectDisplayLocationDidChange, this);
        WebInspector.IssueMessage.addEventListener(WebInspector.IssueMessage.Event.DisplayLocationDidChange, this._handleDebuggerObjectDisplayLocationDidChange, this);
        WebInspector.issueManager.addEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._handleIssueAdded, this);
        WebInspector.issueManager.addEventListener(WebInspector.IssueManager.Event.Cleared, this._handleIssuesCleared, this);

        if (WebInspector.frameResourceManager.mainFrame)
            this._addResourcesRecursivelyForFrame(WebInspector.frameResourceManager.mainFrame);

        for (var script of WebInspector.debuggerManager.knownNonResourceScripts)
            this._addScript(script);
    }

    // Public

    get minimumWidth()
    {
        return this._navigationBar.minimumWidth;
    }

    closed()
    {
        super.closed();

        WebInspector.Frame.removeEventListener(null, null, this);
        WebInspector.debuggerManager.removeEventListener(null, null, this);
        WebInspector.Breakpoint.removeEventListener(null, null, this);
        WebInspector.IssueMessage.removeEventListener(null, null, this);
    }

    showDefaultContentView()
    {
        var currentTreeElement = this._contentTreeOutline.children[0];
        while (currentTreeElement && !currentTreeElement.root) {
            if (currentTreeElement instanceof WebInspector.ResourceTreeElement || currentTreeElement instanceof WebInspector.ScriptTreeElement) {
                this.showDefaultContentViewForTreeElement(currentTreeElement);
                return;
            }

            currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, true);
        }
    }

    treeElementForRepresentedObject(representedObject)
    {
        // The main resource is used as the representedObject instead of Frame in our tree.
        if (representedObject instanceof WebInspector.Frame)
            representedObject = representedObject.mainResource;

        return this.contentTreeOutline.getCachedTreeElement(representedObject);
    }

    // Protected

    saveStateToCookie(cookie)
    {
        console.assert(cookie);

        var selectedTreeElement = this._breakpointsContentTreeOutline.selectedTreeElement;
        if (!selectedTreeElement)
            return;

        var representedObject = selectedTreeElement.representedObject;

        if (representedObject === WebInspector.debuggerManager.allExceptionsBreakpoint) {
            cookie[WebInspector.DebuggerSidebarPanel.SelectedAllExceptionsCookieKey] = true;
            return;
        }

        if (representedObject === WebInspector.debuggerManager.allUncaughtExceptionsBreakpoint) {
            cookie[WebInspector.DebuggerSidebarPanel.SelectedAllUncaughtExceptionsCookieKey] = true;
            return;
        }

        super.saveStateToCookie(cookie);
    }

    restoreStateFromCookie(cookie, relaxedMatchDelay)
    {
        console.assert(cookie);

        // Eagerly resolve the special breakpoints; otherwise, use the default behavior.
        if (cookie[WebInspector.DebuggerSidebarPanel.SelectedAllExceptionsCookieKey])
            this._allExceptionsBreakpointTreeElement.revealAndSelect();
        else if (cookie[WebInspector.DebuggerSidebarPanel.SelectedAllUncaughtExceptionsCookieKey])
            this._allUncaughtExceptionsBreakpointTreeElement.revealAndSelect();
        else
            super.restoreStateFromCookie(cookie, relaxedMatchDelay);
    }

    // Private

    _debuggerWaitingToPause(event)
    {
        this._debuggerPauseResumeButtonItem.enabled = false;
    }

    _debuggerDidPause(event)
    {
        this.contentView.element.insertBefore(this._callStackSection.element, this.contentView.element.firstChild);
        if (this._updatePauseReason())
            this.contentView.element.insertBefore(this._pauseReasonSection.element, this.contentView.element.firstChild);

        this._debuggerPauseResumeButtonItem.enabled = true;
        this._debuggerPauseResumeButtonItem.toggled = true;
        this._debuggerStepOverButtonItem.enabled = true;
        this._debuggerStepIntoButtonItem.enabled = true;

        this.element.classList.add(WebInspector.DebuggerSidebarPanel.DebuggerPausedStyleClassName);
    }

    _debuggerDidResume(event)
    {
        this._callStackSection.element.remove();
        this._pauseReasonSection.element.remove();

        this._debuggerPauseResumeButtonItem.enabled = true;
        this._debuggerPauseResumeButtonItem.toggled = false;
        this._debuggerStepOverButtonItem.enabled = false;
        this._debuggerStepIntoButtonItem.enabled = false;
        this._debuggerStepOutButtonItem.enabled = false;

        this.element.classList.remove(WebInspector.DebuggerSidebarPanel.DebuggerPausedStyleClassName);
    }

    _breakpointsEnabledDidChange(event)
    {
        this._debuggerBreakpointsButtonItem.activated = WebInspector.debuggerManager.breakpointsEnabled;
    }

    _addBreakpoint(breakpoint)
    {
        var sourceCode = breakpoint.sourceCodeLocation.displaySourceCode;
        if (!sourceCode)
            return null;

        var parentTreeElement = this._addTreeElementForSourceCodeToContentTreeOutline(sourceCode);

        // Mark disabled breakpoints as resolved if there is source code loaded with that URL.
        // This gives the illusion the breakpoint was resolved, but since we don't send disabled
        // breakpoints to the backend we don't know for sure. If the user enables the breakpoint
        // it will be resolved properly.
        if (breakpoint.disabled)
            breakpoint.resolved = true;

        var breakpointTreeElement = new WebInspector.BreakpointTreeElement(breakpoint);
        parentTreeElement.insertChild(breakpointTreeElement, insertionIndexForObjectInListSortedByFunction(breakpointTreeElement, parentTreeElement.children, this._compareDebuggerTreeElements));
        if (parentTreeElement.children.length === 1)
            parentTreeElement.expand();
        return breakpointTreeElement;
    }

    _addBreakpointsForSourceCode(sourceCode)
    {
        var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(sourceCode);
        for (var i = 0; i < breakpoints.length; ++i)
            this._addBreakpoint(breakpoints[i], sourceCode);
    }

    _addIssuesForSourceCode(sourceCode)
    {
        var issues = WebInspector.issueManager.issuesForSourceCode(sourceCode);
        for (var issue of issues)
            this._addIssue(issue);
    }

    _addTreeElementForSourceCodeToContentTreeOutline(sourceCode)
    {
        var treeElement = this._breakpointsContentTreeOutline.getCachedTreeElement(sourceCode);
        if (!treeElement) {
            if (sourceCode instanceof WebInspector.SourceMapResource)
                treeElement = new WebInspector.SourceMapResourceTreeElement(sourceCode);
            else if (sourceCode instanceof WebInspector.Resource)
                treeElement = new WebInspector.ResourceTreeElement(sourceCode);
            else if (sourceCode instanceof WebInspector.Script)
                treeElement = new WebInspector.ScriptTreeElement(sourceCode);
        }

        if (!treeElement) {
            console.error("Unknown sourceCode instance", sourceCode);
            return;
        }

        if (!treeElement.parent) {
            treeElement.hasChildren = false;
            treeElement.expand();

            this._breakpointsContentTreeOutline.insertChild(treeElement, insertionIndexForObjectInListSortedByFunction(treeElement, this._breakpointsContentTreeOutline.children, this._compareTopLevelTreeElements.bind(this)));
        }

        return treeElement;
    }

    _addResourcesRecursivelyForFrame(frame)
    {
        this._addResource(frame.mainResource);

        for (var resource of frame.resources)
            this._addResource(resource);

        for (var childFrame of frame.childFrames)
            this._addResourcesRecursivelyForFrame(childFrame);
    }

    _resourceAdded(event)
    {
        this._addResource(event.data.resource);
    }

    _addResource(resource)
    {
        if (![WebInspector.Resource.Type.Document, WebInspector.Resource.Type.Script].includes(resource.type))
            return;

        let treeElement = this._addTreeElementForSourceCodeToContentTreeOutline(resource);
        this._addBreakpointsForSourceCode(resource);
        this._addIssuesForSourceCode(resource);

        if (!this.contentBrowser.currentContentView)
            this.showDefaultContentViewForTreeElement(treeElement);
    }

    _mainResourceDidChange(event)
    {
        if (event.target.isMainFrame()) {
            // Aggressively prune resources now so the old resources are removed before
            // the new main resource is added below. This avoids a visual flash when the
            // prune normally happens on a later event loop cycle.
            this.pruneStaleResourceTreeElements();
            this.contentBrowser.contentViewContainer.closeAllContentViews();
        }

        var resource = event.target.mainResource;
        this._addTreeElementForSourceCodeToContentTreeOutline(resource);
        this._addBreakpointsForSourceCode(resource);
        this._addIssuesForSourceCode(resource);
    }

    _scriptAdded(event)
    {
        this._addScript(event.data.script);
    }

    _addScript(script)
    {
        // COMPATIBILITY(iOS 9): Backends could send the frontend built-in code, filter out JSC internals.
        if (!script.url)
            return;

        // Don't add breakpoints if the script is represented by a Resource. They were
        // already added by _resourceAdded.
        if (script.resource)
            return;

        let treeElement = this._addTreeElementForSourceCodeToContentTreeOutline(script);
        this._addBreakpointsForSourceCode(script);
        this._addIssuesForSourceCode(script);

        if (!this.contentBrowser.currentContentView)
            this.showDefaultContentViewForTreeElement(treeElement);
    }

    _scriptRemoved(event)
    {
        let script = event.data.script;
        let scriptTreeElement = this._breakpointsContentTreeOutline.getCachedTreeElement(script);
        if (!scriptTreeElement)
            return;

        scriptTreeElement.parent.removeChild(scriptTreeElement);
    }

    _scriptsCleared(event)
    {
        for (var i = this._breakpointsContentTreeOutline.children.length - 1; i >= 0; --i) {
            var treeElement = this._breakpointsContentTreeOutline.children[i];
            if (!(treeElement instanceof WebInspector.ScriptTreeElement))
                continue;

            this._breakpointsContentTreeOutline.removeChildAtIndex(i, true, true);
        }
    }

    _breakpointAdded(event)
    {
        var breakpoint = event.data.breakpoint;
        this._addBreakpoint(breakpoint);
    }

    _breakpointRemoved(event)
    {
        var breakpoint = event.data.breakpoint;

        if (this._pauseReasonTreeOutline) {
            var pauseReasonBreakpointTreeElement = this._pauseReasonTreeOutline.getCachedTreeElement(breakpoint);
            if (pauseReasonBreakpointTreeElement)
                pauseReasonBreakpointTreeElement.removeStatusImage();
        }

        var breakpointTreeElement = this._breakpointsContentTreeOutline.getCachedTreeElement(breakpoint);
        console.assert(breakpointTreeElement);
        if (!breakpointTreeElement)
            return;

        this._removeDebuggerTreeElement(breakpointTreeElement);
    }

    _handleDebuggerObjectDisplayLocationDidChange(event)
    {
        var debuggerObject = event.target;

        if (event.data.oldDisplaySourceCode === debuggerObject.sourceCodeLocation.displaySourceCode)
            return;

        var debuggerTreeElement = this._breakpointsContentTreeOutline.getCachedTreeElement(debuggerObject);
        if (!debuggerTreeElement)
            return;

        // A known debugger object (breakpoint, issueMessage, etc.) moved between resources, remove the old tree element
        // and create a new tree element with the updated file.

        var wasSelected = debuggerTreeElement.selected;

        this._removeDebuggerTreeElement(debuggerTreeElement);
        var newDebuggerTreeElement = this._addDebuggerObject(debuggerObject);

        if (newDebuggerTreeElement && wasSelected)
            newDebuggerTreeElement.revealAndSelect(true, false, true, true);
    }

    _removeDebuggerTreeElement(debuggerTreeElement)
    {
        var parentTreeElement = debuggerTreeElement.parent;
        parentTreeElement.removeChild(debuggerTreeElement);

        console.assert(parentTreeElement.parent === this._breakpointsContentTreeOutline);
    }

    _debuggerCallFramesDidChange()
    {
        this._callStackContentTreeOutline.removeChildren();

        var callFrames = WebInspector.debuggerManager.callFrames;
        if (!callFrames || !callFrames.length) {
            this._callStackRow.showEmptyMessage();
            return;
        }

        this._callStackRow.hideEmptyMessage();
        this._callStackRow.element.appendChild(this._callStackContentTreeOutline.element);

        var treeElementToSelect = null;

        var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
        for (var i = 0; i < callFrames.length; ++i) {
            var callFrameTreeElement = new WebInspector.CallFrameTreeElement(callFrames[i]);
            if (callFrames[i] === activeCallFrame)
                treeElementToSelect = callFrameTreeElement;
            this._callStackContentTreeOutline.appendChild(callFrameTreeElement);
        }

        if (treeElementToSelect)
            treeElementToSelect.select(true, true);
    }

    _debuggerActiveCallFrameDidChange()
    {
        var callFrames = WebInspector.debuggerManager.callFrames;
        if (!callFrames)
            return;

        var indexOfActiveCallFrame = callFrames.indexOf(WebInspector.debuggerManager.activeCallFrame);
        // It is useful to turn off the step out button when there is no call frame to go through
        // since there might be call frames in the backend that were removed when processing the call
        // frame payload.
        this._debuggerStepOutButtonItem.enabled = indexOfActiveCallFrame < callFrames.length - 1;
    }

    _breakpointsBeneathTreeElement(treeElement)
    {
        console.assert(treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement);
        if (!(treeElement instanceof WebInspector.ResourceTreeElement) && !(treeElement instanceof WebInspector.ScriptTreeElement))
            return [];

        var breakpoints = [];
        var breakpointTreeElements = treeElement.children;
        for (var i = 0; i < breakpointTreeElements.length; ++i) {
            console.assert(breakpointTreeElements[i] instanceof WebInspector.BreakpointTreeElement);
            console.assert(breakpointTreeElements[i].breakpoint);
            var breakpoint = breakpointTreeElements[i].breakpoint;
            if (breakpoint)
                breakpoints.push(breakpoint);
        }

        return breakpoints;
    }

    _removeAllBreakpoints(breakpoints)
    {
        for (var i = 0; i < breakpoints.length; ++i) {
            var breakpoint = breakpoints[i];
            if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint))
                WebInspector.debuggerManager.removeBreakpoint(breakpoint);
        }
    }

    _toggleAllBreakpoints(breakpoints, disabled)
    {
        for (var i = 0; i < breakpoints.length; ++i)
            breakpoints[i].disabled = disabled;
    }

    _breakpointTreeOutlineDeleteTreeElement(treeElement)
    {
        console.assert(treeElement.selected);
        console.assert(treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement);
        if (!(treeElement instanceof WebInspector.ResourceTreeElement) && !(treeElement instanceof WebInspector.ScriptTreeElement))
            return false;

        var wasTopResourceTreeElement = treeElement.previousSibling === this._allUncaughtExceptionsBreakpointTreeElement;
        var nextSibling = treeElement.nextSibling;

        var breakpoints = this._breakpointsBeneathTreeElement(treeElement);
        this._removeAllBreakpoints(breakpoints);

        if (wasTopResourceTreeElement && nextSibling)
            nextSibling.select(true, true);

        return true;
    }

    _breakpointTreeOutlineContextMenuTreeElement(event, treeElement)
    {
        console.assert(treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement || treeElement.constructor === WebInspector.FolderTreeElement);
        if (!(treeElement instanceof WebInspector.ResourceTreeElement) && !(treeElement instanceof WebInspector.ScriptTreeElement))
            return;

        let breakpoints = this._breakpointsBeneathTreeElement(treeElement);
        let shouldDisable = breakpoints.some((breakpoint) => !breakpoint.disabled);

        let removeAllResourceBreakpoints = () => {
            this._removeAllBreakpoints(breakpoints);
        };

        let toggleAllResourceBreakpoints = () => {
            this._toggleAllBreakpoints(breakpoints, shouldDisable);
        };

        let contextMenu = WebInspector.ContextMenu.createFromEvent(event);
        if (shouldDisable)
            contextMenu.appendItem(WebInspector.UIString("Disable Breakpoints"), toggleAllResourceBreakpoints);
        else
            contextMenu.appendItem(WebInspector.UIString("Enable Breakpoints"), toggleAllResourceBreakpoints);
        contextMenu.appendItem(WebInspector.UIString("Delete Breakpoints"), removeAllResourceBreakpoints);
    }

    _treeSelectionDidChange(event)
    {
        let treeElement = event.data.selectedElement;
        if (!treeElement)
            return;

        // Deselect any other tree elements to prevent two selections in the sidebar.
        for (let treeOutline of this.visibleContentTreeOutlines) {
            if (treeOutline === treeElement.treeOutline)
                continue;

            let selectedTreeElement = treeOutline.selectedTreeElement;
            if (selectedTreeElement)
                selectedTreeElement.deselect();
        }

        if (treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement) {
            WebInspector.showSourceCode(treeElement.representedObject);
            return;
        }

        if (treeElement instanceof WebInspector.CallFrameTreeElement) {
            let callFrame = treeElement.callFrame;
            WebInspector.debuggerManager.activeCallFrame = callFrame;
            WebInspector.showSourceCodeLocation(callFrame.sourceCodeLocation);
            return;
        }

        if (treeElement instanceof WebInspector.IssueTreeElement) {
            WebInspector.showSourceCodeLocation(treeElement.issueMessage.sourceCodeLocation);
            return;
        }

        if (!(treeElement instanceof WebInspector.BreakpointTreeElement) || treeElement.parent.constructor === WebInspector.FolderTreeElement)
            return;

        let breakpoint = treeElement.breakpoint;
        if (treeElement.treeOutline === this._pauseReasonTreeOutline) {
            WebInspector.showSourceCodeLocation(breakpoint.sourceCodeLocation);
            return;
        }

        if (!treeElement.parent.representedObject)
            return;

        console.assert(treeElement.parent.representedObject instanceof WebInspector.SourceCode);
        if (!(treeElement.parent.representedObject instanceof WebInspector.SourceCode))
            return;

        WebInspector.showSourceCodeLocation(breakpoint.sourceCodeLocation);
    }

    _compareTopLevelTreeElements(a, b)
    {
        if (a === this._globalBreakpointsFolderTreeElement)
            return -1;
        if (b === this._globalBreakpointsFolderTreeElement)
            return 1;

        return a.mainTitle.localeCompare(b.mainTitle);
    }

    _compareDebuggerTreeElements(a, b)
    {
        if (!a.debuggerObject || !b.debuggerObject)
            return 0;
        
        var aLocation = a.debuggerObject.sourceCodeLocation;
        var bLocation = b.debuggerObject.sourceCodeLocation;

        var comparisonResult = aLocation.displayLineNumber - bLocation.displayLineNumber;
        if (comparisonResult !== 0)
            return comparisonResult;

        return aLocation.displayColumnNumber - bLocation.displayColumnNumber;
    }

    _updatePauseReason()
    {
        this._pauseReasonTreeOutline = null;

        this._updatePauseReasonGotoArrow();
        return this._updatePauseReasonSection();
    }

    _updatePauseReasonSection()
    {
        var pauseData = WebInspector.debuggerManager.pauseData;

        switch (WebInspector.debuggerManager.pauseReason) {
        case WebInspector.DebuggerManager.PauseReason.Assertion:
            // FIXME: We should include the assertion condition string.
            console.assert(pauseData, "Expected data with an assertion, but found none.");
            if (pauseData && pauseData.message) {
                this._pauseReasonTextRow.text = WebInspector.UIString("Assertion with message: %s").format(pauseData.message);
                return true;
            }

            this._pauseReasonTextRow.text = WebInspector.UIString("Assertion Failed");
            this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
            return true;

        case WebInspector.DebuggerManager.PauseReason.Breakpoint:
            console.assert(pauseData, "Expected breakpoint identifier, but found none.");
            if (pauseData && pauseData.breakpointId) {
                let breakpoint = WebInspector.debuggerManager.breakpointForIdentifier(pauseData.breakpointId);
                this._pauseReasonTreeOutline = this.createContentTreeOutline(true, true);
                this._pauseReasonTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);

                let breakpointTreeElement = new WebInspector.BreakpointTreeElement(breakpoint, WebInspector.DebuggerSidebarPanel.PausedBreakpointIconStyleClassName, WebInspector.UIString("Triggered Breakpoint"));
                let breakpointDetailsSection = new WebInspector.DetailsSectionRow;
                this._pauseReasonTreeOutline.appendChild(breakpointTreeElement);
                breakpointDetailsSection.element.appendChild(this._pauseReasonTreeOutline.element);

                this._pauseReasonGroup.rows = [breakpointDetailsSection];
                return true;
            }
            break;

        case WebInspector.DebuggerManager.PauseReason.CSPViolation:
            console.assert(pauseData, "Expected data with a CSP Violation, but found none.");
            if (pauseData) {
                // COMPATIBILITY (iOS 8): 'directive' was 'directiveText'.
                this._pauseReasonTextRow.text = WebInspector.UIString("Content Security Policy violation of directive: %s").format(pauseData.directive || pauseData.directiveText);
                this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
                return true;
            }
            break;

        case WebInspector.DebuggerManager.PauseReason.DebuggerStatement:
            this._pauseReasonTextRow.text = WebInspector.UIString("Debugger Statement");
            this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
            return true;

        case WebInspector.DebuggerManager.PauseReason.Exception:
            console.assert(pauseData, "Expected data with an exception, but found none.");
            if (pauseData) {
                // FIXME: We should improve the appearance of thrown objects. This works well for exception strings.
                var data = WebInspector.RemoteObject.fromPayload(pauseData);
                this._pauseReasonTextRow.text = WebInspector.UIString("Exception with thrown value: %s").format(data.description);
                this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
                return true;
            }
            break;

        case WebInspector.DebuggerManager.PauseReason.PauseOnNextStatement:
            this._pauseReasonTextRow.text = WebInspector.UIString("Immediate Pause Requested");
            this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
            return true;

        case WebInspector.DebuggerManager.PauseReason.Other:
            console.error("Paused for unknown reason. We should always have a reason.");
            break;
        }

        return false;
    }

    _updatePauseReasonGotoArrow()
    {
        this._pauseReasonLinkContainerElement.removeChildren();

        var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
        if (!activeCallFrame)
            return;

        var sourceCodeLocation = activeCallFrame.sourceCodeLocation;
        if (!sourceCodeLocation)
            return;

        var linkElement = WebInspector.createSourceCodeLocationLink(sourceCodeLocation, false, true);
        this._pauseReasonLinkContainerElement.appendChild(linkElement);
    }

    _addDebuggerObject(debuggerObject)
    {
        if (debuggerObject instanceof WebInspector.Breakpoint)
            return this._addBreakpoint(debuggerObject);

        if (debuggerObject instanceof WebInspector.IssueMessage)
            return this._addIssue(debuggerObject);
    }

    _addIssue(issueMessage)
    {
        var parentTreeElement = this._addTreeElementForSourceCodeToContentTreeOutline(issueMessage.sourceCodeLocation.sourceCode);
        if (!parentTreeElement)
            return null;

        var issueTreeElement = new WebInspector.IssueTreeElement(issueMessage);

        parentTreeElement.insertChild(issueTreeElement, insertionIndexForObjectInListSortedByFunction(issueTreeElement, parentTreeElement.children, this._compareDebuggerTreeElements));
        if (parentTreeElement.children.length === 1)
            parentTreeElement.expand();

        return issueTreeElement;
    }

    _handleIssueAdded(event)
    {
        var issue = event.data.issue;

        // We only want to show issues originating from JavaScript source code.
        if (!issue.sourceCodeLocation || !issue.sourceCodeLocation.sourceCode || (issue.source !== "javascript" && issue.source !== "console-api"))
            return;

        this._addIssue(issue);
    }

    _handleIssuesCleared(event)
    {
        var currentTreeElement = this._contentTreeOutline.children[0];
        var issueTreeElements = [];

        while (currentTreeElement && !currentTreeElement.root) {
            if (currentTreeElement instanceof WebInspector.IssueTreeElement)
                issueTreeElements.push(currentTreeElement);
            currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, true);
        }

        for (var issueTreeElement of issueTreeElements)
            issueTreeElement.parent.removeChild(issueTreeElement);
    }
};

WebInspector.DebuggerSidebarPanel.OffsetSectionsStyleClassName = "offset-sections";
WebInspector.DebuggerSidebarPanel.DebuggerPausedStyleClassName = "paused";
WebInspector.DebuggerSidebarPanel.ExceptionIconStyleClassName = "breakpoint-exception-icon";
WebInspector.DebuggerSidebarPanel.PausedBreakpointIconStyleClassName = "breakpoint-paused-icon";
WebInspector.DebuggerSidebarPanel.GlobalIconStyleClassName = "global-breakpoints-icon";

WebInspector.DebuggerSidebarPanel.SelectedAllExceptionsCookieKey = "debugger-sidebar-panel-all-exceptions-breakpoint";
WebInspector.DebuggerSidebarPanel.SelectedAllUncaughtExceptionsCookieKey = "debugger-sidebar-panel-all-uncaught-exceptions-breakpoint";
