/*
 * file: denview.c
 *
 * (c) P. Kleiweg 1997, 1998, 1999, 2002
 *
 */

#ifndef __BORLANDC__
#ifndef __TURBOC__
#error Borland C or Turbo C compiler required
#endif
#endif

#ifndef __COMPACT__
#  error Memory model COMPACT required
#endif  /* __COMPACT__  */

#include <bios.h>
#include <ctype.h>
#include <io.h>
#include <dir.h>
#include <graphics.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <values.h>

typedef enum { CLS, LBL } NODETYPE;
typedef enum { FALSE = 0, TRUE = 1} BOOL;

typedef struct _cluster {
    int
        index;
    float
        value;
    char
        *text;
    NODETYPE
        node [2];
    union {
        int
            cluster;
        char
            *label;
    } n [2];
} CLUSTER;


CLUSTER
    *cl = NULL;

#define BUFSIZE 1024

int
    top,
    inputline = 0,
    max = 0,
    used = 0,
    maxlabel = 0,
    mindefined = 0;

float
    maxvalue = -MAXFLOAT,
    minvalue = MAXFLOAT,
    xmin,
    ymin,
    xd,
    yd;

char
    buffer [BUFSIZE + 1],
    *programname,
    *no_mem_buffer,
    Out_of_memory [] = "Out of Memory!";

FILE
    *fp;

BOOL
    in_graph = FALSE;


void
    process (int n, float *x, float *y),
    *s_malloc (size_t size),
    *s_realloc (void *block, size_t size),
    get_programname (char const *argv0),
    errit (char const *format, ...),
    syntax (void);

char
    *s_strdup (char const *s);

BOOL
    GetLine (BOOL required);

int main (int argc, char *argv [])
{
    int
        i,
	n,
	gdriver = DETECT,
	gmode,
	errorcode;
    float
	step,
	x,
	y;
    BOOL
	found;

    no_mem_buffer = (char *) malloc (1024);

    get_programname (argv [0]);

    while (argc > 1) {
        if (! strcmp (argv [1], "-b")) {
            argc--;
            argv++;
            if (argc == 1)
                errit ("Missing argument for option -b");
            minvalue = atof (argv [1]);
            mindefined = 1;
        } else
            break;
        argc--;
        argv++;
    }

    switch (argc) {
        case 1:
            if (isatty (fileno (stdin)))
                syntax ();
            fp = stdin;
            break;
	case 2:
            fp = fopen (argv [1], "r");
	    if (! fp)
                errit ("Opening file \"%s\": %s", argv [1], strerror (errno));
            break;
	default:
            syntax ();
    }

    while (GetLine (FALSE)) {
        if (used == max) {
            max += 256;
            cl = (CLUSTER *) s_realloc (cl, max * sizeof (CLUSTER));
        }
        if (sscanf (buffer, "%i %f%n", &(cl [used].index), &(cl [used].value), &i) < 2)
            errit ("Syntax error at line %i: \"%s\"", inputline, buffer);
        while (buffer [i] && isspace ((unsigned char) buffer [i]))
            i++;
	if (buffer [i] && buffer [i] != '#')
	    cl [used].text = s_strdup (buffer + i);
	else
            cl [used].text = NULL;
        for (n = 0; n < 2; n++) {
            GetLine (TRUE);
            switch (buffer [0]) {
  	        case 'l':
                case 'L':
                    cl [used].node [n] = LBL;
                    for (i = 1; buffer [i]; i++)
                        if (! isspace ((unsigned char) buffer [i]))
                            break;
                    if (strlen (buffer + i) > maxlabel)
                        maxlabel = strlen (buffer + i);
		    cl [used].n [n].label = s_strdup (buffer + i);
                    break;
		case 'c':
		case 'C':
                    cl [used].node [n] = CLS;
                    if (sscanf (buffer + 1, "%i", &(cl [used].n [n].cluster)) != 1)
                        errit ("Missing cluster number at line %i", inputline);
                    break;
		default:
                    errit ("Syntax error at line %i: \"%s\"", inputline, buffer);
	    }
        }
	used++;
    }

    if (argc == 2)
        fclose (fp);

    if (!used)
        errit ("No data");

    registerbgidriver (EGAVGA_driver);
    initgraph (&gdriver, &gmode, getenv ("BGI"));
    errorcode = graphresult();
    if (errorcode != grOk) {
	errit (
	    "%s\n\n"
	    "Environment variable BGI can be used to point to\n"
	    "directory where graphic driver files are to be found",
	    grapherrormsg (errorcode)
	);
    }
    in_graph = TRUE;
    settextjustify (RIGHT_TEXT, CENTER_TEXT);

    top = cl [0].index;
    maxvalue = cl [0].value;
    do {
        found = FALSE;
        for (i = 1; i < used; i++)
            if ((cl [i].node [0] == CLS && cl [i].n [0].cluster == top) ||
                (cl [i].node [1] == CLS && cl [i].n [1].cluster == top)
            ) {
                top = cl [i].index;
                maxvalue = cl [i].value;
                found = TRUE;
                break;
	    }
    } while (found);

    if (! mindefined) {
        for (i = 0; i < used; i++)
            if (cl [i].value < minvalue)
                minvalue = cl [i].value;
        if (minvalue > 0)
            minvalue = 0;
    }

    step = pow (10, ceil (log10 (maxvalue - minvalue)) - 1);
    if ((maxvalue - minvalue) / step > 6.0)
	step *= 2.0;
    else if ((maxvalue - minvalue) / step < 3.0)
	step *= 0.5;

    sprintf (buffer, "%*s", maxlabel, "");
    xmin = textwidth (buffer) + 6;
    xd = ((float)(getmaxx () - xmin)) / (maxvalue - minvalue);
    xmin -= xd * minvalue;
    yd = getmaxy () / (((used + 2) > 10) ? (used + 2) : 10);
    ymin = yd / 2;

    setcolor (1);
    for (x = 0; x <= maxvalue; x += (step / 2.0))
        if (x >= minvalue)
            line (xmin + x * xd, 0, xmin + x * xd, getmaxy ());
    for (x = -step / 2.0; x >= minvalue; x -= (step / 2.0))
        if (x <= maxvalue)
            line (xmin + x * xd, 0, xmin + x * xd, getmaxy ());
    setcolor (getmaxcolor ());

    process (top, &x, &y);

    settextjustify (CENTER_TEXT, CENTER_TEXT);
    for (x = 0; x < maxvalue; x += step)
        if (x >= minvalue) {
            sprintf (buffer, "%g", x);
            outtextxy (xmin + x * xd, ymin, buffer);
        }
    for (x = -step; x >= minvalue; x -= step)
        if (x <= maxvalue) {
            sprintf (buffer, "%g", x);
            outtextxy (xmin + x * xd, ymin, buffer);
        }

    _bios_keybrd (_KEYBRD_READ);
    closegraph ();
    in_graph = FALSE;

    return 0;
}

