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

struct node
{
    char name[32];
    char title[32];
    char body[256];
};

struct cmd
{
    char nodes[40];
    char name[32];
    char args[80];
    char text[256];
};

struct desc
{
    char cmds[40];
    int begin;
    int len;
};

char header[1024];

#define MAXNODES    50
#define MAXCMDS     500
#define MAXDESCS    100

struct node * node;
struct cmd * cmd;
struct desc * desc;

struct cmd * cmdtable[MAXCMDS];

int nnodes, ncmds, ndescs, lineno;

int cmp_nodes (const struct node * n1, const struct node * n2)
{
    return (strcmp (n1->title, n2->title));
}

int cmp_cmds (const struct cmd * c1, const struct cmd * c2)
{
    return (strcmp (c1->name, c2->name));
}

int cmp_cmdtable (const struct cmd ** c1, const struct cmd ** c2)
{
    return (strcmp ((*c1)->name, (*c2)->name));
}

void add_node (char * line)
{
    char * name, * longname;

    if (nnodes >= MAXNODES) {
	fprintf (stderr, "too many nodes!\n", __LINE__);
	exit (10);
    }

    line += 5;
    while (isspace (*line)) line ++;

    name = strtok (line, "\t ");
    longname = strtok (NULL, "\n");

    strcpy (node[nnodes].name, name);
    strcpy (node[nnodes].title, longname);
    node[nnodes].body[0] = 0;

    nnodes ++;
} /* add_node */


void add_cmd (char * line)
{
    char * nodes, * name, * args;
    int t;

    if (ncmds >= MAXCMDS) {
	fprintf (stderr, "too many cmds!\n", __LINE__);
	exit (10);
    }

    line += 4;
    while (isspace (*line)) line ++;
    if (!*line)
    {
	fprintf (stderr, "#cmd: unexpected EOL in line %d (%d)\n", lineno, __LINE__);
	exit (10);
    }

    nodes = line;
    while (!isspace (*line) && *line) line ++;

    if (!*line)
    {
	fprintf (stderr, "#cmd: unexpected EOL in line %d (%d)\n", lineno, __LINE__);
	exit (10);
    }

    *line = 0;
    line ++;

    while (isspace (*line)) line ++;
    if (!*line)
    {
	fprintf (stderr, "#cmd: unexpected EOL in line %d (%d)\n", lineno, __LINE__);
	exit (10);
    }

    name = line;
    while (!isspace (*line) && *line) line ++;

    if (!*line)
    {
	fprintf (stderr, "#cmd: unexpected EOL in line %d (%d)\n", lineno, __LINE__);
	exit (10);
    }

    *line = 0;
    line ++;

    while (isspace (*line) && *line) line ++;
    if (!*line)
    {
	fprintf (stderr, "#cmd: unexpected EOL in line %d (%d)\n", lineno, __LINE__);
	exit (10);
    }

    if (*line != '-')
    {
	args = line;

	while (*line)
	{
	    if (isspace(*line) && line[1] == '-')
		break;

	    line ++;
	}

	if (!*line)
	{
	    fprintf (stderr, "#cmd: unexpected EOL in line %d (%d)\n", lineno, __LINE__);
	    exit (10);
	}

	*line = 0;

	for (t=strlen(args)-1; t>=0; t--)
	    if (!isspace (args[t]))
		break;

	args[t+1] = 0;
	line += 2;
    }
    else
    {
	args = line;
	*line = 0;
	line ++;
    }

    while (isspace (*line)) line ++;
    if (!*line)
    {
	fprintf (stderr, "#cmd: unexpected EOL in line %d (%d)\n", lineno, __LINE__);
	exit (10);
    }

    strcpy (cmd[ncmds].nodes, nodes);
    strcpy (cmd[ncmds].name, name);
    strcpy (cmd[ncmds].args, args);
    strcpy (cmd[ncmds].text, line);

    ncmds ++;
} /* add_node */

