package tarau.jinni;
import java.util.Enumeration;

/**
* Datatype for a Prolog clause (H:-B) having a head H and a body b
*/
public class Clause extends Fun {  /**
   *  Builds a clause given ith head and its body
   */
  public Clause(Term head, Term body) {
    super(":-",head,body);
  }

  /**
  * Constructs a clause by parsing its string representation.
  * Note the building of a dictionary of variables, allowing
  * listing of the clause with its original variable names.
  */
  public Clause(String s) {
    super(":-");
    Clause C=clauseFromString(s);
    //IO.mes("CLAUSE:"+C.pprint()+"\nDICT:"+C.dict);
    this.args=C.args;
    this.dict=C.dict;
    this.ground=C.ground;
  }

  /**
  * Extracts a clause from its String representation.
  */
  
  public static Clause clauseFromString(String s) {
    return Parser.clsFromString(s);
  }

  /**
    Reads a goal as a clause containing a dummy header with
    all veriables in it
 */

  public Clause toGoal() {
     Clause G=new Clause(varsOf(),getHead());
     G.dict=dict;
     G.checkIfGround();
     IO.trace("conversion from clause to goal ignores body of: "+pprint());
     return G;
  }

  public static Clause goalFromString(String line) {
     IO.trace("read string: <"+line+">");

     if(null==line) 
       line=Const.anEof.name();
     else if(0==line.length()) 
       return null;

	   Clause C=clauseFromString(line);
     if(null==C) {
       IO.errmes("warning (null Clause):"+line);
       return null;
     }

     //IO.trace("got goal:\n"+C.toGoal()); //OK
		 return C.toGoal();
  }

  /**
  * Detects that a clause is ground (i.e. has no variables)
  */
  final void checkIfGround() {
     ground=varsOf().getArity()==0;
  }

  /**
    Variable dictionary
  */
  HashDict dict=null;

  /**
    Remembers if a clause is ground.
  */
  boolean ground=false;

  /**
    File name and line where sources start and end (if applicable)
  */

  public String fname=null;
  public int begins_at=0;
  public int ends_at=0;

  public void setFile(String fname,int begins_at,int ends_at) {
    this.fname=fname.intern();
    this.begins_at=begins_at;
    this.ends_at=ends_at;
  }

  /**
    Checks if a Clause has been proven ground after
    beeing read in or created.
  */
  final boolean provenGround() {
    return ground;
  }

  /**
  * Prints out a clause as Head:-Body
  */
  private String Clause2String(Clause c) {
    Term h=c.getHead();
    Term t=c.getBody();
    if(t instanceof Conj) return h+":-"+((Conj)t).conjToString();
    return h+":-"+t;
  }

  // uncomment if you want this to be the default toString
  // procedure - it might create read-back problems, though
  //public String toString() {
  //  return Clause2String(this);
  //}


  /**
  * Pretty prints a clause after replacing ugly variable names
  */
  public String pprint() {
    return pprint(false);
  }

  /**
  * Pretty prints a clause after replacing ugly variable names
  */
  public String pprint(boolean replaceAnonymous) {
    String s=Clause2String(this.cnumbervars(replaceAnonymous));
    //if(fname!=null) s="%% "+fname+":"+begins_at+"-"+ends_at+"\n"+s;
    return s;
  }

  /**
    Clause to Term converter: the joy of strong typing:-)
  */
  public Clause toClause() { // overrides toClause in Term
    return this;
  }

  /**
  * Replaces varibles with nice looking upper case
  * constants for printing purposes
  */
  synchronized public Clause cnumbervars(boolean replaceAnonymous) {
     if(dict==null) return (Clause)numbervars();
     if(provenGround()) return this;
     Trail trail=new Trail();
     Enumeration e=dict.keys();

     while(e.hasMoreElements()) {
       Object X=e.nextElement();
       if(X instanceof String) {
         Var V=(Var)dict.get(X);
         long occNb=((Int)dict.get(V)).longValue();
         String s=(occNb<2 && replaceAnonymous)?"_":(String)X;
         // bug: occNb not accurate when adding artif. '[]' head
         V.unify(new PseudoVar(s),trail);
       }
     }
     Clause NewC=(Clause)numbervars();
     trail.unwind(0);
     return NewC;
  }


