package morfologik.fsa.builders;

import java.util.Collections;
import java.util.Set;

import morfologik.fsa.FSA;
import morfologik.fsa.FSAFlags;

/**
 * An FSA with constant-size arc representation produced directly by
 * {@link FSABuilder}.
 * 
 * @see FSABuilder
 */
final class ConstantArcSizeFSA extends FSA {
  /** Size of the target address field (constant for the builder). */
  public final static int TARGET_ADDRESS_SIZE = 4;

  /** Size of the flags field (constant for the builder). */
  public final static int FLAGS_SIZE = 1;

  /** Size of the label field (constant for the builder). */
  public final static int LABEL_SIZE = 1;

  /**
   * Size of a single arc structure.
   */
  public final static int ARC_SIZE = FLAGS_SIZE + LABEL_SIZE + TARGET_ADDRESS_SIZE;

  /** Offset of the flags field inside an arc. */
  public final static int FLAGS_OFFSET = 0;

  /** Offset of the label field inside an arc. */
  public final static int LABEL_OFFSET = FLAGS_SIZE;

  /** Offset of the address field inside an arc. */
  public final static int ADDRESS_OFFSET = LABEL_OFFSET + LABEL_SIZE;

  /** A dummy address of the terminal state. */
  final static int TERMINAL_STATE = 0;

  /**
   * An arc flag indicating the target node of an arc corresponds to a final
   * state.
   */
  public final static int BIT_ARC_FINAL = 1 << 1;

  /** An arc flag indicating the arc is last within its state. */
  public final static int BIT_ARC_LAST = 1 << 0;

  /**
   * An epsilon state. The first and only arc of this state points either to the
   * root or to the terminal state, indicating an empty automaton.
   */
  private final int epsilon;

  /**
   * FSA data, serialized as a byte array.
   */
  private final byte[] data;

  /**
   * @param data
   *          FSA data. There must be no trailing bytes after the last state.
   */
  ConstantArcSizeFSA(byte[] data, int epsilon) {
    assert epsilon == 0 : "Epsilon is not zero?";

    this.epsilon = epsilon;
    this.data = data;
  }

  @Override
  public int getRootNode() {
    return getEndNode(getFirstArc(epsilon));
  }

  @Override
  public int getFirstArc(int node) {
    return node;
  }

  @Override
  public int getArc(int node, byte label) {
    for (int arc = getFirstArc(node); arc != 0; arc = getNextArc(arc)) {
      if (getArcLabel(arc) == label)
        return arc;
    }
    return 0;
  }

  @Override
  public int getNextArc(int arc) {
    if (isArcLast(arc))
      return 0;
    return arc + ARC_SIZE;
  }

  @Override
  public byte getArcLabel(int arc) {
    return data[arc + LABEL_OFFSET];
  }

  /**
   * Fills the target state address of an arc.
   */
  private int getArcTarget(int arc) {
    arc += ADDRESS_OFFSET;
    return (data[arc]           ) << 24 | 
           (data[arc + 1] & 0xff) << 16 | 
           (data[arc + 2] & 0xff) << 8  | 
           (data[arc + 3] & 0xff);
  }

  @Override
  public boolean isArcFinal(int arc) {
    return (data[arc + FLAGS_OFFSET] & BIT_ARC_FINAL) != 0;
  }

  @Override
  public boolean isArcTerminal(int arc) {
    return getArcTarget(arc) == 0;
  }

  private boolean isArcLast(int arc) {
    return (data[arc + FLAGS_OFFSET] & BIT_ARC_LAST) != 0;
  }

  @Override
  public int getEndNode(int arc) {
    return getArcTarget(arc);
  }

  @Override
  public Set<FSAFlags> getFlags() {
    return Collections.emptySet();
  }
}
