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

#include <sys/types.h>
#include <limits.h>
#include <termcap.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <local/bool.h>

#define DISPLAY_C
#define I_FIELDEDIT_C
#define I_FOLDHELP_C
#define I_FOLDING_C
#define I_GETTK_C
#define I_GETMSG_C
#define I_MAIN_C
#define I_MISC_C
#define I_MESSAGES_C
#define I_ORIEDT_C
#define I_PROMPT_C
#define I_SCREEN_C
#define I_SIGNALS_C
#define I_VIRTUAL_C

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

/*{{{  title-strings*/
#define VIEW_TAG (unsigned char*) "View"
#define sVIEW_TAG (unsigned char*) "V"
#define DV_TAG (unsigned char*) "DirView"
#define sDV_TAG (unsigned char*) "DV"
#define DM_TAG (unsigned char*) "DefMac"
#define sDM_TAG (unsigned char*) "D"
#define FOLD_TAG (unsigned char*) "Folding"
#define sFOLD_TAG (unsigned char*) "F"
#define OVER_TAG (unsigned char*) "Over"
#define sOVER_TAG (unsigned char*) "O"
#define AUTO_TAG (unsigned char*) "AutoSave"
#define sAUTO_TAG (unsigned char*) "S"
#define cut_path "..."
/*}}}  */

/*{{{  variables*/
public element *screen_start,*screen_end;
public unsigned char keybinding_name[keyn_lg+1];
public unsigned char *l_modes[MODE_COUNT];
public unsigned char *s_modes[MODE_COUNT];
public unsigned char print_buffer[LINELEN+1];
public int cursor_level = 1,screen_end_level = 1,W_dx;
public int no_title=0;
public int area_size=0;
public int hard_tab=8;
public bool um_active[MODE_COUNT];
public bool inv_ctrl=FALSE;
public bool dirty;
public char *w_name=0;
private unsigned char **area_lines=0;
/*}}}  */
/*{{{  constants*/
#define TITLE_LINE        (screen_lines-area_size-1)
#define AREA_LINE         (screen_lines-area_size)
/*}}}  */

