/*
 * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
 * Copyright (C) 2009 Joseph Pecoraro
 * Copyright (C) 2011 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:
 *
 * 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.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE 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 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.
 */

/**
 * @constructor
 * @extends {WebInspector.View}
 * @param {!WebInspector.Searchable} searchable
 */
WebInspector.SearchableView = function(searchable)
{
    WebInspector.View.call(this);

    this._searchProvider = searchable;
    this.element.addEventListener("keydown", this._onKeyDown.bind(this), false);

    this._footerElementContainer = this.element.createChild("div", "search-bar status-bar hidden");
    this._footerElementContainer.style.order = 100;

    this._footerElement = this._footerElementContainer.createChild("table", "toolbar-search");
    this._footerElement.cellSpacing = 0;

    this._firstRowElement = this._footerElement.createChild("tr");
    this._secondRowElement = this._footerElement.createChild("tr", "hidden");

    // Column 1
    var searchControlElementColumn = this._firstRowElement.createChild("td");
    this._searchControlElement = searchControlElementColumn.createChild("span", "toolbar-search-control");
    this._searchInputElement = this._searchControlElement.createChild("input", "search-replace");
    this._searchInputElement.id = "search-input-field";
    this._searchInputElement.placeholder = WebInspector.UIString("Find");

    this._matchesElement = this._searchControlElement.createChild("label", "search-results-matches");
    this._matchesElement.setAttribute("for", "search-input-field");

    this._searchNavigationElement = this._searchControlElement.createChild("div", "toolbar-search-navigation-controls");

    this._searchNavigationPrevElement = this._searchNavigationElement.createChild("div", "toolbar-search-navigation toolbar-search-navigation-prev");
    this._searchNavigationPrevElement.addEventListener("click", this._onPrevButtonSearch.bind(this), false);
    this._searchNavigationPrevElement.title = WebInspector.UIString("Search Previous");

    this._searchNavigationNextElement = this._searchNavigationElement.createChild("div", "toolbar-search-navigation toolbar-search-navigation-next");
    this._searchNavigationNextElement.addEventListener("click", this._onNextButtonSearch.bind(this), false);
    this._searchNavigationNextElement.title = WebInspector.UIString("Search Next");

    this._searchInputElement.addEventListener("mousedown", this._onSearchFieldManualFocus.bind(this), false); // when the search field is manually selected
    this._searchInputElement.addEventListener("keydown", this._onSearchKeyDown.bind(this), true);
    this._searchInputElement.addEventListener("input", this._onInput.bind(this), false);

    this._replaceInputElement = this._secondRowElement.createChild("td").createChild("input", "search-replace toolbar-replace-control");
    this._replaceInputElement.addEventListener("keydown", this._onReplaceKeyDown.bind(this), true);
    this._replaceInputElement.placeholder = WebInspector.UIString("Replace");

    // Column 2
    this._findButtonElement = this._firstRowElement.createChild("td").createChild("button", "hidden");
    this._findButtonElement.textContent = WebInspector.UIString("Find");
    this._findButtonElement.tabIndex = -1;
    this._findButtonElement.addEventListener("click", this._onNextButtonSearch.bind(this), false);

    this._replaceButtonElement = this._secondRowElement.createChild("td").createChild("button");
    this._replaceButtonElement.textContent = WebInspector.UIString("Replace");
    this._replaceButtonElement.disabled = true;
    this._replaceButtonElement.tabIndex = -1;
    this._replaceButtonElement.addEventListener("click", this._replace.bind(this), false);

    // Column 3
    this._prevButtonElement = this._firstRowElement.createChild("td").createChild("button", "hidden");
    this._prevButtonElement.textContent = WebInspector.UIString("Previous");
    this._prevButtonElement.disabled = true;
    this._prevButtonElement.tabIndex = -1;
    this._prevButtonElement.addEventListener("click", this._onPrevButtonSearch.bind(this), false);

    this._replaceAllButtonElement = this._secondRowElement.createChild("td").createChild("button");
    this._replaceAllButtonElement.textContent = WebInspector.UIString("Replace All");
    this._replaceAllButtonElement.addEventListener("click", this._replaceAll.bind(this), false);

    // Column 4
    this._replaceElement = this._firstRowElement.createChild("td").createChild("span");

    this._replaceCheckboxElement = this._replaceElement.createChild("input");
    this._replaceCheckboxElement.type = "checkbox";
    this._replaceCheckboxElement.id = "search-replace-trigger";
    this._replaceCheckboxElement.addEventListener("change", this._updateSecondRowVisibility.bind(this), false);

    this._replaceLabelElement = this._replaceElement.createChild("label");
    this._replaceLabelElement.textContent = WebInspector.UIString("Replace");
    this._replaceLabelElement.setAttribute("for", "search-replace-trigger");

    // Column 5
    var cancelButtonElement = this._firstRowElement.createChild("td").createChild("button");
    cancelButtonElement.textContent = WebInspector.UIString("Cancel");
    cancelButtonElement.tabIndex = -1;
    cancelButtonElement.addEventListener("click", this.closeSearch.bind(this), false);
    this._minimalSearchQuerySize = 3;

    this._registerShortcuts();
}

