/* cache1.c */

#include "config.h"
#include "object.h"
#include "globals.h"
#include "construct.h"
#include "dbhandle.h"
#include "interp.h"
#include "file.h"

struct obj_link {
  struct object *obj;
  struct obj_link *prev_link;
  struct obj_link *next_link;
  struct obj_link *prev_queue;
  struct obj_link *next_queue;
};

struct obj_link *hash_list[CACHE_HASH];
struct obj_link *cache_head;
struct obj_link *cache_tail;

signed long loaded_obj_count;

/* if access_load_file is 0, then all loads from the db will be from
   the save_name file. if it is non-zero, loads will be from the
   load_name file. */

int access_load_file;

signed long db_obj_to_ref(struct object *obj) {
  if (!obj) return -1;
  return obj->refno;
}

struct obj_link *find_cache(struct object *obj) {
  struct obj_link *curr_link;

  curr_link=hash_list[obj->refno%CACHE_HASH];
  while (curr_link) {
    if (curr_link->obj==obj)
      return curr_link;
    curr_link=curr_link->next_link;
  }
  return NULL;
}

void write_filesystem(FILE *outfile, struct file_entry *parent_dir,
                      char *parent_dir_name) {
  struct file_entry *curr_entry;
  char *buf;

  curr_entry=parent_dir->contents;
  while (curr_entry) {
    fprintf(outfile,"%s/%s\n%ld\n%ld\n",parent_dir_name,
            curr_entry->filename,
            (long) curr_entry->flags,
            (long) curr_entry->owner);
    if (curr_entry->contents) {
      buf=MALLOC(strlen(parent_dir_name)+strlen(curr_entry->filename)+2);
      sprintf(buf,"%s/%s",parent_dir_name,curr_entry->filename);
      write_filesystem(outfile,curr_entry,buf);
      FREE(buf);
    }
    curr_entry=curr_entry->next_file;
  }
}

void freedata(struct object *obj) {
  long loop;
  struct ref_list *curr,*next;

  loop=0;
  if (obj->globals) {
    while (loop<obj->parent->funcs->num_globals) {
      if (obj->globals[loop].type==STRING)
        FREE(obj->globals[loop].value.string);
      loop++;
    }
    FREE(obj->globals);
    obj->globals=NULL;
  }
  curr=obj->refd_by;
  while (curr) {
    next=curr->next;
    FREE(curr);
    curr=next;
  }
  obj->refd_by=NULL;
  obj->flags&=~(RESIDENT);
}

struct object *db_ref_to_obj(signed long refno) {
  struct obj_blk *curr;
  signed long count,index;

  if (refno<0) return NULL;
  count=refno/OBJ_ALLOC_BLKSIZ;
  index=refno%OBJ_ALLOC_BLKSIZ;
  curr=obj_list;
  while (count--)
    curr=curr->next;
  return &(curr->block[index]);
}

void init_globals(char *loadpath, char *savepath, char *panicpath) {
  signed long loop;

  cache_top=0;
  loop=0;
  while (loop<CACHE_HASH) {
    hash_list[loop]=NULL;
    loop++;
  }
  db_top=0;
  edit_list=NULL;
  cache_head=NULL;
  cache_tail=NULL;
  loaded_obj_count=0;
  root_dir=MALLOC(sizeof(struct file_entry));
  root_dir->filename=copy_string("");
  root_dir->flags=READ_OK | DIRECTORY;
  root_dir->owner=0;
  root_dir->contents=NULL;
  root_dir->parent=NULL;
  root_dir->prev_file=NULL;
  root_dir->next_file=NULL;
  num_locals=0;
  locals=NULL;
  c_err_msg=NULL;
  cmd_head=NULL;
  cmd_tail=NULL;
  dest_list=NULL;
  alarm_list=NULL;
  now_time=time2int(time(NULL));
  obj_list=NULL;
  if (loadpath)
    load_name=copy_string(loadpath);
  else
    load_name=copy_string(DEFAULT_LOAD);
  if (savepath)
    save_name=copy_string(savepath);
  else
    save_name=copy_string(DEFAULT_SAVE);
  if (panicpath)
    panic_name=copy_string(panicpath);
  else
    panic_name=copy_string(DEFAULT_PANIC);
  free_obj_list=NULL;
  objects_allocd=0;
  db_top=0;
}

