/*************************************************************************
*									 *
*	 YAP Prolog 							 *
*									 *
*	Yap Prolog was developed at NCCUP - Universidade do Porto	 *
*									 *
* Copyright L.Damas, V.S.Costa and Universidade do Porto 1985-1997	 *
*									 *
**************************************************************************
*									 *
* File:		exo.c							 *
* comments:	Exo compilation						 *
*									 *
* Last rev:     $Date: 2008-07-22 23:34:44 $,$Author: vsc $		 *				 *
* $Log: not supported by cvs2svn $				 	 *
*                                                                        *
*									 *
*************************************************************************/

#include "Yap.h"
#include "clause.h"
#include "yapio.h"
#include "eval.h"
#include "tracer.h"
#include "attvar.h"
#ifdef YAPOR
#include "or.macros.h"
#endif	/* YAPOR */
#ifdef TABLING
#include "tab.macros.h"
#endif /* TABLING */
#if HAVE_STRING_H
#include <string.h>
#endif
#define YAP_Term Term
#define YAP_Atom Atom
#include <udi.h>


static int
compar(const void *ip0, const void *jp0) {
  CACHE_REGS
  BITS32 *ip = (BITS32 *)ip0, *jp = (BITS32 *)jp0;
  Term i = EXO_OFFSET_TO_ADDRESS(LOCAL_exo_it, *ip)[LOCAL_exo_arg];
  Term j = EXO_OFFSET_TO_ADDRESS(LOCAL_exo_it, *jp)[LOCAL_exo_arg];
  //fprintf(stderr, "%ld-%ld\n", IntOfTerm(i), IntOfTerm(j)); 
  return IntOfTerm(i)-IntOfTerm(j);
}

static Int
cmp_extra_args(CELL *si, CELL *sj, struct index_t *it)
{
  UInt m = it->udi_free_args;
  UInt m0 = 1, x;
  
  for (x=0; x< it->arity; x++) {
    if (m0 & m) {
      if (si[x] != sj[x]) {
	if (IsIntTerm(si[x]))
	  return IntOfTerm(si[x])-IntOfTerm(sj[x]);
	return AtomOfTerm(si[x])-AtomOfTerm(sj[x]);
      }
      m -= m0;
      if (m == 0)
	return 0;
    }
    m0 <<= 1;
  }
  return 0;
}

static int
compar2(const void *ip0, const void *jp0) {
  CACHE_REGS
  BITS32 *ip = (BITS32 *)ip0, *jp = (BITS32 *)jp0;
  struct index_t *it = LOCAL_exo_it;
  Term* si = EXO_OFFSET_TO_ADDRESS(it, *ip);
  Term* sj = EXO_OFFSET_TO_ADDRESS(it, *jp);
  int cmp = cmp_extra_args(si, sj, it);
  if (cmp)
    return cmp;
  return IntOfTerm(si[LOCAL_exo_arg])-IntOfTerm(sj[LOCAL_exo_arg]);
}

static int
compare(const BITS32 *ip, Int j USES_REGS) {
  Term i = EXO_OFFSET_TO_ADDRESS(LOCAL_exo_it, *ip)[LOCAL_exo_arg];
  //fprintf(stderr, "%ld-%ld\n", IntOfTerm(i), j); 
  return IntOfTerm(i)-j;
}

static UInt free_args(UInt b[], UInt arity, UInt i) {
  UInt j;
  UInt rc = 0;

  for (j=0; j<arity; j++) {
    if (i !=j && b[j] == 0)
      rc |= 1<<j;
  }
  return rc;
}

static BITS32*
NEXT_DIFFERENT(BITS32 *pt0, BITS32 *pte, struct index_t *it)
{
  Term* si = EXO_OFFSET_TO_ADDRESS(it, pt0[0]);
  Term* sj;

  do {
    pt0++;
    if (pt0 == pte)
      return NULL;
    sj = EXO_OFFSET_TO_ADDRESS(it, *pt0);
  } while (!cmp_extra_args(si, sj, it));
  return pt0;
}

