/*{{{}}}*/
/*{{{  #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>

#define VIRTUAL_C
#define I_LOOP_C
#define I_FOLDING_C
#define I_GETMSG_C
#define I_MESSAGES_C
#define I_ORIEDT_C
#define I_SIGNALS_C
#define I_STRING_C

#include "origami.h"
#include <lib/ori_add_lib.h>
/*}}}  */

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

  /*{{{  constants*/
#  define LINE_PART             (BLOCK_SIZE-sizeof(off_t))
                               /* 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 unsigned 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_vir_tmp[_POSIX_PATH_MAX+1];
                                      /* name of tmp-file          */
  public int vir_nodes=0;             /* number of local buffers   */
  /*}}}  */

  /*{{{  vir_dumb/vprint*/
#  ifdef VIR_DEBUG
     extern FILE *v_dumb;

#    define vprintf(f,x) (fprintf(v_dumb,f,x),fflush(v_dumb))
#    define v_format "^^ %s ^^\n"
     /*{{{  vir_dumb*/
     private void vir_dumb(char*t)
     { if (v_dumb)
        { int i;

          for (i=0;i<vir_nodes;i++)
           { fprintf(v_dumb,
                     "%3d|node %p|f%3d|p%3d|n%3d|%.32s\n",
                     i,
                     (void*)nodes[i].parent,
                     nodes[i].filepo,
                     nodes[i].pre,
                     nodes[i].next,
                     i?nodes[i].t.txt:(unsigned char*)""
                    );
           }
          vprintf(v_format,t);
        }
     }
     /*}}}  */
