/*
 *  FSMInstance.java
 *
 *  Copyright (c) 1998-2001, The University of Sheffield.
 *
 *  This file is part of GATE (see http://gate.ac.uk/), and is free
 *  software, licenced under the GNU Library General Public License,
 *  Version 2, June 1991 (in the distribution as file licence.html,
 *  and also available at http://gate.ac.uk/gate/licence.html).
 *
 *  Valentin Tablan, 05/May/2000
 *
 *  $Id: FSMInstance.java,v 1.21 2002/03/14 16:40:12 hamish Exp $
 */
package gate.fsm;

import gate.*;
import gate.annotation.AnnotationSetImpl;
import gate.util.*;
import gate.jape.*;

import java.util.*;
import java.io.*;

/**
  * The objects of this class represent instances of working Finite State
  * Machine during parsing a gate document (annotation set).
  * In order to completely define the state a FSM is in one needs to store
  * information regarding:
  * -the position in the FSM transition graph
  * -the position in the annotation graph
  * -the set of bindings that occured up to the current state.
  *  note that a set of bindings is an object of type Map that maps names
  * (java.lang.String) to bags of annotations (gate.AnnotationSet)
  */
public class FSMInstance implements Comparable, Cloneable, Serializable {

  /** Debug flag */
  private static final boolean DEBUG = false;

  /** Creates a new FSMInstance object.
    * @param supportGraph the transition graph of the FSM
    * @param FSMPosition the state this instance will be in
    * @param startNode the node in the AnnotationSet where this FSM instance
    * started the matching
    * @ AGPosition the node in the AnnotationSet up to which this FSM Instance
    * advanced during the matching.
    * @param bindings a HashMap that maps from labels (objects of type String)
    * to sets of annotations (objects of type AnnotationSet). This map stores
    * all the bindings that took place during the matching process.
    * This FSMInstance started the matching on an AnnotationSet from "startNode"
    * and advanced to "AGPosition"; during this process it traversed the path in
    * the transition graph "supportGraph" from the initial state to
    * "FSMPosition" and made the bindings stored in "bindings".
    */
  public FSMInstance(FSM supportGraph, State FSMPosition,
                     Node startNode, Node AGPosition, HashMap bindings,
                     Document document) {

    this.supportGraph = supportGraph;
    this.FSMPosition = FSMPosition;
    this.startNode = startNode;
    this.AGPosition = AGPosition;
    this.bindings = bindings;
    this.document = document;
    length = AGPosition.getOffset().longValue() -
             startNode.getOffset().longValue();
    fileIndex = FSMPosition.getFileIndex();
    priority = FSMPosition.getPriority();
  }

  /** Returns the FSM transition graph that backs this FSM instance
    * @return an FSM object
    */
  public FSM getSupportGraph(){ return supportGraph; }

  /** Returns the position in the support graph for this FSM instance
    * @return an object of type State
    */
  public State getFSMPosition(){ return FSMPosition; }

  /** Sets the position in the support transition graph for this FSM instance
    * Convenience method for when the state is not known at construction time.
    */
  public void setFSMPosition(State newFSMPos) {
    FSMPosition = newFSMPos;
    fileIndex = FSMPosition.getFileIndex();
    priority = FSMPosition.getPriority();
  }

  /** Returns the index in the Jape definition file of the rule that caused
    * the generation of the FSM state this instance is in.
    * This value is correct if and only if this FSM instance is in a final
    * state of the FSM transition graph.
    * @return an int value.
    */
  public int getFileIndex(){ return fileIndex; }

  /** Returns the node in the AnnotationSet from which this FSM instance
    * started the matching process.
    * @return a gate.Node object
    */
  public Node getStartAGPosition(){ return startNode; }

  /** Returns the node up to which this FSM instance advanced in the
    * Annotation graph during the matching process.
    * @return a gate.Node object
    */
  public Node getAGPosition(){ return AGPosition; }

  /** Sets the current position in the AnnotationSet.
    * Convenience method for cases when this value is not known at construction
    * time.
    * @param node a position in the AnnotationSet
    */
  public void setAGPosition(Node node){
    AGPosition = node;
    length = AGPosition.getOffset().longValue() -
             startNode.getOffset().longValue();
  }

  /** Gets the map representing the bindings that took place during the matching
    * process this FSM instance performed.
    * @return a HashMap object
    */
  public HashMap getBindings() { return bindings; }

  /** Returns the length of the parsed region in the document under scrutiny.
    * More precisely this is the distnace between the Node in the annotation
    * graph where the matching started and the current position.
    * @return a long value
    */
  public long getLength() { return length; }

  /** Overrides the hashCode method from Object so this obejcts can be stored in
    * hash maps and hash sets.
    */
  public int hashCode() {
    return (int)length ^ priority ^ fileIndex ^ bindings.hashCode() ^
           FSMPosition.getAction().hashCode();
  }

  public boolean equals(Object other){
    if(other instanceof FSMInstance){
      FSMInstance otherFSM = (FSMInstance)other;
      boolean result = length == otherFSM.length &&
             priority == otherFSM.priority &&
             fileIndex == otherFSM.fileIndex &&
             bindings.equals(otherFSM.bindings) &&
             FSMPosition.getAction().equals(otherFSM.FSMPosition.getAction());
      return result;
    }else{
      throw new ClassCastException(other.getClass().toString());
    }
  }

