#ifndef _plan9_
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#else
#include <u.h>
#include <libc.h>
#include <stdio.h>
#endif
#include <config.h>
#include <misc-f.h>
#include "xshl.h"

#define XSHL_ENDLINE (65536)
#define XSHL_ENDPARAGRAPH (65536*2)
#define XSHL_NEWSTART (65536*4)
#define XSHL_SMALL (65536*8)
#define XSHL_SKIPBLANK (65536*16)
#define XSHL_COMMAND (65536*32)
#define XSHL_BLANK (65536*64)
#define XSHL_CLEARATLINE (65536*128)

#define XSHL_AUTOCLEAN (XSHL_ENDLINE|XSHL_ENDPARAGRAPH|XSHL_NEWSTART|XSHL_SKIPBLANK|XSHL_COMMAND|XSHL_BLANK)


#define ALL (~0)
const static struct tag {
    const char *name;
    int andflagenable;
    int orflagenable;
    int andflagdisable;
    int orflagdisable;
} tags[] = {
    {
    "p", ALL, XSHL_SKIPBLANK | XSHL_ENDPARAGRAPH, ALL,
	    XSHL_SKIPBLANK | XSHL_ENDPARAGRAPH}
    , {
    "head", ALL,
	    XSHL_SKIPBLANK | XSHL_BIG | XSHL_CENTERALIGN |
	    XSHL_ENDPARAGRAPH, ~(XSHL_BIG | XSHL_CENTERALIGN),
	    XSHL_SKIPBLANK | XSHL_ENDPARAGRAPH | XSHL_ENDLINE}
    , {
    "emph", ALL, XSHL_EMPH, ~XSHL_EMPH, 0}
    , {
    "br", ALL, XSHL_SKIPBLANK | XSHL_ENDLINE, ALL, 0}
    , {
    "white", ~XSHL_COLORMASK, XSHL_WHITE, ~XSHL_WHITE, 0}
    , {
    "red", ~XSHL_COLORMASK, XSHL_RED, ~XSHL_RED, 0}
    , {
    "black", ~XSHL_COLORMASK, XSHL_BLACK, ~XSHL_BLACK, 0}
    , {
    "right", ALL, XSHL_SKIPBLANK | XSHL_RIGHTALIGN | XSHL_ENDLINE,
	    ~XSHL_RIGHTALIGN, XSHL_SKIPBLANK | XSHL_ENDLINE}
    , {
    "center", ALL, XSHL_SKIPBLANK | XSHL_CENTERALIGN | XSHL_ENDLINE,
	    ~XSHL_CENTERALIGN, XSHL_SKIPBLANK | XSHL_ENDLINE}
    , {
    "tt", ALL, XSHL_MONOSPACE, ~XSHL_MONOSPACE, 0}
    , {
    "dl", ALL, XSHL_SKIPBLANK | XSHL_ENDLINE,
	    ~(XSHL_SMALL | XSHL_EMPH), XSHL_SKIPBLANK | XSHL_ENDLINE}
    , {
    "dt", ~XSHL_SMALL, XSHL_SKIPBLANK | XSHL_ENDLINE | XSHL_EMPH, ALL,
	    0}
    , {
    "dd", ~XSHL_EMPH, XSHL_SKIPBLANK | XSHL_NEWSTART | XSHL_SMALL, ALL,
	    0}
    , {
    NULL}
};

#define MAXINPUT 256

#define SMALLSKIP (4*spacewidth)

struct boxitem {
    char *text;
    struct xshl_context c;
    int xpos;
    int width;
    int height;
    struct boxitem *next;
};
static void freebox(struct boxitem *box)
{
    if (box->c.linktext != NULL)
	free(box->c.linktext);
    free(box->text);
    free(box);
}

static struct xshl_item *pack(struct boxitem *first, struct boxitem *last,
			      int *collectedflags, int width)
{
    struct xshl_item *f = NULL;
    struct xshl_item *l = NULL;
    struct xshl_item *item;
    struct boxitem *curr = first, *ncurr;
    int end = 0;
    char text[256];
    int collected = 0;
    while (curr != last) {
	if (curr->text[0] == 0) {
	    ncurr = curr->next;
	    freebox(curr);
	    curr = ncurr;
	    continue;
	}
	strcpy(text, curr->text);
	item = (struct xshl_item *) malloc(sizeof(struct xshl_item));
	if (item == NULL)
	    return NULL;
	item->x = curr->xpos;
	item->width = curr->width;
	item->c = curr->c;
	if (curr->c.linktext != NULL)
	    curr->c.linktext = mystrdup(curr->c.linktext);
	collected |= item->c.flags;
	ncurr = curr->next;
	freebox(curr);
	curr = ncurr;
	while (curr != last &&
	       curr->xpos == item->x + item->width &&
	       (curr->c.flags & 0xffff) == (item->c.flags & 0xffff)
	       &&
	       ((curr->c.linktext == NULL && item->c.linktext == NULL) ||
		(curr->c.linktext != NULL && item->c.linktext != NULL &&
		 !strcmp(curr->c.linktext, item->c.linktext))))
	{
	    strcat(text, curr->text);
	    item->width += curr->width;
	    collected |= item->c.flags;
	    ncurr = curr->next;
	    freebox(curr);
	    curr = ncurr;
	}
	item->text = mystrdup(text);
	item->next = NULL;
	if (l != NULL)
	    l->next = item;
	else
	    f = item;
	l = item;
	end = item->x + item->width;
    }
    *collectedflags = collected;
    if (collected & (XSHL_CENTERALIGN | XSHL_RIGHTALIGN)) {
	if (collected & XSHL_RIGHTALIGN)
	    end = width - end;
	else
	    end = (width - end) / 2;
	item = f;
	while (item != NULL) {
	    item->x += end;
	    item = item->next;
	}
    }
    return (f);
}

