/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * This file is part of SableCC.                             *
 * See the file "LICENSE" for copyright information and the  *
 * terms and conditions for copying, distribution and        *
 * modification of SableCC.                                  *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package org.sablecc.sablecc;

import java.util.*;

@SuppressWarnings({"rawtypes","unchecked"})
public class DFA
{
  public DFA(NFA nfa)
  {
    this.nfa = nfa;
    construct();
    optimize();
  }

  public NFA nfa;
  public final Vector states = new Vector(0);
  public final Hashtable finder = new Hashtable(1);

  private void optimize()
  {
    Vector transitions = new Vector(0);

    for(int i = 0; i < states.size(); i++)
    {
      DFA.State state = (DFA.State) states.elementAt(i);
      transitions.addElement(new Vector(0));

      for(int j = 0; j < state.transitions.size(); j++)
      {
        int max = 0;
        int st = -1;

        for(int k = 0; k < i; k++)
        {
          int match = match(i,j,k);

          if(match > max)
          {
            max = match;
            st = k;
          }
        }

        if(max < 2)
        {
          ((Vector) transitions.elementAt(i)).addElement(
            state.transitions.elementAt(j));
        }
        else
        {
          DFA.Transition transition1 =
            (DFA.Transition) state.transitions.elementAt(j);
          DFA.Transition transition2 =
            (DFA.Transition) state.transitions.elementAt(j + max - 1);

          DFA.Transition transition =
            new DFA.Transition(
              new CharSet.Interval(
                transition1.interval().start,
                transition2.interval().end),
              -2 - st);

          ((Vector) transitions.elementAt(i)).addElement(transition);
          j += max - 1;
        }
      }
    }

    for(int i = 0; i < states.size(); i++)
    {
      DFA.State state = (DFA.State) states.elementAt(i);
      state.transitions = (Vector) transitions.elementAt(i);
    }
  }

  private int match(int st1, int tr, int st2)
  {
    DFA.State state1 = (DFA.State) states.elementAt(st1);
    DFA.State state2 = (DFA.State) states.elementAt(st2);

    DFA.Transition first =
      (DFA.Transition) state1.transitions.elementAt(tr);

    int j = -1;

    for(int i = 0; i < state2.transitions.size(); i++)
    {
      DFA.Transition transition =
        (DFA.Transition) state2.transitions.elementAt(i);

      if(transition.match(first))
      {
        j = i;
        break;
      }
    }

    if(j == -1)
    {
      return 0;
    }

    int max = 0;
    int i = tr;

    while((i < state1.transitions.size()) &&
          (j < state2.transitions.size()))
    {
      DFA.Transition transition1 =
        (DFA.Transition) state1.transitions.elementAt(i);

      DFA.Transition transition2 =
        (DFA.Transition) state2.transitions.elementAt(j);

      if(!transition1.match(transition2))
      {
        return max;
      }

      max++;
      i++;
      j++;
    }

    return max;
  }

  private void construct()
  {
    computeEClosures();

    IntSet initial = new IntSet();
    initial.or(eclosure(0));

    State state = new State(initial);
    states.addElement(state);
    finder.put(state.nfaStates, new Integer(0));

    int i = -1;
    while(++i < states.size())
    {
      System.out.print(".");

      state = (State) states.elementAt(i);

      CharSet.Interval interval = new CharSet.Interval((char) 0, (char) 0xffff);

      do
      {
        IntSet destination = new IntSet();
        interval.end = (char) 0xffff;
        boolean modified = false;

        int[] elements = state.nfaStates.elements();

        for(int k = 0; k < elements.length; k++)
        {
          int j = elements[k];

          if((nfa.states[j].transitions[0] != null) &&
              (nfa.states[j].transitions[0].chars != null))
          {
            CharSet.Interval overlap =
              nfa.states[j].transitions[0].chars.findOverlap(interval);

            if(overlap != null)
            {
              if(overlap.start > interval.start)
              {
                interval.end = (char) (overlap.start - 1);
              }
              else
              {
                destination.set(nfa.states[j].transitions[0].destination);
                modified = true;

                if(overlap.end < interval.end)
                {
                  interval.end = overlap.end;
                }
              }
            }
          }

          if((nfa.states[j].transitions[1] != null) &&
              (nfa.states[j].transitions[1].chars != null))
          {
            CharSet.Interval overlap =
              nfa.states[j].transitions[1].chars.findOverlap(interval);

            if(overlap != null)
            {
              if(overlap.start > interval.start)
              {
                interval.end = (char) (overlap.start - 1);
              }
              else
              {
                destination.set(nfa.states[j].transitions[1].destination);

                if(overlap.end < interval.end)
                {
                  interval.end = overlap.end;
                }
              }
            }
          }
        }

        if(modified)
        {
          destination = eclosure(destination);
          Integer dest = (Integer) finder.get(destination);

          if(dest != null)
          {
            state.transitions.addElement(
              new Transition((CharSet.Interval) interval.clone(), dest.intValue()));
          }
          else
          {
            State s = new State(destination);
            states.addElement(s);
            finder.put(s.nfaStates, new Integer(states.size() - 1));

            state.transitions.addElement(
              new Transition((CharSet.Interval) interval.clone(), states.size() - 1));
          }
        }

        interval.start = (char) (interval.end + 1);
      }
      while(interval.end != (char) 0xffff);

      //            System.out.println(state);
    }

    //        System.out.println(this);
  }