/*{{{  on_screen*/
public bool on_screen(element *p, int *on_screen_line)
{
  element *q, *old_q;
  bool on_scr;
  int line;

  line = 0;
  q = screen_start;
  on_scr = FALSE;
  do {
    line++;
    old_q = q;
    if (q == p) {
      *on_screen_line = line;
      on_scr = TRUE;
    }
    q = q->next;
  } while (old_q != screen_end);
  on_scr = (on_scr && *on_screen_line > 1 && *on_screen_line < SCREEN_LEN);
  return on_scr;
}
/*}}}  */
/*{{{  txt2scr*/
public int txt2scr(int offset,int x,unsigned char *s)
{ int i=1;
  unsigned char c;

  while (x)
   { if (!(c= *s++))
      /*{{{  string empty, add the missing moves*/
      { i+=x;break; }
      /*}}}  */
     else if (c>=' ')
      /*{{{  normal char -> +1*/
        i++;
      /*}}}  */
     else if (c=='\t' && hard_tab>0)
      /*{{{  move to next tab-position*/
        i=((offset+i+hard_tab-1)/hard_tab)*hard_tab-offset+1;
      /*}}}  */
     else
      /*{{{  move ctrl-width right*/
        i+=inv_ctrl?1:2;
      /*}}}  */
     x--;
   }
  return(i);
}
/*}}}  */
/*{{{  scr2txt*/
public int scr2txt(int offset,int x,unsigned char *s)
{ int i,is;
  unsigned char c;

  is=1;
  i=0;
  while (is<x)
   { if (!(c= *s++))
      /*{{{  string empty, add missing moves*/
      { i+=x-is;break; }
      /*}}}  */
     else if (c>=' ')
      /*{{{  normal char -> move right*/
        is++;
      /*}}}  */
     else if (c=='\t' && hard_tab>0)
      /*{{{  tab handling*/
        is=((offset+is+hard_tab-1)/hard_tab)*hard_tab-offset+1;
      /*}}}  */
     else
      /*{{{  ctrl-handling*/
        is+=inv_ctrl?1:2;
      /*}}}  */
     i++;
   }
  return(i);
}
/*}}}  */
/*{{{  prt_bin_text*/
public int prt_bin_text(int off,int len,bool mark,unsigned char *s)
{ unsigned char *s1=s,*s2=s;
  unsigned char c;
  int max=len;

  while ((c = *s2) && len)
   /*{{{  search ctrl-char and print leading text and ctrl-char*/
   { if (c>=' ')
      /*{{{  scan over normal text*/
      { s2++;off++;len--; }
      /*}}}  */
     else
      /*{{{  print old text and the speciell handled ctrl-char*/
      { *s2='\0';
        pline((char*)s1);
        *s2=c;
        if (c=='\t' && hard_tab>0)
         /*{{{  print as tabs*/
         { do
            { oputc(' ');off++;len--; }
           while (off%hard_tab && len);
         }
         /*}}}  */
        else
         /*{{{  print as ctrl*/
         { if (inv_ctrl) standout(); else { oputc('^');off++;len--; }
           if (len)
            { oputc(ctrl_decode[c]);len--;off++; }
           else
              s2--;
           if (inv_ctrl) standend();
         }
         /*}}}  */
        s1= ++s2;
      }
      /*}}}  */
   }
   /*}}}  */
  if (*s1)
   /*{{{  print missing part of line*/
     if (*s2)
      /*{{{  poke 0 into the text and print*/
      { c= *s2;
        *s2='\0';
        pline(s1);
        *s2=c;
      }
      /*}}}  */
     else
        pline(s1);
   /*}}}  */
  if (mark && !len)
   /*{{{  maybe put overflow mark*/
   { len--;
     while (*s2==' ' || *s2=='\t') s2++;
     oputc(*s2?eoln_str:' ');
   }
   /*}}}  */
  return(max-len);        /* number of printed characters */
}
/*}}}  */
/*{{{  go_prt_clreol*/
public void go_prt_clreol(int x,int y,int off,int len,bool mark,
                          unsigned char *s)
{ gotoxy(x,y);
  x+=prt_bin_text(off,len,mark,s);
  if (x<W_dx) goclreol(x,y);
}
/*}}}  */
/*{{{  write_dsp_line*/
public void write_dsp_line(element *ptr,int level)
{
  if (scr_off) return;
  copyin(print_buffer, ptr, FALSE);
  go_prt_clreol(1,level,enter_depth_spaces,LEN-1,TRUE,print_buffer);
}
/*}}}  */

/*{{{  forward: restore_element*/
public void restore_element(int close_line);
/*}}}  */