  /**
    Converts a clause to a term.
    Note that Head:-true will convert to the term Head.
  */
  public Term toTerm() {
    if(getBody() instanceof true_) return getHead();
    return this;
  }

  /**
    Creates a copy of the clause with variables standardized
    apart, i.e. something like f(s(X),Y,X) becomes f(s(X1),Y1,X1))
    with X1,Y1 variables garantted not to occurring in the
    the current resolvant.
  */
  final Clause ccopy() {
    Clause C=(Clause)copy();
    C.dict=null;
    C.ground=ground;
    return C;
  }

  /**
    Extracts the head of a clause (a Term).
  */
  public final Term getHead() {
    return args[0].ref();
  }

  /**
    Extracts the body of a clause
  */
  public final Term getBody() {
    return args[1].ref();
  }


  /**
    Gets the leftmost (first) goal in the body of a clause,
    i.e. from H:-B1,B2,...,Bn it will extract B1.
  */
  final Term getFirst() {
    Term body=getBody();
    if(body instanceof Conj) return ((Conj)body).args[0].ref();
    else if(body instanceof true_) return null;
    else return body;
  }

  /**
    Gets all but the leftmost goal in the body of a clause,
    i.e. from H:-B1,B2,...,Bn it will extract B2,...,Bn.
    Note that the returned Term is either Conj or True,
    the last one meaning an empty body.

    @see True
    @see Conj
  */
  final Term getRest() {
    Term body=getBody();
    if(body instanceof Conj) return ((Conj)body).args[1].ref();
    else return Const.aTrue;
  }

  /**
    Concatenates 2 Conjunctions
    @see Clause#unfold
  */
  static final Term appendConj(Term x, Term y) {
    y=y.ref();
    if(x instanceof true_) return y;
    if(y instanceof true_) return x; // comment out if using getState
    if(x instanceof Conj) {
      Term curr=((Conj)x).args[0].ref();
      Term cont=
          appendConj(
             ((Conj)x).args[1],y
          );
      //curr.getState(this,cont);
      return new Conj(curr,cont);
    }
    else
      return new Conj(x,y);
  }

  /**
    Algebraic composition operation of 2 Clauses, doing
    the basic resolution step Jinni is based on.
    From A0:-A1,A2...An and B0:-B1...Bm it builds
    (A0:-B1,..Bm,A2,...An) mgu(A1,B0). Note that it returns
    null if A1 and B0 do not unify.

    @see Term#unify()

  */
  Clause unfold(Clause that,Trail trail) {
    Clause result=null;
    Term first=getFirst();
 
    // this is the resolvent, that is the SHARED clause
    // threfore synchronization is over that, not this
    if( first!=null && that.getHead().matches(first) ) {

    //IO.mes("UNFOLD: THIS: >>>:"+trail.name()+" "+that);

      if(!that.provenGround()) that=that.ccopy();

      that.getHead().unify(first,trail);

    //IO.mes("UNFOLD<<<:"+trail.name()+": "+that);

      Term cont=appendConj(that.getBody(),getRest());
      result = new Clause(getHead(),cont);
    }
    return result;
  }

  synchronized final Clause unfold_with_goal(Clause goal,Trail trail) {
      return goal.unfold(this,trail);
  }
  
  synchronized Clause unfoldedCopy(Clause that,Trail trail) {
    int oldtop=trail.size();
    Clause result=unfold(that,trail);
    if(result==null) return null;
    result=result.ccopy();
    trail.unwind(oldtop);
    return result;
  }

  /**
    Returns a key based on the principal functor of the
    head of the clause and its arity.
  */
  public String getKey() {
    return getHead().getKey();
  }

  final boolean isClause() {
    return true;
  }
}
