#include "storage.h"
#include <fcntl.h>
#include "ndbm.h"
#include <stream.h>
#include <iostream.h>
#include <fstream.h>
#include <string.h>
#include <errno.h>
#pragma implementation

static struct DBM* obj_db;

int var_found_on;

/** the following number (MAX_OBJ_SIZE) needs some comment to prevent future 
*** troubles:  to make diskbasing work fast, i need some static block of memory
*** to shove the objects into before i send them off to disk.  Unfortunately,
*** this is kind of open-ended, so i am using a semi-arbitrary value of around
*** .25Mb -- if you ever get an object to exceed .25Mb, you\'ll most likely get
*** Seg.Faults, etc.
***/
#define MAX_OBJ_SIZE 100000   /** no objects over 100k **/

static char bigbuffer [MAX_OBJ_SIZE];

Object_Store::Object_Store(char* dbfilename, int init){
  datum key;
  int o = -1;
 
  if (! (obj_db = dbm_open (dbfilename, (O_RDWR), 0777)))
    {compile (dbfilename);
     cout << "Compile successful.\n";
     return;}
  key.dptr  = (char*)&(o);  /** i store the number of objects in key -1 **/
  key.dsize = sizeof(o);
  key = dbm_fetch (obj_db , key);  /** then retrieve these **/
  if (! key.dptr)
    perror ("dbm_fetch");
  UNPACK_INT (key.dptr, num);
  cout << "Number of objects in database: " << num << "\n";
  allocated = init + num;
  oarray = new struct o_record [allocated];
  for (o = 0 ; o < allocated ; o++)
    {oarray[o].flags = 0;
     oarray[o].optr = (Object*) NULL;}
}


void Object_Store::compile (char* dbfilename, int init){
  datum key, dat;
  int obtotal = -1;
  ifstream *txtfile;
  char filename[40];

  allocated = init;
  /** i just assume this is enough to compile the file with. **/
  oarray = new struct o_record [init];
  strcpy (filename , dbfilename);
  strcat (filename , ".txt");
  txtfile = new ifstream (filename , ios::in);
  if (txtfile->fail())
    {cout << "Could not find text file " << filename << "\n";
     exit (1);}
  for (num = 0 ; txtfile->peek() != EOF && txtfile->peek() != '/' ; num++)
    {oarray[num] . flags = DIRTY_FLAG | ACTIVE_FLAG | LOADED_FLAG;
     cout << "Compiling object " << num << " from file " << filename << "\n";
     oarray[num] . optr = new Object (txtfile, num);}
  if (! (obj_db = dbm_open (dbfilename, (O_RDWR | O_CREAT | O_EXCL), 0777)))
    {cout << "Could not create initial database\n";
     exit (1);}
  update();
  delete txtfile;
}

Object_Store::~Object_Store(){
  int i;
  update();
  for (i=0 ; i<num ; i++)
    if (oarray[i].flags & LOADED_FLAG)
      delete oarray[i].optr;
  delete oarray;
  dbm_close (obj_db);
}


Object* Object_Store::get(int index){
  datum retdat;
  Object* outobj;
  
  long i, j;      /** assuming 32 bit longs **/
  if (!(index>=0 && index<num))
    {cout << "Attempt to access object number out of range:  Object_Store::get\n";
     cout << "Object number: " << index << "\n";
     return (Object*)NULL;}
  if (oarray[index].flags & LOADED_FLAG)
    {oarray[index].flags |= ACTIVE_FLAG;
     return oarray[index].optr;}
  i = 0;
  do
    {j = index | (i << 24);
     retdat.dptr = (char*) &(j);
     retdat.dsize = sizeof (j);
     retdat = dbm_fetch (obj_db , retdat);
     if (! retdat.dptr)
       {cout << "No data returned by index: " << index << "\n";
	perror ("dbm_fetch");
	return (Object*)NULL;}
     memcpy (& (bigbuffer[(i * dbm_block_size)]) , retdat.dptr , retdat.dsize);
     i++;}
  while (retdat.dsize == dbm_block_size);
  outobj = new Object (& (bigbuffer[0]) , index);
  oarray[index].flags = LOADED_FLAG | ACTIVE_FLAG;
  oarray[index].optr  = outobj;
  return outobj;
}