/*{{{  title_op*/
/*{{{  private vars*/
private bool dm_tag=FALSE;
private bool cf_tag=FALSE;
private bool cfold_tag=FALSE;
private bool view_tag=FALSE;
private bool over_tag=FALSE;
private bool auto_tag=FALSE;
/*}}}  */
/*{{{  p_modes*/
private void p_mode(unsigned char *r,bool sh)
{
  unsigned char *x;

  while (*r) r++;
  /*{{{  view*/
  if (view_tag)
   { *r++=comma;
     x=(unsigned char*)(sh?sVIEW_TAG:VIEW_TAG);
     while (*x) *r++ = *x++;
   }
  /*}}}  */
  /*{{{  dirview*/
  if (dir_edit)
   { *r++=comma;
     x=(unsigned char*)(sh?sDV_TAG:DV_TAG);
     while (*x) *r++ = *x++;
   }
  /*}}}  */
  /*{{{  defmac*/
  if (dm_tag)
   { *r++=comma;
     x=(unsigned char*)(sh?sDM_TAG:DM_TAG);
     while (*x) *r++ = *x++;
   }
  /*}}}  */
  /*{{{  folding*/
  if (cfold_tag)
   { *r++=comma;
     x=(unsigned char*)(sh?sFOLD_TAG:FOLD_TAG);
     while (*x) *r++ = *x++;
   }
  /*}}}  */
  /*{{{  over*/
  if (over_tag)
   { *r++=comma;
     x=(unsigned char*)(sh?sOVER_TAG:OVER_TAG);
     while (*x) *r++ = *x++;
   }
  /*}}}  */
  /*{{{  auto*/
  if (auto_tag)
   { *r++=comma;
     x=(unsigned char*)(sh?sAUTO_TAG:AUTO_TAG);
     while (*x) *r++ = *x++;
   }
  /*}}}  */
  /*{{{  append user modes*/
  { int i=MODE_COUNT;

    while (--i>=0)
       if (um_active[i])
        { x=sh?s_modes[i]:l_modes[i];
          while (*x) *r++= *x++;
        }
  }
  /*}}}  */
  *r='\0';
}
/*}}}  */
/*{{{  pathcut*/
/*{{{  comment*/
/*
   pathcut: input  char **name
                   (adress of a pointer to filename)
            output int
                   number of cutten chars
            sideeffect:
                   if there is a '/'( not at start of the filename), then
                   *name is set to the text behind '/'
*/
/*}}}  */
bool pathcut(unsigned char **name)
{
  unsigned char *s= *name;

  if (*s==PATH_C) s++;
  while (*s && *s!=PATH_C) s++;
  if (*s) { *name=s;return(TRUE); }
  return(FALSE);
}
/*}}}  */