static BITS32*
PREV_DIFFERENT(BITS32 *pt0, BITS32 *pte, struct index_t *it)
{
  Term* si = EXO_OFFSET_TO_ADDRESS(it, pt0[0]);
  Term* sj;

  do {
    pt0--;
    if (pt0 == pte)
      return NULL;
    sj = EXO_OFFSET_TO_ADDRESS(it, *pt0);
  } while (!cmp_extra_args(si, sj, it));
  return pt0;
}

static BITS32*
NEXT_MIN(BITS32 *pt0, BITS32 *pte, Term tmin, Term tmax, struct index_t *it)
{
  Term* si = EXO_OFFSET_TO_ADDRESS(it, pt0[0]);
  int do_min, do_max;
  Int min = 0, max = 0;

  if (IsVarTerm(tmin)) {
    do_min = FALSE;
  } else {
    do_min = TRUE;
    min = IntOfTerm(tmin);
  }
  if (IsVarTerm(tmax)) {
    do_max = FALSE;
  } else {
    do_max = TRUE;
    max = IntOfTerm(tmax);
  }

  while ((do_min && IntOfTerm(si[it->udi_arg]) < min) ||
	 (do_max && IntOfTerm(si[it->udi_arg]) > max)) {
    pt0++;
    if (pt0 == pte)
      return NULL;
    si = EXO_OFFSET_TO_ADDRESS(it, *pt0);
  }
  return pt0;
}

static BITS32*
NEXT_MAX(BITS32 *pt0, BITS32 *pte, Term tmin, Term tmax, struct index_t *it)
{
  Term* si = EXO_OFFSET_TO_ADDRESS(it, pt0[0]);
  int do_min, do_max;
  Int min = 0, max = 0;

  if (IsVarTerm(tmin)) {
    do_min = FALSE;
  } else {
    do_min = TRUE;
    min = IntOfTerm(tmin);
  }
  if (IsVarTerm(tmax)) {
    do_max = FALSE;
  } else {
    do_max = TRUE;
    max = IntOfTerm(tmax);
  }

  while ((do_min && IntOfTerm(si[it->udi_arg]) < min) ||
	 (do_max && IntOfTerm(si[it->udi_arg]) > max)) {
    pt0--;
    if (pt0 == pte)
      return NULL;
    si = EXO_OFFSET_TO_ADDRESS(it, *pt0);
  }
  return pt0;
}

static void
IntervalUDIRefitIndex(struct index_t **ip, UInt b[] USES_REGS)
{
  size_t sz;
  struct index_t *it = *ip;
  yamop *code;

  /* hard-wired implementation for the Interval case */
  Int i = it->udi_arg;
  /* it is bound, use hash */
  if (it->bmap & b[i]) return;
  /* no constraints, nothing to gain */
  //if (!IsAttVar(VarOfTerm(Deref(XREGS[i+1])))) return;
  LOCAL_exo_it = it;
  LOCAL_exo_base = it->bcls;
  LOCAL_exo_arity = it->arity;
  LOCAL_exo_arg = i;
  it->udi_free_args = free_args(b, it->arity, i);
  if (!it->key) {
    UInt ncls = it->ap->cs.p_code.NOfClauses, i;
    BITS32 *sorted;
    /* handle ll variables */
    sz = sizeof(BITS32)*(ncls);
    /* allocate space */
    if (!(it->udi_data = (BITS32*)Yap_AllocCodeSpace(sz)))
      return;
    sorted = (BITS32*)it->udi_data;
    for (i=0; i< ncls; i++)
      sorted[i] = i;
    qsort(sorted, (size_t)ncls, sizeof(BITS32), compar); 
    it->links = NULL;
  } else {
    BITS32 *sorted0, *sorted;
    
    /* be conservative */
    if (it->udi_free_args)
      sz = sizeof(BITS32)*(2*it->ntrys+3*it->nentries);
    else
      sz = sizeof(BITS32)*(it->ntrys+2*it->nentries);
    /* allocate space */
    if (!(it->udi_data = (BITS32*)malloc(sz)))
      return;
    sorted0 = sorted = (BITS32 *)it->udi_data;
    sorted++; /* leave an initial hole */
    for (i=0; i < it->hsize; i++) {
      if (it->key[i]) {
	BITS32 *s0 = sorted;
	BITS32 offset = it->key[i], offset0 = offset;
	
	*sorted++ = 0;
	do {
	  *sorted++ = offset;
	  offset = it->links[offset];
	} while (offset);
	// S = EXO_OFFSET_TO_ADDRESS(it, offset0); Yap_DebugPlWrite(S[0]);
	// fprintf(stderr, " key[i]=%d offset=%d  %d\n", it->key[i], offset0, (sorted-s0)-1);
	if (sorted-s0 == 2) {
	  it->links[offset0] = 0;
	  sorted = s0;
	} else {
	  /* number of elements comes first */
	  *s0 = sorted - (s0+1);
	  qsort(s0+1, (size_t)*s0, sizeof(BITS32), compar); 
	  it->links[offset0] = s0-sorted0;
	  if (it->udi_free_args) {
	    memcpy(sorted, s0+1, sizeof(BITS32)*(*s0));
	    qsort(sorted, (size_t)*s0, sizeof(BITS32), compar2); 
	    sorted += *s0;
	  }
	}
      }
    }
    sz = sizeof(BITS32)*(sorted-sorted0);
    it->udi_data = (BITS32 *)realloc((char *)it->udi_data, sz);
  }
  it->is_udi = i+1;
  code = it->code;
  code->opc = Yap_opcode(_try_exo_udi);
  code = NEXTOP(code, lp);
  code->opc = Yap_opcode(_retry_exo_udi);
}

 static BITS32 *
 binary_search(BITS32 *start, BITS32 *end, Int x USES_REGS)
 {
   BITS32 *mid;
   while (start < end) {
     int cmp;
     mid = start + (end-start)/2;
     cmp = compare(mid, x PASS_REGS);
     if (!cmp)
       return mid;
     if (cmp > 0) {
       end = mid-1;
     } else
       start = mid+1;
   }
   return start;
 }

 static yamop *
