/*  $Id: pl-rec.c,v 1.26 1998/02/04 16:22:58 jan Exp $

    Copyright (c) 1990 Jan Wielemaker. All rights reserved.
    See ../LICENCE to find out about your rights.
    jan@swi.psy.uva.nl

    Purpose: recorded database (record[az], recorded, erase)
*/

/*#define O_SECURE 1*/
#include "pl-incl.h"

forwards RecordList lookupRecordList(word);
forwards RecordList isCurrentRecordList(word);

#define RECORDA 0
#define RECORDZ 1

static RecordList recordTable[RECORDHASHSIZE];
static int	  dirtyrecords;

void
initRecords(void)
{ register RecordList *l;
  register int n;

  for(n=0, l=recordTable; n < (RECORDHASHSIZE-1); n++, l++)
    *l = makeTableRef(l+1);
  dirtyrecords = 0;
}


static RecordList
lookupRecordList(register word key)
{ int v = pointerHashValue(key, RECORDHASHSIZE);
  register RecordList l;

  for(l=recordTable[v]; l && !isTableRef(l); l = l->next)
  { if (l->key == key)
      return l;
  }
  l = (RecordList) allocHeap(sizeof(struct recordList) );
  l->next = recordTable[v];
  recordTable[v] = l;
  l->key = key;
  l->firstRecord = l->lastRecord = (Record) NULL;
  l->type = RECORD_TYPE;
  l->references = 0;
  l->flags = 0;

  return l;
}


static RecordList
isCurrentRecordList(register word key)
{ int v = pointerHashValue(key, RECORDHASHSIZE);
  register RecordList l;

  for(l=recordTable[v]; l && !isTableRef(l); l = l->next)
  { if (l->key == key)
      return l;
  }
  return NULL;
}


static void
cleanRecordList(RecordList rl)
{ Record *p = &rl->firstRecord;
  Record r = *p;

  while(r)
  { if ( r->erased )
    { *p = r->next;
      freeRecord(r);
      dirtyrecords--;
      DEBUG(2, Sdprintf("Deleted record, %d dirty left\n", dirtyrecords));
    } else
    { p = &r->next;
    }
    r = *p;
  }
}


		 /*******************************
		 *	    HEAP STORAGE	*
		 *******************************/


#ifndef offsetof
#define offsetof(structure, field) ((int) &(((structure *)NULL)->field))
#endif

#define SIZERECORD  offsetof(struct record, buffer[0])

typedef struct
{ tmp_buffer code;			/* code buffer */
  tmp_buffer vars;			/* variable pointers */
  int	     size;			/* size on global stack */
  int	     nvars;			/* # variables */
} compile_info, *CompileInfo;


#define	PL_TYPE_VARIABLE	(1)	/* variable */
#define PL_TYPE_ATOM		(2)	/* atom */
#define PL_TYPE_INTEGER	  	(3)	/* big integer */
#define PL_TYPE_TAGGED_INTEGER  (4)	/* tagged integer */
#define PL_TYPE_FLOAT	  	(5)	/* double */
#define PL_TYPE_STRING	  	(6)	/* string */
#define PL_TYPE_COMPOUND	(7)	/* compound term */

