/* TableOfReal.c
 *
 * Copyright (C) 1992-2003 Paul Boersma
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * pb 2002/07/16 GPL
 * pb 2003/09/15 TableOfReal_readFromHeaderlessSpreadsheetFile: guard against cells with strings
 */

#include "TableOfReal.h"
#include "NUM2.h"
#include "Matrix.h"
#include "Formula.h"

#include "oo_DESTROY.h"
#include "TableOfReal_def.h"
#include "oo_COPY.h"
#include "TableOfReal_def.h"
#include "oo_EQUAL.h"
#include "TableOfReal_def.h"
#include "oo_WRITE_BINARY.h"
#include "TableOfReal_def.h"
#include "oo_READ_BINARY.h"
#include "TableOfReal_def.h"
#include "oo_DESCRIPTION.h"
#include "TableOfReal_def.h"

static void fprintquotedstring (FILE *f, const char *s) {
	fputc ('\"', f);
	if (s) { char c; while ((c = *s ++) != '\0') { fputc (c, f); if (c == '\"') fputc (c, f); } }
	fputc ('\"', f);
}

static int writeAscii (I, FILE *f) {
	iam (TableOfReal);
	long i, j;
	ascputi4 (my numberOfColumns, f, "numberOfColumns");
	fprintf (f, "\ncolumnLabels []: %s\n", my numberOfColumns >= 1 ? "" : "(empty)");
	for (i = 1; i <= my numberOfColumns; i ++) {
		fprintquotedstring (f, my columnLabels [i]);
		fputc ('\t', f);
	}
	ascputi4 (my numberOfRows, f, "numberOfRows");
	for (i = 1; i <= my numberOfRows; i ++) {
		fprintf (f, "\nrow [%ld]: ", i);
		fprintquotedstring (f, my rowLabels [i]);
		for (j = 1; j <= my numberOfColumns; j ++) {
			double x = my data [i] [j];
			fprintf (f, x == NUMundefined ? "\t--undefined--" : "\t%.17g", x);
		}
	}
	return 1;
}

static int readAscii (I, FILE *f) {
	iam (TableOfReal);
	long i, j;
	my numberOfColumns = ascgeti4 (f);
	if (my numberOfColumns >= 1) {
		if (! (my columnLabels = NUMvector (sizeof (char *), 1, my numberOfColumns))) return 0;
		for (i = 1; i <= my numberOfColumns; i ++)
			if (! (my columnLabels [i] = ascgets2 (f))) return 0;
	}
	my numberOfRows = ascgeti4 (f);
	if (my numberOfRows >= 1) {
		if (! (my rowLabels = NUMvector (sizeof (char *), 1, my numberOfRows))) return 0;
	}
	if (my numberOfRows >= 1 && my numberOfColumns >= 1) {
		if (! (my data = NUMdmatrix (1, my numberOfRows, 1, my numberOfColumns))) return 0;
		for (i = 1; i <= my numberOfRows; i ++) {
			if (! (my rowLabels [i] = ascgets2 (f))) return 0;
			for (j = 1; j <= my numberOfColumns; j ++)
				my data [i] [j] = ascgetr8 (f);
		}
	}
	return 1;
}

static void info (I) {
	iam (TableOfReal);
	Melder_information ("Number of rows = %ld\nNumber of columns = %ld",
		my numberOfRows, my numberOfColumns);
}

static double getNrow (I) { iam (TableOfReal); return my numberOfRows; }
static double getNcol (I) { iam (TableOfReal); return my numberOfColumns; }
static double getMatrix (I, long irow, long icol) {
	iam (TableOfReal);
	if (irow < 1 || irow > my numberOfRows) return NUMundefined;
	if (icol < 1 || icol > my numberOfColumns) return NUMundefined;
	return my data [irow] [icol];
}
static double getRowIndex (I, const char *rowLabel) {
	iam (TableOfReal);
	return TableOfReal_rowLabelToIndex (me, rowLabel);
}
static double getColumnIndex (I, const char *columnLabel) {
	iam (TableOfReal);
	return TableOfReal_columnLabelToIndex (me, columnLabel);
}

class_methods (TableOfReal, Data)
	class_method_local (TableOfReal, destroy)
	class_method_local (TableOfReal, description)
	class_method_local (TableOfReal, copy)
	class_method_local (TableOfReal, equal)
	class_method (writeAscii)
	class_method (readAscii)
	class_method_local (TableOfReal, writeBinary)
	class_method_local (TableOfReal, readBinary)
	class_method (info)
	class_method (getNrow)
	class_method (getNcol)
	class_method (getMatrix)
	class_method (getRowIndex)
	class_method (getColumnIndex)
class_methods_end

