#define YAP_CPP_INTERFACE 1

#include <stdlib.h>

#include <gmp.h>

extern "C" {

#include "Yap.h"

#include "Yatom.h"

#include "YapHeap.h"

#include "pl-shared.h"

#include "clause.h"

#include "yapio.h"

#include "Foreign.h"

#include "attvar.h"

#include "SWI-Stream.h"

#include "YapText.h"

#if HAVE_STDARG_H
#include <stdarg.h>
#endif

#if HAVE_STDINT_H
#include <stdint.h>
#endif

#if HAVE_STRING_H
#include <string.h>
#endif

#if _MSC_VER || defined(__MINGW32__) 
#include <windows.h>
#endif

  // taken from yap_structs.h
#include "iopreds.h"

// we cannot consult YapInterface.h, that conflicts with what we declare, though
// it shouldn't
}

class YAPTermHandle;
class YAPAtom;
class YAPFunctor;
class YAPQuery;

class YAPTerm {
  friend class YAPPredicate;
  friend class YAPTermHandle;
  friend class YAPApplTerm;
  friend class YAPPairTerm;
protected:
  Term t;
public:
  YAPTerm() {} // do nothing constructor
  YAPTerm(int i) { CACHE_REGS t = MkIntegerTerm( i ); }
  YAPTerm(void *ptr)  { CACHE_REGS t = MkIntegerTerm( (Int)ptr ); }
  YAPTerm(Term tn) { t = tn; }
  YAPTerm(char *s) { Term tp ; t = YAP_ReadBuffer(s,&tp); }

  YAP_tag_t  tag();
  YAPTerm  deepCopy();
  bool exactlyEqual(YAPTerm t1);
  bool unify(YAPTerm t1);
  bool unifiable(YAPTerm t1);
  bool variant(YAPTerm t1);
  intptr_t hash(size_t sz, size_t depth, bool variant);
  bool isVar() { return IsVarTerm(t); }
  bool isAtom() { return IsAtomTerm(t); }
  bool isInteger() { return IsIntegerTerm(t); }
  bool isFloat() { return IsFloatTerm(t); }
  bool isCompound() { return !(IsVarTerm(t) || IsNumTerm(t)); }
  bool isAppl() { return IsApplTerm(t); }
  bool isPair() { return IsPairTerm(t); }
  bool isGround() { return Yap_IsGroundTerm(t); }
  bool isList() { return Yap_IsListTerm(t); }
  bool isString() { return IsStringTerm(t); }
};

class YAPVarTerm: private YAPTerm {
public:
  YAPVarTerm(): YAPTerm() { CACHE_REGS t = MkVarTerm(); }
  CELL *getVar() { return VarOfTerm(t); }
  bool unbound() { return IsUnboundVar(VarOfTerm(t)); }
};

class YAPApplTerm: private YAPTerm {
public:
  YAPApplTerm(YAPFunctor f, YAPTerm ts[]);
  YAPApplTerm(YAPFunctor f);
  YAPFunctor getFunctor();
  YAPTerm getArg(unsigned int i);
};

class YAPPairTerm: private YAPTerm {
public:
  YAPPairTerm(YAPTerm hd, YAPTerm tl);
  YAPPairTerm();
  YAPTerm getHead() { return  YAPTerm(HeadOfTerm(t)); }
  YAPTerm getTail() { return  YAPTerm(TailOfTerm(t)); }
};

class YAPAtom {
  friend class YAPPredicate;
  friend class YAPFunctor;
  friend class YAPAtomTerm;
  Atom a;
public:
  YAPAtom( Atom at ) { a = at; }
  YAPAtom( char * s) { a = Yap_LookupAtom( s ); }
  YAPAtom( wchar_t * s) { a = Yap_LookupMaybeWideAtom( s ); }
  YAPAtom( char * s, size_t len) { a = Yap_LookupAtomWithLength( s, len ); }
  YAPAtom( wchar_t * s, size_t len) { a = Yap_LookupMaybeWideAtomWithLength( s, len ); }
  char *name(void);
};

