package tarau.jinni;
import java.io.Reader;import java.io.IOException;import java.io.StreamTokenizer;
import java.util.Vector;


/**
  Lexicographic analyser reading from a stream
*/
class Lexer extends StreamTokenizer {
  protected Reader input;

  public Lexer(Reader I) throws IOException {
    super(I);
    this.input=I;
    parseNumbers();
    eolIsSignificant(true);
    ordinaryChar('.');
    ordinaryChar('-'); // creates problems with -1 etc.
    ordinaryChar('/');
    quoteChar('\'');
    quoteChar('\"');
    wordChar('$');
    wordChar('_');
    slashStarComments(true);
    commentChar('%');
    dict=new HashDict();
  }
  
  /**
     Path+File name based constructor
     Used in prolog2java
  */
 
  public Lexer(String path,String s) throws IOException {
    this(IO.url_or_file(path+s)); // stream
  }

  /**
     String based constructor.
     Used in queries ended by \n + prolog2java.
  */

  public Lexer(String s) throws Exception {
    this(IO.string_to_stream(s));
  }

  public Lexer() throws IOException {
    this(IO.input);
  }

  private final static String anonymous="_".intern();

  private final static String char2string(int c) {
    return ""+(char)c;
  }

  private boolean inClause=false;

  boolean atEOF() {
    boolean yes=(TT_EOF==ttype);
    if(yes) try {
      input.close();
    } 
    catch (IOException e) {
      IO.trace("unable to close atEOF");
    }
    return yes;
  }

  boolean atEOC() {
    return !inClause;
  }

  protected final static Term make_const(String s) {
     return new constToken(s);
  }

  private final static Term make_fun(String s) {
    return new funToken(s);
  }

  private final static Term make_int(double n) {
    return new intToken((int)n);
  }

  private final static Term make_real(double n) {
    return new realToken(n);
  }
  private final static Term make_number(double nval) {     Term T;
     if(Math.floor(nval)==nval)
       T=make_int(nval);
     else
       T=make_real(nval);      return T;
  }    
  private final Term make_var(String s) {
    s=s.intern();
    Var X;
    long occ;
    if(s==anonymous) {
      occ=0;
      X=new Var();
      s=X.toString();
    }
    else {
      X=(Var)dict.get(s);
      if(X==null) {
         occ=1;
         X=new Var();
      }
      else {
        occ=((Int)dict.get(X)).longValue();
        occ++;
      }  
    }
    Int I=new Int(occ);
    dict.put(X,I);
    dict.put(s,X);
    return new varToken(X,new Const(s),I);
  }

  private final void whitespaceChar(char c) {whitespaceChars(c,c);}

  private final void wordChar(char c) {wordChars(c,c);}

  HashDict dict;

  private Term getWord(boolean quoted) throws IOException {
         Term T;
         if(quoted && 0==sval.length()) T=make_const("");
         /* DO NOT DO THIS: quoting is meant to prevent it!!!
         else if("()[]|".indexOf(sval.charAt(0))>=0) {
           switch(sval.charAt(0)) {
             case '(':
                T=new lparToken();
               break;
             case ')':
                T=new rparToken();
               break;
             case '[':
                T=new lbraToken();
               break;
             case ']':
                T=new rbraToken();
               break;
             case '|':
                T=new barToken();
               break;
           }
         }
         */
         else {
           char c=sval.charAt(0);
           if(!quoted && (Character.isUpperCase(c) || '_'==c))
             T=make_var(sval);
           else { // nonvar
             String s=sval; int nt=nextToken();
             pushBack();
             T=('('==nt)? make_fun(s):make_const(s); 
           }
         }
         return T;
  }

  protected Term next() throws IOException {
    int n=nextToken();
    inClause=true;
    Term T; 
    switch (n) {
      case TT_WORD:
       T=getWord(false);
      break;

      case '\'':
        T=getWord(true);
      break;

      case TT_NUMBER:         T=make_number(nval);
      break;      
      case TT_EOF: 
             T=new eofToken();
             inClause=false; 
           break;           
      case TT_EOL: 
           T=next();
           break;            case '-':           if(TT_NUMBER==nextToken()) {               T=make_number(-nval);           }           else {
              pushBack();
              T=make_const(char2string(n));           }           
           break;    
      case ':': if('-'==nextToken()) {
                  T=new iffToken(":-");
                }
                else { 
                  pushBack();
                  T=make_const(char2string(n));
                }
                break;

      case '.': int c=nextToken();
                if(TT_EOL==c || TT_EOF==c) {
                  inClause=false;
                  //dict.clear();  ///!!!: this looses Var names
                  T=new eocToken();
                }
                else {
                  pushBack();
                  T=make_const(char2string(n));  // !!!: sval is gone
                }
                break;

      case '\"': T=new stringToken((constToken)getWord(true));
                break;

      case '(': T=new lparToken(); break;
      case ')': T=new rparToken(); break;
      case '[': T=new lbraToken(); break;
      case ']': T=new rbraToken(); break;
      case '|': T=new barToken(); break;

      case ',': T=new commaToken(); break;
      default: 
               T=make_const(char2string(n));
    }    //IO.mes("TOKEN:"+T);
    return T;
  }
}

