/*{{{  #includes*/
#ifdef CONFIG_H
#   include "config.h"
#endif

#include <sys/types.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#include <local/bool.h>

#define VIRTUAL_C
#define I_GETMSG_C
#define I_MESSAGES_C
#define I_SIGNALS_C

#include "origami.h"
#include "macros.h"
/*}}}  */

/*{{{  #ifdef VIRTUAL - additional types,variables and procedures*/
#ifdef VIRTUAL

  /*{{{  constants*/
#  define LINE_PART             (BLOCK_SIZE-sizeof(int))
                               /* number of chars, which can be stored   */
                               /* in one fileblock                       */

#  define FILE_FREE_COUNT       (((int)(LINE_PART/sizeof(int)))-1)
                               /* number of file-garbage-entries in a    */
                               /* virtuell memoryblock (including a link */
                               /* to next package)                       */
  /*}}}  */
  /*{{{  typedef VIR_ELEMENT*/
  typedef struct {

    element *parent;           /* textnode belonging to this textstring  */
                               /* nodes[0]: not used                     */

    int     pre,next;          /* links for doubled linked LRU-list      */
                               /* nodes[0]: pointer to head and tail of  */
                               /*           lru-list                     */

    int     filepo;            /* position of the unchanged text in file */
                               /* nodes[0]: list of free local buffers   */

    union
     { unsigned char txt[LINELEN+1];
                               /* textline                               */
       int *fre_f_buff;        /* pointer to list of free filebuffers    */
                               /* (used from nodes[0])                   */
     } t;

  } VIR_ELEMENT;
  /*}}}  */

  /*{{{  variables*/
  private VIR_ELEMENT *nodes=0;       /* local buffers             */
  private off_t old_pos=0;
  private int file_used;              /* number of used fileblocks */
  private int tmp_file;               /* file-descriptor for       */
                                      /* temporary file            */
  private int file_garbage;           /* index for handling free   */
                                      /* file-blocks               */
  private char ori_tmp[]=ORI_TMP;     /* name of tmp-file          */
  public int vir_nodes=0;             /* number of local buffers   */
  /*}}}  */

  /*{{{  handle the file-blocks, file-IO*/
  /*{{{  seek_tmp              seek in tmp-file, arguments: offset*/
  private void seek_tmp(off_t po)
  { if (po!=old_pos && -1==(old_pos=lseek(tmp_file,po,SEEK_SET)))
       exit_origami(r_vir_err,get_msg(F_VM_FAIL,"seek",strerror(errno)));
  }
  /*}}}  */
  /*{{{  read_tmp              read from tmp-file, arguments: offset,length,*data*/
  private unsigned char *read_tmp(off_t po,int lg,unsigned char *s)
  { seek_tmp(po);
    if (-1==read(tmp_file,(char *)s,lg))
      exit_origami(r_vir_err,get_msg(F_VM_FAIL,"read",strerror(errno)));
    else
      old_pos+=lg;

    return(s);
  }
  /*}}}  */
  /*{{{  write_tmp             write to tmp-file, drguments: offset,length,*data*/
  private void write_tmp(off_t po,int lg,unsigned char *s)
  {
    seek_tmp(po);
    if (-1==write(tmp_file,(char *)s,lg))
      exit_origami(r_vir_err,get_msg(F_VM_FAIL,"write",strerror(errno)));
    else
      old_pos+=lg;
  }
  /*}}}  */

  /*{{{  get_file_buff         get a new file-block (or previously freed one)*/
  private int get_file_buff(void)
  {
    int x;
    int po;

    if (file_garbage) {
     /*{{{  get fromlist*/
     x=((int*) nodes[0].t.fre_f_buff)[--file_garbage];
     /*{{{  if list now empty, try to get next part*/
     if (!file_garbage) {
       if (po=((int*) nodes[0].t.fre_f_buff)[FILE_FREE_COUNT]) {
         /*{{{  next-link was not 0!, so get the package*/
         read_tmp((off_t)(po)*BLOCK_SIZE+sizeof(int),
                  LINE_PART,
                  (unsigned char*)nodes[0].t.fre_f_buff);
         file_garbage=FILE_FREE_COUNT;
         /*}}}  */
       }
     }
     /*}}}  */
     return(x);
     /*}}}  */
    } else {
     /*{{{  new one*/
     /*{{{  clear the next-pointer in the block!*/
     int dummy=0;
     write_tmp((off_t)(++file_used)*BLOCK_SIZE,sizeof(int),(unsigned char*)&dummy);
     /*}}}  */
     return(file_used);
     /*}}}  */
    }
  }
  /*}}}  */
  /*{{{  free_file_buff        add a file-block to garbage list*/
  private void free_file_buff(int po)
  {
    /*{{{  if current block of garbage list full, save this part*/
    if (file_garbage==FILE_FREE_COUNT) {
      /*{{{  variables*/
      int block_no;
      /*}}}  */

      /*{{{  get number for the free-block in file*/
      block_no= *(nodes[0].t.fre_f_buff);
      /*}}}  */
      write_tmp((off_t)block_no*BLOCK_SIZE+sizeof(int),
                LINE_PART,
                (unsigned char*) nodes[0].t.fre_f_buff);
      nodes[0].t.fre_f_buff[FILE_FREE_COUNT]=block_no;
      file_garbage=0;
    }
    /*}}}  */
    /*{{{  move the free entry to list*/
    nodes[0].t.fre_f_buff[file_garbage++]=po;
    /*}}}  */

    return;
  }
  /*}}}  */
  /*}}}  */
  /*{{{  read/write data-lines*/
  /*{{{  get_line_from_file    read a textline, argument: first block of line*/
  private void get_line_from_file(int po,unsigned char txt[LINELEN+1])
  {
    /*{{{  variables*/
    unsigned char *s=txt;
    int next;
    off_t current_po=(off_t)(po)*BLOCK_SIZE;
    /*}}}  */

    /*{{{  read the line to buff*/
    do {
      /*{{{  read the overflow-index*/
      read_tmp(current_po,sizeof(int),(unsigned char*)&next);
      /*}}}  */
      /*{{{  read data*/
      read_tmp(current_po+sizeof(int),LINE_PART,s);
      s+=LINE_PART;
      /*}}}  */
      current_po=(off_t)(next)*BLOCK_SIZE;
    } while (next);
    *s='\0';
    /*}}}  */
  }
  /*}}}  */
  /*{{{  put_line_to_file      write a textline to file, return first block*/
  private int put_line_to_file(unsigned char *s)
  {
    /*{{{  variables*/
    int po=get_file_buff();
    off_t current_po=(off_t)(po)*BLOCK_SIZE;
    int l=strlen((char *)s);
    int next;
    /*}}}  */

    do {
      /*{{{  write a part*/
      /*{{{  get old overflow-index of the file-block*/
      read_tmp(current_po,sizeof(int),(unsigned char*)&next);
      /*}}}  */
      l -= LINE_PART;
      if (l>0) {
        /*{{{  line must be splitted again*/
        /*{{{  write the part*/
        write_tmp(current_po+sizeof(int),LINE_PART,s);
        s+=LINE_PART;
        /*}}}  */
        if (!next) {
          /*{{{  get new overflow and store it in the fileblock too*/
          next=get_file_buff();
          write_tmp(current_po,sizeof(int),(unsigned char*)&next);
          /*}}}  */
        }
        current_po = next*BLOCK_SIZE;
        /*}}}  */
      } else {
        /*{{{  line is short enough for a packet*/
        if (next) free_file_buff(next);
        next=0;
        write_tmp(current_po,sizeof(int),(unsigned char*)&next);
        write_tmp(current_po+sizeof(int),l+LINE_PART+(l?1:0),s);
        /*}}}  */
      }
      /*}}}  */
    } while (l>0);

    return(po);
  }
  /*}}}  */
  /*}}}  */
  /*{{{  handle the local buffer-nodes*/
  /*{{{  lru_handle            move a local buffer to top of LRU-list*/
  private void lru_handle(int i)
  {
    int n,p;

    nodes[p=nodes[i].pre].next=n=nodes[i].next;
    nodes[n].pre=p;
    nodes[i].pre=0;
    nodes[nodes[i].next=nodes[0].next].pre=i;
    nodes[0].next=i;
  }
  /*}}}  */
  /*{{{  get_local_buff        return a free local buffer (maybe free one)*/
  private int get_local_buff(void)
  {
    int i,x;
    VIR_ELEMENT *x_ptr;

    /*{{{  x=free local-buffer-place,x_ptr=its adress*/
    if (x=nodes[0].filepo) {
      /*{{{  free list not empty*/
      x_ptr= &nodes[x];
      /*{{{  remove from free-list*/
      nodes[0].filepo=nodes[x].filepo;
      /*}}}  */
      /*{{{  append to head of lru*/
      nodes[x_ptr->next=nodes[0].next].pre=x;
      x_ptr->pre=0;
      nodes[0].next=x;
      /*}}}  */
      /*}}}  */
    } else {
      /*{{{  make a place free*/
      /*{{{  get a good place to move to file*/
      x=nodes[0].pre;
      /*{{{  not unchanged, so unchange all not fixed buffers*/
      if (!nodes[x].filepo) {
        i=((vir_nodes-FIXED-1)>>1)+1;
        for (i=((vir_nodes-FIXED-1)>>1)+1;i--;)
         { if (!nodes[x].filepo)
              nodes[x].filepo=put_line_to_file(nodes[x].t.txt);
           x=nodes[x].pre;
         }
        x=nodes[0].pre;
      }
      /*}}}  */
      x_ptr= &nodes[x];
      /*}}}  */
      /*{{{  set element's data-entry to file-block*/
      x_ptr->parent->datas.posi=x_ptr->filepo;
      /*}}}  */
      lru_handle(x);
      /*}}}  */
    }
    /*}}}  */
    x_ptr->filepo=0;
    return(x);
  }
  /*}}}  */
  /*{{{  free_local_buff       add a local buffer to garbage-list*/
  private void free_local_buff(int pos)
  {
    int p,n;
    VIR_ELEMENT *current = &nodes[pos];

    /*{{{  if unchanged, free also the file-block*/
    if (current->filepo) free_file_buff(current->filepo);
    /*}}}  */
    current->t.txt[0]='\0';
    /*{{{  remove from LRU*/
    nodes[p=current->pre].next=n=current->next;
    nodes[n].pre=p;
    /*}}}  */
    /*{{{  put to free locals*/
    current->filepo=nodes[0].filepo;
    nodes[0].filepo=pos;
    /*}}}  */

    return;
  }
  /*}}}  */
  /*}}}  */

