#define _GNU_SOURCE
#include <wchar.h>

#include <stdio.h>
#include <stdlib.h>

#include <assert.h>


static const unsigned int MAX_WIDTH = 512;

typedef struct
{
        unsigned int    ncols;
        unsigned int    nrows;
        wchar_t***      rows;
        int*            widths;
} Table;


Table* table_new(int ncols)
{
        assert ( ncols >= 0 );
        
        Table *t = malloc(sizeof(Table));

        t->ncols = ncols;
        t->nrows = 0;
        t->rows = (wchar_t***)NULL;
        t->widths = NULL;
        
        return t;
}


void table_destroy (Table* t)
{
        /* XXX stub */
}


/* add a row which is a string array of ncols elements */
void table_add_row (Table* t, wchar_t** row)
{
        assert(t);

        if (!t->rows)
                t->rows = (wchar_t***)malloc(sizeof(wchar_t***));
        
        t->rows[t->nrows] = row;

        ++t->nrows;
}

static int max (int x, int y)
{
        return x > y ? x : y;
}

static void table_calc_column_widths (Table* t)
{
        int r, c;
        
        assert(t);
        assert(t->ncols > 0);
        
        if (!t->widths)
                t->widths = (int*)malloc(t->ncols * sizeof(int));
        
        for (r = 0; r < t->nrows; ++r)
                for (c = 0; c < t->ncols; ++c)
                {
                        t->widths[c] = max ( t->widths[c],
                                             wcswidth(t->rows[r][c],
                                                      MAX_WIDTH) );
                }
}

/* render a row */
static void table_render_row (Table* t, int rownum, int ncols, wchar_t** s)
{
        wchar_t** row = t->rows[rownum];
        int len = 1, i;
        size_t newsize;
        static wchar_t* delim   = L" | ";
        static wchar_t* newline = L"\n";

        assert(t);
        assert(s != NULL);
        
        for (i = 0; i < ncols; ++i)
                len += t->widths[i] + wcslen(delim);

        len += wcslen(newline);

        newsize = (wcslen(*s) + len + 1) * sizeof(wchar_t);
        *s = realloc (*s, newsize);

        for (i = 0; i < ncols; ++i)
        {
                int j;
                int nspaces = max(t->widths[i] - wcswidth(row[i], MAX_WIDTH),
                                  0);
                wchar_t* pad = malloc ( nspaces * sizeof(wchar_t) );

                for (j = 0; j < nspaces; ++j)
                       pad[j] = L' '; 

                pad[nspaces] = L'\0';
                
                wcscat (*s, row[i]);
                wcscat (*s, pad);
                if (i + 1 < ncols) 
                        wcscat (*s, delim);
        }

        wcscat (*s, newline);
}


/* render the rows.
 * s must be a null-terminated string */
static void table_render_rows (Table* t, wchar_t** s)
{
        int i;

        assert (**s == L'\0');
        
        for (i = 0; i < t->nrows; ++i)
                table_render_row (t, i, t->ncols, s);
}


wchar_t* table_render(Table* t)
{
        wchar_t* s = malloc(sizeof(wchar_t));
        *s = L'\0';
        
        table_calc_column_widths (t);
        table_render_rows (t, &s);
        
        return s;
}