static void
compile_term_to_heap(Word p, CompileInfo info)
{ word w;

right_recursion:
  w = *p;

  switch(tag(w))
  { case TAG_VAR:
    { int n = info->nvars++;

      *p = (n<<7)|TAG_ATOM|STG_GLOBAL;
      addUnalignedBuffer(&info->vars, p, Word);
      addBuffer(&info->code, PL_TYPE_VARIABLE, char);
      addUnalignedBuffer(&info->code, n, int);

      return;
    }
    case TAG_ATOM:
    { if ( storage(w) == STG_GLOBAL )
      { int n = ((long)(w) >> 7);

	addBuffer(&info->code, PL_TYPE_VARIABLE, char);
	addUnalignedBuffer(&info->code, n, int);
      } else
      { addBuffer(&info->code, PL_TYPE_ATOM, char);
	addUnalignedBuffer(&info->code, w, atom_t);
      }
      return;
    }
    case TAG_INTEGER:
    { long val;

      if ( isTaggedInt(w) )
      { val = valInt(w);
	addBuffer(&info->code, PL_TYPE_TAGGED_INTEGER, char);
      } else
      { info->size += sizeof(long)/sizeof(word) + 2;
	val = valBignum(w);
	addBuffer(&info->code, PL_TYPE_INTEGER, char);
      }
      
      addUnalignedBuffer(&info->code, val, long);
      return;
    }
    case TAG_STRING:
    { Word f  = addressIndirect(w);
      int n   = wsizeofInd(*f);
      int pad = padHdr(*f);		/* see also sizeString() */
      int l   = n*sizeof(word)-pad;

      info->size += n+2;
      addBuffer(&info->code, PL_TYPE_STRING, char);
      addUnalignedBuffer(&info->code, l, int);
      addMultipleBuffer(&info->code, f+1, n, word);
      
      return;
    }
    case TAG_FLOAT:
    { double val = valReal(w);

      info->size += sizeof(double)/sizeof(word) + 2;
      addBuffer(&info->code, PL_TYPE_FLOAT, char);
      addUnalignedBuffer(&info->code, val, double);

      return;
    }
    case TAG_COMPOUND:
    { Functor f = valueTerm(w);
      int arity = arityFunctor(f->definition);

      info->size += arity+1;
      addBuffer(&info->code, PL_TYPE_COMPOUND, char);
      addUnalignedBuffer(&info->code, f->definition, word);
      p = f->arguments;
      for(; --arity > 0; p++)
      { compile_term_to_heap(p, info);
      }
      goto right_recursion;
    }
    case TAG_REFERENCE:
      p = unRef(w);
      goto right_recursion;
  }
}



Record
compileTermToHeap(term_t t)
{ compile_info info;
  Record record;
  Word *p;
  int n, size;

  SECURE(checkData(valTermRef(t)));

  initBuffer(&info.code);
  initBuffer(&info.vars);
  info.size = 0;
  info.nvars = 0;

  compile_term_to_heap(valTermRef(t), &info);
  n = info.nvars;
  p = (Word *)info.vars.base;
  while(--n >= 0)
    setVar(**p++);
  discardBuffer(&info.vars);
  
  size = SIZERECORD + sizeOfBuffer(&info.code);
  record = allocHeap(size);
  record->gsize = info.size;
  record->nvars = info.nvars;
  record->size = size;
  record->erased = FALSE;
  memcpy(record->buffer, info.code.base, sizeOfBuffer(&info.code));
  discardBuffer(&info.code);

  return record;
}


typedef struct
{ char *data;
  Word *vars;
  Word gstore;
} copy_info, *CopyInfo;

#define fetchBuf(b, var, type) \
		do \
		{ *var = *((type *)(b)->data); \
		  (b)->data += sizeof(type); \
		} while(0)
#define fetchUnalignedBuf(b, var, type) \
		do \
		{ memcpy(var, (b)->data, sizeof(type)); \
		  (b)->data += sizeof(type); \
		} while(0)
#define fetchMultipleBuf(b, var, times, type) \
		do \
		{ int _n = (times) * sizeof(type); \
		  memcpy(var, (b)->data, _n); \
		  (b)->data += _n; \
		} while(0)


#ifndef WORDS_PER_DOUBLE
#define WORDS_PER_DOUBLE ((sizeof(double)+sizeof(word)-1)/sizeof(word))
#endif