#  else
#    define vir_dumb(x)
#    define vprintf(f,x)
#  endif
  /*}}}  */

  /*{{{  v_error*/
  private void v_error(char const*const s)
  {
    exit_origami(r_vir_err,get_msg(F_VM_FAIL,s,strerror(errno)));
  }
  /*}}}  */
  /*{{{  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)))
       v_error("seek");
  }
  /*}}}  */
  /*{{{  read_tmp              read from tmp-file, arguments: offset,length,*data*/
  private unsigned char *read_tmp(off_t po,int lg,unsigned char * const s)
  { seek_tmp(po);
    if (-1==read(tmp_file,(char *)s,lg))
      v_error("read");
    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 const * const s)
  {
    seek_tmp(po);
    if (-1==write(tmp_file,(char *)s,lg))
      v_error("write");
    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*/
     vprintf(v_format,"expand v-file");
     /*{{{  clear the next-pointer in the block!*/
     { int dummy=0;

       write_tmp((off_t)(++file_used)*BLOCK_SIZE,sizeof(off_t),(unsigned char*)&dummy);
     }
     vir_debug((off_t)(file_used*BLOCK_SIZE));
     if (!file_used)
        v_error("append");
     /*}}}  */
     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 * const txt)
  {
    /*{{{  variables*/
    unsigned char *s=txt;
    int next;
    off_t current_po=(off_t)(po)*BLOCK_SIZE;
    int lg=LINELEN;
    /*}}}  */

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

    do {
      /*{{{  write a part*/
      /*{{{  get old overflow-index of the file-block*/
      read_tmp(current_po,sizeof(off_t),(unsigned char*)&next);
      /*}}}  */
      l -= LINE_PART;
      if (l>0) {
        /*{{{  line must be splitted again*/
        /*{{{  write the part*/
        write_tmp(current_po+sizeof(off_t),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(off_t),(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(off_t),(unsigned char*)&next);
        write_tmp(current_po+sizeof(off_t),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) {
        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;
    vir_dumb("buff get local");

    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;
    /*}}}  */
    vir_dumb("buff free local");

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

#endif
/*}}}  */

#ifdef VIRTUAL
  /*{{{  init_data_handle      init the local buffer and the tmp-file*/
  public int init_data_handle(void)
  {
    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=paket_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*/
       tmpnam(ori_vir_tmp);
       if ((tmp_file=open(ori_vir_tmp,O_RDWR | O_CREAT,0600))<0)
          return(1);
       if (-1==(old_pos=lseek(tmp_file,(off_t) BLOCK_SIZE,SEEK_SET)))
          v_error("seek");
       /*}}}  */
       vir_dumb("start");
     }
     /*}}}  */
    return(0);
  }
  /*}}}  */
  /*{{{  end_data_handle       close and unlink tmp-file*/
  public int end_data_handle(void)
  {
    return(vir_nodes && (close(tmp_file) || unlink(ori_vir_tmp)));
  }
  /*}}}  */
#endif

/*{{{  get_data                get the data belonging to a node*/
#ifdef VIRTUAL
  public unsigned char *get_data(element const * const pin)
  { element *p=(element*)pin;

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

          if ((i=p->datas.posi)==0)
           /*{{{  empty string*/
           { vprintf(v_format,"get empty");
             
             return((unsigned char*)empty_text);
           }
           /*}}}  */
          else if (i==-vir_nodes)
           /*{{{  short string*/
           { vprintf(v_format,"get short");

             return(p->x.txt);
           }
           /*}}}  */
          else if (i<0)
           /*{{{  local buffer*/
           { lru_handle(-i);
             vir_dumb("get local");
             
             return(nodes[-i].t.txt);
           }
           /*}}}  */
          else
           /*{{{  get from file*/
           { VIR_ELEMENT *cur;
             
             
             p->datas.posi = -(po=get_local_buff());
             cur = &nodes[po];
             cur->parent=p;
             cur->filepo=i;
             get_line_from_file(i,cur->t.txt);
             vir_dumb("get file");
             
             return(cur->t.txt);
           }
           /*}}}  */
        }
        /*}}}  */
#   endif
    return(p->datas.dat);
  }
#else
  /* defined as a macro in origami.h */
#endif
/*}}}  */
/*{{{  set_data                set the data of a node to new value*/
public void set_data
 ( element * const p,
   unsigned char const * const s,
   boolean const txt
 )
{ int l;

  l=ustrlen(s);
#ifdef VIRTUAL
  if (vir_nodes)
   /*{{{  virtuell line-handling is active*/
   { int i,po;
     
     /*{{{  i= old position of text*/
     i=p->datas.posi;
     /*}}}  */
     if (l>=sizeof(fold_data) || !txt)
      /*{{{  new string must be stored extern (to long or fold))*/
      { if (i<0 && i!=-vir_nodes)
         /*{{{  old in buffer*/
         { VIR_ELEMENT *current = &nodes[-i];
           
           /*{{{  free old data*/
           if (current->filepo) free_file_buff(current->filepo);
           /*}}}  */
           ustrcpy(current->t.txt,s);
           current->filepo=0;
           lru_handle(-i);
           vir_dumb("set buff text");
         }
         /*}}}  */
        else
         /*{{{  old in file or empty or short*/
         {
           /*{{{  maybe free the filedata*/
           if (i>0) 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;
             ustrcpy(current->t.txt,s);
             current->filepo=0;
           }
           /*}}}  */
           vir_dumb("set file/empty text");
         }
         /*}}}  */
      }
      /*}}}  */
     else
      /*{{{  new string short enough, to be stored in the node*/
      {
#        ifdef VIR_DEBUG
           char *du;
#        endif
        
        /*{{{  handle old data*/
        if (i==0 || i==-vir_nodes)
         /*{{{  ok, both in node*/
         {
#           ifdef VIR_DEBUG
              du=l?"set empty/short short":"set empty/short empty";
#           endif
         }
         /*}}}  */
        else if (i<0)
         /*{{{  local, free the local buffer*/
         { free_local_buff(-i);
#           ifdef VIR_DEBUG
              du=l?"set local short":"set local empty";
#           endif
         }
         /*}}}  */
        else
         /*{{{  in file, free the filebuffer*/
         { free_file_buff(i);
#           ifdef VIR_DEBUG
              du=l?"set file short":"set file empty";
#           endif
         }
         /*}}}  */
        /*}}}  */
        /*{{{  set new data*/
        if (l)
         /*{{{  copy to node*/
         { p->datas.posi= -vir_nodes;
           ustrcpy(p->x.txt,s);
         }
         /*}}}  */
        else
         /*{{{  mark empty*/
         { p->datas.posi=0;
         }
         /*}}}  */
        /*}}}  */
        vprintf(v_format,du);
      }
      /*}}}  */
   }
   /*}}}  */
  else
#endif
   /*{{{  memory*/
   { unsigned char *to_free;

     /*{{{  store, if any memory to free (don't free, maybe new is part of old!)*/
     to_free=p->datas.dat;
     if (to_free==empty_text || to_free==p->x.txt)
        to_free=0;
     /*}}}  */
     if (l)
      /*{{{  new string is not empty, malloc and copy*/
      { if
         (    txt
           && l<sizeof(fold_data)
           && (    (    bd.m.select_mode!=fold_selection
                     && bd.m.select_mode!=unmark_fold_selection
                   )
                || p!=bd.f.select_ptr
              )
         )
           p->datas.dat=p->x.txt;
        else
           if (!(p->datas.dat=ori_malloc(l+1)))
              exit_origami(r_mem_full,M_NO_MEMORY);
        ustrcpy(p->datas.dat,s);
      }
      /*}}}  */
     else
      /*{{{  empty text, use default empty-text-pointer*/
        p->datas.dat=(unsigned char*)empty_text;
      /*}}}  */
     if (to_free)
        paket_free(to_free);
   }
   /*}}}  */
}
/*}}}  */
/*{{{  set_0_data              set the data of a node to empty*/
public void set_0_data(element * const p)
{

#ifdef VIRTUAL
  if (vir_nodes)
     set_data(p,empty_text,False);
  else
#endif
   /*{{{  memory*/
   { if (p->datas.dat!=empty_text && p->datas.dat!=p->x.txt)
        paket_free(p->datas.dat);
     p->datas.dat=(unsigned char*)empty_text;
   }
   /*}}}  */
}
/*}}}  */
/*{{{  new_data                create empty data for a new malloced node*/
#ifdef VIRTUAL
  /*{{{  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)
      /*{{{  virtual*/
      { p->datas.posi=0;

        vprintf(v_format,"new");
      }
      /*}}}  */
     else
#  endif
      /*{{{  memory*/
        p->datas.dat=(unsigned char*)empty_text;
      /*}}}  */
  }
#else
  /* defined as a macro in origami.h */
#endif
/*}}}  */