void add_desc (char * line, FILE * fh)
{
    int len = strlen (line);

    line[len-1] = 0;

    if (ndescs >= MAXNODES) {
	fprintf (stderr, "too many nodes!\n", __LINE__);
	exit (10);
    }

    line += 5;
    while (isspace (*line)) line ++;

    strcpy (desc[ndescs].cmds, line);
    desc[ndescs].begin = ftell (fh);

    if (ndescs)
	desc[ndescs-1].len = desc[ndescs].begin - len - desc[ndescs-1].begin;

    ndescs ++;
} /* add_desc */

int isinlist (char * elem, char * list)
{
    char * pos;
    int len;

    pos = strstr (list, elem);
    len = strlen (elem);

    /* is inside a list, if it is the first word or the last one or
       delimited by ',' */
    return (pos && (pos == list || pos[-1] == ',') && !isalpha(pos[len]));
} /* isinlist */

char fmt_buf[100];
#define MAXWIDTH 78
#define INDENT 20

void fmt_cmd (FILE * out, struct cmd * cmd)
{
    int t, len, i;
    char * last_space, * curr, * text;

    for (t=0; t<ndescs; t++)
	if (isinlist (cmd->name, desc[t].cmds))
	    break;

    if (t != ndescs) /* found */
    {
	fprintf (out, "@{\" %s \" LINK \"desc%d\"}", cmd->name, t);
	len = strlen (cmd->name)+3;

	if (cmd->args[0])
	{
	    fprintf (out, " @{B}%s@{UB}", cmd->args);
	    len += strlen (cmd->args)+1;
	}
    }
    else
    {
	fprintf (out, "@{B}%s", cmd->name);
	len = strlen (cmd->name);

	if (cmd->args[0])
	{
	    fprintf (out, " %s", cmd->args);
	    len += strlen (cmd->args)+1;
	}

	fprintf (out, "@{UB}");
    }

    if (len >= INDENT)
    {
	fputc (' ', out);
	len ++;
    }
    else while (len < INDENT)
    {
	fputc (' ', out);
	len ++;
    }

    last_space = NULL;
    curr = fmt_buf;
    text = cmd->text;

    while (*text)
    {
	if (len > MAXWIDTH)
	{
	    *curr = 0;

	    if (!last_space)
		last_space = curr;
	    else
		last_space[-1] = 0;

	    fprintf (out, "%s\n", fmt_buf);

	    strcpy (fmt_buf, last_space);

	    for (len=0; len < INDENT; len++)
		fputc (' ', out);

	    last_space = NULL;

	    i = strlen (fmt_buf);
	    curr = fmt_buf + i;
	    len += i;
	}
	else
	{
again:
	    *curr = *text ++;

	    if (isspace (*curr))
		last_space = curr+1;
	    else if (*curr == '@' && *text == '{')
	    {
		curr ++;

		while (*text != '}')
		{
		    *curr ++ = *text ++;
		}

		goto again; /* copy } too */
	    }

	    len ++;
	    curr ++;
	}
    }

    *curr = 0;

    fprintf (out, "%s", fmt_buf);
} /* fmt_cmd */