int TableOfReal_init (I, long numberOfRows, long numberOfColumns) {
	iam (TableOfReal);
	if (numberOfRows < 1 || numberOfColumns < 1)
		return Melder_error ("(TableOfReal_init:) Cannot create cell-less table.");
	my numberOfRows = numberOfRows;
	my numberOfColumns = numberOfColumns;
	if (! (my rowLabels = NUMvector (sizeof (char *), 1, numberOfRows))) return 0;
	if (! (my columnLabels = NUMvector (sizeof (char *), 1, numberOfColumns))) return 0;
	if (! (my data = NUMdmatrix (1, my numberOfRows, 1, my numberOfColumns))) return 0;
	return 1;
}

TableOfReal TableOfReal_create (long numberOfRows, long numberOfColumns) {
	TableOfReal me = new (TableOfReal);
	if (! me || ! TableOfReal_init (me, numberOfRows, numberOfColumns)) forget (me);
	return me;
}

int TableOfReal_removeRow (I, long irow) {
	iam (TableOfReal);
	double **data = NULL;
	long i, j;
	if (my numberOfRows == 1)
		{ Melder_error ("Cannot remove the only row."); goto end; }
	if (irow < 1 || irow > my numberOfRows)
		{ Melder_error ("No row %ld.", irow); goto end; }
	data = NUMdmatrix (1, my numberOfRows - 1, 1, my numberOfColumns); cherror
	for (i = 1; i <= my numberOfColumns; i ++) {
		for (j = 1; j < irow; j ++) data [j] [i] = my data [j] [i];
		for (j = irow; j < my numberOfRows; j ++) data [j] [i] = my data [j + 1] [i];
	}
	Melder_free (my rowLabels [irow]);
	for (j = irow; j < my numberOfRows; j ++) my rowLabels [j] = my rowLabels [j + 1];
	NUMdmatrix_free (my data, 1, 1);
	my data = data;
	my numberOfRows --;
end:
	iferror return Melder_error ("(TableOfReal_removeRow:) Not performed.");
	return 1;
}

TableOfReal TableOfReal_selectRowsWhereColumn (I, long column, int which, double criterion) {
	iam (TableOfReal);
	TableOfReal thee = NULL;
	long irow, icol, n = 0;
	if (column < 1 || column > my numberOfColumns)
		{ Melder_error ("No column %ld.", column); goto end; }
	for (irow = 1; irow <= my numberOfRows; irow ++) {
		double value = my data [irow] [column];
		int keep =
			which == TableOfReal_EQUAL_TO && value == criterion ||
			which == TableOfReal_NOT_EQUAL_TO && value != criterion ||
			which == TableOfReal_LESS_THAN && value < criterion ||
			which == TableOfReal_LESS_THAN_OR_EQUAL_TO && value <= criterion ||
			which == TableOfReal_GREATER_THAN && value > criterion ||
			which == TableOfReal_GREATER_THAN_OR_EQUAL_TO && value >= criterion;
		if (keep) n ++;
	}
	if (n == 0)
		{ Melder_error ("No row matches this criterion."); goto end; }
	thee = TableOfReal_create (n, my numberOfColumns);
	for (icol = 1; icol <= my numberOfColumns; icol ++) {
		thy columnLabels [icol] = Melder_strdup (my columnLabels [icol]); cherror
	}
	n = 0;
	for (irow = 1; irow <= my numberOfRows; irow ++) {
		double value = my data [irow] [column];
		int keep =
			which == TableOfReal_EQUAL_TO && value == criterion ||
			which == TableOfReal_NOT_EQUAL_TO && value != criterion ||
			which == TableOfReal_LESS_THAN && value < criterion ||
			which == TableOfReal_LESS_THAN_OR_EQUAL_TO && value <= criterion ||
			which == TableOfReal_GREATER_THAN && value > criterion ||
			which == TableOfReal_GREATER_THAN_OR_EQUAL_TO && value >= criterion;
		if (keep) {
			n ++;
			thy rowLabels [n] = Melder_strdup (my rowLabels [irow]); cherror
			for (icol = 1; icol <= my numberOfColumns; icol ++)
				thy data [n] [icol] = my data [irow] [icol];
		}
	}
end:
	iferror {
		forget (thee);
		Melder_error ("(TableOfReal_selectRowsWhereColumn:) Not performed.");
	}
	return thee;
}

int TableOfReal_insertRow (I, long irow) {
	iam (TableOfReal);
	double **data = NULL;
	char **rowLabels = NULL;
	long i, j;
	if (irow < 1 || irow > my numberOfRows + 1)
		{ Melder_error ("Cannot create row %ld.", irow); goto end; }
	data = NUMdmatrix (1, my numberOfRows + 1, 1, my numberOfColumns); cherror
	rowLabels = NUMpvector (1, my numberOfRows + 1);
	for (j = 1; j < irow; j ++)	{
		rowLabels [j] = my rowLabels [j];
		for (i = 1; i <= my numberOfColumns; i ++) data [j] [i] = my data [j] [i];
	}
	for (j = my numberOfRows + 1; j > irow; j --) {
		rowLabels [j] = my rowLabels [j - 1];
		for (i = 1; i <= my numberOfColumns; i ++) data [j] [i] = my data [j - 1] [i];
	}
	NUMdmatrix_free (my data, 1, 1);
	NUMpvector_free (my rowLabels, 1);
	my data = data;
	my rowLabels = rowLabels;
	my numberOfRows ++;
end:
	iferror return Melder_error ("(TableOfReal_insertRow:) Not performed.");
	return 1;
}