#ifndef isspace
#define isspace(c)  ((c)==' '||(c)=='\t'||(c)=='\n')
#endif
static char xshllink[32];
static int flags;
static struct boxitem *xshl_readbox(void *data, int (*get) (void *))
{
    char inputbuf[256];
    int c;
    int i;
    char command[16];
    char parameter[32];
    if (flags & XSHL_BLANK) {
	struct boxitem *box = (struct boxitem *) malloc(sizeof(*box));
	box->width = 0;
	box->next = NULL;
	box->height = flags & XSHL_BIG ? 0 : 1;
	box->text = mystrdup(" ");
	box->c.flags = flags | XSHL_CLEARATLINE;
	box->c.linktext = NULL;
	if (xshllink[0] != 0)
	    box->c.linktext = mystrdup(xshllink);
	flags &= ~XSHL_AUTOCLEAN;
	flags |= XSHL_SKIPBLANK;
	return (box);
    }
    if (flags & XSHL_COMMAND) {
	int i = 0;
	flags &= ~XSHL_COMMAND;
	parameter[0] = 0;
	do {
	    c = command[i] = get(data);
	    if (i < 15)
		i++;
	}
	while (c && c != '>' && !isspace(c));
	command[i - 1] = 0;
	if (c != '>' && c) {
	    do {
		c = get(data);
	    }
	    while (c && c != '>' && isspace(c));
	    if (c && c != '>') {
		i = 1;
		parameter[0] = c;
		do {
		    c = parameter[i] = get(data);
		    if (i < 31)
			i++;
		}
		while (c && c != '>' && !isspace(c));
		parameter[i - 1] = 0;
		if (isspace(c)) {
		    do {
			c = get(data);
		    }
		    while (c && c != '>' && isspace(c));
		}
	    }
	}
	{
	    int i;
	    int disabled = 0;
	    if (command[0] == '/')
		disabled = 1;
	    for (i = 0;
		 tags[i].name != NULL
		 && strcmp(tags[i].name, command + disabled); i++);
	    if (tags[i].name != NULL) {
		if (disabled) {
		    flags &= tags[i].andflagdisable;
		    flags |= tags[i].orflagdisable;
		} else {
		    flags &= tags[i].andflagenable;
		    flags |= tags[i].orflagenable;
		}
	    } else {
		if (!strcmp(command + disabled, "a")
		    || !strcmp(command + disabled, "tutor")) {
		    if (disabled)
			xshllink[0] = 0;
		    else
			strcpy(xshllink, parameter);
		} else
		    while (c != '>')
			c = get(data);
	    }
	}
	if (c == '>')
	    c = get(data);
    } else
	c = get(data);
    if (!c) {
	return NULL;
    }
    if (flags & XSHL_SKIPBLANK) {
	while (isspace(c)) {
	    c = get(data);
	    if (!c) {
		return NULL;
	    }
	}
	flags &= ~XSHL_SKIPBLANK;
    }
    i = 0;
    inputbuf[i++] = c;
    while (c && c != '<' && !isspace(c)) {
	c = get(data);
	inputbuf[i++] = c;
	if (i > 255)
	    i = 255;
    }
    inputbuf[i - 1] = 0;
    if (i == 1 && !c) {
	return NULL;
    }
    if (i == 1 && isspace(c)) {
	flags |= XSHL_BLANK;
	return xshl_readbox(data, get);
    }
    if (i == 1 && c == '<') {
	flags |= XSHL_COMMAND;
	return xshl_readbox(data, get);
    }
    {
	struct boxitem *box = (struct boxitem *) malloc(sizeof(*box));
	box->width = 0;
	box->next = NULL;
	box->height = flags & XSHL_BIG ? 0 : 1;
	box->text = mystrdup(inputbuf);
	box->c.flags = flags | XSHL_CLEARATLINE;
	box->c.linktext = NULL;
	if (xshllink[0] != 0)
	    box->c.linktext = mystrdup(xshllink);
	flags &= ~XSHL_AUTOCLEAN;
	if (isspace(c))
	    flags |= XSHL_BLANK;
	if (c == '<')
	    flags |= XSHL_COMMAND;
	return (box);
    }
    /*We are at the first word */
}