  /** Returns a clone of this object.
    * The cloning is done bitwise except for the bindings that are cloned by
    * themselves
    * @return an Object value that is actually a FSMInstance object
    */
  public Object clone() {
    //do a classic clone except for bindings which need to be cloned themselves
    try {
      FSMInstance clone = (FSMInstance)super.clone();
      clone.bindings = (HashMap)bindings.clone();
      return clone;
    } catch (CloneNotSupportedException cnse) {
      cnse.printStackTrace(Err.getPrintWriter());
      return null;
    }
  }

  /*
  public Object clone() {
  //do a classic clone except for bindings which need to be cloned themselves
  //Out.println("Clone!");
    FSMInstance clone = FSMInstance.getNewInstance(this.supportGraph,
                                                   this.FSMPosition,
                                                   this.startNode,
                                                   this.AGPosition,
                                                   null);
    clone.bindings = (HashMap)(bindings.clone());
    return (FSMInstance)clone;
  }
  */

  /** Implementation of the compareTo method required by the Comparable
    * interface. The comparison is based on the size of the matched region and
    * the index in the definition file of the rule associated to this FSM
    * instance (which needs to be in a final state)
    * The order imposed by this method is the priority needed in case of a
    * multiple match.
    */
  public int compareTo(Object obj) {
    if (obj instanceof FSMInstance) {
      if(obj == this) return 0;
      FSMInstance other = (FSMInstance)obj;
      if(length < other.getLength()) return -1;
      else if(length > other.getLength()) return 1;
      //equal length
      else if(priority < other.priority) return -1;
      else if(priority > other.priority) return 1;
      //equal priority
      else return other.fileIndex - fileIndex;
    } else throw new ClassCastException(
                    "Attempt to compare a FSMInstance object to an object " +
                    "of type " + obj.getClass()+"!");
  }

  /** Returns a textual representation of this FSM instance.
    */
  public String toString() {
    String res = "";
    RightHandSide rhs = getFSMPosition().getAction();
    if(rhs != null){
      res += rhs.getPhaseName() + "." + rhs.getRuleName() + ": \"";
      try{
        res += document.getContent().getContent(
                        getStartAGPosition().getOffset(),
                        getAGPosition().getOffset()).toString() + "\"";
      }catch(InvalidOffsetException ioe){
        ioe.printStackTrace(Err.getPrintWriter());
      }

      Iterator labelIter = bindings.keySet().iterator();
      res += "\n{";
      while(labelIter.hasNext()){
        String label = (String)labelIter.next();
        Collection annots = (Collection)bindings.get(label);
        res += "\n" + label + ": ";
        Iterator annIter = annots.iterator();
        while(annIter.hasNext()){
          Annotation ann  = (Annotation)annIter.next();
          res += ann.getType() + "(\"";
          try{
            res += document.getContent().
                            getContent(ann.getStartNode().getOffset(),
                                       ann.getEndNode().getOffset()).toString();
          }catch(InvalidOffsetException ioe){
            ioe.printStackTrace(Err.getPrintWriter());
          }
          res += "\") ";
        }
      }
      res += "\n}";
    }else{
      res +=  "FSM position :" + FSMPosition.getIndex() +
              "\nFirst matched ANN at:" + startNode.getId() +
              "\nLast matched ANN at :" + AGPosition.getId() +
              "\nPriority :" + priority +
              "\nFile index :" + fileIndex +
              "\nBindings     :" + bindings;
    }
    return res;
  }

  /** The FSM for which this FSMInstance is an instance of. */
  private FSM supportGraph;

  /** The current state of this FSMInstance */
  private State FSMPosition;

  /** The place (Node) in the AnnotationSet where the matching started*/
  private Node AGPosition, startNode;

  /** A map from java.lang.String to gate.AnnotationSet describing all the
    * bindings that took place during matching.
    * needs to be HashMap instead of simply Map in order to cloneable
    */
  private HashMap bindings;

  /** The size of the matched region in the Annotation Set*/
  private long length = 0;

  /**
    * The index in the definition file of the rule from which the AGPosition
    * state was generated.
    */
  private int fileIndex;


  private Document document;
  /**
    * The priority in the definition file of the rule from which the AGPosition
    * state was generated.
    */
  private int priority;

  /** Static method that provides new FSM instances. This method handles some
    * basic object pooling in order to reuse the FSMInstance objects.
    * This is considered to be a good idea because during jape transducing
    * a large number of FSMIntances are needed for short periods.
    */
  public static FSMInstance getNewInstance(FSM supportGraph, State FSMPosition,
                                           Node startNode, Node AGPosition,
                                           HashMap bindings, Document doc) {
    FSMInstance res;
    if(myInstances.isEmpty()) res = new FSMInstance(supportGraph, FSMPosition,
                                                    startNode, AGPosition,
                                                    bindings, doc);
    else {
      res = (FSMInstance)myInstances.removeFirst();
      res.supportGraph = supportGraph;
      res.FSMPosition = FSMPosition;
      res.startNode = startNode;
      res.AGPosition = AGPosition;
      res.bindings = bindings;
    }
    return res;
  }

  /** Static method used to return a FSMInstance that is not needed anymore
    */
  public static void returnInstance(FSMInstance ins) {
    myInstances.addFirst(ins);
  }

  /** Release all the FSMInstances that are not currently in use */
  public static void clearInstances() {
    myInstances = new LinkedList();
  }

  /** The list of existing instances of type FSMInstance */
  private static LinkedList myInstances;

  /** The offset in the input List where the last matched annotation was*/
  static{
    myInstances = new LinkedList();
  }

} // FSMInstance
