/* $Id: text-table.cc,v 1.14 1997/04/13 04:26:42 dps Exp $ */
/* Ascii table layout */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#ifdef __GNUC__
#define alloca __builtin_alloca
#else
#if HAVE_ALLOCA_H
#include <alloca.h>
#else /* Do not have alloca.h */
#ifdef _AIX
#pragma alloca
#else /* not _AIX */
extern "C" char *alloca(int);
#endif /* _AIX */
#endif /* HAVE_ALLOCA_H */
#endif /* __GNUC__ */

#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif /* HAVE_STRINGS_H */
#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif /* HAVE_CTYPE_H */
#include "tblock.h"
#include "text-table.h"
#include "lib.h"

struct rdata
{
   struct wd_info w;
   const char *data;
};


/* Print out a number of spaces */
static inline void p_sp(int n, FILE *out)
{
   int i;

   for (i=0; i<n; i++)
       fputc(' ', out);
}

/* Print row after folding of columns has been done */
static void basic_print_row(int ncols,
                           const struct rdata *cols,
                           char sep, FILE *out)
{
   const char **lptr, *s, *t;
   int i, flg, nsp;
   align_t al;
   num_info n;

   /* Alloca line position pointers */
   if ((lptr=(const char **) alloca(ncols*sizeof(const char *)))==NULL)
   {
       fprintf(stderr, "basic_print_row: fatal alloca failure\n");
       exit(1);
   }

   for (i=0; i<ncols; i++)
       lptr[i]=cols[i].data;

   while (1)
   {
       /* Check for some content */
       flg=0;
       for (i=0; i<ncols; i++)
           if (*lptr[i]!='\0')
               flg=1;
       if (!flg) break;

       /* Print the content */
       for (i=0; i<ncols; i++)
       {
           s=lptr[i];

           /* Find the extent and decimal point pos of current item */
           for (t=s, nsp=cols[i].w.width; *t!='\0'; nsp--, t++)
           {
               if (*t=='\n')
                   break;
           }

           if (s==t)
           {
               p_sp(nsp, out);
           }
           else
           {
               /* There is some content, print it */
               al=cols[i].w.align;
               if (cols[i].w.dp_col>=0)
               {
                   n=scan_num(s);
                   if (n.dot_pos!=-1)
                   {
                       p_sp(cols[i].w.dp_col-n.dot_pos, out);
                       fwrite((void *) s, sizeof(char), t-s, out);
                       p_sp(nsp-cols[i].w.dp_col+n.dot_pos, out);
                       al=ALIGN_DP;
                   }
               }

               switch(al)
               {
               case ALIGN_DP:
                   break;

               case ALIGN_LEFT:
                   fwrite((void *) s, sizeof(char), t-s, out);
                   p_sp(nsp, out);
                   break;

               case ALIGN_CENTER:
                   p_sp(nsp/2, out);
                   fwrite((void *) s, sizeof(char), t-s, out);
                   p_sp(nsp-(nsp/2), out); // Avoid rounding related problems
                   break;

               case ALIGN_RIGHT:
                   p_sp(nsp, out);
                   fwrite((void *) s, sizeof(char), t-s, out);
                   break;

               default:
                   fprintf(stderr,"basic_print_row: Invalid alignment\n");
                   break;
               }
           }
           fputc(sep, out);    // Seperate columns by a space

           /* Update lptr[i] */
           if (*t=='\n')
               t++;            // Advance past newlines;
           lptr[i]=t;
       }
       fputc('\n', out);       // Print out a newline
   }
}