int TableOfReal_removeColumn (I, long icol) {
	iam (TableOfReal);
	double **data = NULL;
	long i, j;
	if (my numberOfColumns == 1)
		{ Melder_error ("Cannot remove the only column."); goto end; }
	if (icol < 1 || icol > my numberOfColumns)
		{ Melder_error ("No column %ld.", icol); goto end; }
	data = NUMdmatrix (1, my numberOfRows, 1, my numberOfColumns - 1); cherror
	for (i = 1; i <= my numberOfRows; i ++) {
		for (j = 1; j < icol; j ++) data [i] [j] = my data [i] [j];
		for (j = icol; j < my numberOfColumns; j ++) data [i] [j] = my data [i] [j + 1];
	}
	Melder_free (my columnLabels [icol]);
	for (j = icol; j < my numberOfColumns; j ++) my columnLabels [j] = my columnLabels [j + 1];
	NUMdmatrix_free (my data, 1, 1);
	my data = data;
	my numberOfColumns --;
end:
	iferror return Melder_error ("(TableOfReal_removeColumn:) Not performed.");
	return 1;
}

int TableOfReal_insertColumn (I, long icol) {
	iam (TableOfReal);
	double **data = NULL;
	char **columnLabels = NULL;
	long i, j;
	if (icol < 1 || icol > my numberOfColumns + 1)
		{ Melder_error ("Cannot create column %ld.", icol); goto end; }
	data = NUMdmatrix (1, my numberOfRows, 1, my numberOfColumns + 1); cherror
	columnLabels = NUMpvector (1, my numberOfColumns + 1); cherror
	for (j = 1; j < icol; j ++) {
		columnLabels [j] = my columnLabels [j];
		for (i = 1; i <= my numberOfRows; i ++) data [i] [j] = my data [i] [j];
	}
	for (j = my numberOfColumns + 1; j > icol; j --) {
		columnLabels [j] = my columnLabels [j - 1];
		for (i = 1; i <= my numberOfRows; i ++) data [i] [j] = my data [i] [j - 1];
	}
	NUMdmatrix_free (my data, 1, 1);
	NUMpvector_free (my columnLabels, 1);
	my data = data;
	my columnLabels = columnLabels;
	my numberOfColumns ++;
end:
	iferror return Melder_error ("(TableOfReal_insertColumn:) Not performed.");
	return 1;
}

void TableOfReal_setRowLabel (I, long irow, const char *label) {
	iam (TableOfReal);
	if (irow < 1 || irow > my numberOfRows || label == my rowLabels [irow]) return;
	Melder_free (my rowLabels [irow]);
	my rowLabels [irow] = Melder_strdup (label);
}

void TableOfReal_setColumnLabel (I, long icol, const char *label) {
	iam (TableOfReal);
	if (icol < 1 || icol > my numberOfColumns || label == my columnLabels [icol]) return;
	Melder_free (my columnLabels [icol]);
	my columnLabels [icol] = Melder_strdup (label);
}

long TableOfReal_rowLabelToIndex (I, const char *label) {
	iam (TableOfReal);
	long irow;
	for (irow = 1; irow <= my numberOfRows; irow ++)
		if (my rowLabels [irow] && strequ (my rowLabels [irow], label))
			return irow;
	return 0;
}

long TableOfReal_columnLabelToIndex (I, const char *label) {
	iam (TableOfReal);
	long icol;
	for (icol = 1; icol <= my numberOfColumns; icol ++)
		if (my columnLabels [icol] && strequ (my columnLabels [icol], label))
			return icol;
	return 0;
}

double TableOfReal_getColumnMean (I, long icol) {
	iam (TableOfReal);
	double sum = 0.0;
	long irow;
	if (icol < 1 || icol > my numberOfColumns) return NUMundefined;
	if (my numberOfRows < 1) return NUMundefined;
	for (irow = 1; irow <= my numberOfRows; irow ++)
		sum += my data [irow] [icol];
	return sum / my numberOfRows;
}

double TableOfReal_getColumnStdev (I, long icol) {
	iam (TableOfReal);
	double mean = TableOfReal_getColumnMean (me, icol), sum = 0.0, d;
	long irow;
	if (icol < 1 || icol > my numberOfColumns) return NUMundefined;
	if (my numberOfRows < 2) return NUMundefined;
	for (irow = 1; irow <= my numberOfRows; irow ++)
		sum += ( d = my data [irow] [icol] - mean, d * d );
	return sqrt (sum / (my numberOfRows - 1));
}