public void title_op(TITLE_OP op)
{
  /*{{{  variables*/
  unsigned char filename[_POSIX_PATH_MAX+1];
  unsigned char statline[LINELEN+1];
  unsigned char *x;
  int tail;
  static bool title_changed=TRUE;
  /*}}}  */

  switch(op) {
    /*{{{  show the line*/
    case UPDTITLE:
       if (!title_changed) break;
    case PRTTITLE:
       if (!scr_off && !no_title)
        /*{{{  do the print*/
        {
          /*{{{  get filename*/
          if (real_tail == real_head)
            *filename = '\0';
          else
            strcpy((char*)filename,(char *)get_data(real_tail));
          /*}}}  */
          /*{{{  data -> statline*/
          strcpy((char*)statline, cf_tag ? " * " : (char*)tspace );
          strcat((char*)statline,keybinding_name[0]?(char*)keybinding_name
                                                   :DEFAULT_NAME);
          strcat((char*)statline," " VERSION " (");
          /*{{{  get correct language-mark, x at end of the current string*/
          { f_c_types i=F_C_NONE;
            unsigned char *s= &language_string[0];

            /*{{{  skip first entries*/
            while (i!=dialect)
             { while (*s==' ') s++;
               while (*s!=' ') s++;
               i++;
             }
            while (*s==' ') s++;
            /*}}}  */
            /*{{{  append current language*/
            x=statline;
            while (*x) x++;
            while (*s && *s!=' ') *x++= *s++;
            *x='\0';
            if (dialect == F_C_USER)
             /*{{{  append user-comments*/
             { *x++=' ';
               strcpy((char*)x,(char *)dialect_start[dialect]);
               strcat((char*)x,(char*)tspace+2);
               strcat((char*)x,(char *)dialect_end[dialect]);
               while (*x) x++;
             }
             /*}}}  */
            /*}}}  */
          }
          /*}}}  */
          /*{{{  modes at end of line*/
          p_mode(x,FALSE);
          strcat((char*)x,") ");
          /*}}}  */
          /*{{{  maybe w_name*/
          if (w_name) strcat((char*)x,w_name);
          /*}}}  */
          /*{{{  filename*/
          strcat((char*)x," ");
          strcat((char*)x,(char*)filename);
          /*}}}  */
          /*}}}  */
          tail=W_dx-2*sg-strlen((char*)statline);
          /*{{{  if to long, try short tags and maybe cut filename*/
          if (tail<0) {
            unsigned char *fn=filename;
            bool loop=TRUE;
            bool cut=FALSE;

            /*{{{  modes*/
            *x='\0';
            p_mode(x,TRUE);
            strcat((char*)statline,") ");
            /*}}}  */
            /*{{{  maybe w_name*/
            if (w_name) { strcat((char*)x,w_name);strcat((char*)x," "); }
            /*}}}  */
            while (*x) x++;
            while (loop) {
              /*{{{  cat filename to statusline*/
              *x='\0';
              if (cut) strcpy((char*)x,cut_path);
              strcat((char*)x,(char*)fn);
              /*}}}  */
              tail=W_dx-2*sg-strlen((char*)statline);
              if (tail<0 && pathcut(&fn)) cut=TRUE; else loop=FALSE;
            }
          }
          /*}}}  */
          spaces(statline+strlen((char*)statline),tail);
          gotoxy(1,TITLE_LINE);
          standout();
          prt_bin_text(0,LEN,FALSE,statline);
          standend();
          title_changed=FALSE;
        }
        /*}}}  */
       break;
    /*}}}  */
    /*{{{  change*/
    case CHGTITLE:
      title_changed=TRUE;
      break;
    /*}}}  */
    /*{{{  SETs*/
    case SET_DM:
      if (!dm_tag)    title_changed=dm_tag=TRUE;
      break;
    case SET_FOLD:
      if (!cfold_tag) title_changed=cfold_tag=TRUE;
      break;
    case SET_CF:
      if (!cf_tag)    title_changed=cf_tag=TRUE;
      break;
    case SET_VIEW:
      if (!view_tag)  title_changed=view_tag=TRUE;
      break;
    case SET_OVER:
      if (!over_tag)  title_changed=over_tag=TRUE;
      break;
    case SET_AUTO:
      if (!auto_tag)  title_changed=auto_tag=TRUE;
      break;
    /*}}}  */
    /*{{{  RESETs*/
    case RESET_DM:
      if (dm_tag)     title_changed=TRUE;dm_tag=FALSE;
      break;
    case RESET_FOLD:
      if (cfold_tag)  title_changed=TRUE;cfold_tag=FALSE;
      break;
    case RESET_CF:
      if (cf_tag)     title_changed=TRUE;cf_tag=FALSE;
      break;
    case RESET_VIEW:
      if (view_tag)   title_changed=TRUE;view_tag=FALSE;
      break;
    case RESET_OVER:
      if (over_tag)   title_changed=TRUE;over_tag=FALSE;
      break;
    case RESET_AUTO:
      if (auto_tag)   title_changed=TRUE;auto_tag=FALSE;
      break;
    /*}}}  */
  }
}
/*}}}  */