void process (int n, float *x, float *y)
{
    int
	i,
	j;
    float
	xx [2],
	yy [2];

    for (i = 0; i < used; i++)
	if (cl [i].index == n)
	    break;

    if (i == used)
	errit ("Missing cluster number %i", n);

    for (j = 0; j < 2; j++) {
	if (cl [i].node [j] == CLS)
	    process (cl [i].n [j].cluster, &(xx [j]), &(yy [j]));
	else {
            outtextxy (xmin + (minvalue * xd) - 4, ymin, cl [i].n [j].label);
            xx [j] = xmin + (minvalue * xd);
	    yy [j] = ymin;
	    ymin += yd;
	}
    }

    *x = xmin + cl [i].value * xd;
    *y = (yy [0] + yy [1]) / 2.0;

    line (xx [0], yy [0], *x, yy [0]);
    line (xx [1], yy [1], *x, yy [1]);
    line (*x, yy [0], *x, yy [1]);

    if (cl [i].text) {
	settextjustify (LEFT_TEXT, BOTTOM_TEXT);
	outtextxy (*x + 4, (yy [0] > yy [1]) ? yy [0] : yy [1], cl [i].text);
	settextjustify (RIGHT_TEXT, CENTER_TEXT);
    }
}

BOOL GetLine (BOOL required)
/* Lees een regel in
 * Plaats in buffer
 * Negeer lege regels en regels die beginnen met #
 */
{
    int
        i;

    for (;;) {
        if (fgets (buffer, BUFSIZE, fp) == NULL) {
            if (required)
                errit ("Unexpected end of file");
            else
                return FALSE;
        }
        inputline++;
        i = strlen (buffer);
        while (i && isspace ((unsigned char) buffer [i - 1]))
            buffer [--i] = '\0';
        i = 0;
        while (buffer [i] && isspace ((unsigned char) buffer [i]))
            i++;
        if (buffer [i] == '#')
            continue;
	if (buffer [i]) {
            memmove (buffer, buffer + i, strlen (buffer) + 1);
            return TRUE;
        }
    }
}

void *s_malloc (size_t size)
{
    void
        *p;

    p = malloc (size);
    if (! p) {
        free (no_mem_buffer);
        errit (Out_of_memory);
    }
    return p;
}

void *s_realloc (void *block, size_t size)
{
    void
        *p;

    p = realloc (block, size);
    if (! p) {
        free (no_mem_buffer);
        errit (Out_of_memory);
    }
    return p;
}

char *s_strdup (char const *s)
{
    char
        *s1;

    if (s) {
        s1 = (char *) s_malloc (strlen (s) + 1);
        strcpy (s1, s);
    } else {
        s1 = (char *) s_malloc (1);
        s1 [0] = '\0';
    }
    return s1;
}

void errit (char const *format, ...)
{
    va_list
	list;

    if (in_graph) {
	closegraph ();
	in_graph = FALSE;
    }

    fprintf (stderr, "\nError %s: ", programname);

    va_start (list, format);
    vfprintf (stderr, format, list);

    fprintf (stderr, "\n\n");

    exit (1);
}

void get_programname (char const *argv0)
{
#ifdef __MSDOS__
    char
        name [MAXFILE];
    fnsplit (argv0, NULL, NULL, name, NULL);
    programname = strdup (name);
#else   /* unix */
    char
        *p;
    p = strrchr (argv0, '/');
    if (p)
        programname = strdup (p + 1);
    else
        programname = strdup (argv0);
#endif    
}

void syntax ()
{
    printf (
        "\nDendrogram Viewer, (c) P. Kleiweg 1997, 1998, 1999, 2002\n"
        "\nUsage: %s [-b float] [cluster file]\n\n"
        "\t-b : base offset, may be negative\n"
        "\t     (default minimum of 0.0 and smallest value in cluster file)\n\n",
        programname
    );
    puts (
        "# Example cluster file\n"
        "1 .12\n"
        "L Norwegian\n"
        "L Swedish\n"
        "2 .15\n"
        "C 1\n"
        "L Danish\n"
        "3 .3\n"
        "L Dutch\n"
        "L German\n"
        "4 .35 Nordic group\n"
        "L Icelandic\n"
        "C 2\n"
        "5 .7\n"
        "C 4\n"
        "C 3\n"
    );
    exit (0);
}