class YAPStringTerm: private YAPTerm {
public:
  YAPStringTerm(char *s) ;
  YAPStringTerm(char *s, size_t len);
  YAPStringTerm(wchar_t *s) ;
  YAPStringTerm(wchar_t *s, size_t len);
  const char *getString() { return StringOfTerm(t); }
};

class YAPAtomTerm: private YAPTerm {
public:
  YAPAtomTerm(YAPAtom a): YAPTerm() { t = MkAtomTerm(a.a); }
  YAPAtomTerm(Atom a): YAPTerm()  { t = MkAtomTerm(a); }
  YAPAtomTerm(char *s) ;
  YAPAtomTerm(char *s, size_t len);
  YAPAtomTerm(wchar_t *s) ;
  YAPAtomTerm(wchar_t *s, size_t len);
  YAPAtom getAtom() { return YAPAtom(AtomOfTerm(t)); }
};

class YAPFunctor {
  friend class YAPApplTerm;
  friend class YAPPredicate;
  Functor f;
public:
  YAPFunctor( char * s, unsigned int arity) { f = Yap_MkFunctor( Yap_LookupAtom( s ), arity ); }
  YAPFunctor( wchar_t * s, unsigned int arity) { f = Yap_MkFunctor( Yap_LookupWideAtom( s ), arity ); }
  YAPFunctor( YAPAtom at, unsigned int arity) { f = Yap_MkFunctor( at.a, arity ); }
  YAPFunctor( Functor ff) { f = ff; }

  Atom name(void) {
    return NameOfFunctor( f );
  }

  unsigned int arity(void) {
    return ArityOfFunctor( f );
  }
};

class YAPTermHandle {
  long int handle;
public:
  YAPTermHandle(Term t) {   
   CACHE_REGS
   handle = Yap_InitSlot(t PASS_REGS);
 }
  ~YAPTermHandle(void) {   
   CACHE_REGS
     Yap_RecoverSlots(1 PASS_REGS);
 }

  YAPTerm get() {   
   CACHE_REGS
     return new YAPTerm( Yap_GetFromSlot(handle PASS_REGS) );
  }
  
  void set(YAPTerm t) {   
   CACHE_REGS
     Yap_PutInSlot(handle, t.t PASS_REGS);
  }
};
  
class YAPPredicate {
  friend class YAPQuery;
  PredEntry *ap;
  // trick to communicate t[] back to yapquery
  YAPPredicate(char *s, Term **th) {   
   CACHE_REGS
     Term t, tp, m = CurrentModule ;
   t = YAP_ReadBuffer(s,&tp);
   t = Yap_StripModule(t, &m);
   if (IsVarTerm(t) || IsNumTerm(t))
     ap = NULL;
   if (IsAtomTerm(t)) {
     ap = RepPredProp(PredPropByAtom(AtomOfTerm(t), m));
     *th = NULL;
   } else if (IsApplTerm(t)) {
     ap = RepPredProp(PredPropByFunc(FunctorOfTerm(t), m));
     *th = RepAppl(t)+1;
   } else {
     ap = NULL;
   }
  }
public:
  YAPPredicate(PredEntry *pe) {   
    ap = pe;
  }
  YAPPredicate(YAPFunctor f) {   
   CACHE_REGS
   ap = RepPredProp(PredPropByFunc(f.f,CurrentModule));
  }
  YAPPredicate(YAPFunctor f, YAPTerm mod) {   
   ap = RepPredProp(PredPropByFunc(f.f,mod.t));
  }
  YAPPredicate(YAPAtom at, YAPTerm mod) {   
   ap = RepPredProp(PredPropByAtom(at.a,mod.t));
  }
  YAPPredicate(YAPAtom at) {   
   CACHE_REGS
   ap = RepPredProp(PredPropByAtom(at.a,CurrentModule));
  }
  YAPPredicate(YAPAtom at, unsigned int arity, YAPTerm mod) {   
    if (arity) {
      Functor f = Yap_MkFunctor(at.a, arity);
      ap = RepPredProp(PredPropByFunc(f,mod.t));
    } else {
      ap = RepPredProp(PredPropByAtom(at.a,mod.t));
    }
  }
  YAPPredicate(YAPAtom at, unsigned int arity) {   
    CACHE_REGS
    if (arity) {
      Functor f = Yap_MkFunctor(at.a, arity);
      ap = RepPredProp(PredPropByFunc(f,CurrentModule));
    } else {
      ap = RepPredProp(PredPropByAtom(at.a,CurrentModule));
    }
  }
  YAPPredicate(char *s) {   
   CACHE_REGS
     Term t, tp, m = CurrentModule ;
   t = YAP_ReadBuffer(s,&tp);
   t = Yap_StripModule(t, &m);
   if (IsVarTerm(t) || IsNumTerm(t))
     ap = NULL;
   if (IsAtomTerm(t)) {
     ap = RepPredProp(PredPropByAtom(AtomOfTerm(t), m));
   } else {
     ap = RepPredProp(PredPropByFunc(FunctorOfTerm(t), m));
   }
  }
  int call(YAPTerm ts[]);
};
 
