/*
 * Copyright (C) 2013 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.
 */

/**
 * @constructor
 * @param {string} query
 */
WebInspector.FilePathScoreFunction = function(query)
{
    this._query = query;
    this._queryUpperCase = query.toUpperCase();
    this._score = null;
    this._sequence = null;
    this._dataUpperCase = "";
    this._fileNameIndex = 0;
}

WebInspector.FilePathScoreFunction.prototype = {
    /**
     * @param {string} data
     * @param {?Array<number>} matchIndexes
     * @return {number}
     */
    score: function(data, matchIndexes)
    {
        if (!data || !this._query)
            return 0;
        var n = this._query.length;
        var m = data.length;
        if (!this._score || this._score.length < n * m) {
            this._score = new Int32Array(n * m * 2);
            this._sequence = new Int32Array(n * m * 2);
        }
        var score = this._score;
        var sequence = /** @type {!Int32Array} */ (this._sequence);
        this._dataUpperCase = data.toUpperCase();
        this._fileNameIndex = data.lastIndexOf("/");
        for (var i = 0; i < n; ++i) {
            for (var j = 0; j < m; ++j) {
                var skipCharScore = j === 0 ? 0 : score[i * m + j - 1];
                var prevCharScore = i === 0 || j === 0 ? 0 : score[(i - 1) * m + j - 1];
                var consecutiveMatch = i === 0 || j === 0 ? 0 : sequence[(i - 1) * m + j - 1];
                var pickCharScore = this._match(this._query, data, i, j, consecutiveMatch);
                if (pickCharScore && prevCharScore + pickCharScore >= skipCharScore) {
                    sequence[i * m + j] = consecutiveMatch + 1;
                    score[i * m + j] = (prevCharScore + pickCharScore);
                } else {
                    sequence[i * m + j] = 0;
                    score[i * m + j] = skipCharScore;
                }
            }
        }
        if (matchIndexes)
            this._restoreMatchIndexes(sequence, n, m, matchIndexes);
        return score[n * m - 1];
    },

    /**
     * @param {string} data
     * @param {number} j
     * @return {boolean}
     */
    _testWordStart: function(data, j)
    {
        var prevChar = data.charAt(j - 1);
        return j === 0 || prevChar === "_" || prevChar === "-" || prevChar === "/" ||
            (data[j - 1] !== this._dataUpperCase[j - 1] && data[j] === this._dataUpperCase[j]);
    },

    /**
     * @param {!Int32Array} sequence
     * @param {number} n
     * @param {number} m
     * @param {!Array<number>} out
     */
    _restoreMatchIndexes: function(sequence, n, m, out)
    {
        var i = n - 1, j = m - 1;
        while (i >= 0 && j >= 0) {
            switch (sequence[i * m + j]) {
            case 0:
                --j;
                break;
            default:
                out.push(j);
                --i;
                --j;
                break;
            }
        }
        out.reverse();
    },

    /**
     * @param {string} query
     * @param {string} data
     * @param {number} i
     * @param {number} j
     * @return {number}
     */
    _singleCharScore: function(query, data, i, j)
    {
        var isWordStart = this._testWordStart(data, j);
        var isFileName = j > this._fileNameIndex;
        var isPathTokenStart = j === 0 || data[j - 1] === "/";
        var isCapsMatch = query[i] === data[j] && query[i] === this._queryUpperCase[i];
        var score = 10;
        if (isPathTokenStart)
            score += 4;
        if (isWordStart)
            score += 2;
        if (isCapsMatch)
            score += 6;
        if (isFileName)
            score += 4;
        // promote the case of making the whole match in the filename
        if (j === this._fileNameIndex + 1 && i === 0)
            score += 5;
        if (isFileName && isWordStart)
            score += 3;
        return score;
    },

    /**
     * @param {string} query
     * @param {string} data
     * @param {number} i
     * @param {number} j
     * @param {number} sequenceLength
     * @return {number}
     */
    _sequenceCharScore: function(query, data, i, j, sequenceLength)
    {
        var isFileName = j > this._fileNameIndex;
        var isPathTokenStart = j === 0 || data[j - 1] === "/";
        var score = 10;
        if (isFileName)
            score += 4;
        if (isPathTokenStart)
            score += 5;
        score += sequenceLength * 4;
        return score;
    },

    /**
     * @param {string} query
     * @param {string} data
     * @param {number} i
     * @param {number} j
     * @param {number} consecutiveMatch
     * @return {number}
     */
    _match: function(query, data, i, j, consecutiveMatch)
    {
        if (this._queryUpperCase[i] !== this._dataUpperCase[j])
            return 0;

        if (!consecutiveMatch)
            return this._singleCharScore(query, data, i, j);
        else
            return this._sequenceCharScore(query, data, i, j - consecutiveMatch, consecutiveMatch);
    }
}