static void NUMrationalize (double x, long *numerator, long *denominator) {
	double epsilon = 1e-6;
	*numerator = 1;
	for (*denominator = 1; *denominator <= 100000; (*denominator) ++) {
		double numerator_d = x * *denominator, rounded = floor (numerator_d + 0.5);
		if (fabs (rounded - numerator_d) < epsilon) {
			*numerator = rounded;
			return;
		}
	}
	*denominator = 0;   /* Failure. */
}

static void print4 (char *buffer, double value, int iformat, int width, int precision) {
	char formatString [40];
	if (value == NUMundefined) strcpy (buffer, "undefined");
	else if (iformat == 4) {
		long numerator, denominator;
		NUMrationalize (value, & numerator, & denominator);
		if (numerator == 0)
			sprintf (buffer, "0");
		else if (denominator > 1)
			sprintf (buffer, "%ld/%ld", numerator, denominator);
		else
			sprintf (buffer, "%.7g", value);
	} else {
		sprintf (formatString, "%%%d.%d%c", width, precision, iformat == 1 ? 'f' : iformat == 2 ? 'e' : 'g');
		sprintf (buffer, formatString, value);
	}
}

Matrix TableOfReal_to_Matrix (I) {
	iam (TableOfReal);
	long i, j;
	Matrix thee = Matrix_createSimple (my numberOfRows, my numberOfColumns); cherror
	for (i = 1; i <= my numberOfRows; i ++) for (j = 1; j <= my numberOfColumns; j ++)
		thy z [i] [j] = my data [i] [j];
end:
	iferror return NULL;
	return thee;
}

TableOfReal Matrix_to_TableOfReal (I) {
	iam (Matrix);
	long i, j;
	TableOfReal thee = TableOfReal_create (my ny, my nx); cherror
	for (i = 1; i <= my ny; i ++) for (j = 1; j <= my nx; j ++)
		thy data [i] [j] = my z [i] [j];
end:
	iferror return NULL;
	return thee;
}

int TableOfReal_formula (I, const char *expression, thou) {
	iam (TableOfReal);
	thouart (TableOfReal);
	long irow, icol;
	if (! Formula_compile (NULL, me, expression, FALSE, TRUE)) return 0;
	if (thee == NULL) thee = me;
	for (irow = 1; irow <= my numberOfRows; irow ++) {
		for (icol = 1; icol <= my numberOfColumns; icol ++) {
			double result;
			if (! Formula_run (irow, icol, & result, NULL)) return 0;
			thy data [irow] [icol] = result;
		}
	}
	return 1;
}

static void fixRows (TableOfReal me, long *rowmin, long *rowmax) {
	if (*rowmax < *rowmin) { *rowmin = 1; *rowmax = my numberOfRows; }
	else if (*rowmin < 1) *rowmin = 1;
	else if (*rowmax > my numberOfRows) *rowmax = my numberOfRows;
}
static void fixColumns (TableOfReal me, long *colmin, long *colmax) {
	if (*colmax < *colmin) { *colmin = 1; *colmax = my numberOfColumns; }
	else if (*colmin < 1) *colmin = 1;
	else if (*colmax > my numberOfColumns) *colmax = my numberOfColumns;
}
static double getMaxRowLabelWidth (TableOfReal me, Graphics g, long rowmin, long rowmax) {
	double maxWidth = 0.0;
	long row;
	if (! my rowLabels) return 0.0;
	fixRows (me, & rowmin, & rowmax);
	for (row = rowmin; row <= rowmax; row ++) if (my rowLabels [row] && my rowLabels [row] [0]) {
		double textWidth = Graphics_textWidth_ps (g, my rowLabels [row]);
		if (textWidth > maxWidth) maxWidth = textWidth;
	}
	return maxWidth;
}
static double getLeftMargin (Graphics g) {
	return Graphics_dxMMtoWC (g, 1);
}
static double getLineSpacing (Graphics g) {
	return Graphics_dyMMtoWC (g, 1.5 * Graphics_inqFontSize (g) * 25.4 / 72);
}
static double getMaxColumnLabelHeight (TableOfReal me, Graphics g, long colmin, long colmax) {
	double maxHeight = 0.0, lineSpacing = getLineSpacing (g);
	long col;
	if (! my columnLabels) return 0.0;
	fixRows (me, & colmin, & colmax);
	for (col = colmin; col <= colmax; col ++) if (my columnLabels [col] && my columnLabels [col] [0]) {
		if (! maxHeight) maxHeight = lineSpacing;
	}
	return maxHeight;
}