#endif
/*}}}  */

/*{{{  init_data_handle        init the local buffer and the tmp-file*/
public int init_data_handle(void)
{
#  ifdef VIRTUAL
    if (vir_nodes) {
      /*{{{  virtuell linehandling is active, initialise the buffers*/
      int i;

      /*{{{  minimum size of table is checked*/
      if (vir_nodes<=FIXED) vir_nodes=FIXED+1;
      /*}}}  */
      /*{{{  get the table*/
      if (!(nodes=(VIR_ELEMENT*)malloc(vir_nodes*sizeof(VIR_ELEMENT)+LINE_PART)))
        exit_origami(r_mem_full,M_NO_MEMORY);
      /*}}}  */
      /*{{{  init the table*/
      /*{{{  normal nodes*/
      for (i=vir_nodes-2;i;i--) nodes[i].filepo=i+1;
      nodes[vir_nodes-1].filepo=0;
      /*}}}  */
      /*{{{  root-node*/
      nodes[0].pre=0;
      nodes[0].next=0;
      nodes[0].filepo=1;
      nodes[0].t.fre_f_buff=(int*)&nodes[vir_nodes];
      nodes[0].t.fre_f_buff[FILE_FREE_COUNT]=0;
      /*}}}  */
      /*}}}  */
      file_garbage=0;
      file_used=0;
      /*{{{  open the tmp-file and set to position BLOCK_SIZE*/
      mktemp(ori_tmp);
      if ((tmp_file=open(ori_tmp,O_RDWR | O_CREAT,0600))<0) return(1);
      if (-1==(old_pos=lseek(tmp_file,(off_t) BLOCK_SIZE,SEEK_SET)))
        exit_origami(r_vir_err,get_msg(F_VM_FAIL,"seek",strerror(errno)));
      /*}}}  */
      /*}}}  */
    }
#  endif
  return(0);
}
/*}}}  */
/*{{{  end_data_handle         close and unlink tmp-file*/
public int end_data_handle(void)
{
#  ifdef VIRTUAL
    if (vir_nodes) return(close(tmp_file) || unlink(ori_tmp));
#  endif
  return(0);
}
/*}}}  */