int main (int argc, char ** argv)
{
    FILE * in;
    char puffer[1024];
    char line[256];
    int llen;
    int state;
    int t, i, j, nx;

    if (!(in = fopen (argv[1], "r")) ) {
	fprintf (stderr, "file '%s' not open!\n", argv[1], __LINE__);
	exit (10);
    }

    if (!(node = malloc (sizeof (struct node) * MAXNODES)) ) {
	fprintf (stderr, "no memory!\n", __LINE__);
	exit (10);
    }

    if (!(cmd = malloc (sizeof (struct cmd) * MAXCMDS)) ) {
	fprintf (stderr, "no memory!\n", __LINE__);
	exit (10);
    }

    if (!(desc = malloc (sizeof (struct desc) * MAXDESCS)) ) {
	fprintf (stderr, "no memory!\n", __LINE__);
	exit (10);
    }

    state = 0;

    *header = 0;
    *puffer = 0;

    while (fgets (line, sizeof(line), in))
    {
	llen = strlen (line);
	lineno ++;

	switch (state)
	{
	case 0: /* header */
	    if (strncmp ("#toc", line, 4))
		strcat (header, line);
	    else
		state = 1;

	    break;

	case 1: /* parse #node */
	    if (!strncmp ("#node", line, 5))
	    {
		add_node (line);
		state = 2;
	    }

	    break;

	case 2: /* parse node-body until next node or #cmd */
	    if (!strncmp ("#node", line, 5))
	    {
		add_node (line);
		state = 2;
	    }
	    else if (!strncmp ("#cmd", line, 4))
	    {
		add_cmd (line);
		state = 3;
	    }
	    else
		strcat (node[nnodes-1].body, line);

	    break;

	case 3: /* parse #cmd until #long */
	    if (!strncmp ("#cmd", line, 4))
	    {
		add_cmd (line);
	    }
	    else if (!strncmp ("#long", line, 5))
	    {
		add_desc (line, in);
		state = 4;
	    }

	    break;

	case 4: /* parse #long until EOF */
	    if (!strncmp ("#long", line, 5))
	    {
		add_desc (line, in);
		state = 4;
	    }

	    break;
	}
    }

    if (ndescs)
	desc[ndescs-1].len = ftell (in) - llen - desc[ndescs-1].begin;

    rewind (in);

    qsort (node, nnodes, sizeof (struct node),
	    (int (*)(const void *,const void *))cmp_nodes);
    qsort (cmd, ncmds, sizeof (struct cmd),
	    (int (*)(const void *,const void *))cmp_cmds);

    fputs (header, stdout);

    llen = 0;

    for (t=0; t<nnodes; t++)
    {
	i = strlen (node[t].title);

	if (i > llen)
	    llen = i;
    }

    for (t=0; t<nnodes; t++)
	fprintf (stdout, "    @{\" %*s \" LINK \"%s\"}\n", -llen, node[t].title, node[t].name);

    fprintf (stdout, "\n@ENDNODE \"MAIN\"\n\n");

    for (t=0; t<nnodes; t++)
    {
	fprintf (stdout, "@NODE \"%s\" \"%s\"\n", node[t].name, node[t].title);
	fprintf (stdout, "@TOC \"MAIN\"\n\n");
	fputs (node[t].body, stdout);

	nx=0;

	for (i=0; i<ncmds; i++)
	    if (isinlist (node[t].name, cmd[i].nodes))
		cmdtable[nx++] = &cmd[i];

	if (nx)
	{
	    qsort (cmdtable, nx, sizeof (struct cmd *),
		    (int (*)(const void *,const void *))cmp_cmdtable);

	    for (i=0; i<nx; i++)
	    {
		fmt_cmd (stdout, cmdtable[i]);
	    }
	}
	else
	    fprintf (stderr, "Unused nodes: %s\n", node[t].name);

	fprintf (stdout, "@ENDNODE \"%s\"\n\n", node[t].name);
    }

    for (t=0; t<ndescs; t++)
    {
	sprintf (line, "desc%d", t);

	fprintf (stdout, "@NODE \"%s\" \"%s\"\n", line, desc[t].cmds);
	fprintf (stdout, "@TOC \"MAIN\"\n\n");

	for (i=0; i<ncmds; i++)
	    if (isinlist (cmd[i].name, desc[t].cmds))
		fprintf (stdout, "@{B}%s %s@{UB}\n", cmd[i].name, cmd[i].args);

	fseek (in, desc[t].begin, SEEK_SET);
	for (i=desc[t].len; i>0; i-=j)
	{
	    j = i < sizeof (puffer) ? i : sizeof (puffer);

	    j = fread (puffer, 1, j, in);
	    if (j < 0) {
		fprintf (stderr, "read error!\n", __LINE__);
		exit (10);
	    }

	    fwrite (puffer, 1, j, stdout);
	}
	/* fprintf (stdout, "insert %d bytes from %d\n", desc[t].len, desc[t].begin); */

	fprintf (stdout, "@ENDNODE \"%s\"\n\n", line);
    }

    fprintf (stdout, "@NODE \"index\" \"Index\"\n");
    fprintf (stdout, "@TOC \"MAIN\"\n\n");

    for (t=0; t<ncmds; t++)
	fmt_cmd (stdout, &cmd[t]);

    fprintf (stdout, "@ENDNODE \"index\"\n\n");

} /* main */