void TableOfReal_drawAsNumbers (I, Graphics g, long rowmin, long rowmax, int iformat, int precision) {
	iam (TableOfReal);
	double leftMargin, lineSpacing, maxTextWidth, maxTextHeight;
	long row, col;
	fixRows (me, & rowmin, & rowmax);
	Graphics_setInner (g);
	Graphics_setWindow (g, 0.5, my numberOfColumns + 0.5, 0, 1);
	leftMargin = getLeftMargin (g), lineSpacing = getLineSpacing (g);   /* Not earlier! */
	maxTextWidth = getMaxRowLabelWidth (me, g, rowmin, rowmax);
	maxTextHeight = getMaxColumnLabelHeight (me, g, 1, my numberOfColumns);

	Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_BOTTOM);
	for (col = 1; col <= my numberOfColumns; col ++) {
		if (my columnLabels && my columnLabels [col] && my columnLabels [col] [0])
			Graphics_text (g, col, 1, my columnLabels [col]);
	}
	for (row = rowmin; row <= rowmax; row ++) {
		double y = 1 - lineSpacing * (row - rowmin + 0.6);
		Graphics_setTextAlignment (g, Graphics_RIGHT, Graphics_HALF);
		if (my rowLabels && my rowLabels [row] && my rowLabels [row] [0])
			Graphics_text (g, 0.5 - leftMargin, y, my rowLabels [row]);
		Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
		for (col = 1; col <= my numberOfColumns; col ++) {
			char text [40];
			print4 (text, my data [row] [col], iformat, 0, precision);
			Graphics_text (g, col, y, text);
		}
	}
	if (maxTextHeight) {
		double left = 0.5;
		if (maxTextWidth > 0.0) left -= maxTextWidth + 2 * leftMargin;
		Graphics_line (g, left, 1, my numberOfColumns + 0.5, 1);
	}
	Graphics_unsetInner (g);
}

void TableOfReal_drawAsNumbers_if (I, Graphics g, long rowmin, long rowmax, int iformat, int precision,
	const char *conditionFormula)
{
	iam (TableOfReal);
	double leftMargin, lineSpacing, maxTextWidth, maxTextHeight;
	long row, col;
	Matrix original = TableOfReal_to_Matrix (me), conditions = Data_copy (original);
	fixRows (me, & rowmin, & rowmax);
	Graphics_setInner (g);
	Graphics_setWindow (g, 0.5, my numberOfColumns + 0.5, 0, 1);
	leftMargin = getLeftMargin (g), lineSpacing = getLineSpacing (g);   /* Not earlier! */
	maxTextWidth = getMaxRowLabelWidth (me, g, rowmin, rowmax);
	maxTextHeight = getMaxColumnLabelHeight (me, g, 1, my numberOfColumns);
	if (! Matrix_formula (original, conditionFormula, conditions))
		{ forget (original); forget (conditions); Melder_flushError ("Numbers not drawn."); return; }

	Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_BOTTOM);
	for (col = 1; col <= my numberOfColumns; col ++) {
		if (my columnLabels && my columnLabels [col] && my columnLabels [col] [0])
			Graphics_text (g, col, 1, my columnLabels [col]);
	}
	for (row = rowmin; row <= rowmax; row ++) {
		double y = 1 - lineSpacing * (row - rowmin + 0.6);
		Graphics_setTextAlignment (g, Graphics_RIGHT, Graphics_HALF);
		if (my rowLabels && my rowLabels [row] && my rowLabels [row] [0])
			Graphics_text (g, 0.5 - leftMargin, y, my rowLabels [row]);
		Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
		for (col = 1; col <= my numberOfColumns; col ++) if (conditions -> z [row] [col] != 0.0) {
			char text [40];
			print4 (text, my data [row] [col], iformat, 0, precision);
			Graphics_text (g, col, y, text);
		}
	}
	if (maxTextHeight) {
		double left = 0.5;
		if (maxTextWidth > 0.0) left -= maxTextWidth + 2 * leftMargin;
		Graphics_line (g, left, 1, my numberOfColumns + 0.5, 1);
	}
	Graphics_unsetInner (g);
	forget (original);
	forget (conditions);
}

void TableOfReal_drawVerticalLines (I, Graphics g, long rowmin, long rowmax) {
	iam (TableOfReal);
	double leftMargin, lineSpacing, maxTextWidth, maxTextHeight;
	long colmin = 1, colmax = my numberOfColumns, col;
	fixRows (me, & rowmin, & rowmax);
	Graphics_setInner (g);
	Graphics_setWindow (g, colmin - 0.5, colmax + 0.5, 0, 1);
	leftMargin = getLeftMargin (g), lineSpacing = getLineSpacing (g);   /* Not earlier! */
	maxTextWidth = getMaxRowLabelWidth (me, g, rowmin, rowmax);
	maxTextHeight = getMaxColumnLabelHeight (me, g, 1, my numberOfColumns);

	if (maxTextWidth > 0.0) colmin -= 1;
	for (col = colmin + 1; col <= colmax; col ++)
		Graphics_line (g, col - 0.5, 1 + maxTextHeight, col - 0.5, 1 - lineSpacing * (rowmax - rowmin + 1));
	Graphics_unsetInner (g);
}