void pass_data(FILE *infile) {
  char c;
  char buf[ITOA_BUFSIZ+1];
  int done;
  long count;

  done=0;
  while (!done) {
    c=fgetc(infile);
    switch (c) {
      case 'I':
      case 'O':
        fgets(buf,ITOA_BUFSIZ+1,infile);
        break;
      case 'S':
        fgets(buf,ITOA_BUFSIZ+1,infile);
        count=atol(buf);
        while (count--) fgetc(infile);
        break;
      case '.':
        done=1;
        break;
      case EOF:
        return;
        break;
    }
  }
  fgets(buf,ITOA_BUFSIZ+1,infile);
  c=fgetc(infile);
  while (c!='.' && c!=EOF) c=fgetc(infile);
  fgets(buf,ITOA_BUFSIZ+1,infile);
}

void writeinstr(FILE *outfile,struct var *v) {
  if (v->type==FUNC_CALL)
    fprintf(outfile,"%d\n",(int) FUNC_NAME);
  else
    fprintf(outfile,"%d\n",(int) v->type);
  switch (v->type) {
    case INTEGER:
      fprintf(outfile,"%ld\n",(long) v->value.integer);
      break;
    case STRING:
    case FUNC_NAME:
      fprintf(outfile,"%ld\n%s",(long) strlen(v->value.string),
              v->value.string);
      break;
    case OBJECT:
      fprintf(outfile,"%ld\n",(long) v->value.objptr->refno);
      break;
    case ASM_INSTR:
      fprintf(outfile,"%d\n",(int) v->value.instruction);
      break;
    case GLOBAL_L_VALUE:
    case LOCAL_L_VALUE:
      fprintf(outfile,"%d\n%d\n",(int) v->value.l_value.ref,
              (int) v->value.l_value.size);
      break;
    case FUNC_CALL:
      fprintf(outfile,"%ld\n%s",(long) strlen(v->value.func_call->funcname),
              v->value.func_call->funcname);
      break;
    case NUM_ARGS:
    case ARRAY_SIZE:
    case JUMP:
    case BRANCH:
    case NEW_LINE:
      fprintf(outfile,"%ld\n",(long) v->value.num);
      break;
    default:
      break;
  }
}

char *readstring(FILE *infile) {
  static char buf[ITOA_BUFSIZ+1];
  static char *string;
  static long len,loop;

  fgets(buf,ITOA_BUFSIZ+1,infile);
  len=atol(buf);
  loop=0;
  string=MALLOC(len+1);
  while (loop<len) {
    string[loop]=fgetc(infile);
    loop++;
  }
  string[loop]='\0';
  return string;
}

void readinstr(FILE *infile,struct var *v) {
  static char type;
  static char buf[ITOA_BUFSIZ+1];

  fgets(buf,ITOA_BUFSIZ+1,infile);
  type=atoi(buf);
  v->type=type;
  switch (type) {
    case INTEGER:
      fgets(buf,ITOA_BUFSIZ+1,infile);
      v->value.integer=atol(buf);
      break;
    case STRING:
    case FUNC_NAME:
      v->value.string=readstring(infile);
      break;
    case OBJECT:
      fgets(buf,ITOA_BUFSIZ+1,infile);
      v->value.objptr=db_ref_to_obj(atol(buf));
      break;
    case ASM_INSTR:
      fgets(buf,ITOA_BUFSIZ+1,infile);
      v->value.instruction=atoi(buf);
      break;
    case GLOBAL_L_VALUE:
    case LOCAL_L_VALUE:
      fgets(buf,ITOA_BUFSIZ+1,infile);
      v->value.l_value.ref=atoi(buf);
      fgets(buf,ITOA_BUFSIZ+1,infile);
      v->value.l_value.size=atoi(buf);
      break;
    case NUM_ARGS:
    case ARRAY_SIZE:
    case JUMP:
    case BRANCH:
    case NEW_LINE:
      fgets(buf,ITOA_BUFSIZ+1,infile);
      v->value.num=atol(buf);
      break;
    default:
      break;
  }
}