class varToken extends Fun {
  public varToken(Var X,Const C,Int I) {
    super("varToken",3); args[0]=X; args[1]=C; args[2]=I;
  }
}

class intToken extends Fun {
  public intToken(int i) {super("intToken",new Int(i));}
}

class realToken extends Fun {
  public realToken(double i) {super("realToken",new Real(i));}
}

class constToken extends Fun {
  public constToken(Const c) {
    super("constToken",c);
    args[0]=c.toBuiltin();
  }

  public constToken(String s) {
    this(new Const(s));
  }
}

class stringToken extends Fun {
  public stringToken(constToken c) {
    super("stringToken",(Const)(c.args[0]));
  }
}

class funToken extends Fun {
  public funToken(String s) {
    super("funToken",new Fun(s));
  }
}

class eocToken extends Fun {
  public eocToken() {super("eocToken",new Const("end_of_clause"));}
}

class eofToken extends Fun {
  public eofToken() {super("eofToken",Const.anEof);
  }
}

class iffToken extends Fun {
  public iffToken(String s) {super("iffToken",new Const(s));}
}

class Token extends Const {
  Token(String s) {
    super(s);
  }
}

class lparToken extends Token {
  public lparToken() {super("(");}
}

class rparToken extends Token {
  public rparToken() {super(")");}
}

class lbraToken extends Token {
  public lbraToken() {super("[");}
}

class rbraToken extends Token {
  public rbraToken() {super("]");}
}

class barToken extends Token {
  public barToken() {super("|");}
}

class commaToken extends Token {
  public commaToken() {super(",");}
}


