/*
 *  $Id: columns.c,v 1.12 2022/04/03 22:38:16 tom Exp $
 *
 *  columns.c -- implements column-alignment
 *
 *  Copyright 2008-2019,2022	Thomas E. Dickey
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License, version 2.1
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to
 *	Free Software Foundation, Inc.
 *	51 Franklin St., Fifth Floor
 *	Boston, MA 02110, USA.
 */

#include <dlg_internals.h>

#define each(row, data) \
 		row = 0, data = target; \
 		row < num_rows; \
		++row, data = next_row(data, per_row)

static char *
column_separator(void)
{
    char *result = 0;

    if ((result = dialog_vars.column_separator) != 0) {
	if (*result == '\0')
	    result = 0;
    }
    return result;
}

static char **
next_row(char **target, int per_row)
{
    char *result = (char *) target;
    result += per_row;
    return (char **) (void *) result;
}

static char *
next_col(char *source, unsigned offset)
{
    char *mark = column_separator();
    char *result = source + offset;
    if (offset)
	result += strlen(mark);
    return strstr(result, mark);
}

/*
 * Parse the source string, storing the offsets and widths of each column in
 * the corresponding arrays.  Return the number of columns.
 */
static unsigned
split_row(char *source, unsigned *offsets, unsigned *widths)
{
    int mark = (int) strlen(column_separator());
    char *next = 0;
    unsigned result = 0;
    unsigned offset = 0;

    do {
	if (result) {
	    offset = (unsigned) (mark + next - source);
	    widths[result - 1] = offset - offsets[result - 1] - (unsigned) mark;
	}
	offsets[result] = offset;
	++result;
    } while ((next = next_col(source, offset)) != 0);

    offset = (unsigned) strlen(source);
    widths[result - 1] = offset - offsets[result - 1];

    return result;
}

/*
 * The caller passes a pointer to a struct or array containing pointers
 * to strings that we may want to copy and reformat according to the column
 * separator.
 */
void
dlg_align_columns(char **target, int per_row, int num_rows)
{
    if (column_separator()) {
	char **value;
	unsigned numcols = 1;
	size_t maxcols = 0;
	unsigned *widths;
	unsigned *offsets;
	unsigned *maxwidth;
	unsigned realwidth;
	unsigned n;
	int row;

	/* first allocate arrays for workspace */
	for (each(row, value)) {
	    size_t len = strlen(*value);
	    if (maxcols < len)
		maxcols = len;
	}
	++maxcols;
	widths = dlg_calloc(unsigned, maxcols);
	offsets = dlg_calloc(unsigned, maxcols);
	maxwidth = dlg_calloc(unsigned, maxcols);

	assert_ptr(widths, "dlg_align_columns");
	assert_ptr(offsets, "dlg_align_columns");
	assert_ptr(maxwidth, "dlg_align_columns");

	/* now, determine the number of columns and the column-widths */
	for (each(row, value)) {
	    unsigned cols = split_row(*value, offsets, widths);
	    if (numcols < cols)
		numcols = cols;
	    for (n = 0; n < cols; ++n) {
		if (maxwidth[n] < widths[n])
		    maxwidth[n] = widths[n];
	    }
	}
	realwidth = numcols - 1;
	for (n = 0; n < numcols; ++n) {
	    realwidth += maxwidth[n];
	}

	/* finally, construct reformatted strings */
	for (each(row, value)) {
	    unsigned cols = split_row(*value, offsets, widths);
	    unsigned offset = 0;
	    char *text = dlg_malloc(char, realwidth + 1);

	    assert_ptr(text, "dlg_align_columns");

	    memset(text, ' ', (size_t) realwidth);
	    for (n = 0; n < cols; ++n) {
		memcpy(text + offset, *value + offsets[n], (size_t) widths[n]);
		offset += maxwidth[n] + 1;
	    }
	    text[realwidth] = 0;
	    *value = text;
	}

	free(widths);
	free(offsets);
	free(maxwidth);
    }
}

/*
 * Free temporary storage used while making column-aligned data.
 */
void
dlg_free_columns(char **target, int per_row, int num_rows)
{
    if (column_separator()) {
	int row;
	char **value;

	for (each(row, value)) {
	    free(*value);
	}
    }
}