WebInspector.SearchableView.findShortcuts = function()
{
    if (WebInspector.SearchableView._findShortcuts)
        return WebInspector.SearchableView._findShortcuts;
    WebInspector.SearchableView._findShortcuts = [WebInspector.KeyboardShortcut.makeDescriptor("f", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta)];
    if (!WebInspector.isMac())
        WebInspector.SearchableView._findShortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F3));
    return WebInspector.SearchableView._findShortcuts;
}

WebInspector.SearchableView.cancelSearchShortcuts = function()
{
    if (WebInspector.SearchableView._cancelSearchShortcuts)
        return WebInspector.SearchableView._cancelSearchShortcuts;
    WebInspector.SearchableView._cancelSearchShortcuts = [WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Esc)];
    return WebInspector.SearchableView._cancelSearchShortcuts;
}

WebInspector.SearchableView.findNextShortcut = function()
{
    if (WebInspector.SearchableView._findNextShortcut)
        return WebInspector.SearchableView._findNextShortcut;
    WebInspector.SearchableView._findNextShortcut = [];
    if (!WebInspector.isMac())
        WebInspector.SearchableView._findNextShortcut.push(WebInspector.KeyboardShortcut.makeDescriptor("g", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta));
    return WebInspector.SearchableView._findNextShortcut;
}

WebInspector.SearchableView.findPreviousShortcuts = function()
{
    if (WebInspector.SearchableView._findPreviousShortcuts)
        return WebInspector.SearchableView._findPreviousShortcuts;
    WebInspector.SearchableView._findPreviousShortcuts = [];
    if (!WebInspector.isMac())
        WebInspector.SearchableView._findPreviousShortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor("g", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta | WebInspector.KeyboardShortcut.Modifiers.Shift));
    return WebInspector.SearchableView._findPreviousShortcuts;
}