Interval(struct index_t *it, Term min, Term max, Term op, BITS32 off USES_REGS)
 {
   BITS32 *c;
   BITS32 n;
   BITS32 *pt;
   BITS32 *end;
   Atom at;

   LOCAL_exo_it = it;
   LOCAL_exo_base = it->bcls;
   LOCAL_exo_arity = it->arity;
   LOCAL_exo_arg = it->udi_arg;
   if (!it->links) {
     c = (BITS32 *)it->udi_data;
     n = it->nels;
     pt  = c;
     end  = c+(n-1);
   } else if (it->links[off]) {
     c = (BITS32 *)it->udi_data;
     n = c[it->links[off]];
     pt = c;
     end  = c+(it->links[off]+n);
     // fprintf(stderr," %d links %d=%d \n", off, it->links[off], n);
   } else {
     if (!IsVarTerm(min)) {
       Int x;
       if (!IsIntegerTerm(min)) {
	 min = Yap_Eval(min);
	 if (!IsIntegerTerm(min)) {
	   Yap_Error(TYPE_ERROR_INTEGER, min, "data-base constraint");
	   return FAILCODE;
	 }
       }
       x = IntegerOfTerm(min);
       if (x >= IntegerOfTerm(S[LOCAL_exo_arg])) {
	 return FAILCODE;
       }     
     }
     if (!IsVarTerm(max)) {
       Int x;
       if (!IsIntegerTerm(max)) {
	 max = Yap_Eval(max);
	 if (!IsIntegerTerm(max)) {
	   Yap_Error(TYPE_ERROR_INTEGER, max, "data-base constraint");
	   return FAILCODE;
	 }
       }
       x = IntegerOfTerm(max);
       if (x <= IntegerOfTerm(S[LOCAL_exo_arg])) {
	 return FAILCODE;
       }     
     }
     return NEXTOP(NEXTOP(it->code,lp),lp);
   }

   if (!IsVarTerm(min)) {
     Int x;
     if (!IsIntegerTerm(min)) {
      min = Yap_Eval(min);
      if (!IsIntegerTerm(min)) {
	Yap_Error(TYPE_ERROR_INTEGER, min, "data-base constraint");
	return FAILCODE;
      }
     }
     x = IntegerOfTerm(min);
     if (n > 8) {
       int cmp;
       pt = binary_search(pt, end, x PASS_REGS);
       while ( pt < end+1 && (cmp = compare(pt, x PASS_REGS)) <= 0 ) {
	 if (cmp > 0) break;
	 pt++;
       }
     } else {
       while ( pt < end+1 && compare(pt, x PASS_REGS) <= 0 ) {
	 pt++;
       }
     }
     if (pt > end) 
       return FAILCODE;
   }
   if (!IsVarTerm(max)) {
     Int x;
     BITS32 *pt1;
     Int n = end-pt;

     if (!IsIntegerTerm(max)) {
      max = Yap_Eval(max);
      if (!IsIntegerTerm(max)) {
	Yap_Error(TYPE_ERROR_INTEGER, max, "data-base constraint");
	return FAILCODE;
      }
     }
     x = IntegerOfTerm(max);
     if (n > 8) {
       int cmp;
       pt1 = binary_search(pt, end, x PASS_REGS);
       while ( pt1 >= pt && (cmp = compare(pt1, x PASS_REGS)) >= 0 ) {
	 if (cmp < 0) break;
	 pt1--;
       }
     } else {
       pt1 = end;
       while ( pt1 >= pt && compare(pt1, x PASS_REGS) >= 0 ) {
	 pt1--;
       }
     }
     if (pt1 < pt) 
       return FAILCODE;
     end = pt1;
   }
   if (IsVarTerm(op)) {
     S = EXO_OFFSET_TO_ADDRESS(it, pt[0]);
     if (pt < end ) {
       YENV[-1] = (CELL)( end );
       YENV[-2] = (CELL)( pt+1 );
       YENV -= 2;
       return it->code;
     }
     return NEXTOP(NEXTOP(it->code,lp),lp);
   }
   at = AtomOfTerm(op);
   if (at == AtomAny || at == AtomMinimum) {
     S = EXO_OFFSET_TO_ADDRESS(it, pt[0]);
   } else if (at == AtomMaximum) {
     S = EXO_OFFSET_TO_ADDRESS(it, end[0]);
   } else if (at == AtomUnique) {
     if (end-2 > pt)
       return FAILCODE;
     S = EXO_OFFSET_TO_ADDRESS(it, pt[0]);
   } else if (at == AtomMin) {
     S = EXO_OFFSET_TO_ADDRESS(it, pt[0]);
     if (it->udi_free_args) {
       BITS32 *ptn;
       pt  = c+(it->links[off]+n+1);
       end = pt+n;
       pt = NEXT_MIN(pt, end, min, max, it);
       if (!pt)
	 return FAILCODE;
       S = EXO_OFFSET_TO_ADDRESS(it, pt[0]);
       ptn = NEXT_DIFFERENT(pt, end, it);
       if (ptn) 
	 ptn = NEXT_MIN(ptn, end, min, max, it);
       if ( ptn ) {
	 YENV[-1] = min;    // what we are doing
	 YENV[-2] = max;    // what we are doing
	 YENV[-3] = (CELL) end;    // what we are doing
	 YENV[-4] = MkAtomTerm(AtomMin);    // what we are doing
	 YENV[-5] = (CELL)( ptn );    // where we are in pt0 array
	 YENV -= 5;
	 return it->code;
       }
     }
     return NEXTOP(NEXTOP(it->code,lp),lp);
   } else if (at == AtomMax) {
     S = EXO_OFFSET_TO_ADDRESS(it, pt[0]);
     if (it->udi_free_args) {
       BITS32 *ptn;
       end  = c+(it->links[off]+n);
       pt = end+n;
       pt = NEXT_MAX(pt, end, min, max, it);
       if (!pt)
	 return FAILCODE;
       S = EXO_OFFSET_TO_ADDRESS(it, pt[0]);
       ptn = PREV_DIFFERENT(pt, end, it);
       if (ptn) 
	 ptn = NEXT_MAX(ptn, end, min, max, it);
       if ( ptn ) {
	 YENV[-1] = min;    // what we are doing
	 YENV[-2] = max;    // what we are doing
	 YENV[-3] = (CELL) end;    // what we are doing
	 YENV[-4] = MkAtomTerm(AtomMax);    // what we are doing
	 YENV[-5] = (CELL)( ptn );    // where we are in pt0 array
	 YENV -= 5;
	 return it->code;
       }
     }
     return NEXTOP(NEXTOP(it->code,lp),lp);
   }
   return NEXTOP(NEXTOP(it->code,lp),lp);
 }