struct xshl_line *xshl_interpret(void *data, int (*get) (void *),
				 int width, int getwidth(void *, int flags,
							 const char *text),
				 int startflags, int smallheight,
				 int bigheight)
{
    int spacewidth = getwidth(data, 0, " ");
    int cflags;
    struct boxitem *first = NULL;
    struct boxitem *last = NULL;
    struct boxitem *item;
    struct boxitem *lastword = NULL;
    int maxlines = 200;
    struct xshl_line *lines =
	(struct xshl_line *) malloc(maxlines * sizeof(*lines));
    int nlines = 0;
    int ypos = 0;
    flags = startflags | XSHL_SKIPBLANK;
    xshllink[0] = 0;
    while ((item = xshl_readbox(data, get)) != NULL) {
	if (last == NULL) {
	    if (item->text[0] == ' ' && !item->text[1]) {
		freebox(item);
		continue;
	    }
	    lastword = NULL;
	    first = item, item->xpos =
		item->c.flags & XSHL_SMALL ? SMALLSKIP : 0;
	} else {
	    if (item->text[0] == ' ' && !item->text[1]) {
		lastword = item;
	    }
	    last->next = item, item->xpos = last->xpos + last->width;
	}
	last = item;
	item->width = getwidth(data, item->c.flags, item->text);
	if (item->c.flags & (XSHL_ENDLINE | XSHL_ENDPARAGRAPH)
	    || ((item->c.flags & XSHL_NEWSTART)
		&& item->xpos + spacewidth > SMALLSKIP)) {
	    if (nlines > maxlines - 1)
		maxlines *= 2, lines =
		    (struct xshl_line *) realloc(lines,
						 (maxlines) *
						 sizeof(*lines));
	    if (first != NULL) {
		lines[nlines].first = pack(first, last, &cflags, width);
		lines[nlines].y = ypos;
		nlines++;
		if (ypos & XSHL_BIG)
		    ypos += bigheight;
		else
		    ypos += smallheight;
	    }
	    if (item->c.flags & (XSHL_ENDPARAGRAPH))
		ypos += smallheight;
	    first = last;
	    item->xpos =
		item->c.flags & item->c.flags & XSHL_SMALL ? SMALLSKIP : 0;
	    lastword = NULL;
	} else if (item->c.flags & XSHL_NEWSTART)
	    item->xpos = SMALLSKIP;
	if (item->xpos + item->width > width) {
	    if (lastword == NULL) {
		item = first;
		while (item != NULL) {
		    struct boxitem *c = item->next;
		    freebox(item);
		    item = c;
		}
		first = last = NULL;
	    } else {
		int xpos;
		if (nlines > maxlines - 1)
		    maxlines *= 2, lines =
			(struct xshl_line *) realloc(lines,
						     (maxlines) *
						     sizeof(*lines));
		lines[nlines].first =
		    pack(first, lastword, &cflags, width);
		lines[nlines].y = ypos;
		nlines++;
		if (ypos & XSHL_BIG)
		    ypos += bigheight;
		else
		    ypos += smallheight;
		if (lastword != NULL)
		    first = lastword->next;
		else
		    first = NULL;
		item = first;
		if (item != NULL) {
		    xpos =
			item->c.flags & item->c.
			flags & XSHL_SMALL ? SMALLSKIP : 0;
		    while (item != NULL) {
			item->xpos = xpos;
			xpos += item->width;
			item = item->next;
		    }
		}
		freebox(lastword);
		lastword = NULL;
		if (first == NULL)
		    last = NULL;
	    }
	}
    }
    if (last != NULL) {
	lines[nlines].y = ypos;
	lines[nlines].first = pack(first, last->next, &cflags, width);
    }
    nlines++;
    lines[nlines].y = -1;
    lines =
	(struct xshl_line *) realloc(lines, (nlines + 2) * sizeof(*lines));
    return (lines);
}


int xshl_textlen(void *data, int flags, const char *text)
{
    return ((int) strlen(text));
}

void xshl_print(int startskip, struct xshl_line *lines)
{
    int i = 0;
    int y = 0;
    while (lines[i].y >= 0) {
	int xpos = -startskip;
	struct xshl_item *item;
	while (y < lines[i].y) {
	    putc('\n', stdout), y++;
	}
	item = lines[i].first;
	while (item) {
	    while (xpos < item->x) {
		putc(' ', stdout), xpos++;
	    }
	    xpos += item->width;
	    item = item->next;
	}
	i++;
    }
}

void xshl_free(struct xshl_line *lines)
{
    int i = 0;
    while (lines[i].y >= 0) {
	struct xshl_item *item, *nextitem;
	item = lines[i].first;
	while (item) {
	    nextitem = item->next;
	    if (item->c.linktext != NULL)
		free(item->c.linktext);
	    free(item->text);
	    free(item);
	    item = nextitem;
	}
	i++;
    }
    free(lines);
}
