/* $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;
    }
}