/*{{{  restore_area_line*/
private void restore_area_line(int y)
{
  if (scr_off) return;
  /*{{{  print area_line*/
  goclreol(1,AREA_LINE+y);
  gotoxy(1,AREA_LINE+y);
  { bool wsg=!no_title && (y==(area_size-1));

    if (wsg) standout();
    prt_bin_text(0,LEN-(wsg?sg+sg:0),FALSE,area_lines[y]);
    if (wsg) standend();
  }
  /*}}}  */
}
/*}}}  */
/*{{{  store_area_line*/
private void store_area_line(unsigned char *x,int no)
{
  /*{{{  maybe free old data*/
  if (area_lines[no]!=empty_text) free(area_lines[no]);
  /*}}}  */
  /*{{{  store data*/
  if (!*x)
     area_lines[no]=empty_text;
  else
   /*{{{  malloc and copy*/
   { if (!(area_lines[no]=(unsigned char *)malloc(strlen((char*)x)+1)))
        exit_origami(r_mem_full,M_NO_MEMORY);
     strcpy((char*)area_lines[no],(char*)x);
   }
   /*}}}  */
  /*}}}  */
}
/*}}}  */
/*{{{  set_area*/
void set_area (void)
{ int i;

  /*{{{  remove old area*/
  if (area_size)
   { i=area_size;
     while (i) store_area_line(empty_text,--i);
     free(area_lines);
   }
  /*}}}  */
  /*{{{  get new area-size*/
  i=area_size=macro_int[get_arg()];
  if (area_size<0) area_size=0;
  if (SCREEN_LEN<=0 || !area_size)
   { area_size=0;
     restore_element(SCREEN_LEN/2);
     if (i) message(get_msg(M_NO_AREA),TRUE);
     return;
   }
  /*}}}  */
  /*{{{  get place for new arealines*/
  if (!(area_lines=(unsigned char **)malloc(area_size*sizeof(char*))))
     exit_origami(r_mem_full,M_NO_MEMORY);
  /*}}}  */
  /*{{{  clear the buffers and update screen*/
  while (i--) area_lines[i]=empty_text;
  restore_element(SCREEN_LEN/2);
  /*}}}  */
}
/*}}}  */
/*{{{  write_area*/
void write_area (void)
{
  /*{{{  variables*/
  int x;
  int ex;
  int y;
  unsigned char *p=print_buffer;
  /*}}}  */

  /*{{{  get position to write to*/
  ex=x=macro_int[get_arg()]-1;
  y=macro_int[get_arg()]-1;
  /*}}}  */
  /*{{{  check position*/
  if (!area_size || x<0 || y<0 || y>=area_size)
   { prompt_string(print_buffer);
     message(get_msg(M_NO_AREA),TRUE);
     return;
   }
  /*}}}  */
  /*{{{  copy line to printbuffer and set p to correct x-position for writing*/
  strcpy((char*)print_buffer,(char*)area_lines[y]);
  while (x--) if (*p) p++; else { *p=' ';*(++p)='\0'; }
  /*}}}  */
  /*{{{  copy string to correct position*/
  prompt_string(p);
  /*}}}  */
  /*{{{  restore rest of old line*/
  ex+=strlen((char*)p);
  if (ex<strlen((char*)area_lines[y])) strcat((char*)p,(char*)(area_lines[y]+ex));
  /*}}}  */
  /*{{{  move new line to storage and show it*/
  store_area_line(print_buffer,y);
  restore_area_line(y);
  /*}}}  */
}
/*}}}  */
/*{{{  pipe_area*/
void pipe_area (void)
{ FILE *areafile=0;
  int i=0;

  prompt_string(print_buffer);
  /*{{{  if no area active -> error*/
  if (!area_size) { message(get_msg(M_NO_AREA),TRUE);return; }
  /*}}}  */
  put_vars();
  if (areafile=popen((char*)print_buffer,"r"))
   /*{{{  read new data*/
   {
     while (i<area_size)
      /*{{{  read single line*/
      { fgets((char*)print_buffer,LINELEN,areafile);
        /*{{{  copy to local storage*/
        /*{{{  cut a trailling \n*/
        if (print_buffer[0])
         { unsigned char *s=print_buffer+strlen((char*)print_buffer)-1;

           if (*s=='\n') *s='\0';
         }
        /*}}}  */
        store_area_line(print_buffer,i);
        restore_area_line(i);
        /*}}}  */
        i++;
      }
      /*}}}  */
     macro_int[ocl_arg]=pclose(areafile);
   }
   /*}}}  */
  else
     message(get_msg(M_CANTOPEN),TRUE);
}
/*}}}  */