static void
copy_record(Word p, CopyInfo b)
{ int tag;

right_recursion:
  fetchBuf(b, &tag, char);
  switch(tag)
  { case PL_TYPE_VARIABLE:
    { int n;

      fetchUnalignedBuf(b, &n, int);
      if ( b->vars[n] )
      { if ( p > b->vars[n] )		/* ensure the reference is in the */
	  *p = makeRef(b->vars[n]);	/* right direction! */
	else
	{ setVar(*p);			/* wrong way.  make sure b->vars[n] */
	  *b->vars[n] = makeRef(p);	/* stays at the real variable */
	  b->vars[n] = p;
	}
      } else
      {	setVar(*p);
	b->vars[n] = p;
      }
      
      return;
    }
    case PL_TYPE_ATOM:
    { atom_t val;

      fetchUnalignedBuf(b, &val, atom_t);
      *p = val;

      return;
    }
    case PL_TYPE_TAGGED_INTEGER:
    { long val;

      fetchUnalignedBuf(b, &val, long);
      *p = consInt(val);

      return;
    }
    case PL_TYPE_INTEGER:
    { long val;

      fetchUnalignedBuf(b, &val, long);
      *p = consPtr(b->gstore, TAG_INTEGER|STG_GLOBAL);
      *b->gstore++ = mkIndHdr(1, TAG_INTEGER);
      *b->gstore++ = val;
      *b->gstore++ = mkIndHdr(1, TAG_INTEGER);

      return;
    }
    case PL_TYPE_FLOAT:
    { double val;

      fetchUnalignedBuf(b, &val, double);
      *p = consPtr(b->gstore, TAG_FLOAT|STG_GLOBAL);
      *b->gstore++ = mkIndHdr(WORDS_PER_DOUBLE, TAG_FLOAT);
      memcpy(b->gstore, &val, WORDS_PER_DOUBLE * sizeof(word));
      b->gstore += WORDS_PER_DOUBLE;
      *b->gstore++ = mkIndHdr(WORDS_PER_DOUBLE, TAG_FLOAT);

      return;
    }
    case PL_TYPE_STRING:
    { int len, lw, pad;
      word hdr;

      fetchUnalignedBuf(b, &len, int);
      lw = (len+sizeof(word))/sizeof(word); /* see globalNString() */
      pad = (lw*sizeof(word) - len);
      *p = consPtr(b->gstore, TAG_STRING|STG_GLOBAL);
      *b->gstore++ = hdr = mkStrHdr(lw, pad);
      memcpy(b->gstore, b->data, lw * sizeof(word));
      b->gstore += lw;
      *b->gstore++ = hdr;
      b->data += lw * sizeof(word);

      return;
    }
    case PL_TYPE_COMPOUND:
    { word fdef;
      int arity;

      fetchUnalignedBuf(b, &fdef, word);
      arity = arityFunctor(fdef);

      *p = consPtr(b->gstore, TAG_COMPOUND|STG_GLOBAL);
      *b->gstore++ = fdef;
      p = b->gstore;
      b->gstore += arity;
      for(; --arity > 0; p++)
	copy_record(p, b);
      goto right_recursion;
    }
  }
}


void
copyRecordToGlobal(term_t copy, Record r)
{ copy_info b;
  Word *p;
  int n;

  b.data = r->buffer;
  if ( r->nvars > 0 )
  { if ( !(b.vars = alloca(sizeof(Word) * r->nvars)) )
      fatalError("alloca() failed");
    for(p = b.vars, n=r->nvars; --n >= 0;)
      *p++ = 0;
  }
  b.gstore = allocGlobal(r->gsize);
  
  copy_record(valTermRef(copy), &b);
  if ( b.gstore != gTop )
  { Sdprintf("b.gstore = %p, gTop = %p\n", b.gstore, gTop);
    Sdprintf("Term = ");
    pl_write_canonical(copy);
    Sdprintf("\n");
  }

  SECURE(checkData(valTermRef(copy)));
}

		 /*******************************
		 *     STRUCTURAL EQUIVALENCE	*
		 *******************************/

typedef struct
{ char *data;
  tmp_buffer vars;
} se_info, *SeInfo;


