/*
 * 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.RulesStyleDetailsPanel = class RulesStyleDetailsPanel extends WI.StyleDetailsPanel
{
    constructor(delegate)
    {
        super(delegate, "rules", "rules", WI.UIString("Styles \u2014 Rules"));

        this._sections = [];
        this._inspectorSection = null;
        this._isInspectorSectionPendingFocus = false;
        this._ruleMediaAndInherticanceList = [];
        this._propertyToSelectAndHighlight = null;

        this._emptyFilterResultsElement = WI.createMessageTextView(WI.UIString("No Results Found"));

        this._boundRemoveSectionWithActiveEditor = this._removeSectionWithActiveEditor.bind(this);
    }

    // Public

    refresh(significantChange)
    {
        // We only need to do a rebuild on significant changes. Other changes are handled
        // by the sections and text editors themselves.
        if (!significantChange) {
            super.refresh();
            return;
        }

        if (!this._forceSignificantChange) {
            this._sectionWithActiveEditor = null;
            for (var section of this._sections) {
                if (!section.editorActive)
                    continue;

                this._sectionWithActiveEditor = section;
                break;
            }

            if (this._sectionWithActiveEditor) {
                this._sectionWithActiveEditor.addEventListener(WI.CSSStyleDeclarationSection.Event.Blurred, this._boundRemoveSectionWithActiveEditor);
                return;
            }
        }

        var newSections = [];
        var newDOMFragment = document.createDocumentFragment();

        var previousMediaList = [];
        var previousSection = null;

        var pseudoElements = this.nodeStyles.pseudoElements;
        var pseudoElementsStyle = [];
        for (var pseudoIdentifier in pseudoElements)
            pseudoElementsStyle = pseudoElementsStyle.concat(pseudoElements[pseudoIdentifier].orderedStyles);

        var orderedPseudoStyles = uniqueOrderedStyles(pseudoElementsStyle);
        // Reverse the array to allow ensure that splicing the array will not mess with the order.
        if (orderedPseudoStyles.length)
            orderedPseudoStyles.reverse();

        function mediaListsEqual(a, b)
        {
            a = a || [];
            b = b || [];

            if (a.length !== b.length)
                return false;

            for (var i = 0; i < a.length; ++i) {
                var aMedia = a[i];
                var bMedia = b[i];

                if (aMedia.type !== bMedia.type)
                    return false;

                if (aMedia.text !== bMedia.text)
                    return false;

                if (!aMedia.sourceCodeLocation && bMedia.sourceCodeLocation)
                    return false;

                if (aMedia.sourceCodeLocation && !aMedia.sourceCodeLocation.isEqual(bMedia.sourceCodeLocation))
                    return false;
            }

            return true;
        }

        function uniqueOrderedStyles(orderedStyles)
        {
            var uniqueStyles = [];

            for (var style of orderedStyles) {
                var rule = style.ownerRule;
                if (!rule) {
                    uniqueStyles.push(style);
                    continue;
                }

                var found = false;
                for (var existingStyle of uniqueStyles) {
                    if (rule.isEqualTo(existingStyle.ownerRule)) {
                        found = true;
                        break;
                    }
                }
                if (!found)
                    uniqueStyles.push(style);
            }

            return uniqueStyles;
        }

        function appendStyleSection(style)
        {
            var section = style.__rulesSection;

            if (!section) {
                section = new WI.CSSStyleDeclarationSection(this, style);
                style.__rulesSection = section;
            } else
                section.refresh();

            if (this._isInspectorSectionPendingFocus && style.isInspectorRule())
                this._inspectorSection = section;

            // Reset lastInGroup in case the order/grouping changed.
            section.lastInGroup = false;

            newDOMFragment.appendChild(section.element);
            newSections.push(section);

            previousSection = section;
        }

        function insertMediaOrInheritanceLabel(style)
        {
            if (previousSection && previousSection.style.type === WI.CSSStyleDeclaration.Type.Inline)
                previousSection.lastInGroup = true;

            var hasMediaOrInherited = [];

            if (previousSection && previousSection.style.node !== style.node) {
                previousSection.lastInGroup = true;

                var prefixElement = document.createElement("strong");
                prefixElement.textContent = WI.UIString("Inherited From: ");

                let inheritedLabel = newDOMFragment.appendChild(document.createElement("div"));
                inheritedLabel.className = "label";

                inheritedLabel.appendChild(prefixElement);

                inheritedLabel.appendChild(WI.linkifyNodeReference(style.node, {
                    maxLength: 100,
                    excludeRevealElement: true,
                }));

                hasMediaOrInherited.push(inheritedLabel);
            }

            // Only include the media list if it is different from the previous media list shown.
            var currentMediaList = (style.ownerRule && style.ownerRule.mediaList) || [];
            if (!mediaListsEqual(previousMediaList, currentMediaList)) {
                previousMediaList = currentMediaList;

                // Break the section group even if the media list is empty. That way the user knows
                // the previous displayed media list does not apply to the next section.
                if (previousSection)
                    previousSection.lastInGroup = true;

                for (var media of currentMediaList) {
                    var prefixElement = document.createElement("strong");
                    prefixElement.textContent = WI.UIString("Media: ");

                    var mediaLabel = document.createElement("div");
                    mediaLabel.className = "label";
                    mediaLabel.append(prefixElement, media.text);

                    if (media.sourceCodeLocation) {
                        const options = {
                            dontFloat: true,
                            ignoreNetworkTab: true,
                            ignoreSearchTab: true,
                        };
                        mediaLabel.append(" \u2014 ", WI.createSourceCodeLocationLink(media.sourceCodeLocation, options));
                    }

                    newDOMFragment.appendChild(mediaLabel);

                    hasMediaOrInherited.push(mediaLabel);
                }
            }

            if (!hasMediaOrInherited.length && style.type !== WI.CSSStyleDeclaration.Type.Inline) {
                if (previousSection && !previousSection.lastInGroup)
                    hasMediaOrInherited = this._ruleMediaAndInherticanceList.lastValue;
                else {
                    var prefixElement = document.createElement("strong");
                    prefixElement.textContent = WI.UIString("Media: ");

                    var mediaLabel = document.createElement("div");
                    mediaLabel.className = "label";
                    mediaLabel.append(prefixElement, "all");

                    newDOMFragment.appendChild(mediaLabel);
                    hasMediaOrInherited.push(mediaLabel);
                }
            }

            this._ruleMediaAndInherticanceList.push(hasMediaOrInherited);
        }

        function insertAllMatchingPseudoStyles(force)
        {
            if (!orderedPseudoStyles.length)
                return;

            if (force) {
                for (var j = orderedPseudoStyles.length - 1; j >= 0; --j) {
                    var pseudoStyle = orderedPseudoStyles[j];
                    insertMediaOrInheritanceLabel.call(this, pseudoStyle);
                    appendStyleSection.call(this, pseudoStyle);
                }
                orderedPseudoStyles = [];
            }

            if (!previousSection)
                return;

            var ownerRule = previousSection.style.ownerRule;
            if (!ownerRule)
                return;

            for (var j = orderedPseudoStyles.length - 1; j >= 0; --j) {
                var pseudoStyle = orderedPseudoStyles[j];
                if (!pseudoStyle.ownerRule.selectorIsGreater(ownerRule.mostSpecificSelector))
                    continue;

                insertMediaOrInheritanceLabel.call(this, pseudoStyle);
                appendStyleSection.call(this, pseudoStyle);
                ownerRule = pseudoStyle.ownerRule;
                orderedPseudoStyles.splice(j, 1);
            }
        }

        this._ruleMediaAndInherticanceList = [];
        var orderedStyles = uniqueOrderedStyles(this.nodeStyles.orderedStyles);
        for (var style of orderedStyles) {
            var isUserAgentStyle = style.ownerRule && style.ownerRule.type === WI.CSSStyleSheet.Type.UserAgent;
            insertAllMatchingPseudoStyles.call(this, isUserAgentStyle || style.inherited);

            insertMediaOrInheritanceLabel.call(this, style);
            appendStyleSection.call(this, style);
        }

        // Just in case there are any pseudo-selectors left that haven't been added.
        insertAllMatchingPseudoStyles.call(this, true);

        if (previousSection)
            previousSection.lastInGroup = true;

        this.element.removeChildren();
        this.element.appendChild(newDOMFragment);
        this.element.appendChild(this._emptyFilterResultsElement);

        this._sections = newSections;

        for (var i = 0; i < this._sections.length; ++i)
            this._sections[i].updateLayout();

        super.refresh();
    }

    scrollToSectionAndHighlightProperty(property)
    {
        if (!this._visible) {
            this._propertyToSelectAndHighlight = property;
            return false;
        }

        for (var section of this._sections) {
            if (section.highlightProperty(property))
                return true;
        }

        return false;
    }

    cssStyleDeclarationSectionEditorFocused(focusedSection)
    {
        for (let section of this._sections) {
            if (section !== focusedSection)
                section.clearSelection();
        }

        this._sectionWithActiveEditor = focusedSection;
    }

    cssStyleDeclarationSectionEditorNextRule(currentSection)
    {
        currentSection.clearSelection();

        var index = this._sections.indexOf(currentSection);
        this._sections[index < this._sections.length - 1 ? index + 1 : 0].focusRuleSelector();
    }

    cssStyleDeclarationSectionEditorPreviousRule(currentSection, selectLastProperty) {
        currentSection.clearSelection();

        if (selectLastProperty || !currentSection.selectorEditable) {
            var index = this._sections.indexOf(currentSection);
            index = index > 0 ? index - 1 : this._sections.length - 1;

            var section = this._sections[index];
            while (section.locked) {
                index = index > 0 ? index - 1 : this._sections.length - 1;
                section = this._sections[index];
            }

            section.focus();
            section.selectLastProperty();
            return;
        }

        currentSection.focusRuleSelector(true);
    }

    filterDidChange(filterBar)
    {
        for (var labels of this._ruleMediaAndInherticanceList) {
            for (var i = 0; i < labels.length; ++i) {
                labels[i].classList.toggle(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName, filterBar.hasActiveFilters());

                if (i === labels.length - 1)
                    labels[i].classList.toggle("filter-matching-label", filterBar.hasActiveFilters());
            }
        }

        var matchFound = !filterBar.hasActiveFilters();
        for (var i = 0; i < this._sections.length; ++i) {
            var section = this._sections[i];

            if (section.findMatchingPropertiesAndSelectors(filterBar.filters.text) && filterBar.hasActiveFilters()) {
                if (this._ruleMediaAndInherticanceList[i].length) {
                    for (var label of this._ruleMediaAndInherticanceList[i])
                        label.classList.remove(WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName);
                } else
                    section.element.classList.add(WI.GeneralStyleDetailsSidebarPanel.FilterMatchingSectionHasLabelClassName);

                matchFound = true;
            }
        }

        this.element.classList.toggle("filter-non-matching", !matchFound);
    }

    newRuleButtonClicked()
    {
        if (this.nodeStyles.node.isInUserAgentShadowTree())
            return;

        for (let existingRule of this.nodeStyles.rulesForSelector()) {
            if (this.focusEmptySectionWithStyle(existingRule.style))
                return;
        }

        this._isInspectorSectionPendingFocus = true;
        let newInspectorRuleSelector = this.nodeStyles.node.appropriateSelectorFor(true);
        this.nodeStyles.addRule(newInspectorRuleSelector);
    }

    newRuleButtonContextMenu(event)
    {
        if (this.nodeStyles.node.isInUserAgentShadowTree())
            return;

        let styleSheets = WI.cssStyleManager.styleSheets.filter(styleSheet => styleSheet.hasInfo() && !styleSheet.isInlineStyleTag() && !styleSheet.isInlineStyleAttributeStyleSheet());
        if (!styleSheets.length)
            return;

        const justSelector = true;
        let selector = this.nodeStyles.node.appropriateSelectorFor(justSelector);

        let contextMenu = WI.ContextMenu.createFromEvent(event);

        const handler = null;
        const disabled = true;
        contextMenu.appendItem(WI.UIString("Available Style Sheets"), handler, disabled);

        for (let styleSheet of styleSheets) {
            contextMenu.appendItem(styleSheet.displayName, () => {
                const text = "";
                this.nodeStyles.addRule(selector, text, styleSheet.id);
            });
        }
    }

    sectionForStyle(style)
    {
        if (style.__rulesSection)
            return style.__rulesSection;

        for (let section of this._sections) {
            if (section.style === style)
                return section;
        }
        return null;
    }

    focusEmptySectionWithStyle(style)
    {
        if (style.hasProperties())
            return false;

        let section = this.sectionForStyle(style);
        if (!section)
            return false;

        section.focus();
        return true;
    }

    // Protected

    shown()
    {
        super.shown();

        // Associate the style and section objects so they can be reused.
        // Also update the layout in case we changed widths while hidden.
        for (var i = 0; i < this._sections.length; ++i) {
            var section = this._sections[i];
            section.style.__rulesSection = section;
            section.updateLayout();
        }

        // If there was an active section and the panel was hidden, refresh the section in case
        // changes were made to the underlying resource.
        if (this._sectionWithActiveEditor)
            this._sectionWithActiveEditor.refreshEditor();
    }

    hidden()
    {
        super.hidden();

        // Disconnect the style and section objects so they have a chance
        // to release their objects when this panel is not visible.
        for (var i = 0; i < this._sections.length; ++i)
            delete this._sections[i].style.__rulesSection;
    }

    sizeDidChange()
    {
        for (var i = 0; i < this._sections.length; ++i)
            this._sections[i].updateLayout();
    }

    nodeStylesRefreshed(event)
    {
        super.nodeStylesRefreshed(event);

        if (this._propertyToSelectAndHighlight) {
            this.scrollToSectionAndHighlightProperty(this._propertyToSelectAndHighlight);
            this._propertyToSelectAndHighlight = null;
        }

        if (this._isInspectorSectionPendingFocus) {
            this._isInspectorSectionPendingFocus = false;

            if (this._inspectorSection) {
                this._inspectorSection.focus();
                this._inspectorSection = null;
            }
        }
    }

    // Private

    _removeSectionWithActiveEditor(event)
    {
        this._sectionWithActiveEditor.removeEventListener(WI.CSSStyleDeclarationSection.Event.Blurred, this._boundRemoveSectionWithActiveEditor);
        this.refresh(true);
    }
};