void TableOfReal_drawLeftAndRightLines (I, Graphics g, long rowmin, long rowmax) {
	iam (TableOfReal);
	double leftMargin, lineSpacing, maxTextWidth, maxTextHeight, left, right, top, bottom;
	long colmin = 1, colmax = my numberOfColumns;
	fixRows (me, & rowmin, & rowmax);
	Graphics_setInner (g);
	Graphics_setWindow (g, colmin - 0.5, colmax + 0.5, 0, 1);
	leftMargin = getLeftMargin (g), lineSpacing = getLineSpacing (g);
	maxTextWidth = getMaxRowLabelWidth (me, g, rowmin, rowmax);
	maxTextHeight = getMaxColumnLabelHeight (me, g, 1, my numberOfColumns);

	left = 0.5;
	if (maxTextWidth > 0.0) left -= maxTextWidth + 2 * lineSpacing;
	right = colmax + 0.5;
	top = 1 + maxTextHeight;
	bottom = 1 - lineSpacing * (rowmax - rowmin + 1);
	Graphics_line (g, left, top, left, bottom);
	Graphics_line (g, right, top, right, bottom);
	Graphics_unsetInner (g);
}

void TableOfReal_drawHorizontalLines (I, Graphics g, long rowmin, long rowmax) {
	iam (TableOfReal);
	double leftMargin, lineSpacing, maxTextWidth, maxTextHeight, left, top, right;
	long colmin = 1, colmax = my numberOfColumns, row;
	fixRows (me, & rowmin, & rowmax);
	Graphics_setInner (g);
	Graphics_setWindow (g, colmin - 0.5, colmax + 0.5, 0, 1);
	leftMargin = getLeftMargin (g), lineSpacing = getLineSpacing (g);
	maxTextWidth = getMaxRowLabelWidth (me, g, rowmin, rowmax);
	maxTextHeight = getMaxColumnLabelHeight (me, g, 1, my numberOfColumns);

	left = 0.5;
	top = rowmin;
	if (maxTextWidth > 0.0) left -= maxTextWidth + 2 * lineSpacing;
	if (maxTextHeight > 0.0) rowmin -= 1;
	right = colmax + 0.5;
	for (row = rowmin; row < rowmax; row ++) {
		double y = 1 - lineSpacing * (row - top + 1);
		Graphics_line (g, left, y, right, y);
	}
	Graphics_unsetInner (g);
}

void TableOfReal_drawTopAndBottomLines (I, Graphics g, long rowmin, long rowmax) {
	iam (TableOfReal);
	double leftMargin, lineSpacing, maxTextWidth, maxTextHeight, left, top, right, bottom;
	long colmin = 1, colmax = my numberOfColumns;
	fixRows (me, & rowmin, & rowmax);
	Graphics_setInner (g);
	Graphics_setWindow (g, colmin - 0.5, colmax + 0.5, 0, 1);
	leftMargin = getLeftMargin (g), lineSpacing = getLineSpacing (g);
	maxTextWidth = getMaxRowLabelWidth (me, g, rowmin, rowmax);
	maxTextHeight = getMaxColumnLabelHeight (me, g, 1, my numberOfColumns);

	left = 0.5;
	if (maxTextWidth > 0.0) left -= maxTextWidth + 2 * lineSpacing;
	right = colmax + 0.5;
	top = 1 + maxTextHeight;
	bottom = 1 - lineSpacing * (rowmax - rowmin + 1);
	Graphics_line (g, left, top, right, top);
	Graphics_line (g, left, bottom, right, bottom);
	Graphics_unsetInner (g);
}

