/*{{{}}}*/
/*{{{  #includes*/
/* _POSIX_C_SOURCE & Solaris & xdr ... no way */
#include "xdr.h"

#define _POSIX_SOURCE   1
#define _POSIX_C_SOURCE 2

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cat.h"
#include "default.h"
#include "display.h"
#include "eval.h"
#include "misc.h"
#include "parser.h"
#include "scanner.h"
#include "sheet.h"
/*}}}  */
/*{{{  #defines*/
#define SHEET(s,x,y,z) (*(s->sheet+(x)*s->dimz*s->dimy+(y)*s->dimz+(z)))
/*}}}  */

/*{{{  variables*/
/* Used during evaluation of a cell to specify the currently updated cell */
Sheet *upd_sheet;
int upd_x;
int upd_y;
int upd_z;
/*}}}  */

/*{{{  resize        -- check if sheet needs to be resized in any dimension*/
static void resize(Sheet *sheet, int x, int y, int z)
{
  if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz)
  {
    /*{{{  variables*/
    Cell **newsheet;
    int *newcolumn;
    unsigned int ndimx,ndimy,ndimz;
    /*}}}  */
  
    sheet->changed=1;
    ndimx=(x>=sheet->dimx ? x+1 : sheet->dimx);
    ndimy=(y>=sheet->dimy ? y+1 : sheet->dimy);
    ndimz=(z>=sheet->dimz ? z+1 : sheet->dimz);
    /*{{{  allocate new sheet*/
    newsheet=malloc(ndimx*ndimy*ndimz*sizeof(Cell*));
    for (x=0; x<ndimx; ++x) for (y=0; y<ndimy; ++y) for (z=0; z<ndimz; ++z)  
    {
      if (x<sheet->dimx && y<sheet->dimy && z<sheet->dimz) *(newsheet+x*ndimz*ndimy+y*ndimz+z)=SHEET(sheet,x,y,z);
      else *(newsheet+x*ndimz*ndimy+y*ndimz+z)=(Cell*)0;
    }
    if (sheet->sheet!=(Cell**)0) free(sheet->sheet);
    sheet->sheet=newsheet;
    /*}}}  */
    /*{{{  allocate new columns*/
    if (x>sheet->dimx || z>=sheet->dimz)
    {
      newcolumn=malloc(ndimx*ndimz*sizeof(int));
      for (x=0; x<ndimx; ++x) for (z=0; z<ndimz; ++z)
      {
        if (x<sheet->dimx && z<sheet->dimz) *(newcolumn+x*ndimz+z)=*(sheet->column+x*sheet->dimz+z);
        else *(newcolumn+x*ndimz+z)=DEF_COLUMNWIDTH;
      }
      if (sheet->column!=(int*)0) free(sheet->column);
      sheet->column=newcolumn;
    }
    /*}}}  */
    sheet->dimx=ndimx;
    sheet->dimy=ndimy;
    sheet->dimz=ndimz;
  }
}
/*}}}  */
/*{{{  initcell      -- initialise new cell*/
static void initcell(Sheet *sheet, int x, int y, int z)
{
  assert(x>=0);
  assert(y>=0);
  assert(z>=0);
  resize(sheet,x,y,z);
  if (SHEET(sheet,x,y,z)==(Cell*)0)
  {
    sheet->changed=1;
    SHEET(sheet,x,y,z)=malloc(sizeof(Cell));
    SHEET(sheet,x,y,z)->contents=(Token**)0;
    SHEET(sheet,x,y,z)->label=(char*)0;
    SHEET(sheet,x,y,z)->adjust=DEF_ADJUST;
    SHEET(sheet,x,y,z)->precision=DEF_PRECISION;
    SHEET(sheet,x,y,z)->shadowed=0;
    SHEET(sheet,x,y,z)->scientific=DEF_SCIENTIFIC;
    SHEET(sheet,x,y,z)->value.type=EMPTY;
    SHEET(sheet,x,y,z)->locked=0;
  }
}
/*}}}  */
/*{{{  copycell      -- copy a cell*/
static void copycell(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2)
{
  /*{{{  variables*/
  Token **run;
  int i,len;
  /*}}}  */

  if (x1<sheet1->dimx && y1<sheet1->dimy && z1<sheet1->dimz)
  {
    sheet2->changed=1;
    if (SHEET(sheet1,x1,y1,z1)==(Cell*)0) freecell(sheet2,x2,y2,z2);
    else
    /*{{{  copy first cell to second*/
    {
      freecell(sheet2,x2,y2,z2);
      initcell(sheet2,x2,y2,z2);
      memcpy(SHEET(sheet2,x2,y2,z2),SHEET(sheet1,x1,y1,z1),sizeof(Cell));
      if (SHEET(sheet1,x1,y1,z1)->contents!=(Token**)0)
      {
        for (len=1,run=SHEET(sheet1,x1,y1,z1)->contents; *run!=(Token*)0; ++len,++run);
        SHEET(sheet2,x2,y2,z2)->contents=malloc(len*sizeof(Token*));
        for (i=0; i<len; ++i)
        {
          if (*(SHEET(sheet1,x1,y1,z1)->contents+i)==(Token*)0) *(SHEET(sheet2,x2,y2,z2)->contents+i)=(Token*)0;
          else
          {
            *(SHEET(sheet2,x2,y2,z2)->contents+i)=malloc(sizeof(Token));
            **(SHEET(sheet2,x2,y2,z2)->contents+i)=tcopy(**(SHEET(sheet1,x1,y1,z1)->contents+i));
          }
        }
      }
      if (SHEET(sheet1,x1,y1,z1)->label!=(char*)0) SHEET(sheet2,x2,y2,z2)->label=strcpy(malloc(strlen(SHEET(sheet1,x1,y1,z1)->label)+1),SHEET(sheet1,x1,y1,z1)->label);
      SHEET(sheet2,x2,y2,z2)->value.type=EMPTY;
    }
    /*}}}  */
  }
  else freecell(sheet2,x2,y2,z2);
}
/*}}}  */
/*{{{  printthis     -- should this page get printed?*/
static int printthis(const char *s, int z)
{
  int ok,p,q;

  assert(s!=(const char*)0);
  if (*s=='\0') ok=1;
  else 
  {
    ok=0;
    while (*s)
    {
      p=0; while (*s>='0' && *s<='9') p=10*p+*s++-'0';
      if (p==z) { ok=1; break; }
      if (*s=='-')
      {
        s++;
        q=0; while (*s>='0' && *s<='9') q=10*q+*s++-'0';
        if (z>=p && z<=q) { ok=1; break; }
      }
      if (*s==',') s++;
    }
  }
  return ok;
}
/*}}}  */
/*{{{  swapblock     -- swap two non-overlapping blocks of cells*/
static void swapblock(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2, int xdist, int ydist, int zdist)
{
  int xoff, yoff, zoff;

  for (xoff=0; xoff<xdist; ++xoff)
  for (yoff=0; yoff<ydist; ++yoff)
  for (zoff=0; zoff<zdist; ++zoff)
  {
    Cell *t;

    resize(sheet1,x1+xoff,y1+yoff,z1+zoff);
    resize(sheet2,x2+xoff,y2+yoff,z2+zoff);
    t=SHEET(sheet1,x1+xoff,y1+yoff,z1+zoff);
    SHEET(sheet1,x1+xoff,y1+yoff,z1+zoff)=SHEET(sheet2,x2+xoff,y2+yoff,z2+zoff);
    SHEET(sheet2,x2+xoff,y2+yoff,z2+zoff)=t;
  }
  sheet1->changed=1;
  sheet2->changed=1;
}
/*}}}  */
/*{{{  cmpcell       -- compare to cells with given order flags*/
/*{{{  Notes*/
/*
Compare the _values_ of two cells.  The result is -1 if first is smaller
than second, 0 if they are equal and 1 if the first is bigger than the
second.  A result of 2 means they are not comparable.
*/
/*}}}  */
static int cmpcell(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2, int sortkey)
{
  /*{{{  empty cells are smaller than any non-empty cell*/
  if (x1>=sheet1->dimx || y1>=sheet1->dimy || z1>=sheet1->dimz || SHEET(sheet1,x1,y1,z1)==(Cell*)0 || SHEET(sheet1,x1,y1,z1)->value.type==EMPTY)
  {
    if (x2>=sheet2->dimx || y2>=sheet2->dimy || z2>=sheet2->dimz || SHEET(sheet2,x2,y2,z2)==(Cell*)0 || SHEET(sheet2,x2,y2,z2)->value.type==EMPTY) return 0;
    else return (sortkey&ASCENDING ? -1 : 1);
  }
  if (x2>=sheet2->dimx || y2>=sheet2->dimy || z2>=sheet2->dimz || SHEET(sheet2,x2,y2,z2)==(Cell*)0 || SHEET(sheet2,x2,y2,z2)->value.type==EMPTY) return (sortkey&ASCENDING ? 1 : -1);
  /*}}}  */
  switch (SHEET(sheet1,x1,y1,z1)->value.type)
  {
    /*{{{  STRING*/
    case STRING:
    {
      if (SHEET(sheet2,x2,y2,z2)->value.type==STRING)
      {
        int r;

        r=strcmp(SHEET(sheet1,x1,y1,z1)->value.u.string,SHEET(sheet2,x2,y2,z2)->value.u.string);
        if (r<0) return (sortkey&ASCENDING ? -1 : 1);
        else if (r==0) return 0;
        else return (sortkey&ASCENDING ? 1 : -1);
      }
      return 2;
    }
    /*}}}  */
    /*{{{  FLOAT*/
    case FLOAT:
    {
      if (SHEET(sheet2,x2,y2,z2)->value.type==FLOAT)
      {
        if (SHEET(sheet1,x1,y1,z1)->value.u.flt<SHEET(sheet2,x2,y2,z2)->value.u.flt) return (sortkey&ASCENDING ? -1 : 1);
        else if (SHEET(sheet1,x1,y1,z1)->value.u.flt==SHEET(sheet2,x2,y2,z2)->value.u.flt) return 0;
        else return (sortkey&ASCENDING ? 1 : -1);
      }
      if (SHEET(sheet2,x2,y2,z2)->value.type==INT)
      {
        if (SHEET(sheet1,x1,y1,z1)->value.u.flt<SHEET(sheet2,x2,y2,z2)->value.u.integer) return (sortkey&ASCENDING ? -1 : 1);
        else if (SHEET(sheet1,x1,y1,z1)->value.u.flt==SHEET(sheet2,x2,y2,z2)->value.u.integer) return 0;
        else return (sortkey&ASCENDING ? 1 : -1);
      }
      return 2;
    }
    /*}}}  */
    /*{{{  INT*/
    case INT:
    {
      if (SHEET(sheet2,x2,y2,z2)->value.type==INT)
      {
        if (SHEET(sheet1,x1,y1,z1)->value.u.integer<SHEET(sheet2,x2,y2,z2)->value.u.integer) return (sortkey&ASCENDING ? -1 : 1);
        else if (SHEET(sheet1,x1,y1,z1)->value.u.integer==SHEET(sheet2,x2,y2,z2)->value.u.integer) return 0;
        else return (sortkey&ASCENDING ? 1 : -1);
      }
      if (SHEET(sheet2,x2,y2,z2)->value.type==FLOAT)
      {
        if (SHEET(sheet1,x1,y1,z1)->value.u.integer<SHEET(sheet2,x2,y2,z2)->value.u.flt) return (sortkey&ASCENDING ? -1 : 1);
        else if (SHEET(sheet1,x1,y1,z1)->value.u.integer==SHEET(sheet2,x2,y2,z2)->value.u.flt) return 0;
        else return (sortkey&ASCENDING ? 1 : -1);
      }
      return 2;
    }
    /*}}}  */
    default: return 2;
  }
}
/*}}}  */

