/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 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;


public class NFA implements Cloneable
{
  public State[] states;

  private NFA(int size)
  {
    System.out.print(".");
    states = new State[size];
  }

  public NFA()
  {
    this(2);
    states[0] = new State();
    states[0].transitions[0] = new Transition(null, 1);
    states[1] = new State();
  }

  public NFA(CharSet chars)
  {
    this(2);
    states[0] = new State();
    states[0].transitions[0] = new Transition(chars, 1);
    states[1] = new State();
  }

  public NFA(String string)
  {
    this(string.length() + 1);

    for(int i = 0; i < string.length(); i++)
    {
      states[i] = new State();
      states[i].transitions[0] = new Transition(new CharSet(string.charAt(i)), i + 1);
    }

    states[string.length()] = new State();
  }

  private NFA(NFA nfa)
  {
    this(nfa.states.length);

    for(int i = 0; i < nfa.states.length; i++)
    {
      states[i] = new State(nfa.states[i]);
    }
  }

  public NFA zeroOrMore()
  {
    NFA nfa = new NFA(states.length + 2);
    nfa.states[0] = new State();
    nfa.states[0].transitions[0] = new Transition(null, 1);
    nfa.states[0].transitions[1] = new Transition(null, states.length + 1);

    for(int i = 0; i < states.length; i++)
    {
      nfa.states[i + 1] = new State(states[i]);

      if(nfa.states[i + 1].transitions[0] != null)
      {
        nfa.states[i + 1].transitions[0].destination += 1;
      }

      if(nfa.states[i + 1].transitions[1] != null)
      {
        nfa.states[i + 1].transitions[1].destination += 1;
      }
    }

    nfa.states[states.length].transitions[0] = new Transition(null, 1);
    nfa.states[states.length].transitions[1] = new Transition(null, states.length + 1);

    nfa.states[states.length + 1] = new State();

    return nfa;
  }

  public NFA zeroOrOne()
  {
    NFA nfa = new NFA(states.length + 2);
    nfa.states[0] = new State();
    nfa.states[0].transitions[0] = new Transition(null, 1);
    nfa.states[0].transitions[1] = new Transition(null, states.length + 1);

    for(int i = 0; i < states.length; i++)
    {
      nfa.states[i + 1] = new State(states[i]);

      if(nfa.states[i + 1].transitions[0] != null)
      {
        nfa.states[i + 1].transitions[0].destination += 1;
      }

      if(nfa.states[i + 1].transitions[1] != null)
      {
        nfa.states[i + 1].transitions[1].destination += 1;
      }
    }

    nfa.states[states.length].transitions[1] = new Transition(null, states.length + 1);

    nfa.states[states.length + 1] = new State();

    return nfa;
  }

  public NFA oneOrMore()
  {
    NFA nfa = new NFA(states.length + 2);
    nfa.states[0] = new State();
    nfa.states[0].transitions[0] = new Transition(null, 1);

    for(int i = 0; i < states.length; i++)
    {
      nfa.states[i + 1] = new State(states[i]);

      if(nfa.states[i + 1].transitions[0] != null)
      {
        nfa.states[i + 1].transitions[0].destination += 1;
      }

      if(nfa.states[i + 1].transitions[1] != null)
      {
        nfa.states[i + 1].transitions[1].destination += 1;
      }
    }

    nfa.states[states.length].transitions[0] = new Transition(null, 1);
    nfa.states[states.length].transitions[1] = new Transition(null, states.length + 1);

    nfa.states[states.length + 1] = new State();

    return nfa;
  }

  public NFA concatenate(NFA next)
  {
    NFA nfa = new NFA(states.length + next.states.length - 1);

    for(int i = 0; i < states.length - 1; i++)
    {
      nfa.states[i] = new State(states[i]);
    }

    for(int i = 0; i < next.states.length; i++)
    {
      nfa.states[states.length + i - 1] = new State(next.states[i]);

      if(nfa.states[states.length + i - 1].transitions[0] != null)
      {
        nfa.states[states.length + i - 1].transitions[0].destination +=
          states.length - 1;
      }

      if(nfa.states[states.length + i - 1].transitions[1] != null)
      {
        nfa.states[states.length + i - 1].transitions[1].destination +=
          states.length - 1;
      }
    }

    return nfa;
  }