static yamop *
IntervalEnterUDIIndex(struct index_t *it USES_REGS)
{
  Int i = it->udi_arg;
  Term  t = XREGS[i+1], a1;
  BITS32 off = EXO_ADDRESS_TO_OFFSET(it, S);
  //  printf("off=%d it=%p %p---%p\n", off, it, it->cls, S);
  attvar_record *attv;

  t = Deref(t);
  if (!IsVarTerm(t))
    return FALSE;
  if(!IsAttVar(VarOfTerm(t)))
    return Interval(it, MkVarTerm(), MkVarTerm(), MkVarTerm(), off PASS_REGS);
  attv = RepAttVar(VarOfTerm(t));
  t =  attv->Atts;
  a1 = ArgOfTerm(2,t);
  if (IsVarTerm(a1)) {
    Yap_Error(INSTANTIATION_ERROR, t, "executing exo_interval constraints");
    return FAILCODE;
  } else if (!IsApplTerm(a1)) {
    Yap_Error(TYPE_ERROR_COMPOUND, a1, "executing exo_interval constraints");
    return FAILCODE;
  } else {
    return Interval(it, ArgOfTerm(1,a1), ArgOfTerm(2,a1), ArgOfTerm(3,a1), off PASS_REGS);
  }
}

static int
IntervalRetryUDIIndex(struct index_t *it USES_REGS)
{
  CELL *w = (CELL*)(B+1)+it->arity;
  if (IsVarTerm(w[2])) {
    BITS32 *end = (BITS32 *) w[2],
      *pt = (BITS32 *) w[1];
    BITS32 f = *pt;

    S = EXO_OFFSET_TO_ADDRESS(it, f);
    if (pt++ == end) return FALSE;
    w[1] = (CELL)pt;
  } else {
    BITS32 *pt0 = (BITS32 *)w[1];
    BITS32 *pte = (BITS32 *)w[3];
    Atom what = AtomOfTerm(w[2]);
    Term min = w[5];
    Term max = w[4];

    S = EXO_OFFSET_TO_ADDRESS(it, pt0[0]);
    if ( what == AtomMin ) {
      pt0 = NEXT_DIFFERENT(pt0, pte, it);
      if (pt0) 
	pt0 = NEXT_MIN(pt0, pte, min, max, it); 
    } else {
      pt0 = PREV_DIFFERENT(pt0, pte, it);
      if (pt0) 
	pt0 = NEXT_MAX(pt0, pte, min, max, it); 
    }
    if (!pt0) {
      return FALSE;
    }
    w[1] = (CELL)pt0;
  }
  return TRUE;
}