/*{{{  forceupdate   -- clear all update flags*/
void forceupdate(Sheet *sheet)
{
  int i;

  for (i=0; i<sheet->dimx*sheet->dimy*sheet->dimz; ++i) if (*(sheet->sheet+i)!=(Cell*)0) (*(sheet->sheet+i))->updated=0;
}
/*}}}  */
/*{{{  freecell      -- free one cell*/
void freecell(Sheet *sheet, int x, int y, int z)
{
  if (sheet->sheet!=(Cell**)0 && x<sheet->dimx && y<sheet->dimy && z<sheet->dimz && SHEET(sheet,x,y,z)!=(Cell*)0)
  {
    tvecfree(SHEET(sheet,x,y,z)->contents);
    tfree(&(SHEET(sheet,x,y,z)->value));
    free(SHEET(sheet,x,y,z));
    SHEET(sheet,x,y,z)=(Cell*)0;      
    sheet->changed=1;
  }
}
/*}}}  */
/*{{{  columnwidth   -- get width of column*/
int columnwidth(Sheet *sheet, int x, int z)
{
  if (x<sheet->dimx && z<sheet->dimz) return (*(sheet->column+x*sheet->dimz+z));
  else return DEF_COLUMNWIDTH;
}
/*}}}  */
/*{{{  setwidth      -- set width of column*/
void setwidth(Sheet *sheet, int x, int z, int width)
{
  resize(sheet,x,1,z);
  sheet->changed=1;
  *(sheet->column+x*sheet->dimz+z)=width;
}
/*}}}  */
/*{{{  cellwidth     -- get width of a cell*/
int cellwidth(Sheet *sheet, int x, int y, int z)
{
  int width;

  if (shadowed(sheet,x,y,z)) return 0;
  width=columnwidth(sheet,x,z);
  for (++x; shadowed(sheet,x,y,z); width+=columnwidth(sheet,x,z),++x);
  return width;
}
/*}}}  */
/*{{{  putcont       -- assign new contents*/
void putcont(Sheet *sheet, int x, int y, int z, Token **t)
{
  sheet->changed=1;
  if (t!=(Token**)0)
  /*{{{  store new value*/
  {
    resize(sheet,x,y,z);
    initcell(sheet,x,y,z);
    tvecfree(SHEET(sheet,x,y,z)->contents);
    SHEET(sheet,x,y,z)->contents=t;
  }
  /*}}}  */
  else freecell(sheet,x,y,z);
  forceupdate(sheet);
}
/*}}}  */
/*{{{  getcont       -- get contents*/
Token **getcont(Sheet *sheet, int x, int y, int z)
{
  if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz) return (Token**)0;
  else if (SHEET(sheet,x,y,z)==(Cell*)0) return (Token**)0;
  else return SHEET(sheet,x,y,z)->contents;
}
/*}}}  */
/*{{{  getvalue      -- get tcopy()ed value*/
Token getvalue(Sheet *sheet, int x, int y, int z)
{
  /*{{{  variables*/
  Token result;
  /*}}}  */

  if (x<0 || y<0 || z<0)
  /*{{{  return error*/
  {
    result.type=EEK;
    result.u.err=strmalloc(NEGLOC);
  }
  /*}}}  */
  else if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz || SHEET(sheet,x,y,z)==(Cell*)0 || SHEET(sheet,x,y,z)->contents==(Token**)0)
  /*{{{  return empty value*/
  result.type=EMPTY;
  /*}}}  */
  else
  /*{{{  update value of this cell if needed and return it*/
  {
    if (SHEET(sheet,x,y,z)->updated==0)
    {
      /*{{{  variables*/
      Sheet *old_sheet;
      int old_x,old_y,old_z;
      Token oldvalue;
      /*}}}  */

      old_sheet=upd_sheet;
      old_x=upd_x;
      old_y=upd_y;
      old_z=upd_z;
      upd_sheet=sheet;
      upd_x=x;
      upd_y=y;
      upd_z=z;
      oldvalue=SHEET(sheet,x,y,z)->value;
      SHEET(sheet,x,y,z)->updated=1;
      SHEET(sheet,x,y,z)->value=eval(SHEET(sheet,x,y,z)->contents);
      tfree(&oldvalue);
      upd_sheet=old_sheet;
      upd_x=old_x;
      upd_y=old_y;
      upd_z=old_z;
    }
    result=tcopy(SHEET(sheet,x,y,z)->value);
  }
  /*}}}  */
  return result;
}
/*}}}  */
/*{{{  geterror      -- get malloc()ed error string*/
char *geterror(Sheet *sheet, int x, int y, int z)
{
  Token v;

  if ((v=getvalue(sheet,x,y,z)).type!=EEK) 
  {
    tfree(&v);
    return (char*)0;
  }
  else
  {
    return (v.u.err);
  }
}
/*}}}  */
/*{{{  printvalue    -- get ASCII representation of value*/
void printvalue(char *s, size_t size, int star, int quote, int scientific, int precision, Sheet *sheet, int x, int y, int z)
{
  Token *tv[2],t;

  t=getvalue(sheet,x,y,z); tv[0]=&t;
  tv[1]=(Token*)0;
  print(s,size,star,quote,scientific,precision,tv);
  tfree(&t);
}
/*}}}  */
/*{{{  getadjust     -- get cell adjustment*/
Adjust getadjust(Sheet *sheet, int x, int y, int z)
{
  if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz || SHEET(sheet,x,y,z)==(Cell*)0) return DEF_ADJUST;
  else return (SHEET(sheet,x,y,z)->adjust);
}  
/*}}}  */
/*{{{  setadjust     -- set cell adjustment*/
void setadjust(Sheet *sheet, int x, int y, int z, Adjust adjust)
{
  sheet->changed=1;
  resize(sheet,x,y,z);
  initcell(sheet,x,y,z);
  SHEET(sheet,x,y,z)->adjust=adjust;
}  
/*}}}  */
/*{{{  shadow        -- shadow cell by left neighbour*/
void shadow(Sheet *sheet, int x, int y, int z, int yep)
{
  sheet->changed=1;
  initcell(sheet,x,y,z);
  SHEET(sheet,x,y,z)->shadowed=yep;
}  
/*}}}  */
/*{{{  shadowed      -- is cell shadowed?*/
int shadowed(Sheet *sheet, int x, int y, int z)
{
  return (x<sheet->dimx && y<sheet->dimy && z<sheet->dimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->shadowed);
}  
/*}}}  */
/*{{{  lockcell      -- lock cell*/
void lockcell(Sheet *sheet, int x, int y, int z, int yep)
{
  sheet->changed=1;
  initcell(sheet,x,y,z);
  SHEET(sheet,x,y,z)->locked=yep;
}  
/*}}}  */
/*{{{  locked        -- is cell locked?*/
int locked(Sheet *sheet, int x, int y, int z)
{
  return (x<sheet->dimx && y<sheet->dimy && z<sheet->dimz && SHEET(sheet,x,y,z)!=(Cell*)0 && SHEET(sheet,x,y,z)->locked);
}  
/*}}}  */
/*{{{  setscientific -- cell value should be displayed in scientific notation*/
void setscientific(Sheet *sheet, int x, int y, int z, int yep)
{
  sheet->changed=1;
  resize(sheet,x,y,z);
  initcell(sheet,x,y,z);
  SHEET(sheet,x,y,z)->scientific=yep;
}  
/*}}}  */
/*{{{  getscientific -- should value be displayed in scientific notation?*/
int getscientific(Sheet *sheet, int x, int y, int z)
{
  if (x<sheet->dimx && y<sheet->dimy && z<sheet->dimz && SHEET(sheet,x,y,z)!=(Cell*)0) return SHEET(sheet,x,y,z)->scientific;
  else return DEF_SCIENTIFIC;
}  
/*}}}  */
/*{{{  setprecision  -- set cell precision*/
void setprecision(Sheet *sheet, int x, int y, int z, int precision)
{
  sheet->changed=1;
  resize(sheet,x,y,z);
  initcell(sheet,x,y,z);
  SHEET(sheet,x,y,z)->precision=precision;
}
/*}}}  */
/*{{{  getprecision  -- get cell precision*/
int getprecision(Sheet *sheet, int x, int y, int z)
{
  if (x<sheet->dimx && y<sheet->dimy && z<sheet->dimz && SHEET(sheet,x,y,z)!=(Cell*)0) return SHEET(sheet,x,y,z)->precision;
  else return DEF_PRECISION;
}  
/*}}}  */
/*{{{  getlabel      -- get cell label*/
const char *getlabel(Sheet *sheet, int x, int y, int z)
{
  if (x>=sheet->dimx || y>=sheet->dimy || z>=sheet->dimz || SHEET(sheet,x,y,z)==(Cell*)0 || SHEET(sheet,x,y,z)->label==(char*)0) return "";
  else return (SHEET(sheet,x,y,z)->label);
}  
/*}}}  */
/*{{{  setlabel      -- set cell label*/
void setlabel(Sheet *sheet, int x, int y, int z, const char *buf)
{
  sheet->changed=1;
  resize(sheet,x,y,z);
  initcell(sheet,x,y,z);
  if (SHEET(sheet,x,y,z)->label!=(char*)0) free(SHEET(sheet,x,y,z)->label);
  if (*buf!='\0') SHEET(sheet,x,y,z)->label=strcpy(malloc(strlen(buf)+1),buf);
  else SHEET(sheet,x,y,z)->label=(char*)0;
}  
/*}}}  */
/*{{{  findlabel     -- return cell location for a given label*/
/*{{{  Notes*/
/*

This function probably should be rewritten using a label cache.  I'll do
so if it becomes a reported performance problem. :)

*/
/*}}}  */
Token findlabel(Sheet *sheet, const char *label)
{
  /*{{{  variables*/
  Token result;
  int x,y,z;
  /*}}}  */

  for (x=0; x<sheet->dimx; ++x) for (y=0; y<sheet->dimy; ++y) for (z=0; z<sheet->dimz; ++z)
  {
    if (strcmp(label,getlabel(sheet,x,y,z))==0)
    {
      result.type=LOCATION;
      result.u.location[0]=x;
      result.u.location[1]=y;
      result.u.location[2]=z;
      return result;
    }
  }
  result.type=EEK;
  result.u.err=strcpy(malloc(strlen(NOLABEL)+1),NOLABEL);
  return result;
}
/*}}}  */
/*{{{  freesheet     -- free an entire spread sheet*/
void freesheet(Sheet *sheet)
{
  /*{{{  variables*/
  int x,y,z;
  /*}}}  */

  sheet->changed=0;
  for (x=0; x<sheet->dimx; ++x) for (y=0; y<sheet->dimy; ++y) for (z=0; z<sheet->dimz; ++z)
  {
    freecell(sheet,x,y,z);
  }
  for (x=0; x<sheet->dimx; ++x) for (z=0; z<sheet->dimz; ++z)
  {
    *(sheet->column+x*sheet->dimz+z)=DEF_COLUMNWIDTH;
  }
  forceupdate(sheet);
}
/*}}}  */
/*{{{  savexdr       -- save a spread sheet in XDR*/
const char *savexdr(Sheet *sheet, const char *name)
{
  /*{{{  variables*/
  FILE *fp;
  XDR xdrs;
  int x,y,z;
  /*}}}  */
  
  if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
  xdrstdio_create(&xdrs,fp,XDR_ENCODE);
  for (x=0; x<sheet->dimx; ++x) for (z=0; z<sheet->dimz; ++z)
  {
    int width;
    int u;

    width=columnwidth(sheet,x,z);
    if (width!=DEF_COLUMNWIDTH)
    {
      u=0;
      if (xdr_int(&xdrs,&u)==0 || xdr_column(&xdrs,&x,&z,&width)==0)
      {
        xdr_destroy(&xdrs);
        fclose(fp);
        return strerror(errno);
      }
    }
    for (y=0; y<sheet->dimy; ++y)
    {
      if (SHEET(sheet,x,y,z)!=(Cell*)0)
      {
        u=1;
        if (xdr_int(&xdrs,&u)==0 || xdr_int(&xdrs,&x)==0 || xdr_int(&xdrs,&y)==0 || xdr_int(&xdrs,&z)==0 || xdr_cell(&xdrs,SHEET(sheet,x,y,z))==0)
        {
          xdr_destroy(&xdrs);
          fclose(fp);
          return strerror(errno);
        }
      }
    }
  }
  xdr_destroy(&xdrs);
  if (fclose(fp)==-1) return strerror(errno);
  sheet->changed=0;
  return (const char*)0;
}
/*}}}  */
/*{{{  savetbl       -- save as tbl tyble body*/
/*

To do: quote \ and . ' in first column

*/
const char *savetbl(Sheet *sheet, const char *name, const char *pages)
{
  /*{{{  variables*/
  FILE *fp;
  int x,y,z;
  char buf[1024];
  /*}}}  */
  
  /*{{{  asserts*/
  assert(sheet!=(Sheet*)0);
  assert(name!=(const char*)0);
  assert(pages!=(const char*)0);
  /*}}}  */
  if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
  for (z=0; z<sheet->dimz; ++z)
  {
    if (printthis(pages,z))
    /*{{{  output it*/
    {
      for (y=0; y<sheet->dimy; ++y)
      {
        if (y) fputs(".T&\n",fp);
        for (x=0; x<sheet->dimx; ++x)
        {
          if (x>0) fputc(' ',fp);
          if (shadowed(sheet,x,y,z)) fputc('s',fp);
          else switch (getadjust(sheet,x,y,z))
          {
            case LEFT: fputc('l',fp); break;
            case RIGHT: fputc('r',fp); break;
            case CENTER: fputc('c',fp); break;
            default: assert(0);
          }
        }
        fputs(".\n",fp);
        for (x=0; x<sheet->dimx; ++x)
        {
        if (x>0) fputc('\t',fp);    
        if (SHEET(sheet,x,y,z)!=(Cell*)0)
        {
          printvalue(buf,sizeof(buf),0,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z);
          fputs(buf,fp);
        }
        }
        fputc('\n',fp);
      }
    }
    /*}}}  */
  }
  if (fclose(fp)==-1) return strerror(errno);
  return (const char*)0;
}
/*}}}  */
/*{{{  savetext      -- save as text*/
const char *savetext(Sheet *sheet, const char *name, const char *pages)
{
  /*{{{  variables*/
  FILE *fp;
  int x,y,z;
  /*}}}  */
  
  /*{{{  asserts*/
  assert(sheet!=(Sheet*)0);
  assert(name!=(const char*)0);
  assert(pages!=(const char*)0);
  /*}}}  */
  if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
  for (z=0; z<sheet->dimz; ++z)
  {
    if (printthis(pages,z))
    /*{{{  output it*/
    {
      for (y=0; y<sheet->dimy; ++y)   
      {
        size_t size,fill;
        
        for (x=0; x<sheet->dimx; ++x) if (SHEET(sheet,x,y,z)!=(Cell*)0)
        {
          char *buf;

          size=cellwidth(sheet,x,y,z);
          buf=malloc(size+1);
          printvalue(buf,size+1,1,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z);
          adjust(getadjust(sheet,x,y,z),buf,size+1);
          fputs(buf,fp);
          for (fill=strlen(buf); fill<size; ++fill) fputc(' ',fp);      
          free(buf);
        }
        else
        {
          for (fill=0; fill<size; ++fill) fputc(' ',fp);
        }
        fputc('\n',fp);
      }
      if (z<sheet->dimz-1) fputs("\f",fp);
    }
    /*}}}  */
  }
  if (fclose(fp)==-1) return strerror(errno);
  return (const char*)0;
}
/*}}}  */
/*{{{  saveport      -- save as portable text*/
const char *saveport(Sheet *sheet, const char *name)
{
  /*{{{  variables*/
  FILE *fp;
  int x,y,z;
  /*}}}  */

  /*{{{  asserts*/
  assert(sheet!=(Sheet*)0);
  assert(name!=(const char*)0);
  /*}}}  */
  if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
  fprintf(fp,"# This is a saved teapot work sheet.\n");
  for (z=0; z<sheet->dimz; ++z)
  {
    for (y=0; y<sheet->dimy; ++y)   
    {
      for (x=0; x<sheet->dimx; ++x) 
      {
        if (y==0) if (columnwidth(sheet,x,z)!=DEF_COLUMNWIDTH) fprintf(fp,"W%d %d %d\n",x,z,columnwidth(sheet,x,z));
        if (SHEET(sheet,x,y,z)!=(Cell*)0)
        {
          fprintf(fp,"C%d %d %d ",x,y,z);
          if (SHEET(sheet,x,y,z)->adjust!=DEF_ADJUST) fprintf(fp,"A%c ","lrc"[SHEET(sheet,x,y,z)->adjust]);
          if (SHEET(sheet,x,y,z)->label) fprintf(fp,"L%s ",SHEET(sheet,x,y,z)->label);
          if (SHEET(sheet,x,y,z)->precision!=DEF_PRECISION) fprintf(fp,"P%d ",SHEET(sheet,x,y,z)->precision);
          if (SHEET(sheet,x,y,z)->shadowed) fprintf(fp,"S ");
          if (SHEET(sheet,x,y,z)->scientific!=DEF_SCIENTIFIC) fprintf(fp,"E ");
          if (SHEET(sheet,x,y,z)->locked) fprintf(fp,"C ");
          if (SHEET(sheet,x,y,z)->contents)
          {
            char buf[4096];

            fputc(':',fp);
            print(buf,sizeof(buf),0,1,SHEET(sheet,x,y,z)->scientific,SHEET(sheet,x,y,z)->precision,SHEET(sheet,x,y,z)->contents);
            fputs(buf,fp);
          }
          fputc('\n',fp);
        }
      }
    }
  }
  if (fclose(fp)==-1) return strerror(errno);
  return (const char*)0;
}
/*}}}  */
/*{{{  savelatex     -- save as LaTeX table body*/
/*

To do: optionally quote _ $ \ ^

*/
const char *savelatex(Sheet *sheet, const char *name, const char *pages)
{
  /*{{{  variables*/
  FILE *fp;
  int x,y,z;
  char buf[1024];
  /*}}}  */
  
  /*{{{  asserts*/
  assert(sheet!=(Sheet*)0);
  assert(name!=(const char*)0);
  assert(pages!=(const char*)0);
  /*}}}  */
  if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
  fprintf(fp,"%% Generated by teapot\n");
  for (z=0; z<sheet->dimz; ++z)
  {
    if (printthis(pages,z))
    /*{{{  output it*/
    {
      fprintf(fp,"\\begin{tabular}{");
      for (x=0; x<sheet->dimx; ++x) fputc('l',fp);
      fprintf(fp,"}\n");
      for (y=0; y<sheet->dimy; ++y)
      {
        for (x=0; x<sheet->dimx; ++x)
        {
          int multicols;

          if (x>0 && x<sheet->dimx-1) fputc('&',fp);
          for (multicols=x; multicols<sheet->dimx && shadowed(sheet,multicols,y,z); ++multicols);
          multicols=multicols-x+1;
          fprintf(fp,"\\multicolumn{%d}{",multicols);
          switch (getadjust(sheet,x,y,z))
          {
            case LEFT: fputc('l',fp); break;
            case RIGHT: fputc('r',fp); break;
            case CENTER: fputc('c',fp); break;
            default: assert(0);
          }
          printvalue(buf,sizeof(buf),0,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z);
          fprintf(fp,"}{%s}",buf);
        }
        if (y<sheet->dimy-1) fprintf(fp,"\\\\\n"); else fprintf(fp,"\n");
      }
      fprintf(fp,"\\end{tabular}\n");
    }
    /*}}}  */
  }
  if (fclose(fp)==-1) return strerror(errno);
  return (const char*)0;
}
/*}}}  */
/*{{{  loadport      -- load from portable text*/
const char *loadport(Sheet *sheet, const char *name)
{
  /*{{{  variables*/
  static char errbuf[80];
  FILE *fp;
  int x,y,z;
  char buf[4096];
  int line;
  const char *ns,*os;
  const char *err;
  int precision;
  char *label;
  Adjust adjust;
  int shadowed;
  int scientific;
  int locked;
  Token **contents;
  int width;
  /*}}}  */

  if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno);
  err=(const char*)0;
  line=1;
  while (fgets(buf,sizeof(buf),fp)!=(char*)0)
  {
    /*{{{  remove nl*/
    width=strlen(buf);
    if (width>0 && buf[width-1]=='\n') buf[width-1]='\0';
    /*}}}  */
    switch (buf[0])
    {
      /*{{{  C       -- parse cell*/
      case 'C':
      {
        adjust=DEF_ADJUST;
        precision=DEF_PRECISION;
        label=(char*)0;
        contents=(Token**)0;
        shadowed=0;
        scientific=DEF_SCIENTIFIC;
        locked=0;
        /*{{{  parse x y and z*/
        os=ns=buf+1;
        x=posnumber(os,&ns);
        if (os==ns)
        {
          sprintf(errbuf,XERR,line);
          err=errbuf;
          goto eek;
        }
        while (*ns==' ') ++ns;
        os=ns;
        y=posnumber(os,&ns);
        if (os==ns)
        {
          sprintf(errbuf,YERR,line);
          err=errbuf;  
          goto eek;
        }
        while (*ns==' ') ++ns;
        os=ns;
        z=posnumber(os,&ns);
        if (os==ns)
        {
          sprintf(errbuf,ZERR,line);
          err=errbuf;    
          goto eek;
        }
        /*}}}  */
        /*{{{  parse optional attributes*/
        do
        {
          while (*ns==' ') ++ns;
          switch (*ns)
          {
            /*{{{  A       -- adjustment*/
            case 'A':
            {
              ++ns;
              switch (*ns)
              {
                case 'l': adjust=LEFT; ++ns; break;
                case 'r': adjust=RIGHT; ++ns; break;
                case 'c': adjust=CENTER; ++ns; break;
                default:  sprintf(errbuf,ADJERR,line); err=errbuf; goto eek;
              }
              break;
            }
            /*}}}  */
            /*{{{  L       -- label*/
            case 'L':
            {
              char buf[1024],*p;

              p=buf;
              ++ns;
              while (*ns && *ns!=' ') { *p=*ns; ++p; ++ns; }
              *p='\0';
              label=strmalloc(buf);
              break;
            }
            /*}}}  */
            /*{{{  P       -- precision*/
            case 'P':
            {
              os=++ns;
              precision=posnumber(os,&ns);
              if (os==ns)
              {
                sprintf(errbuf,PRECERR,line);
                err=errbuf;
                goto eek;
              }
              break;
            }
            /*}}}  */
            /*{{{  S       -- shadowed*/
            case 'S':
            {
              if (x==0)
              {
            	sprintf(errbuf,SHADOWBOO2,x,y,z,line);
            			err=errbuf;
            			goto eek;
            			}
              ++ns;
              shadowed=1;
              break;
            }
            /*}}}  */
            /*{{{  E       -- scientific*/
            case 'E':
            {
              ++ns;
              scientific=1;
              break;
            }
            /*}}}  */
            /*{{{  O       -- locked*/
            case 'O':
            {
              ++ns;
              locked=1;
              break;
            }
            /*}}}  */
            /*{{{  : \0    -- do nothing*/
            case ':':
            case '\0': break;
            /*}}}  */
            /*{{{  default -- error*/
            default: sprintf(errbuf,OPTERR,*ns,line); err=errbuf; goto eek;
            /*}}}  */
          }
        } while (*ns!=':' && *ns!='\0');
        /*}}}  */
        /*{{{  convert remaining string into token sequence*/
        if (*ns)
        {
          ++ns;
          contents=scan(&ns);
          if (contents==(Token**)0)
          {
            tvecfree(contents);
            sprintf(errbuf,EXPRERR,line);
            err=errbuf;
            goto eek;
          }
        }
        /*}}}  */
        initcell(sheet,x,y,z);
        SHEET(sheet,x,y,z)->adjust=adjust;
        SHEET(sheet,x,y,z)->label=label;
        SHEET(sheet,x,y,z)->precision=precision;
        SHEET(sheet,x,y,z)->shadowed=shadowed;
        SHEET(sheet,x,y,z)->scientific=scientific;  
        SHEET(sheet,x,y,z)->locked=locked;
        SHEET(sheet,x,y,z)->contents=contents;
        break;
      }
      /*}}}  */
      /*{{{  W       -- column width*/
      case 'W':
      {
        /*{{{  parse x and z*/
        os=ns=buf+1;
        x=posnumber(os,&ns);
        if (os==ns)
        {
          sprintf(errbuf,XERR,line);
          err=errbuf;
          goto eek;
        }
        while (*ns==' ') ++ns;
        os=ns;
        z=posnumber(os,&ns);
        if (os==ns)
        {
          sprintf(errbuf,ZERR,line);
          err=errbuf;
          goto eek;
        }
        /*}}}  */
        /*{{{  parse width*/
        while (*ns==' ') ++ns;
        os=ns;
        width=posnumber(os,&ns);
        if (os==ns)
        {
          sprintf(errbuf,WIDTHERR,line);
          err=errbuf;
          goto eek;
        }  
        /*}}}  */
        setwidth(sheet,x,z,width);
        break;
      }
      /*}}}  */
      /*{{{  #       -- comment*/
      case '#': break;
      /*}}}  */
      /*{{{  default -- error*/
      default:
      {
        sprintf(errbuf,NOTAG,buf[0],line);
        err=errbuf;
        goto eek;
      }
      /*}}}  */
    }
    ++line;
  }
  eek:
  if (fclose(fp)==-1 && err==(const char*)0) err=strerror(errno);
  sheet->changed=0;
  forceupdate(sheet);
  return err;
}
/*}}}  */
/*{{{  loadxdr       -- load a spread sheet in XDR*/
const char *loadxdr(Sheet *sheet, const char *name)
{
  /*{{{  variables*/
  FILE *fp;
  XDR xdrs;
  int x,y,z;
  int width;
  int u;
  int olderror;
  /*}}}  */

  if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno);
  xdrstdio_create(&xdrs,fp,XDR_DECODE);
  freesheet(sheet);
  while (xdr_int(&xdrs,&u)) switch (u)
  {
    /*{{{  0       -- column width element*/
    case 0:
    {
      if (xdr_column(&xdrs,&x,&z,&width)==0)
      {
        olderror=errno;
        xdr_destroy(&xdrs);
        fclose(fp); /* no use in checking this, better report the previous error */
        return strerror(olderror);
      }
      setwidth(sheet,x,z,width);
      break;
    }
    /*}}}  */
    /*{{{  1       -- cell element*/
    case 1:
    {
      if (xdr_int(&xdrs,&x)==0 || xdr_int(&xdrs,&y)==0 || xdr_int(&xdrs,&z)==0)
      {
        olderror=errno;
        xdr_destroy(&xdrs);
        fclose(fp); /* no use in checking this, better report the previous error */
        return strerror(olderror);
      }
      initcell(sheet,x,y,z);
      if (xdr_cell(&xdrs,SHEET(sheet,x,y,z))==0) 
      {
        freecell(sheet,x,y,z);
        olderror=errno;
        xdr_destroy(&xdrs);
        fclose(fp); /* no use in checking this, better report the previous error */
        return strerror(olderror);
      }
      break;
    }
    /*}}}  */
    /*{{{  default -- should not happen*/
    default: assert(0);
    /*}}}  */
  }
  xdr_destroy(&xdrs);
  if (fclose(fp)==-1) return strerror(errno);
  sheet->changed=0;
  forceupdate(sheet);
  return (const char*)0;
}
/*}}}  */
/*{{{  insertcube    -- insert a block*/
void insertcube(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction ins)
{
  /*{{{  variables*/
  int x,y,z;
  /*}}}  */

  switch (ins)
  {
    /*{{{  IN_X    */
    case IN_X:
    {
      for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) for (x=sheet->dimx+x2-x1; x>x2; --x)
      {
        resize(sheet,x,y,z);
        SHEET(sheet,x,y,z)=SHEET(sheet,x-(x2-x1+1),y,z);
        SHEET(sheet,x-(x2-x1+1),y,z)=(Cell*)0;
      }
      break;
    }
    /*}}}  */
    /*{{{  IN_Y    */
    case IN_Y:
    {
      for (z=z1; z<=z2; ++z) for (x=x1; x<=x2; ++x) for (y=sheet->dimy+y2-y1; y>y2; --y)
      {
        resize(sheet,x,y,z);
        SHEET(sheet,x,y,z)=SHEET(sheet,x,y-(y2-y1+1),z);
        SHEET(sheet,x,y-(y2-y1+1),z)=(Cell*)0;
      }
      break;
    }
    /*}}}  */
    /*{{{  IN_Z*/
    case IN_Z:
    {
      for (y=y1; y<=y2; ++y) for (x=x1; x<=x2; ++x) for (z=sheet->dimz+z2-z1; z>z2; --z)
      {
        resize(sheet,x,y,z);
        SHEET(sheet,x,y,z)=SHEET(sheet,x,y,z-(z2-z1+1));
        SHEET(sheet,x,y,z-(z2-z1+1))=(Cell*)0;
      }
      break;
    }
    /*}}}  */
    /*{{{  default*/
    default: assert(0);
    /*}}}  */
  }
  sheet->changed=1;
  forceupdate(sheet);
}
/*}}}  */
/*{{{  deletecube    -- delete a block*/
void deletecube(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction del)
{
  /*{{{  variables*/
  int x,y,z;
  /*}}}  */

  /*{{{  free cells in marked block*/
  for (x=x1; x<=x2; ++x)
  for (y=y1; y<=y2; ++y)
  for (z=z1; z<=z2; ++z)
  freecell(sheet,x,y,z);
  /*}}}  */
  switch (del)
  {
    /*{{{  IN_X*/
    case IN_X:
    {
      for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) for (x=x1; x<=sheet->dimx-(x2-x1+1); ++x)
      {
        if (x+(x2-x1+1)<sheet->dimx && y<sheet->dimy && z<sheet->dimz)
        {
        SHEET(sheet,x,y,z)=SHEET(sheet,x+(x2-x1+1),y,z);
        SHEET(sheet,x+(x2-x1+1),y,z)=(Cell*)0;
        }
      }
      break;
    }
    /*}}}  */
    /*{{{  IN_Y*/
    case IN_Y:
    {
      for (z=z1; z<=z2; ++z) for (x=x1; x<=x2; ++x) for (y=y1; y<=sheet->dimy-(y2-y1+1); ++y)
      {
        if (x<sheet->dimx && y+(y2-y1+1)<sheet->dimy && z<sheet->dimz)
        {
          SHEET(sheet,x,y,z)=SHEET(sheet,x,y+(y2-y1+1),z);
          SHEET(sheet,x,y+(y2-y1+1),z)=(Cell*)0;
        }
      }
      break;
    }
    /*}}}  */
    /*{{{  IN_Z*/
    case IN_Z:
    {
      for (y=y1; y<=y2; ++y) for (x=x1; x<=x2; ++x) for (z=z1; z<=sheet->dimz-(z2-z1+1); ++z)
      {
        if (x<sheet->dimx && y<sheet->dimy && z+(z2-z1+1)<sheet->dimz)
        {
        SHEET(sheet,x,y,z)=SHEET(sheet,x,y,z+(z2-z1+1));
        SHEET(sheet,x,y,z+(z2-z1+1))=(Cell*)0;
        }
      }
      break;
    }
    /*}}}  */
    /*{{{  default*/
    default: assert(0);
    /*}}}  */
  }
  sheet->changed=1;
  forceupdate(sheet);
}
/*}}}  */
/*{{{  moveblock     -- move a block*/
void moveblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3, int copy)
{
  /*{{{  variables*/
  int dirx, diry, dirz;
  int widx, widy, widz;
  int x, y, z;
  int xf, xt, yf, yt, zf, zt;
  /*}}}  */

  if (x1==x3 && y1==y3 && z1==z3) return;
  widx=(x2-x1);
  widy=(y2-y1);
  widz=(z2-z1);
  if (x3>x1) { dirx=-1; xf=widx; xt=-1; } else { dirx=1; xf=0; xt=widx+1; }
  if (y3>y1) { diry=-1; yf=widy; yt=-1; } else { diry=1; yf=0; yt=widy+1; }
  if (z3>z1) { dirz=-1; zf=widz; zt=-1; } else { dirz=1; zf=0; zt=widz+1; }
  for (x=xf; x!=xt; x+=dirx)
  for (y=yf; y!=yt; y+=diry)
  for (z=zf; z!=zt; z+=dirz)
  {
    if (copy)
    {
      copycell(sheet,x1+x,y1+y,z1+z,sheet,x3+x,y3+y,z3+z);
    }
    else
    {
      if (x1+x<sheet->dimx && y1+y<sheet->dimy && z1+z<sheet->dimz)
      {
        resize(sheet,x3+x,y3+y,z3+z);
        SHEET(sheet,x3+x,y3+y,z3+z)=SHEET(sheet,x1+x,y1+y,z1+z);
        SHEET(sheet,x1+x,y1+y,z1+z)=(Cell*)0;
      }
      else
      {
        freecell(sheet,x3+x,y3+y,z3+z);
      }
    }
  }
  sheet->changed=1;
  forceupdate(sheet);
}
/*}}}  */
/*{{{  sortblock     -- sort a block*/
/*{{{  Notes*/
/*
The idea is to sort a block of cells in one direction by swapping the
planes which are canonical to the sort key vectors.  An example is to
sort a two dimensional block line-wise with one column as sort key.
You can have multiple sort keys which all have the same direction and
you can sort a cube plane-wise.
*/
/*}}}  */
const char *sortblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction dir, Sortkey *sk, size_t sklen)
{
  /*{{{  variables*/
  int x,y,z;
  int incx,incy,incz;
  int distx,disty,distz;
  int i,r,norel,work;
  /*}}}  */

  norel=0;
  order(&x1,&x2);
  order(&y1,&y2);
  order(&z1,&z2);
  distx=(x2-x1+1);
  disty=(y2-y1+1);
  distz=(z2-z1+1);
  switch (dir)
  {
    case IN_X: incx=1; --x2; incy=0; incz=0; distx=1; break;
    case IN_Y: incx=0; incy=1; --y2; incz=0; disty=1; break;
    case IN_Z: incx=0; incy=0; incz=1; --z2; distz=1; break;
  }
  do
  {
    work=0;
    for (x=x1,y=y1,z=z1; x<=x2&&y<=y2&&z<=z2; x+=incx,y+=incy,z+=incz)
    {
      for (i=0; i<sklen; ++i)
      {
        r=cmpcell(sheet,x+sk[i].x,y+sk[i].y,z+sk[i].z,sheet,x+incx+sk[i].x,y+incy+sk[i].y,z+incz+sk[i].z,sk[i].sortkey);
        if (r==2) norel=1;
        if (r==-1 || r==1) break;
      }
      if (r==1)
      {
        swapblock(sheet,dir==IN_X ? x : x1,dir==IN_Y ? y : y1,dir==IN_Z ? z : z1,sheet,dir==IN_X ? x+incx : x1,dir==IN_Y ? y+incy : y1,dir==IN_Z ? z+incz : z1,distx,disty,distz);
        work=1;
      }
    }
    x2-=incx;
    y2-=incy;
    z2-=incz;
  } while (work);
  forceupdate(sheet);
  if (norel) return UNCOMPAR;
  else return (const char*)0;
}
/*}}}  */