void Object_Store::update (){
  char* bufptr;
  long i, j, k, l;
  datum d, key;
  i = -1;
  key.dptr = (char*)&(i);
  key.dsize = sizeof (i);
  d.dptr = (char*)&(num);
  d.dsize = sizeof (num);
  if (dbm_store (obj_db , key , d , DBM_REPLACE) < 0)
    {cout << "Unable to store number of objects\n";
     perror ("dbm_store");
     exit (1);}
  for (i=0 ; i<num ; i++)
    {if (oarray [i].flags & ACTIVE_FLAG)
       {if (oarray[i].flags & DIRTY_FLAG)
	  {bufptr = &(bigbuffer[0]);
	   bufptr = oarray[i].optr->pack_object(bufptr);
	   /**** slice it up into little dbm_block_size - pieces ****/
	   k = ((bufptr - &(bigbuffer[0]) + 1) / dbm_block_size + 1);
	   for (j = 0 ; j < k ; j++)
	     {l = i | (j << 24);
	      key.dptr  = (char*) &(l);
	      key.dsize = sizeof(l);
	      d.dptr = & (bigbuffer [j * dbm_block_size]);
	      d.dsize = ((j < (k - 1)) ?
			 dbm_block_size :
			 ((bufptr - d.dptr + 1) % dbm_block_size));
	      if (dbm_store (obj_db , key , d , DBM_REPLACE) < 0)
		{perror ("dbm_store");
		 cout << "Unable to archive " << d.dsize << " bytes.\n";
		 exit (1);}}
	   /** now make sure the object did not shrink ... **/
	   do
	     {l = i | (j << 24);
	      key.dptr = (char*) &(l);
	      key.dsize = sizeof (l);
	      j++;}
	   while (! dbm_delete (obj_db , key));}
        oarray[i] . flags = LOADED_FLAG;}
     else if (oarray [i].flags == LOADED_FLAG)
       {oarray[i].flags = 0;
	delete oarray[i].optr;
        oarray[i].optr = (Object*) NULL;}}
}

void Object_Store::grow (int by){
  struct o_record * temp;
  int i;
  allocated += by;
  temp = new struct o_record [allocated];
  for (i=0 ; i<num ; i++)
    temp[i] = oarray[i];
  delete oarray;
  oarray = temp;
}

#define CHECK_OBJ_RANGE(a) if ((a)<0 || (a)>num)            \
    {cout << "Object access out of range: " << (a) << "\n";                \
     return 0;}
#define CHECK_OBJ_RANGE_PTR(a)  if ((a)<0 || (a)>num)       \
    {cout << "Object access out of range\n";                \
     return NULL;}
#define DIRTY(a)           oarray[(a)].flags |= DIRTY_FLAG;

int Object_Store::addvar (int obid, String* sym, Value* dat){
  int retval;
  CHECK_OBJ_RANGE(obid);
  retval = (get(obid))->set_sym (dat, sym);
  DIRTY(obid);
  return retval;
} 

int Object_Store::rmvar (int obid , String* sym){
  int retval;
  CHECK_OBJ_RANGE(obid);
  retval = (get(obid))->rm_sym (sym);
  DIRTY(obid);
  return retval;
}

Value* Object_Store::listvars (int obid) {
  CHECK_OBJ_RANGE_PTR(obid);
  return (get(obid))->list_vars();
}

#define lookup_one(a,b)  ((get((a)))->lookup_var((b)))

#define MAX_PLIST_LENGTH 250
static long plist [MAX_PLIST_LENGTH];

int Object_Store::fill_plist (int obid){
  int psize, index, i, j;
  intlist* current_p;
  for (psize = 1, plist[index = 0] = obid ; index < psize ; index++)
    {if (plist[index] < 0)
       continue;
     current_p = (get(plist[index]))->parents;     
     for (i=0 ; i < (current_p->size) ; i++)
       {if (plist[psize-1] != current_p->list[i])
	  {plist[psize] = current_p->list[i];
	   for (j=0 ; current_p->list[i] != plist[j] ; j++);
	   if (j < psize)  plist[j] = -1;
	   psize++;}}}
  return psize;
}

Value* Object_Store::pass_vlookup (int obid, String* sym){
  int psize, index;
  Value* retval;
  CHECK_OBJ_RANGE_PTR(obid);
  psize = fill_plist (obid);
  for (index = 1 ; index < psize ; index++)
    if (plist[index] >= 0)
      if (retval = lookup_one(plist[index] , sym))
	{var_found_on = plist[index];
	 return retval;}
  var_found_on = -1;
  return (Value*)NULL;
}