void writedata(FILE *outfile,struct object *obj) {
  long loop;
  struct ref_list *curr;

  loop=0;
  if (obj->flags & GARBAGE) {
    fprintf(outfile,".END\n.END\n");
    return;
  }
  while (loop<obj->parent->funcs->num_globals) {
    switch (obj->globals[loop].type) {
      case INTEGER:
        fprintf(outfile,"I%ld\n",(long) obj->globals[loop].value.integer);
        break;
      case OBJECT:
        fprintf(outfile,"O%ld\n",(long) obj->globals[loop].value.objptr->
                refno);
        break;
      case STRING:
        fprintf(outfile,"S%ld\n%s",(long) strlen(obj->globals[loop].value.
                string),obj->globals[loop].value.string);
        break;
      default:
        fprintf(outfile,"?");
        break;
    }
    loop++;
  }
  fprintf(outfile,".END\n");
  curr=obj->refd_by;
  while (curr) {
    fprintf(outfile,"%ld\n%ld\n",(long) curr->ref_obj->refno,
            (long) curr->ref_num);
    curr=curr->next;
  }
  fprintf(outfile,".END\n");
}

void readdata(FILE *infile, struct object *obj) {
  long loop;
  char c;
  char buf[ITOA_BUFSIZ+1];
  struct ref_list *curr;

  loop=0;
  if (obj->parent->funcs->num_globals)
    obj->globals=MALLOC(sizeof(struct var)*(obj->parent->funcs->num_globals));
  else
    obj->globals=NULL;
  while (loop<obj->parent->funcs->num_globals) {
    c=fgetc(infile);
    switch (c) {
      case 'I':
        obj->globals[loop].type=INTEGER;
        fgets(buf,ITOA_BUFSIZ+1,infile);
        obj->globals[loop].value.integer=atol(buf);
        break;
      case 'O':
        obj->globals[loop].type=OBJECT;
        fgets(buf,ITOA_BUFSIZ+1,infile);
        obj->globals[loop].value.objptr=db_ref_to_obj(atol(buf));
        break;
      case 'S':
        obj->globals[loop].type=STRING;
        obj->globals[loop].value.string=readstring(infile);
        break;
      default:
        obj->globals[loop].type=INTEGER;
        obj->globals[loop].value.integer=0;
        break;
    }
    loop++;
  }
  fgets(buf,ITOA_BUFSIZ+1,infile);
  if (feof(infile) || strcmp(buf,".END\n")) {
    log_sysmsg("  cache: readdata() encountered corrupt data");
    return;
  }
  fgets(buf,ITOA_BUFSIZ+1,infile);
  while (!feof(infile) && strcmp(buf,".END\n")) {
    curr=MALLOC(sizeof(struct ref_list));
    curr->ref_obj=db_ref_to_obj(atol(buf));
    fgets(buf,ITOA_BUFSIZ+1,infile);
    curr->ref_num=atoi(buf);
    curr->next=obj->refd_by;
    obj->refd_by=curr;
    fgets(buf,ITOA_BUFSIZ+1,infile);
  }
}

void unload_object(struct object *obj) {
  struct obj_link *this_link;
  char *buf;

  this_link=find_cache(obj);
  if (this_link) {
    loaded_obj_count--;
    if (this_link->prev_link)
      this_link->prev_link->next_link=this_link->next_link;
    else
      hash_list[obj->refno%CACHE_HASH]=this_link->next_link;
    if (this_link->next_link)
      this_link->next_link->prev_link=this_link->prev_link;
    if (this_link->prev_queue)
      this_link->prev_queue->next_queue=this_link->next_queue;
    else
      cache_head=this_link->next_queue;
    if (this_link->next_queue)
      this_link->next_queue->prev_queue=this_link->prev_queue;
    else
      cache_tail=this_link->prev_queue;
    FREE(this_link);
  }
  if (obj->obj_state==FROM_DB || obj->obj_state==FROM_CACHE ||
      obj->obj_state==DIRTY)
    freedata(obj);
  obj->obj_state=DIRTY;
  obj->flags&=~(RESIDENT);
}