/*{{{  restore*/
public void restore(int from)
{
  element *p=screen_start;
  int h;

  x_offset=0;
  for (h = 1; h <= screen_end_level; h++) {
    /*{{{  display if under from*/
    if (h >= from) write_dsp_line(p, h);
    /*}}}  */
    p = p->next;
  }
  if (screen_end_level < SCREEN_LEN)
   { for (h=screen_end_level+1;h<screen_lines;h++)
      { goclreol(1,h); }
   }
  for (from=0;from<area_size;restore_area_line(from++));
  title_op(CHGTITLE);
  dirty=FALSE;
}
/*}}}  */
/*{{{  restore_to_end*/
public void restore_to_end(int from)
{  /*if select_on then set select separately*/
  screen_end = current;
  screen_end_level = cursor_level;
  while (screen_end_level < SCREEN_LEN && screen_end != tail) {
    screen_end_level++;
    screen_end = screen_end->next;
  }
  restore(from);
}
/*}}}  */
/*{{{  restore_element*/
public void restore_element(int close_line)
{  /*if select_on then set select separately*/
  int level, dist_to_end;
  int dist_to_start;

  dist_to_start = to_start((int)(SCREEN_LEN - 1));
  dist_to_end = to_bottom((int)(SCREEN_LEN - 2));
  cursor_level = close_line;
  if (cursor_level < 2) cursor_level = 2;
  if (cursor_level >= SCREEN_LEN) cursor_level = SCREEN_LEN - 1;
  if (dist_to_start < cursor_level) cursor_level = dist_to_start;
  while (cursor_level+dist_to_end < SCREEN_LEN && dist_to_start > cursor_level)
    cursor_level++;
  screen_start = current;
  for (level = cursor_level - 1; level >= 1; level--)
    screen_start = screen_start->prec;
  restore_to_end(1);
  for (level=0;level<area_size;restore_area_line(level++));
}
/*}}}  */
/*{{{  restore_or_restore_to_end*/
public void restore_or_restore_to_end(void)
{ element *p;

  if (screen_start != head->next) {
    restore_element(SCREEN_LEN/2);
    return;
  }
  p = head->next;
  cursor_level = 1;
  while (p != current) {
    cursor_level++;
    p = p->next;
  }
  restore_to_end(cursor_level);
}
/*}}}  */

/*{{{  up_a_bit*/
public void up_a_bit(int from)
{ int dirty_line=0;

  if (from!=SCREEN_LEN)
   { gotoxy(1,from);
     if (DelLine())
        dirty_line=from;
     else
      { gotoxy(1,SCREEN_LEN);
        if (insLine()) dirty_line=SCREEN_LEN;
      }
     if (dirty_line) restore_to_end(dirty_line);
   }
  gotoxy(1,from);
}
/*}}}  */
/*{{{  down_a_bit*/
public void down_a_bit(int from)
{ int dirty_line=0;

  if (from!=SCREEN_LEN)
   { gotoxy(1,from);
     if (insLine())
        dirty_line=from;
     else
      { gotoxy(1,SCREEN_LEN+1);
        if (DelLine()) { dirty_line=SCREEN_LEN+1;message(empty_text,FALSE); }
      }
     if (dirty_line) restore_to_end(dirty_line);
   }
  gotoxy(1,from);
}
/*}}}  */
/*{{{  whole_screen_up*/
public void whole_screen_up(void)
{
  if (screen_start == tail) return;
  screen_start = screen_start->next;
  cursor_level--;
  if (screen_end != tail) {
    screen_end = screen_end->next;
    write_dsp_line(screen_end, SCREEN_LEN);
  } else
    screen_end_level--;
  up_a_bit(1);
}
/*}}}  */
/*{{{  whole_screen_down*/
public void whole_screen_down(void)
{
  if (screen_start == head->next) return;
  screen_start = screen_start->prec;
  cursor_level++;
  if (screen_end_level == SCREEN_LEN)
    screen_end = screen_end->prec;
  else
    screen_end_level++;
  down_a_bit(1);
  write_dsp_line(screen_start, 1);
}
/*}}}  */