WebInspector.SearchableView.prototype = {
    /**
     * @param {!KeyboardEvent} event
     */
    _onKeyDown: function(event)
    {
        var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
        var handler = this._shortcuts[shortcutKey];
        if (handler && handler(event))
            event.consume(true);
    },

    _registerShortcuts: function()
    {
        this._shortcuts = {};

        /**
         * @param {!Array.<!WebInspector.KeyboardShortcut.Descriptor>} shortcuts
         * @param {function()} handler
         * @this {WebInspector.SearchableView}
         */
        function register(shortcuts, handler)
        {
            for (var i = 0; i < shortcuts.length; ++i)
                this._shortcuts[shortcuts[i].key] = handler;
        }

        register.call(this, WebInspector.SearchableView.findShortcuts(), this.handleFindShortcut.bind(this));
        register.call(this, WebInspector.SearchableView.cancelSearchShortcuts(), this.handleCancelSearchShortcut.bind(this));
        register.call(this, WebInspector.SearchableView.findNextShortcut(), this.handleFindNextShortcut.bind(this));
        register.call(this, WebInspector.SearchableView.findPreviousShortcuts(), this.handleFindPreviousShortcut.bind(this));
    },

    /**
     * @param {number} minimalSearchQuerySize
     */
    setMinimalSearchQuerySize: function(minimalSearchQuerySize)
    {
        this._minimalSearchQuerySize = minimalSearchQuerySize;
    },

    /**
     * @param {boolean} replaceable
     */
    setReplaceable: function(replaceable)
    {
        this._replaceable = replaceable;
    },

    /**
     * @param {number} matches
     */
    updateSearchMatchesCount: function(matches)
    {
        this._searchProvider.currentSearchMatches = matches;
        this._updateSearchMatchesCountAndCurrentMatchIndex(this._searchProvider.currentQuery ? matches : 0, -1);
    },

    /**
     * @param {number} currentMatchIndex
     */
    updateCurrentMatchIndex: function(currentMatchIndex)
    {
        this._updateSearchMatchesCountAndCurrentMatchIndex(this._searchProvider.currentSearchMatches, currentMatchIndex);
    },

    /**
     * @return {boolean}
     */
    isSearchVisible: function()
    {
        return this._searchIsVisible;
    },

    closeSearch: function()
    {
        this.cancelSearch();
        if (WebInspector.currentFocusElement().isDescendant(this._footerElementContainer))
            WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
    },

    _toggleSearchBar: function(toggled)
    {
        this._footerElementContainer.classList.toggle("hidden", !toggled);
        this.doResize();
    },

    cancelSearch: function()
    {
        if (!this._searchIsVisible)
            return;
        this.resetSearch();
        delete this._searchIsVisible;
        this._toggleSearchBar(false);
    },

    resetSearch: function()
    {
        this._clearSearch();
        this._updateReplaceVisibility();
        this._matchesElement.textContent = "";
    },

    /**
     * @return {boolean}
     */
    handleFindNextShortcut: function()
    {
        if (!this._searchIsVisible)
            return false;
        this._searchProvider.jumpToNextSearchResult();
        return true;
    },

    /**
     * @return {boolean}
     */
    handleFindPreviousShortcut: function()
    {
        if (!this._searchIsVisible)
            return false;
        this._searchProvider.jumpToPreviousSearchResult();
        return true;
    },

    /**
     * @return {boolean}
     */
    handleFindShortcut: function()
    {
        this.showSearchField();
        return true;
    },

    /**
     * @return {boolean}
     */
    handleCancelSearchShortcut: function()
    {
        if (!this._searchIsVisible)
            return false;
        this.closeSearch();
        return true;
    },

    /**
     * @param {boolean} enabled
     */
    _updateSearchNavigationButtonState: function(enabled)
    {
        this._replaceButtonElement.disabled = !enabled;
        this._prevButtonElement.disabled = !enabled;
        if (enabled) {
            this._searchNavigationPrevElement.classList.add("enabled");
            this._searchNavigationNextElement.classList.add("enabled");
        } else {
            this._searchNavigationPrevElement.classList.remove("enabled");
            this._searchNavigationNextElement.classList.remove("enabled");
        }
    },

    /**
     * @param {number} matches
     * @param {number} currentMatchIndex
     */
    _updateSearchMatchesCountAndCurrentMatchIndex: function(matches, currentMatchIndex)
    {
        if (!this._currentQuery)
            this._matchesElement.textContent = "";
        else if (matches === 0 || currentMatchIndex >= 0)
            this._matchesElement.textContent = WebInspector.UIString("%d of %d", currentMatchIndex + 1, matches);
        else if (matches === 1)
            this._matchesElement.textContent = WebInspector.UIString("1 match");
        else
            this._matchesElement.textContent = WebInspector.UIString("%d matches", matches);
        this._updateSearchNavigationButtonState(matches > 0);
    },

    showSearchField: function()
    {
        if (this._searchIsVisible)
            this.cancelSearch();

        this._toggleSearchBar(true);

        this._updateReplaceVisibility();
        if (WebInspector.currentFocusElement() !== this._searchInputElement) {
            var selection = window.getSelection();
            if (selection.rangeCount) {
                var queryCandidate = selection.toString().replace(/\r?\n.*/, "");
                if (queryCandidate)
                    this._searchInputElement.value = queryCandidate;
            }
        }
        this._performSearch(false, false);
        this._searchInputElement.focus();
        this._searchInputElement.select();
        this._searchIsVisible = true;
    },

    _updateReplaceVisibility: function()
    {
        this._replaceElement.classList.toggle("hidden", !this._replaceable);
        if (!this._replaceable) {
            this._replaceCheckboxElement.checked = false;
            this._updateSecondRowVisibility();
        }
    },

    /**
     * @param {!Event} event
     */
    _onSearchFieldManualFocus: function(event)
    {
        WebInspector.setCurrentFocusElement(event.target);
    },

    /**
     * @param {!KeyboardEvent} event
     */
    _onSearchKeyDown: function(event)
    {
        if (isEnterKey(event)) {
            // FIXME: This won't start backwards search with Shift+Enter correctly.
            if (!this._currentQuery)
                this._performSearch(true, true);
            else
                this._jumpToNextSearchResult(event.shiftKey);
        }
    },

    /**
     * @param {!KeyboardEvent} event
     */
    _onReplaceKeyDown: function(event)
    {
        if (isEnterKey(event))
            this._replace();
    },

    /**
     * @param {boolean=} isBackwardSearch
     */
    _jumpToNextSearchResult: function(isBackwardSearch)
    {
        if (!this._currentQuery || !this._searchNavigationPrevElement.classList.contains("enabled"))
            return;

        if (isBackwardSearch)
            this._searchProvider.jumpToPreviousSearchResult();
        else
            this._searchProvider.jumpToNextSearchResult();
    },

    _onNextButtonSearch: function(event)
    {
        if (!this._searchNavigationNextElement.classList.contains("enabled"))
            return;
        // Simulate next search on search-navigation-button click.
        this._jumpToNextSearchResult();
        this._searchInputElement.focus();
    },

    _onPrevButtonSearch: function(event)
    {
        if (!this._searchNavigationPrevElement.classList.contains("enabled"))
            return;
        // Simulate previous search on search-navigation-button click.
        this._jumpToNextSearchResult(true);
        this._searchInputElement.focus();
    },

    _clearSearch: function()
    {
        delete this._currentQuery;
        if (!!this._searchProvider.currentQuery) {
            delete this._searchProvider.currentQuery;
            this._searchProvider.searchCanceled();
        }
        this._updateSearchMatchesCountAndCurrentMatchIndex(0, -1);
    },

    /**
     * @param {boolean} forceSearch
     * @param {boolean} shouldJump
     */
    _performSearch: function(forceSearch, shouldJump)
    {
        var query = this._searchInputElement.value;
        if (!query || (!forceSearch && query.length < this._minimalSearchQuerySize && !this._currentQuery)) {
            this._clearSearch();
            return;
        }

        this._currentQuery = query;
        this._searchProvider.currentQuery = query;
        this._searchProvider.performSearch(query, shouldJump);
    },

    _updateSecondRowVisibility: function()
    {
        var secondRowVisible = this._replaceCheckboxElement.checked;
        this._footerElementContainer.classList.toggle("replaceable", secondRowVisible);
        this._footerElement.classList.toggle("toolbar-search-replace", secondRowVisible);
        this._secondRowElement.classList.toggle("hidden", !secondRowVisible);
        this._prevButtonElement.classList.toggle("hidden", !secondRowVisible);
        this._findButtonElement.classList.toggle("hidden", !secondRowVisible);
        this._replaceCheckboxElement.tabIndex = secondRowVisible ? -1 : 0;

        if (secondRowVisible)
            this._replaceInputElement.focus();
        else
            this._searchInputElement.focus();
        this.doResize();
    },

    _replace: function()
    {
        /** @type {!WebInspector.Replaceable} */ (this._searchProvider).replaceSelectionWith(this._replaceInputElement.value);
        delete this._currentQuery;
        this._performSearch(true, true);
    },

    _replaceAll: function()
    {
        /** @type {!WebInspector.Replaceable} */ (this._searchProvider).replaceAllWith(this._searchInputElement.value, this._replaceInputElement.value);
    },

    _onInput: function(event)
    {
        this._onValueChanged();
    },

    _onValueChanged: function()
    {
        this._performSearch(false, true);
    },

    __proto__: WebInspector.View.prototype
}

/**
 * @interface
 */
WebInspector.Searchable = function()
{
}

WebInspector.Searchable.prototype = {
    searchCanceled: function() { },

    /**
     * @param {string} query
     * @param {boolean} shouldJump
     */
    performSearch: function(query, shouldJump) { },

    jumpToNextSearchResult: function() { },

    jumpToPreviousSearchResult: function() { }
}

/**
 * @interface
 */
WebInspector.Replaceable = function()
{
}

WebInspector.Replaceable.prototype = {
    /**
     * @param {string} text
     */
    replaceSelectionWith: function(text) { },

    /**
     * @param {string} query
     * @param {string} replacement
     */
    replaceAllWith: function(query, replacement) { }
}
