/*{{{}}}*/
/*{{{  #includes*/
#include <assert.h>
#include <ctype.h>
#include <curses.h>
#include <stdlib.h>
#include <string.h>

#include "cat.h"
#include "default.h"
#include "display.h"
#include "eval.h"
#include "main.h"
#include "misc.h"
#include "sheet.h"
#include "wgetc.h"
/*}}}  */

/*{{{  adjust       -- readjust a left adjusted string in a buffer*/
void adjust(Adjust a, char *s, size_t n)
{
  assert(s!=(char*)0);
  assert(strlen(s)<n);
  switch (a)
  {
    /*{{{  LEFT*/
    case LEFT: break;
    /*}}}  */
    /*{{{  RIGHT*/
    case RIGHT: 
    {
      size_t len;

      len=strlen(s)+1;
      if (len!=n)
      {
        memmove(s+n-len,s,len);
        memset(s,' ',n-len);
      }
      break;
    }
    /*}}}  */
    /*{{{  CENTER*/
    case CENTER: 
    {
      size_t len,pad;

      len=strlen(s);
      pad=((n-1)-len)/2;
      assert((pad+len)<n);
      memmove(s+pad,s,len);
      memset(s,' ',pad);
      memset(s+pad+len,' ',n-pad-len-1);
      *(s+n-1)='\0';
      break;
    }  
    /*}}}  */
    /*{{{  default*/
    default: assert(0);
    /*}}}  */
  }
}
/*}}}  */
/*{{{  redraw       -- redraw whole screen*/
void redraw(void)
{
  touchwin(curscr);
  wrefresh(curscr);
}
/*}}}  */
/*{{{  redraw_sheet -- draw a sheet with cell cursor*/
void redraw_sheet(Sheet *sheet)
{
  /*{{{  variables*/
  size_t mx;
  int my,width,col,x,y,again;
  char pbuf[80];
  char *buf;
  const char *label;
  char *err;
  /*}}}  */
  
  /*{{{  asserts*/
  assert(sheet!=(Sheet*)0);
  assert(sheet->curx>=0);
  assert(sheet->cury>=0);
  assert(sheet->curz>=0);
  /*}}}  */
  wattron(sheet->window,DEF_NUMBER);
  getmaxyx(sheet->window,my,mx);
  /*{{{  correct offsets to keep cursor visible*/
  while (shadowed(sheet,sheet->curx,sheet->cury,sheet->curz))
  {
    --(sheet->curx);
    assert(sheet->curx>=0);
  }
  if (sheet->cury-sheet->offy>(my-3)) sheet->offy=sheet->cury-my+3;
  if (sheet->cury<sheet->offy) sheet->offy=sheet->cury;
  if (sheet->curx<sheet->offx) sheet->offx=sheet->curx;
  do
  {
    again=0;
    for (width=4,x=sheet->offx,col=0; width<=mx; width+=columnwidth(sheet,x,sheet->curz),++x,++col);
    --col;
    if (sheet->curx!=sheet->offx)
    {
      if (col==0) { ++sheet->offx; again=1; }
      else if (sheet->curx-sheet->offx>=col) { ++sheet->offx; again=1; }
    }
  } while (again);
  /*}}}  */
  /*{{{  draw x numbers*/
  for (width=4; width<mx; ++width) mvwaddch(sheet->window,0,width,' ');
  for (width=4,x=sheet->offx; width<mx; width+=col,++x)
  {
    char *buf;

    col=columnwidth(sheet,x,sheet->curz);
    buf=malloc((size_t)((col+1)<10 ? 10 : col+1));
    sprintf(buf,"%d",x);
    adjust(CENTER,buf,(size_t)col);
    assert(mx>=width);
    if ((mx-width)<col) buf[mx-width]='\0';
    mvwaddstr(sheet->window,0,width,buf);
    free(buf);
  }
  /*}}}  */
  /*{{{  draw y numbers*/
  for (y=1; y<(my-1); ++y) mvwprintw(sheet->window,y,0,"%-4d",y-1+sheet->offy);
  /*}}}  */
  /*{{{  draw z number*/
  mvwprintw(sheet->window,0,0,"%-4d",sheet->curz);
  /*}}}  */
  wattroff(sheet->window,DEF_NUMBER);
  /*{{{  draw elements*/
  for (y=1; y<my-1; ++y) for (width=4,x=sheet->offx; width<mx; width+=columnwidth(sheet,x,sheet->curz),++x)
  {
    size_t size,realsize,fill;

    if ((size=cellwidth(sheet,x,y-1+sheet->offy,sheet->curz)))
    {
      int invert;
      
      buf=malloc(size+1);
      printvalue(buf,size+1,1,1,getscientific(sheet,x,y-1+sheet->offy,sheet->curz),getprecision(sheet,x,y-1+sheet->offy,sheet->curz),sheet,x,y-1+sheet->offy,sheet->curz);
      adjust(getadjust(sheet,x,y-1+sheet->offy,sheet->curz),buf,size+1);
      if (width+size>=mx)
      {
        assert((mx-width)<(size+1));
        *(buf+mx-width)='\0';
        realsize=mx-width;
      }
      else realsize=size;
      invert=
      (
      (sheet->mark1x!=-1) &&
      ((x>=sheet->mark1x && x<=sheet->mark2x) || (x>=sheet->mark2x && x<=sheet->mark1x)) &&
      ((y-1+sheet->offy>=sheet->mark1y && y-1+sheet->offy<=sheet->mark2y) || (y-1+sheet->offy>=sheet->mark2y && y-1+sheet->offy<=sheet->mark1y)) &&
      ((sheet->curz>=sheet->mark1z && sheet->curz<=sheet->mark2z) || (sheet->curz>=sheet->mark2z && sheet->curz<=sheet->mark1z))
      );
      if (x==sheet->curx && (y-1+sheet->offy)==sheet->cury) invert=(sheet->marking ? 1 : 1-invert);
      if (invert) wattron(sheet->window,DEF_CELLCURSOR);
      mvwaddstr(sheet->window,y,width,buf);
      for (fill=strlen(buf); fill<realsize; ++fill) waddch(sheet->window,' ');
      if (invert) wattroff(sheet->window,DEF_CELLCURSOR);
      free(buf);
    }
  }
  /*}}}  */
  /*{{{  draw contents of current element*/
  buf=malloc(mx+1);
  label=getlabel(sheet,sheet->curx,sheet->cury,sheet->curz);
  assert(label!=(const char*)0);
  if (*label=='\0') sprintf(pbuf,"@(%d,%d,%d)=",sheet->curx,sheet->cury,sheet->curz);
  else sprintf(pbuf,"@(%s)=",label);
  strncpy(buf,pbuf,mx+1);
  buf[mx]='\0';
  if ((err=geterror(sheet,sheet->curx,sheet->cury,sheet->curz))!=(const char*)0)
  {
    strncpy(buf,err,width-strlen(buf));
    free(err);
    buf[mx]='\0';
  }
  else print(buf+strlen(buf),mx+1-strlen(buf),0,1,getscientific(sheet,sheet->curx,sheet->cury,sheet->curz),-1,getcont(sheet,sheet->curx,sheet->cury,sheet->curz));
  mvwaddstr(sheet->window,my-1,0,buf);
  if (strlen(buf)<mx) wclrtoeol(sheet->window);
  free(buf);
  /*}}}  */
  wrefresh(sheet->window);
}
/*}}}  */
/*{{{  line_edit    -- line editor function*/
int line_edit(Sheet *sheet, char *buf, size_t size, WINDOW *w, const char *prompt, size_t *x, size_t *offx)
{
  /*{{{  variables*/
  size_t promptlen;
  int i,mx,my,insert;
  int in_editor;
  chtype c;
  /*}}}  */

  /*{{{  asserts*/
  assert(w!=(WINDOW*)0);
  assert(buf!=(char*)0);
  assert(prompt!=(char*)0);  
  assert(x!=(size_t*)0);  
  assert(offx!=(size_t*)0);  
  /*}}}  */
  curs_set(1);
  getmaxyx(w,my,mx);
  promptlen=strlen(prompt)+1;
  mvwaddstr(w,0,0,prompt); waddch(w,' ');
  insert=1;
  in_editor=1;
  do
  {
    /*{{{  correct offx to cursor stays visible*/
    if (*x<*offx) *offx=*x;
    if ((*x-*offx)>(mx-promptlen-1)) *offx=*x-mx+promptlen+1;
    /*}}}  */
    /*{{{  display buffer*/
    wmove(w,0,(int)promptlen);
    for (i=promptlen; buf[i-promptlen+*offx]!='\0' && i<mx; ++i) waddch(w,(chtype)(buf[i-promptlen+*offx]));
    if (i!=mx) wclrtoeol(w);
    /*}}}  */
    /*{{{  show cursor*/
    wmove(w,0,(int)(*x-*offx+promptlen));
    /*}}}  */
    wrefresh(w);
    c=wgetc(w);
    if (in_editor) switch (c)
    /*{{{  edit line*/
    {
      /*{{{  LEFT*/
      case KEY_LEFT: if (*x>0) --(*x); break;
      /*}}}  */
      /*{{{  RIGHT*/
      case KEY_RIGHT: if (*x<strlen(buf)) ++(*x); break;
      /*}}}  */
      /*{{{  BACKSPACE      */
      case KEY_BACKSPACE:
      {
        if (*x>0)
        {
          memmove(buf+*x-1,buf+*x,strlen(buf+*x)+1);
          --(*x);
        }
        break;
      }
      /*}}}  */
      /*{{{  DC*/
      case KEY_DC:
      {
        if (*x<strlen(buf)) memmove(buf+*x,buf+*x+1,strlen(buf+*x));
        break;
      }
      /*}}}  */
      /*{{{  BEG*/
      case KEY_BEG:
      {
        *x=0;
        break;
      }  
      /*}}}  */
      /*{{{  END*/
      case KEY_END:
      {
        *x=strlen(buf);
        break;
      }  
      /*}}}  */
      /*{{{  IC*/
      case KEY_IC:
      {
        insert=1-insert;
        break;
      }  
      /*}}}  */
      /*{{{  EIC*/
      case KEY_EIC:
      {
        insert=0;
        break;
      }
      /*}}}  */
      /*{{{  control t*/
      case '\024':
      {
        if (*x>0 && (strlen(buf)<(size-1) || *x!=strlen(buf)))
        {
          char c;

          c=*(buf+*x);
          *(buf+*x)=*(buf+*x-1);
          if (c=='\0')
          {
            c=' ';
            *(buf+*x+1)='\0';
          }
          *(buf+*x-1)=c;
          ++(*x);
        }
        break;
      }
      /*}}}  */
      /*{{{  control backslash*/
      case '\034':
      {
        int curx,level;

        curx=*x;
        level=1;
        switch (buf[curx])
        {
          /*{{{  (    */
          case '(':
          {
            if (buf[curx]) ++curx;
            while (buf[curx]!='\0' && level>0) 
            {
              if (buf[curx]==')') --level;
              else if (buf[curx]=='(') ++level;
              if (level) ++curx;
            }
            if (level==0) *x=curx;
            break;
          }
          /*}}}  */
          /*{{{  )*/
          case ')':
          {
            if (curx>0) --curx;
            while (curx>0 && level>0) 
            {
              if (buf[curx]==')') ++level;
              else if (buf[curx]=='(') --level;
              if (level) --curx;
            }
            if (level==0) *x=curx;
            break;
          }
          /*}}}  */
        }   
        break;
      }
      /*}}}  */
      /*{{{  DL*/
      case KEY_DL:
      {
        *(buf+*x)='\0';
        break;
      }
      /*}}}  */
      /*{{{  control o*/
      case '\017': if (sheet!=(Sheet*)0) in_editor=0; break;
      /*}}}  */
      /*{{{  default*/
      default:
      {
        if (c<' ' || c>=256 || strlen(buf)==(size-1)) break;
        if (insert) memmove(buf+*x+1,buf+*x,strlen(buf)-*x+1);
        else if (*x==strlen(buf)) *(buf+*x+1)='\0';
        *(buf+*x)=c;
        ++(*x);
        break;
      }
      /*}}}  */
    }
    /*}}}  */
    else switch (c)
    /*{{{  move around in sheet*/
    {
      /*{{{  ^o -- switch back to line editor*/
      case '\017': in_editor=1; break;
      /*}}}  */
      /*{{{     -- move around in sheet*/
      case KEY_UP:
      case KEY_DOWN:
      case KEY_RIGHT:
      case KEY_LEFT:
      case '<':
      case '>':
      case KEY_BEG:
      case KEY_END:
      case '+':
      case '-':
      case '*':
      case '_': do_sheetcmd(sheet,c); redraw_sheet(sheet); break;
      /*}}}  */
      /*{{{   v -- insert value of current cell*/
      case 'v':
      {
        /*{{{  variables*/
        char valbuf[1024];
        /*}}}  */

        printvalue(valbuf,sizeof(valbuf),0,1,getscientific(sheet,sheet->curx,sheet->cury,sheet->curz),-1,sheet,sheet->curx,sheet->cury,sheet->curz);
        if (strlen(buf)==(size-strlen(valbuf))) break;
        memmove(buf+*x+strlen(valbuf),buf+*x,strlen(buf)-*x+strlen(valbuf));
        memcpy(buf+*x,valbuf,strlen(valbuf));
        (*x)+=strlen(valbuf);
        break;
      }
      /*}}}  */
      /*{{{   p -- insert position of current cell  */
      case 'p':
      {
        /*{{{  variables*/
        char valbuf[1024];
        /*}}}  */

        sprintf(valbuf,"(%d,%d,%d)",sheet->curx,sheet->cury,sheet->curz);
        if (strlen(buf)==(size-strlen(valbuf))) break;
        memmove(buf+*x+strlen(valbuf),buf+*x,strlen(buf)-*x+strlen(valbuf));
        memcpy(buf+*x,valbuf,strlen(valbuf));
        (*x)+=strlen(valbuf);
        break;
      }
      /*}}}  */
    }
    /*}}}  */
  } while (c!=KEY_ENTER && c!=KEY_CANCEL);
  curs_set(0);
  wmove(w,0,0);
  wclrtoeol(w);
  wrefresh(w);
  return (c==KEY_CANCEL ? -1 : 0);
}
/*}}}  */
/*{{{  line_numedit -- number line editor function*/
int line_numedit(int *n, WINDOW *w, const char *prompt, size_t *x, size_t *offx)
{
  /*{{{  variables*/
  char buf[20];
  const char *s;
  Token **t;
  /*}}}  */
            
  /*{{{  asserts*/
  assert(w!=(WINDOW*)0);
  assert(prompt!=(char*)0);  
  assert(x!=(size_t*)0);  
  assert(offx!=(size_t*)0);  
  /*}}}  */
  t=(Token**)0;
  sprintf(buf,"%d",*n);  
  s=buf+strlen(buf);
  do
  {
    tvecfree(t);
    *x=s-buf;
    if (line_edit((Sheet*)0,buf,sizeof(buf),w,prompt,x,offx)==-1) return -1;
    s=buf;
    t=scan(&s);
  } while ((*s!='\0' && t==(Token**)0) || !(t!=(Token**)0 && ((*t)==(Token*)0 || ((*t)->type==INT && (*t)->u.integer>=0 && *(t+1)==(Token*)0))));
  *n=(*t)->u.integer;
  tvecfree(t);
  return 0;
}
/*}}}  */
/*{{{  line_idedit  -- identifier line editor function*/
int line_idedit(char *ident, size_t size, WINDOW *w, const char *prompt, size_t *x, size_t *offx)
{
  /*{{{  variables*/
  const char *s;
  Token **t;
  /*}}}  */
            
  t=(Token**)0;
  s=ident+strlen(ident);
  do
  {
    tvecfree(t);
    *x=s-ident;
    if (line_edit((Sheet*)0,ident,255,w,prompt,x,offx)==-1) return -1;
    s=ident;
    t=scan(&s);
  } while ((*s!='\0' && t==(Token**)0) || !(t!=(Token**)0 && ((*t)==(Token*)0 || ((*t)->type==IDENT && *(t+1)==(Token*)0))));
  tvecfree(t);
  return 0;
}
/*}}}  */
/*{{{  line_menu    -- one line menu*/
/*{{{  Notes*/
/*

The choices are terminated by the last element having a (const char*)str
field.  Each item can be chosen by tolower(*str) or by the key stored in
the c field.  Use a space as first character of str, if you only want the
function key to work.

*/
/*}}}  */
int line_menu(WINDOW *w, const char *prompt, const MenuChoice *choice, int curx)
{
  /*{{{  variables*/
  size_t maxx,x,offx,width,mx,my;
  chtype c;
  size_t promptlen;
  /*}}}  */

  /*{{{  asserts*/
  assert(w!=(WINDOW*)0);
  assert(prompt!=(const char*)0);
  assert(choice!=(const MenuChoice*)0);
  assert(curx>=0);
  /*}}}  */
  getmaxyx(w,my,mx);
  mvwaddstr(w,0,0,prompt);
  promptlen=strlen(prompt);
  for (maxx=0; (choice+maxx)->str!=(const char*)0; ++maxx);
  offx=0;
  do
  {
    wmove(w,0,(int)promptlen);
    /*{{{  correct offset so choice is visible*/
    if (curx<=offx) offx=curx;
    else do
    {
      for (width=promptlen,x=offx; x<maxx && width+strlen((choice+x)->str+1)+1<=mx; width+=strlen((choice+x)->str)+1,++x);
      --x;
      if (x<curx) ++offx;
    } while (x<curx);
    /*}}}  */
    /*{{{  show visible choices*/
    for (width=promptlen,x=offx; x<maxx && width+strlen((choice+x)->str+1)+1<=mx; width+=strlen((choice+x)->str+1)+1,++x)
    {
      waddch(w,' ');
      if (x==curx) wattron(w,DEF_MENU);
      waddstr(w,(char*)(choice+x)->str+1);
      if (x==curx) wattroff(w,DEF_MENU);
    }
    /*}}}  */
    if (width<mx) wclrtoeol(w);    
    wrefresh(w);
    switch (c=wgetc(w))
    {
      /*{{{  KEY_LEFT              -- move to previous item*/
      case KEY_BACKSPACE:
      case KEY_LEFT: if (curx>0) --curx; else curx=maxx-1; break;
      /*}}}  */
      /*{{{  Space, Tab, KEY_RIGHT -- move to next item*/
      case ' ':
      case '\t':
      case KEY_RIGHT: if (curx<(maxx-1)) ++curx; else curx=0; break;
      /*}}}  */
      /*{{{  default               -- search choice keys*/
      default:
      {
        int i;

        for (i=0; (choice+i)->str!=(const char*)0; ++i)
        {
          if (c==tolower(*((choice+i)->str)) || (choice+i)->c==c)
          {
            c=KEY_ENTER;
            curx=i;
          }
        }
      }
      /*}}}  */
    }
  }
  while (c!=KEY_ENTER && c!=KEY_CANCEL);
  wmove(w,0,0);
  wclrtoeol(w);
  wrefresh(w);
  return (c==KEY_CANCEL ? -1 : curx);
}
/*}}}  */
/*{{{  line_ok      -- one line yes/no menu*/
int line_ok(WINDOW *w, const char *prompt, int curx)
{
  /*{{{  variables*/
  MenuChoice menu[3];
  int result;
  /*}}}  */

  /*{{{  asserts*/
  assert(w!=(WINDOW*)0);
  assert(curx==0 || curx==1);
  /*}}}  */
  menu[0].str=strmalloc(NO); menu[0].c='\0';
  menu[1].str=strmalloc(YES); menu[1].c='\0';
  menu[2].str=(char*)0;
  result=line_menu(w,prompt,menu,curx);
  free(menu[0].str);
  free(menu[1].str);
  return result;
}
/*}}}  */
/*{{{  line_msg     -- one line message which will be cleared by someone else*/
void line_msg(WINDOW *w, const char *prompt, const char *msg)
{
  /*{{{  variables*/
  int mx,my,width;
  /*}}}  */

  /*{{{  asserts*/
  assert(w!=(WINDOW*)0);
  assert(msg!=(const char*)0);
  /*}}}  */
  getmaxyx(w,my,mx);
  width=1;
  mvwaddch(w,0,0,'[');
  if (prompt!=(const char*)0)
  {
    for (; width<mx && *prompt!='\0'; ++width,++prompt) waddch(w,(chtype)(*prompt));
    if (width<mx) { waddch(w,' '); ++width; }
  }
  for (; width<mx && *msg!='\0'; ++width,++msg) waddch(w,(chtype)(*msg));
  if (width<mx) waddch(w,']');
  if (width+1<mx) wclrtoeol(w);
  wrefresh(w);
}
/*}}}  */