void add_loaded(struct object *obj) {
  signed long hash_value;
  struct obj_link *new_link;

  if (!obj) return;
  if (new_link=find_cache(obj)) {
    if (new_link->prev_queue)
      new_link->prev_queue->next_queue=new_link->next_queue;
    if (new_link->next_queue)
      new_link->next_queue->prev_queue=new_link->prev_queue;
    if (new_link==cache_tail)
      cache_tail=new_link->prev_queue;
    if (new_link==cache_head)
      cache_head=new_link->next_queue;
  } else {
    hash_value=obj->refno%CACHE_HASH;
    new_link=MALLOC(sizeof(struct obj_link));
    new_link->obj=obj;
    new_link->prev_link=NULL;
    new_link->next_link=hash_list[hash_value];
    if (hash_list[hash_value])
      hash_list[hash_value]->prev_link=new_link;
    hash_list[hash_value]=new_link;
    loaded_obj_count++;
  }
  obj->flags|=RESIDENT;
  if (cache_head)
    cache_head->prev_queue=new_link;
  new_link->next_queue=cache_head;
  new_link->prev_queue=NULL;
  cache_head=new_link;
  if (!cache_tail)
    cache_tail=new_link;
}

void unload_data() {
  struct obj_link *curr_link;
  FILE *outfile;
  signed long place;

  while (loaded_obj_count>CACHE_SIZE) {
    curr_link=cache_tail;
    curr_link->prev_queue->next_queue=NULL;
    cache_tail=curr_link->prev_queue;
    if (curr_link->prev_link)
      curr_link->prev_link->next_link=curr_link->next_link;
    else
      hash_list[curr_link->obj->refno%CACHE_HASH]=curr_link->next_link;
    if (curr_link->next_link)
      curr_link->next_link->prev_link=curr_link->prev_link;
    if (curr_link->obj->flags & GARBAGE) {
      curr_link->obj->obj_state=DIRTY;
    } else
      if (curr_link->obj->obj_state==DIRTY) {
        outfile=fopen(cache_path,"a");
        if (!outfile) {
          log_sysmsg("  cache: unload_data() unable to write to cache");
          return;
	}
        place=ftell(outfile);
        writedata(outfile,curr_link->obj);
        cache_top=ftell(outfile);
        fclose(outfile);
        curr_link->obj->obj_state=IN_CACHE;
        curr_link->obj->file_offset=place;
      } else
        if (curr_link->obj->obj_state==FROM_DB)
          curr_link->obj->obj_state=IN_DB;
        else
          if (curr_link->obj->obj_state==FROM_CACHE)
            curr_link->obj->obj_state=IN_CACHE;
    freedata(curr_link->obj);
    FREE(curr_link);
    loaded_obj_count--;
  }
}

void load_data(struct object *obj) {
  FILE *infile;

  if (obj->flags & RESIDENT) {
    add_loaded(obj);
    return;
  }
  if (obj->flags & GARBAGE) return;
  if (obj->obj_state==IN_CACHE) {
    infile=fopen(cache_path,"r");
    if (!infile) {
      log_sysmsg("  cache: load_data() unable to read from cache");
      return;
    }
    fseek(infile,obj->file_offset,SEEK_SET);
    readdata(infile,obj);
    fclose(infile);
    obj->obj_state=FROM_CACHE;
  } else
    if (obj->obj_state==IN_DB) {
      infile=fopen((access_load_file ? load_name : save_name),"r");
      if (!infile) {
        log_sysmsg("  cache: load_data() unable to read from database");
        return;
      }
      fseek(infile,obj->file_offset,SEEK_SET);
      readdata(infile,obj);
      fclose(infile);
      obj->obj_state=FROM_DB;
    }
  add_loaded(obj);
}

void writeverb(FILE *outfile, struct verb *curr_verb) {
  if (!curr_verb) return;
  writeverb(outfile,curr_verb->next);
  fprintf(outfile,"%d\n%ld\n%s%ld\n%s",(int) curr_verb->is_xverb,
          (long) strlen(curr_verb->verb_name),
          curr_verb->verb_name,
          (long) strlen(curr_verb->function),
          curr_verb->function);
}