static struct udi_control_block IntervalCB;

typedef struct exo_udi_access_t {
  CRefitExoIndex refit;
} exo_udi_encaps_t;

static struct exo_udi_access_t ExoCB;

static void *
IntervalUdiInit (Term spec, int arg, int arity) {
  ExoCB.refit = IntervalUDIRefitIndex;
  return (void *)&ExoCB;
}

static void *
IntervalUdiInsert (void *control,
                Term term, int arg, void *data)
{
  CACHE_REGS

  struct index_t **ip = (struct index_t **)term;
  (*ip)->udi_arg = arg-1;
  (ExoCB.refit)(ip, LOCAL_ibnds PASS_REGS);
  (*ip)->udi_first = (void *)IntervalEnterUDIIndex;
  (*ip)->udi_next = (void *)IntervalRetryUDIIndex;
  return control;
}

static int IntervalUdiDestroy(void *control)
{
  return TRUE;
}



void Yap_udi_Interval_init(void) {
  UdiControlBlock cb = &IntervalCB;
  Atom name = Yap_LookupAtom("exo_interval");
  memset((void *) cb,0, sizeof(*cb));

  /*TODO: ask vitor why this gives a warning*/
  cb->decl= (YAP_Atom)name;
  Yap_MkEmptyWakeUp(name);
  cb->init= IntervalUdiInit;
  cb->insert=IntervalUdiInsert;
  cb->search=NULL;
  cb->destroy=IntervalUdiDestroy;

  Yap_UdiRegister(cb);
}