/*{{{  get_data                get the data belonging to a node*/
public unsigned char *get_data(element *p)
{
#  ifdef VIRTUAL
    if (vir_nodes) {
      /*{{{  virtuell linehandling is active*/
      int i,po;

      if ((i=p->datas.posi)<0) {
        /*{{{  local buffer*/
        lru_handle(-i);
        return(nodes[-i].t.txt);
        /*}}}  */
      } else if (i==0) {
        /*{{{  empty_string*/
        return(empty_text);
        /*}}}  */
      } else {
        /*{{{  get from file*/
        p->datas.posi = -(po=get_local_buff());
        nodes[po].parent=p;
        nodes[po].filepo=i;
        get_line_from_file(i,nodes[po].t.txt);
        return(nodes[po].t.txt);
        /*}}}  */
      }
      /*}}}  */
    }
#  endif
  return(p->datas.dat);
}
/*}}}  */
/*{{{  set_data                set the data of a node to new value*/
public void set_data(element *p, unsigned char *s)
{ unsigned char *to_free=0;

#  ifdef VIRTUAL
    if (vir_nodes) {
      /*{{{  virtuell line-handling is active*/
      int i,po;

      /*{{{  i= old position of text*/
      i=p->datas.posi;
      /*}}}  */
      if (*s) {
        /*{{{  new string not empty*/
        if (i<0) {
          /*{{{  old in buffer*/
          VIR_ELEMENT *current = &nodes[-i];

          /*{{{  free old data*/
          if (current->filepo) free_file_buff(current->filepo);
          /*}}}  */
          strcpy((char *)current->t.txt,(char *)s);
          current->filepo=0;
          lru_handle(-i);

          return;
          /*}}}  */
        } else {
          /*{{{  old in file or empty*/
          /*{{{  maybe free the filedata*/
          if (i) free_file_buff(i);
          /*}}}  */
          /*{{{  put data to new local buffer*/
          p->datas.posi = -(po=get_local_buff());
          { VIR_ELEMENT *current = &nodes[po];

            current->parent=p;
            strcpy((char *)current->t.txt,(char *)s);
            current->filepo=0;
          }
          /*}}}  */

          return;
          /*}}}  */
        }
        /*}}}  */
      } else {
        /*{{{  new string is empty, no buffer needed!*/
        if (i==0)
          /*{{{  ok, both empty*/
          return;
          /*}}}  */
        else if (i<0) {
          /*{{{  local, free the local buffer*/
          p->datas.posi=0;
          free_local_buff(-i);
          return;
          /*}}}  */
        } else {
          /*{{{  in file, free the filebuffer*/
          free_file_buff(i);
          p->datas.posi=0;
          return;
          /*}}}  */
        }
        /*}}}  */
      }
      /*}}}  */
    }
#  endif
  /*{{{  memory*/
  /*{{{  maybe free old text*/
  if (p->datas.dat!=empty_text) to_free=p->datas.dat;
  /*}}}  */
  if (*s) {
    /*{{{  new string is not empty, malloc and copy*/
    if (!(p->datas.dat=(unsigned char*)malloc(strlen((char *)s)+1)))
      exit_origami(r_mem_full,M_NO_MEMORY);
    strcpy((char *)p->datas.dat,(char *)s);
    /*}}}  */
  } else
    /*{{{  empty text, use above defined pointer*/
    p->datas.dat=empty_text;
    /*}}}  */
  if (to_free) free(to_free);

  return;
  /*}}}  */
}
/*}}}  */
/*{{{  new_data                create empty data for a new malloced node*/
/*{{{  comment*/
/* new_data can be used more than once for the same node                 */
/* only if set-data is used for a node, a call of new_data will destroy  */
/* the garbage-collection-data.                                          */
/*}}}  */
public void new_data(element *p)
{
#  ifdef VIRTUAL
    if (vir_nodes) {
      p->datas.posi=0;

      return;
    }
#  endif
  /*{{{  memory*/
  p->datas.dat=empty_text;

  return;
  /*}}}  */
}
/*}}}  */