/// interface to a YAP Query
/// uses an SWI-like status info internally
class YAPQuery: private YAPPredicate {
  int q_open;
  int q_state;
  Term *q_g;
  yamop *q_p, *q_cp;
  jmp_buf q_env;
  int q_flags;
  YAP_dogoalinfo q_h;
  YAPQuery *oq;
  void initQuery( Term *t );
public:
  /// full constructor, is given a functor, module, and an array of terms that must hav at least 
  /// the same arity as the functor.
  YAPQuery(YAPFunctor f, YAPTerm mod, YAPTerm t[]);
  YAPQuery(YAPFunctor f, YAPTerm t[]);
  YAPQuery(YAPPredicate p, YAPTerm t[]);
  YAPQuery(char *s);
  //  YAPQuery(YAPTerm t);
  int next();
  void cut();
  void close();
};

class YAPParams;

class YAP {
public:
  YAP(YAPParams const& params);
};

class YAPParams {
  friend YAP;
  YAP_init_args init_args;
public:
  YAPParams();
  // sets all the default values for each data member
  YAPParams& savedState( char * f);
  YAPParams& stackSize(size_t sz);
  YAPParams& trailSize(size_t sz);
  YAPParams& maxStackSize(size_t sz);
  YAPParams& maxTrailSize(size_t sz);
  YAPParams& libDir(char *p);
  YAPParams& bootFile(char *f);
  YAPParams& goal(char *g);
  YAPParams& topLevel(char *g);
  YAPParams& script(bool v);
  YAPParams& fastBoot(bool v);
};

inline YAPParams::YAPParams()
{ Yap_InitDefaults( &init_args, NULL ); }

inline YAPParams& YAPParams::savedState( char * f)
{ init_args.SavedState = f; return *this; }

inline YAPParams& YAPParams::stackSize(size_t sz)
{ init_args.StackSize = sz; return *this; }

inline YAPParams& YAPParams::trailSize(size_t sz)
{ init_args.TrailSize = sz; return *this; }

inline YAPParams& YAPParams::maxStackSize(size_t sz)
{ init_args.MaxStackSize = sz; return *this; }

inline YAPParams& YAPParams::maxTrailSize(size_t sz)
{ init_args.MaxTrailSize = sz; return *this; }

inline YAPParams& YAPParams::libDir(char *p)
{ init_args.YapLibDir = p; return *this; }

inline YAPParams& YAPParams::bootFile(char *f)
{ init_args.YapPrologBootFile = f; return *this; }

inline YAPParams& YAPParams::goal(char *g)
{ init_args.YapPrologGoal = g; return *this; }

inline YAPParams& YAPParams::topLevel(char *g)
{ init_args.YapPrologTopLevelGoal = g; return *this; }

inline YAPParams& YAPParams::script(bool v)
{ init_args.HaltAfterConsult = v; return *this; }

inline YAPParams& YAPParams::fastBoot(bool v)
{ init_args.FastBoot = v; return *this; }