void TableOfReal_drawAsSquares (I, Graphics g, long rowmin, long rowmax,
	long colmin, long colmax, int garnish)
{
	iam (TableOfReal);
	double datamax, dx = 1, dy = 1;
	long i, j;
	int colour = Graphics_inqColour (g);
	fixRows (me, & rowmin, & rowmax);
	fixColumns (me, & colmin, & colmax);
	
	Graphics_setInner (g);
	Graphics_setWindow (g, colmin - 0.5, colmax + 0.5, rowmin - 0.5, rowmax + 0.5);
	datamax = my data [rowmin] [colmin];
	for (i = 1; i <= my numberOfRows; i ++) for (j = 1; j <= my numberOfColumns; j ++)
		if (fabs (my data [i] [j]) > datamax) datamax = fabs (my data [i] [j]);
	
	for (i = rowmin; i <= rowmax; i ++) {
		double y = rowmax + rowmin - i;
		for (j = colmin; j <= colmax; j ++) {
			double x = j;
			/* two neighbouring squares should not touch -> 0.95 */
			double d = 0.95 * sqrt (fabs (my data[i][j]) / datamax);
			double x1WC = x - d * dx / 2, x2WC = x + d * dx / 2;
			double y1WC = y - d * dy / 2, y2WC = y + d * dy / 2;
			Graphics_setGrey (g, my data[i][j] > 0.0 ? 1.0 : 0.0);
			Graphics_fillRectangle (g, x1WC, x2WC, y1WC, y2WC);
			Graphics_setColour (g, colour);
			Graphics_rectangle (g, x1WC, x2WC , y1WC, y2WC);
		}
	}
	Graphics_setGrey (g, 0.0);
	Graphics_unsetInner (g);
	if (garnish) {
		for (i = rowmin; i <= rowmax; i ++) if (my rowLabels [i]) 
			Graphics_markLeft (g, rowmax + rowmin - i, 0, 0, 0, my rowLabels [i]);
		for (j = colmin; j <= colmax; j ++) if (my columnLabels [j])
			Graphics_markTop (g, j, 0, 0, 0, my columnLabels [j]);
	}
}

Any TablesOfReal_append (I, thou) {
	iam (TableOfReal); thouart (TableOfReal);
	TableOfReal him = NULL;
	long irow, icol;
	if (thy numberOfColumns != my numberOfColumns)
		{ Melder_error ("Numbers of columns do not match."); goto end; }
	him = Thing_new (my methods); cherror
	TableOfReal_init (him, my numberOfRows + thy numberOfRows, my numberOfColumns); cherror
	/* Unsafe: new attributes not initialized. */
	for (icol = 1; icol <= my numberOfColumns; icol ++) {
		TableOfReal_setColumnLabel (him, icol, my columnLabels [icol]); cherror
	}
	for (irow = 1; irow <= my numberOfRows; irow ++) {
		TableOfReal_setRowLabel (him, irow, my rowLabels [irow]); cherror
		for (icol = 1; icol <= my numberOfColumns; icol ++)
			his data [irow] [icol] = my data [irow] [icol];
	}
	for (irow = 1; irow <= thy numberOfRows; irow ++) {
		long hisRow = irow + my numberOfRows;
		TableOfReal_setRowLabel (him, hisRow, thy rowLabels [irow]); cherror
		for (icol = 1; icol <= my numberOfColumns; icol ++)
			his data [hisRow] [icol] = thy data [irow] [icol];
	}
end:
	iferror { forget (him); Melder_error ("(TablesOfReal_append:) Not performed."); }
	return him;
}

Any TablesOfReal_appendMany (Collection me) {
	TableOfReal him = NULL, thee;
	long itab, irow, icol, nrow, ncol;
	if (my size == 0) return Melder_errorp ("Cannot add zero tables.");
	thee = my item [1];
	nrow = thy numberOfRows;
	ncol = thy numberOfColumns;
	for (itab = 2; itab <= my size; itab ++) {
		thee = my item [itab];
		nrow += thy numberOfRows;
		if (thy numberOfColumns != ncol)
			{ Melder_error ("Numbers of columns do not match."); goto end; }
	}
	him = Thing_new (thy methods); cherror
	TableOfReal_init (him, nrow, ncol); cherror
	/* Unsafe: new attributes not initialized. */
	for (icol = 1; icol <= ncol; icol ++) {
		TableOfReal_setColumnLabel (him, icol, thy columnLabels [icol]); cherror
	}
	nrow = 0;
	for (itab = 1; itab <= my size; itab ++) {
		thee = my item [itab];
		for (irow = 1; irow <= thy numberOfRows; irow ++) {
			nrow ++;
			TableOfReal_setRowLabel (him, nrow, thy rowLabels [irow]); cherror
			for (icol = 1; icol <= ncol; icol ++)
				his data [nrow] [icol] = thy data [irow] [icol];
		}
	}
	Melder_assert (nrow == his numberOfRows);
end:
	iferror { forget (him); Melder_error ("(TablesOfReal_appendMany:) Not performed."); }
	return him;
}