static int
se_record(Word p, SeInfo info)
{ word w;
  int stag;

right_recursion:
  fetchBuf(info, &stag, char);
unref_cont:
  w = *p;

  switch(tag(w))
  { case TAG_VAR:
      if ( stag == PL_TYPE_VARIABLE )
      { int n = entriesBuffer(&info->vars, Word);
	int i;

	fetchUnalignedBuf(info, &i, int);
	if ( i != n )
	  fail;

	*p = (n<<7)|TAG_ATOM|STG_GLOBAL;
	addUnalignedBuffer(&info->vars, p, Word);
	succeed;
      }
      fail;
    case TAG_ATOM:
      if ( storage(w) == STG_GLOBAL )
      { if ( stag == PL_TYPE_VARIABLE )
	{ int n = ((long)(w) >> 7);
	  int i;

	  fetchUnalignedBuf(info, &i, int);
	  if ( i == n )
	    succeed;
	}
	fail;
      } else if ( stag == PL_TYPE_ATOM )
      { atom_t val;

	fetchUnalignedBuf(info, &val, atom_t);
	if ( val == w )
	  succeed;
      }

      fail;
    case TAG_INTEGER:
      if ( isTaggedInt(w) )
      { if ( stag == PL_TYPE_TAGGED_INTEGER )
	{ long val = valInt(w);
	  long v2;

	  fetchUnalignedBuf(info, &v2, long);
	  if ( v2 == val )
	    succeed;
	}
      } else
      { if ( stag == PL_TYPE_INTEGER )
	{ long val = valBignum(w);
	  long v2;

	  fetchUnalignedBuf(info, &v2, long);
	  if ( v2 == val )
	    succeed;
	}
      }
      fail;
    case TAG_STRING:
      if ( stag == PL_TYPE_STRING )
      { int len;
	char *s1 = valString(w);
	word m  = *((Word)addressIndirect(w));
	int wn  = wsizeofInd(m);

	fetchUnalignedBuf(info, &len, int);
	if ( wn == len && memcmp(s1, info->data, len * sizeof(word)) == 0 )
	{ info->data += len * sizeof(word);
	  succeed;
	}
      }
      fail;
    case TAG_FLOAT:
      if ( stag == PL_TYPE_FLOAT )
      { double val = valReal(w);
	
	if ( memcmp(&val, info->data, sizeof(double)) == 0 )
	{ info->data += sizeof(double);
	  succeed;
	}
      }

      fail;
    case TAG_COMPOUND:
      if ( stag == PL_TYPE_COMPOUND )
      { Functor f = valueTerm(w);
	word fdef;

	fetchUnalignedBuf(info, &fdef, word);
	if ( fdef == f->definition )
	{ int arity = arityFunctor(fdef);

	  p = f->arguments;
	  for(; --arity > 0; p++)
	  { if ( !se_record(p, info) )
	      fail;
	  }
	  goto right_recursion;
	}
      }

      fail;
    case TAG_REFERENCE:
      p = unRef(w);
      goto unref_cont;
    default:
      assert(0);
      fail;
  }
}


int
structuralEqualArg1OfRecord(term_t t, Record r)
{ se_info info;
  int n, rval;
  Word *p;

  initBuffer(&info.vars);
  info.data = r->buffer + sizeof(char) + sizeof(word);
					/* skip PL_TYPE_COMPOUND <functor> */
  rval = se_record(valTermRef(t), &info);
  n = entriesBuffer(&info.vars, Word);
  p = (Word *)info.vars.base;
  while(--n >= 0)
    setVar(**p++);
  discardBuffer(&info.vars);

  return rval;
}


