/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is JIM.
 *
 * The Initial Developer of the Original Code is
 * Pawel Chmielowski <prefiks@o2.pl>
 *
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Pawel Chmielowski <prefiks@o2.pl>
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * utils.FiniteAutomaton
 *
 *
 */

function FiniteAutomaton(pattern, accepts)
{
    var ch, len, prefix, i, j;

    if (typeof(pattern) == "object") {
        this.states = pattern;
        this.accepts = accepts;
        this.state = 0;
        return;
    }
    len = pattern.length;
    prefix = -1;
    this.states = new Array(len+1);
    this.accepts = new Array(len+1);
    this.state = 0;

    for (i = 0; i < len; i++) {
        ch = pattern.charAt(i);
        this.states[i] = { };
        for (j in this.states[prefix]) 
            this.states[i][j] = this.states[prefix][j];
        this.states[i][ch] = i+1;
        while (prefix >= 0 && pattern.charAt(prefix) != ch)
            prefix--;
        prefix++;
    }
    this.states[len] = this.states[prefix];
    this.accepts[len] = [pattern];
}

FiniteAutomaton.prototype =
{
    eat: function(str)
    {
        for (i = 0; i < str.length; i++)
            this.state = this.states[this.state][str.charAt(i)] || 0;
        this.found = this.accepts[this.state];
    },

    reset: function()
    {
        this.state = 0;
    },

    merge: function(automaton)
    {
        var jobs, job, i, trans, state, states, accepts, st1, st2, sst1, sst2, last;

        trans = new Array(this.states.length);
        for (i = 0; i < this.states.length; i++)
            trans[i] = new Array(automaton.states.length);
        trans[0][0] = 0;
        jobs = [ [0,0] ];
        states = [];
        accepts = [];
        state = -1;
        last = 0;

        while (job = jobs.shift()) {
            st1 = this.states[job[0]];
            st2 = automaton.states[job[1]];
            states[++state] = { };

            for (i in st1) {
                sst1 = st1[i] || 0;
                sst2 = st2[i] || 0;
                if (trans[sst1][sst2] == null) {
                    trans[sst1][sst2] = ++last;
                    jobs.push([sst1, sst2]);
                }
                states[state][i] = trans[sst1][sst2];
                accepts[state] = this.accepts[job[0]] ? 
                    (automaton.accepts[job[1]] ? 
                        this.accepts[job[0]].concat(automaton.accepts[job[1]]) :
                        this.accepts[job[0]]) :
                    automaton.accepts[job[1]];
            }
            for (i in st2) {
                if (st1[i] == null) {
                    sst1 = st1[i] || 0;
                    sst2 = st2[i] || 0;
                    if (trans[sst1][sst2] == null) {
                        trans[sst1][sst2] = ++last;
                        jobs.push([sst1, sst2]);
                    }
                    states[state][i] = trans[sst1][sst2];
                    accepts[state] = this.accepts[job[0]] ? 
                        (automaton.accepts[job[1]] ? 
                            this.accepts[job[0]].concat(automaton.accepts[job[1]]) :
                            this.accepts[job[0]]) :
                        automaton.accepts[job[1]];
                }
            }
        }
        return new FiniteAutomaton(states, accepts);
    }
}