static void TableOfReal_sort (TableOfReal me, int useLabels, long column1, long column2) {
	long i, j;
	for (i = 1; i < my numberOfRows; i ++) for (j = i + 1; j <= my numberOfRows; j ++) {
		char *tmpString;
		long column;
		if (useLabels) {
			if (my rowLabels [i] != NULL) {
				if (my rowLabels [j] != NULL) {
					int compare = strcmp (my rowLabels [i], my rowLabels [j]);
					if (compare < 0) continue;
					if (compare > 0) goto swap;
				} else goto swap;
			} else if (my rowLabels [j] != NULL) continue;
		}
		/*
		 * If we arrive here, the two labels are equal or both NULL (or useLabels is FALSE).
		 */
		if (column1 > 0 && column1 <= my numberOfColumns) {
			if (my data [i] [column1] < my data [j] [column1]) continue;
			if (my data [i] [column1] > my data [j] [column1]) goto swap;
		}
		if (column2 > 0 && column2 <= my numberOfColumns) {
			if (my data [i] [column2] < my data [j] [column2]) continue;
			if (my data [i] [column2] > my data [j] [column2]) goto swap;
		}
		/*
		 * If we arrive here, everything is equal.
		 */
		continue;
	swap:
		tmpString = my rowLabels [i];
		my rowLabels [i] = my rowLabels [j];
		my rowLabels [j] = tmpString;
		for (column = 1; column <= my numberOfColumns; column ++) {
			double tmpValue = my data [i] [column];
			my data [i] [column] = my data [j] [column];
			my data [j] [column] = tmpValue;
		}
	}
}

void TableOfReal_sortByLabel (I, long column1, long column2) {
	iam (TableOfReal);
	TableOfReal_sort (me, TRUE, column1, column2);
}

void TableOfReal_sortByColumn (I, long column1, long column2) {
	iam (TableOfReal);
	TableOfReal_sort (me, FALSE, column1, column2);
}

int TableOfReal_writeToHeaderlessSpreadsheetFile (TableOfReal me, MelderFile file) {
	FILE *f = Melder_fopen (file, "w");
	long i, j;
	if (! f) return 0;
	fprintf (f, "rowLabel");
	for (i = 1; i <= my numberOfColumns; i ++) {
		fprintf (f, "\t%s", my columnLabels [i] && my columnLabels [i] [0] ? my columnLabels [i] : "?");
	}
	for (i = 1; i <= my numberOfRows; i ++) {
		fprintf (f, "\n%s", my rowLabels [i] && my rowLabels [i] [0] ? my rowLabels [i] : "?");
		for (j = 1; j <= my numberOfColumns; j ++) {
			double x = my data [i] [j];
			fprintf (f, x == NUMundefined ? "\t--undefined--" : "\t%.17g", x);
		}
	}
	if (! Melder_fclose (file, f)) return 0;
	MelderFile_setMacTypeAndCreator (file, 'TEXT', 0);
	return 1;
}

TableOfReal TableOfReal_readFromHeaderlessSpreadsheetFile (MelderFile file) {
	FILE *f = NULL;
	TableOfReal me = NULL;
	char element [101];
	long nrow, ncol, nelements, irow, icol;

	f = Melder_fopen (file, "rb");
	if (! f) goto end;

	/*
	 * Count number of columns.
	 */
	ncol = 0;
	for (;;) {
		int kar = fgetc (f);
		if (kar == '\n' || kar == '\r' || kar == EOF) break;
		if (kar == ' ' || kar == '\t') continue;
		ncol ++;
		do {
			kar = fgetc (f);
		} while (kar != ' ' && kar != '\t' && kar != '\n' && kar != '\r' && kar != EOF);
		if (kar == '\n' || kar == '\r' || kar == EOF) break;
	}
	ncol --;
	if (ncol < 1) { Melder_error ("No columns."); goto end; }

	/*
	 * Count number of elements.
	 */
	rewind (f);
	nelements = 0;
	for (;;) {
		if (fscanf (f, "%s", & element) < 1) break;   /* Zero or end-of-file. */
		nelements ++;
	}

	/*
	 * Check if all columns are complete.
	 */
	if (! nelements || nelements % (ncol + 1)) {
		Melder_error ("The number of elements (%ld) is not a multiple of the number of columns plus 1 (%ld).",
			nelements, ncol + 1);
		goto end;
	}

	/*
	 * Create empty table.
	 */

	nrow = nelements / (ncol + 1) - 1;
	me = TableOfReal_create (nrow, ncol);
	if (! me) goto end;

	/*
	 * Read elements.
	 */

	rewind (f);
	fscanf (f, "%s", element);   /* Ignore header of column with labels. */
	for (icol = 1; icol <= ncol; icol ++) {
		fscanf (f, "%s", element);
		TableOfReal_setColumnLabel (me, icol, element);
	}
	for (irow = 1; irow <= nrow; irow ++) {
		fscanf (f, "%s", element);
		TableOfReal_setRowLabel (me, irow, element);
		for (icol = 1; icol <= ncol; icol ++) {
			fscanf (f, "%s", element);   /* Because some columns may contain strings. */
			my data [irow] [icol] = atof (element);   /* If cell contains a string, this will be 0. */
		}
	}

end:
	Melder_fclose (file, f);
	iferror { forget (me); return Melder_errorp (
		"(TableOfReal_readFromHeaderlessSpreadsheetFile:) File %s not read.", MelderFile_messageName (file)); }
	return me;
}

/* End of file TableOfReal.c */