  private IntSet[] eclosures;

  private void computeEClosures()
  {
    eclosures = new IntSet[nfa.states.length];

    for(int i = 0; i < nfa.states.length; i++)
    {
      System.out.print(".");

      IntSet set
        = new IntSet();
      eclosure(i, set
                );
      eclosures[i] = set
                       ;
    }

    System.out.println();
  }

  private IntSet eclosure(int state)
  {
    return eclosures[state];
  }

  private void eclosure(int state, IntSet nfaStates)
  {
    if(eclosures[state] != null)
    {
      nfaStates.or(eclosures[state]);
      return;
    }

    nfaStates.set(state);

    if((nfa.states[state].transitions[0] != null) &&
        (nfa.states[state].transitions[0].chars == null) &&
        (!nfaStates.get(nfa.states[state].transitions[0].destination)))

    {
      eclosure(nfa.states[state].transitions[0].destination, nfaStates);
    }

    if((nfa.states[state].transitions[1] != null) &&
        (nfa.states[state].transitions[1].chars == null) &&
        (!nfaStates.get(nfa.states[state].transitions[1].destination)))

    {
      eclosure(nfa.states[state].transitions[1].destination, nfaStates);
    }
  }

  private IntSet eclosure(IntSet nfaStates)
  {
    IntSet result = new IntSet();

    int[] elements = nfaStates.elements();

    for(int j = 0; j < elements.length; j++)
    {
      int i = elements[j];

      result.or(eclosure(i));
    }

    return result;
  }

  @Override
  public String toString()
  {
    StringBuffer result = new StringBuffer();

    for(int i = 0; i < states.size(); i++)
    {
      result.append(i + ": " + states.elementAt(i) +
                    System.getProperty("line.separator"));
    }

    return result.toString();
  }

  public static class State
  {
    public State(IntSet nfaStates)
    {
      this.nfaStates = nfaStates;
    }

    public IntSet nfaStates = new IntSet();
    public Vector transitions = new Vector(0);
    public int accept;

    @Override
    public String toString()
    {
      StringBuffer result = new StringBuffer();

      /*            for(int i = 0; i < nfaStates.size(); i++)
                  {
                      if(nfaStates.get(i))
                      {
                          if(nfa.states[i].accept != null)
                          {
                              result.append("(" + nfa.states[i].accept + ")");
                          }
                      }
                  }*/

      for(int i = 0; i < transitions.size(); i++)
      {
        result.append(transitions.elementAt(i) + ",");
      }

      return result /*+ " " + nfaStates*/ + "";
    }
  }

  public static class Transition
  {
    private char start;
    private char end;

    public int destination;

    public Transition(CharSet.Interval interval, int destination)
    {
      this.start = interval.start;
      this.end = interval.end;
      this.destination = destination;
    }

    public CharSet.Interval interval()
    {
      return new CharSet.Interval(start, end);
    }

    public Transition(Transition transition)
    {
      start = transition.start;
      end = transition.end;
      destination = transition.destination;
    }

    @Override
    public String toString()
    {
      return destination + ":[" + interval() + "]";
    }

    public boolean match(Transition transition)
    {
      return (start == transition.start) &&
             (end == transition.end) &&
             (destination == transition.destination);
    }
  }
}