  public NFA alternate(NFA next)
  {
    NFA nfa = new NFA(states.length + next.states.length + 2);

    nfa.states[0] = new State();
    nfa.states[0].transitions[0] = new Transition(null, 1);
    nfa.states[0].transitions[1] = new Transition(null, states.length + 1);

    for(int i = 0; i < states.length; i++)
    {
      nfa.states[i + 1] = new State(states[i]);

      if(nfa.states[i + 1].transitions[0] != null)
      {
        nfa.states[i + 1].transitions[0].destination += 1;
      }

      if(nfa.states[i + 1].transitions[1] != null)
      {
        nfa.states[i + 1].transitions[1].destination += 1;
      }
    }

    nfa.states[states.length].transitions[0] =
      new Transition(null, states.length + next.states.length + 1);

    for(int i = 0; i < next.states.length; i++)
    {
      nfa.states[states.length + i + 1] = new State(next.states[i]);

      if(nfa.states[states.length + i + 1].transitions[0] != null)
      {
        nfa.states[states.length + i + 1].transitions[0].destination +=
          states.length + 1;
      }

      if(nfa.states[states.length + i + 1].transitions[1] != null)
      {
        nfa.states[states.length + i + 1].transitions[1].destination +=
          states.length + 1;
      }
    }

    nfa.states[states.length + next.states.length].transitions[0] =
      new Transition(null, states.length + next.states.length + 1);

    nfa.states[states.length + next.states.length + 1] = new State();

    return nfa;
  }

  public NFA merge(NFA next)
  {
    NFA nfa = new NFA(states.length + next.states.length + 1);

    nfa.states[0] = new State();
    nfa.states[0].transitions[0] = new Transition(null, 1);
    nfa.states[0].transitions[1] = new Transition(null, states.length + 1);

    for(int i = 0; i < states.length; i++)
    {
      nfa.states[i + 1] = new State(states[i]);

      if(nfa.states[i + 1].transitions[0] != null)
      {
        nfa.states[i + 1].transitions[0].destination += 1;
      }

      if(nfa.states[i + 1].transitions[1] != null)
      {
        nfa.states[i + 1].transitions[1].destination += 1;
      }
    }

    for(int i = 0; i < next.states.length; i++)
    {
      nfa.states[states.length + i + 1] = new State(next.states[i]);

      if(nfa.states[states.length + i + 1].transitions[0] != null)
      {
        nfa.states[states.length + i + 1].transitions[0].destination +=
          states.length + 1;
      }

      if(nfa.states[states.length + i + 1].transitions[1] != null)
      {
        nfa.states[states.length + i + 1].transitions[1].destination +=
          states.length + 1;
      }
    }

    return nfa;
  }

  @Override
  public Object clone()
  {
    return new NFA(this);
  }

  @Override
  public String toString()
  {
    StringBuffer result = new StringBuffer();
    for(int i = 0; i < states.length; i++)
    {
      result.append(i + ":" + states[i] + System.getProperty("line.separator"));
    }
    return result.toString();
  }

  public static class State
  {
    public String accept;

    public Transition[] transitions = new Transition[2];

    public State()
    {}

    public State(State state)
    {
      if(state.accept != null)
      {
        accept = state.accept;
      }

      if(state.transitions[0] != null)
      {
        transitions[0] = new Transition(state.transitions[0]);
      }

      if(state.transitions[1] != null)
      {
        transitions[1] = new Transition(state.transitions[1]);
      }
    }

    @Override
    public String toString()
    {
      StringBuffer result = new StringBuffer();
      if(accept != null)
      {
        result.append("(" + accept + ") ");
      }
      if(transitions[0] != null)
      {
        result.append(" " + transitions[0]);
      }
      if(transitions[1] != null)
      {
        result.append(" " + transitions[1]);
      }
      return result.toString();
    }
  }

  public static class Transition
  {
    public CharSet chars;
    public int destination;

    public Transition(CharSet chars, int destination)
    {
      this.chars = chars;
      this.destination = destination;
    }

    public Transition(Transition transition)
    {
      chars = transition.chars;
      destination = transition.destination;
    }

    @Override
    public String toString()
    {
      return destination + ":{" + chars + "}";
    }
  }
}