/* Split the elements of a row */
static void print_row(int ncols, const struct rdata *dp, FILE *out)
{
   static const char null_string[]={'\0'};
   struct rdata *rd;
   tblock *d;
   char *s, *t;
   int i;

   /* Allocate structures */
   if ((rd=(struct rdata *) alloca(ncols*sizeof(rdata)))==NULL)
   {
       fprintf(stderr, "print_row: fatal alloca failure\n");
       exit(1);
   }

   /* Fold lines */
   for (i=0; i<ncols; i++)
   {
       rd[i].w=dp[i].w;
       if (dp[i].data==NULL)
       {
           rd[i].data=null_string; // Avoid complication of null in
                                   // basic_print_row
           continue;               // Move on to next item
       }

       d=word_wrap(dp[i].data, "\n", "\n", dp[i].w.width, 0);
       if ((rd[i].data=strdup(*d))==NULL)
       {
           fprintf(stderr, "text_table::print_row: fatal alloca failure\n");
           exit(1);
       }
       else
       {
           /* Filter out CH_SUSPECT */
           for(s=t=(char *) rd[i].data; *s!='\0'; s++)
           {
               if (*s==CH_SUSPECT)
                   continue;
               *(t++)=*s;
           }
           *t='\0';
       }
       delete(d);
   }
   basic_print_row(ncols, rd, ' ', out); // Printing the rows is actually
                                         // quite complex.
   for (i=0; i<ncols; i++)
   {
       if (rd[i].data!=null_string)
           free((void *) rd[i].data);  // Free data
   }
}


/* O(n^2) table width reduction algorithm, n is small in almost all cases */
static void shrink_widths(int ncols, struct rdata *cols, int mtw)
{
   int i, j, tw, maxw;

   for (tw=0, i=0; i<ncols; i++)
       tw+=cols[i].w.width;

   mtw-=ncols;                 // Take account of column seperators

   /* Simply reduce the maximum width column width by one until
      enougn has been trimed */
   while (tw>mtw)
   {
       maxw=0; j=-1;
       for (i=0; i<ncols; i++)
       {
           if (maxw<cols[i].w.width && cols[i].w.align!=ALIGN_DP)
           {
               j=i;
               maxw=cols[i].w.width;
           }
       }

       if (j==-1)
       {
           fprintf(stderr, "Can not shrink overwidth table\n");
           continue;
       }
       cols[j].w.width--;
       tw--;
   }
}

/* Returns NULL or text message */
const char *text_table::print_table(int wd, FILE *out)
{
   int i,j;
   struct rdata *d;
   const struct col_info *col;

   if ((d=(struct rdata *) alloca(cols*sizeof(struct rdata)))==NULL)
   {
       cerr<<"text_table::print_table alloca failute (fatal)\n";
       return "[Table omitted due to lack of memory]";
   }
   if (cols==0 || rows==0)
   {
       fputs("[empty tabel ignored]\n", out);
       return "[Ignored empty table]";
   }

   for (i=0, col=cdata; col!=NULL; i++, col=col->next)
       d[i].w=find_width(rows, col->data);
   shrink_widths(cols, d, wd);

   for (i=0; i<rows; i++)
   {
       for (j=0, col=cdata; col!=NULL; j++, col=col->next)
           d[j].data=(col->data)[i];
       print_row(cols, d, out);
   }
   return NULL;
}

/* Set */
int text_table::set(int c, int r, const char *s)
{
   struct col_info *col;
   int i;

   if (c<0 || c>=cols || r<0 || r>=rows)
   {
       cerr<<"Invalid request to set "<<c<<','<<r<<" in "
           <<cols<<'x'<<rows<<" table (value "<<s<<")\n";
       return 0;
   }

   for (col=cdata, i=0; i<c && col!=NULL; i++, col=col->next) ;
   if (col!=NULL)
   {
       if (col->data[r]!=NULL)
           free((void *) col->data[r]);
       col->data[r]=strdup(s);
   }
   return 1;
}


/* Constructor */
text_table::text_table(int c, int r)
{
   int i, j;
   struct col_info *col, **nptr;

   cols=c;
   rows=r;
   cdata=NULL;                 // Hardenning against cols=0
   for (nptr=&cdata, i=0; i<cols; i++)
   {
       col=new(struct col_info);
       *nptr=col;
       col->next=NULL;
       nptr=&(col->next);
       if ((col->data=
            (const char **) malloc(rows*(sizeof(const char *))))==NULL)
       {
           cerr<<"text_table::constructor: malloc failure (fatal)\n";
           exit(1);
       }
       for (j=0; j<rows; j++)
           (col->data)[j]=NULL;
   }
}

/* Destructor */
text_table::~text_table()
{
   int i;
   struct col_info *col, *nxt;

   for (col=cdata; col!=NULL;)
   {
       for (i=0; i<rows; i++)
           if (col->data[i]==NULL)
               free((void *) col->data[i]);
       nxt=col->next;
       free(col);
       col=nxt;
   }
}