Value* Object_Store::lookup_var  (int obid, String* sym){
  Value* retval;
  CHECK_OBJ_RANGE_PTR(obid);
  if (! (retval = lookup_one(obid, sym)))
    return pass_vlookup (obid, sym);
  var_found_on = obid;
  return retval;
}

Val_List* Object_Store::collect_var (int obid, String* sym){
  int psize, index;
  Val_List* retval=NULL;
  Value* tempval;
  psize = fill_plist (obid);
  for (index = 0 ; index < psize ; index++)
    if (plist[index] >= 0)
      if (tempval = lookup_one(plist[index] , sym))
	retval = new Val_List (tempval->grab(), retval);
  return retval;
}


int Object_Store::addcmd  (int obid , Val_List* key , String* data){
  int retval;
  CHECK_OBJ_RANGE(obid);
  retval = (get(obid))->add_cmd  (key, data);
  DIRTY(obid);
  return retval;
}

int Object_Store::rmcmd  (int obid , String* sym){
  int retval;
  CHECK_OBJ_RANGE(obid);
  retval = (get(obid))->rm_cmd (sym);
  DIRTY(obid);
  return retval;
}

int Object_Store::purgecmds  (int obid){
  int retval;
  CHECK_OBJ_RANGE(obid);
  (get(obid))->purge_cmds();
  DIRTY(obid);
  return 1;
}

#define match_one(a,b)  ((get((a)))->match_cmd((b)))

String* Object_Store::match_cmd  (int obid , String* input){
  String* retval;
  int psize, i;

  CHECK_OBJ_RANGE_PTR(obid);
  psize = fill_plist (obid);
  for (i=0 ; i < psize ; i++)
    if (plist[i] >= 0 && (retval = match_one(plist[i], input)))
      return retval;
  return NULL;
}

Value* Object_Store::listmatches (int obid, String* input){
  Val_List* retlist = NULL;
  Val_List* templist;
  int psize, i;
  CHECK_OBJ_RANGE_PTR(obid);
  psize = fill_plist (obid);
  for (i=(psize-1) ; i >= 0 ; i--)
    if (plist[i] >= 0 && (templist = (get(plist[i]))->all_matches(input)))
      {templist->cat (retlist);
       retlist = templist;}
  return new Value (retlist);
}

Value* Object_Store::listcmds (int obid){
  CHECK_OBJ_RANGE_PTR(obid);
  return (get(obid))->list_cmds();
}

int Object_Store::clone_obj (int obid){
  CHECK_OBJ_RANGE(obid);
  oarray[num].flags = LOADED_FLAG | ACTIVE_FLAG | DIRTY_FLAG;
  oarray[num].optr = new Object (num, obid);
  num++;
  if (num >= allocated)
    grow();
  return (num-1);
}

Value* Object_Store::listparents (int obid){
  CHECK_OBJ_RANGE_PTR(obid);
  return (get(obid))->get_parent_list();
}

int Object_Store::inparents (int obid, int cparent){
  return (get(obid))->parents->check_is_inlist (cparent);
}

int Object_Store::chparents (int obid, Val_List* newparents){
  Value* tempval;
  int i, j, anc, ilist[MAXPARENTS+1];
  intlist* current_p;

  CHECK_OBJ_RANGE(obid);
  for (i=0 ; newparents && i<MAXPARENTS ; i++ , newparents=newparents->next)
    {tempval = newparents->elem;
     ilist[i] = tempval->obj_val();
     for (j=0 ; j<i ; j++)
       if (ilist[j] == ilist[i])
	 return 0;
     CHECK_OBJ_RANGE( ilist[i] );}
  ilist[i] = -1;

/** check for cycles ... expand out the full parent tree and look for obid **/
  memcpy (plist , ilist , (i * (sizeof (int))));
  for (anc = 0 ; anc < i ; anc++)
    {if (plist[anc] == obid)
       return 0;
     current_p = (get(plist[anc]))->parents;
     memcpy ((plist + i) , current_p->list,
	     (current_p->size * (sizeof (int))));
     i += current_p->size;}

  i = (get(obid))->change_parents (ilist);
  DIRTY(obid);
  return i;
}

int Object_Store::obj_exists (int index){
  return (index>=0 && index<num);
}

void Object_Store::dump_to_stdout(){
  int i;
  Object* ob;
  for (i=0 ; i<num ; i++)
    {ob = get(i);
     ob->dump_to_stdout();
     delete ob;
     oarray[i].flags = 0;
     oarray[i].optr = NULL;}
}