/**
  Simplified Prolog parser:
  Synatax supported:
  a0:-a1,...,an.
  - no operators ( except toplevel :- and ,)
  - use quotes to create special symbol names, i.e.
  compute('+',1,2, Result) and  write(':-'(a,','(b,c)))
*/

  class Parser extends Lexer {
    
  public Parser(Reader I) throws IOException {
    super(I);
  }
  
  /*
    used in prolog2java
  */
  public Parser(String p,String s) throws IOException {
    super(p,s);
  }

  public Parser(String s) throws Exception {
   super(s);
  }

  /**
    Main Parser interface: reads a clause together
    with variable name information
  */
  public Clause readClause() { 
    Clause t=null; boolean verbose=false;
    try {
       t=readClauseOrEOF();
       //IO.mes("GOT Clause:"+t);
    }
	/**
	  catch built exception clauses which are defined
	  in lib.pro - allowing to recover or be quiet about
	  such errors.
	*/
    catch(ParserException e)
    {
       t=errorClause(e,"syntax_error",lineno(),verbose);
       try {
        while(!atEOC() && !atEOF())
          next();
       }
       catch (IOException toIgnore) {
       }
    }
    catch(IOException e) {
       t=errorClause(e,"io_exception",lineno(),verbose);
    }
    catch(Exception e) {
       t=errorClause(e,"unexpected_syntax_exception",lineno(),true);
    }
    return t;
  }

  static final Clause errorClause(Exception e,String type,
                                     int line,boolean verbose) {

    String mes=e.getMessage();
    if(null==mes) mes="unknown_error";
    Fun f=new Fun("error",
                  new Const(type),
                  new Const(mes),
                  new Fun("line",new Int(line))
                );
	  Clause C=new Clause(f,Const.aTrue);
    if(verbose) { 
      IO.errmes(type+" error at line:"+line);
      IO.errmes(C.pprint(),e);
    }
    return C;  
  }

  static public final boolean isError(Clause C) {
    Term H=C.getHead();
    if(H instanceof Fun &&
       "error".equals(((Fun)H).name()) && 
        H.getArity()==3 &&  
        ! (((Fun)H).args[0].ref() instanceof Var) 
      )
      return true;
    return false;
  }

  static public final void showError(Clause C) {
     IO.errmes("*** "+C);
  }

  static protected final Clause toClause(Term T,HashDict dict) {
    Clause C=T.toClause(); // adds ...:-true if missing
    C.dict=dict;
    return C;
  }

  private Clause readClauseOrEOF() throws IOException {

     dict=new HashDict();

     Term n=next();

     //IO.mes("readClauseOrEOF 0:"+n);

     if(n instanceof eofToken) return null; // $$toClause(n.token(),dict);

     if(n instanceof iffToken) {
       n=next();
       Term t=getTerm(n);
       Term bs=getConjCont(t);
       Clause C=new Clause(new Const("init"),bs); 
       C.dict=dict;
       return C;
     }

     Term h=getTerm(n);

     //IO.mes("readClauseOrEOF 1:"+h);

     n=next();

     //IO.mes("readClauseOrEOF 2:"+n);

     if(n instanceof eocToken || n instanceof eofToken) 
       return toClause(h,dict);

     //IO.mes("readClauseOrEOF 3:"+b);

     Clause C=null;
     if(n instanceof iffToken) {
       Term t=getTerm();
       Term bs=getConjCont(t);
       C=new Clause(h,bs); 
       C.dict=dict;
     }
     else if(n instanceof commaToken) {
       Term b=getTerm();
       Term bs=getConjCont(b);
       C=toClause(new Conj(h,bs),dict);
     }
     else {
       throw new ParserException(
           "':-' or '.' or ','", "bad body element",n);
     }
     return C;
  } 

  private final Term getConjCont(Term curr) 
     throws IOException {

     Term n=next();Term t=null;
     if(n instanceof eocToken) t=curr;
     else if(n instanceof commaToken) {
       Term other=getTerm();
       t=new Conj(curr,getConjCont(other));
     }
     if(null==t) {
       throw new ParserException(
           "'.'", "bad body element",n);
     }
     return t;
  } 

  protected final Term getTerm(Term n) throws IOException {
     Term t=n.token();
     if(n instanceof varToken || 
        n instanceof intToken ||
        n instanceof realToken ||
        n instanceof constToken) {
        // is just OK as it is
     }
     else if(n instanceof stringToken) {
       t=((Nonvar)((stringToken)n).args[0]).toChars();
       //IO.mes("getTerm:stringToken-->"+t);

     }
     else if(n instanceof lbraToken) {
       t=getList();
     }
     else if(n instanceof funToken) {
       Fun f=(Fun)t;
       f.args=getArgs();
       t=f.toBuiltin();
     }
     else throw new ParserException(
       "var,int,real,constant,'[' or functor","bad term",n);
     return t;
  } 

  protected Term getTerm() throws IOException {
     Term n=next();
     return getTerm(n);
  } 

  private final Term[] getArgs() throws IOException {
    Term n=next(); 
    if(!(n instanceof lparToken)) 
       throw new ParserException("'('","in getArgs",n);
    Vector v=new Vector();
    Term t=getTerm();
    v.addElement(t);
    for(;;) {
      n=next();
      if(n instanceof rparToken) {
        Term args[]=new Term[v.size()];
        v.copyInto(args);
        return args;
      }
      else if (n instanceof commaToken) {
        t=getTerm();
        v.addElement(t);
      }
      else { 
        throw new ParserException("',' or ')'","bad arg",n);
      }
    }
  } 

  private final Term getList() throws IOException {
     Term n=next();
     if(n instanceof rbraToken) return Const.aNil;
     Term t=getTerm(n);
     return getListCont(t);
  } 

  private final Term getListCont(Term curr) 
          throws IOException {
     //IO.trace("curr: "+curr);
     Term n=next();Term t=null;
     if(n instanceof rbraToken) t=new Cons(curr,Const.aNil);
     else if(n instanceof barToken) {
        t=new Cons(curr,getTerm());
        n=next(); 
        if(!(n instanceof rbraToken)) {
          throw new ParserException("']'","bad list end after '|'",n);
        }
     }
     else if(n instanceof commaToken) {
       Term other=getTerm();
       t=new Cons(curr,getListCont(other));
     }
     if(t==null) 
        throw new ParserException("| or ]","bad list continuation",n);
     return t;
  }
  

  private static final String patchEOFString(String s) {
    if(!(s.lastIndexOf(".")>=s.length()-2)) s=s+".";
    return s;
  }

  public static Clause clsFromString(String s) {
    if(null==s) return null;
    s=patchEOFString(s);
    Clause t=null;
    try {
      Parser p;
        p=new Parser(s);
        t=p.readClause();
    }
    catch(Exception e) { // nothing expected to catch
       IO.errmes("unexpected parsing error",e);
    }
    if(t.dict==null) t.ground=false;
    else t.ground=t.dict.isEmpty();
    return t;
  }

}


class ParserException extends IOException {
   public ParserException(String e,String f,Term n) {
     super(
           "expected: "+e+", found: "+f+
           "'"+n+"'");
   }
}