bool
freeRecord(Record record)
{ freeHeap(record, record->size);

  succeed;
}

		/********************************
		*       PROLOG CONNECTION       *
		*********************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The key is stored as an atom, integer  or functor header as found on the
global-stack. A functor is a type with  the   same  mask as an atom, but
using the STG_GLOBAL storage indicator.  So,   the  first line denotes a
real atom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

bool
unifyKey(term_t key, word val)
{ if ( (isAtom(val) && storage(val) != STG_GLOBAL) ||
       isTaggedInt(val) )
    return _PL_unify_atomic(key, val);

  return PL_unify_functor(key, (functor_t) val);
}


word
getKey(term_t key)
{ Word k = valTermRef(key);
  deRef(k);

  if ( isAtom(*k) || isTaggedInt(*k) )
    return *k;
  else if ( isTerm(*k) )
    return (word)functorTerm(*k);
  else
    return (word)NULL;
}


word
pl_current_key(term_t k, word h)
{ RecordList l;

  switch( ForeignControl(h) )
  { case FRG_FIRST_CALL:
      l = recordTable[0];
      break;
    case FRG_REDO:
      l = ForeignContextPtr(h);
      break;
    case FRG_CUTTED:
    default:
      succeed;
  }

  for(; l; l = l->next)
  { while(isTableRef(l) )
    { l = unTableRef(RecordList, l);
      if ( !l )
	fail;
    }
    if ( l->firstRecord == NULL || unifyKey(k, l->key) == FALSE )
      continue;

    return_next_table(RecordList, l, ;);
  }

  fail;
}

static bool
record(term_t key, term_t term, term_t ref, int az)
{ RecordList l;
  Record copy;
  word k;

  if ( !(k = getKey(key)) )
    return warning("record%c/3: illegal key", az == RECORDA ? 'a' : 'z');

  l = lookupRecordList(k);
  copy = compileTermToHeap(term);
  copy->list = l;

  TRY(PL_unify_pointer(ref, copy));
  if ( !l->firstRecord )
  { copy->next = (Record) NULL;
    l->firstRecord = l->lastRecord = copy;
    succeed;
  }
  if ( az == RECORDA )
  { copy->next = l->firstRecord;
    l->firstRecord = copy;
    succeed;
  }
  copy->next = (Record) NULL;
  l->lastRecord->next = copy;
  l->lastRecord = copy;

  succeed;
}

word
pl_recorda(term_t key, term_t term, term_t ref)
{ return record(key, term, ref, RECORDA);
}

word
pl_recordz(term_t key, term_t term, term_t ref)
{ return record(key, term, ref, RECORDZ);
}

word
pl_recorded(term_t key, term_t term, term_t ref, word h)
{ RecordList rl;
  Record record;
  word k;
  term_t copy;

  DEBUG(5, Sdprintf("recorded: h=0x%lx, control = %d\n",
		    h, ForeignControl(h)));

  switch( ForeignControl(h) )
  { case FRG_FIRST_CALL:
      if ( PL_get_pointer(ref, (void **)&record) )
      { if ( !isRecord(record) )
	  return warning("recorded/3: Invalid reference");
	if ( !unifyKey(key, record->list->key) )
	  fail;
	copy = PL_new_term_ref();
	copyRecordToGlobal(copy, record);
	return PL_unify(term, copy);
      }
      if ( !(k = getKey(key)) )
	return warning("recorded/3: illegal key");
      if ( !(rl = isCurrentRecordList(k)) )
	fail;
      record = rl->firstRecord;
      break;
    case FRG_REDO:
    { RecordList rl;

      record = ForeignContextPtr(h);
      rl = record->list;

      if ( --rl->references == 0 && true(rl, R_DIRTY) )
      { while(record && record->erased )
	  record = record->next;	/* find a valid record */
	cleanRecordList(rl);
      }
      DEBUG(0, assert(rl->references >= 0));
      break;
    }
    case FRG_CUTTED:
    { RecordList rl;

      record = ForeignContextPtr(h);
      rl = record->list;

      if ( --rl->references == 0 && true(rl, R_DIRTY) )
	cleanRecordList(rl);
    }
      /* FALLTHROUGH */
    default:
      succeed;
  }

  copy = PL_new_term_ref();
  for( ;record; record = record->next )
  { mark m;

    if ( record->erased )
      continue;

    Mark(m);
    copyRecordToGlobal(copy, record);	/* unifyRecordToGlobal()? */
    if ( PL_unify(term, copy) && PL_unify_pointer(ref, record) )
    { if ( !record->next )
	succeed;
      else
      { record->list->references++;
	ForeignRedoPtr(record->next);
      }
    }
    Undo(m);
  }

  fail;
}


word
pl_erase(term_t ref)
{ Record record;
  Record prev, r;
  RecordList l;

  if ( !PL_get_pointer(ref, (void **)&record) ||
       !inCore(record))
    return warning("erase/1: Invalid reference");

  if ( isClause(record) )
  { Clause clause = (Clause) record;
  
    if ( true(clause->procedure->definition, LOCKED) &&
	 false(clause->procedure->definition, DYNAMIC) )
      return warning("erase/1: Attempt to erase clause from system predicate");

    return retractClauseProcedure(clause->procedure, clause);
  }
  
  if ( !isRecord(record) )
    return warning("erase/1: Invalid reference");

#if O_DEBUGGER
  callEventHook(PLEV_ERASED, record);
#endif

  l = record->list;
  if ( l->references )			/* a recorded has choicepoints */
  { record->erased = TRUE;
    set(l, R_DIRTY);
    dirtyrecords++;
    DEBUG(2, Sdprintf("%d Delayed record destruction\n", dirtyrecords));
    succeed;
  }

  if ( record == l->firstRecord )
  { if ( record->next == (Record) NULL )
      l->lastRecord = (Record) NULL;
    l->firstRecord = record->next;
    freeRecord(record);
    succeed;
  }

  prev = l->firstRecord;
  r = prev->next;
  for(; r; prev = r, r = r->next)
  { if (r == record)
    { if ( r->next == (Record) NULL )
        l->lastRecord = prev;
      prev->next = r->next;
      freeRecord(r);
      succeed;
    }
  }

  return warning("erase/1: Invalid reference");
}
