/*
 * keywords.c: keep track of all cross-reference keywords
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "buttress.h"

#define heapparent(x) (((x)+1)/2-1)
#define heaplchild(x) (2*(x)+1)
#define heaprchild(x) (2*(x)+2)

#define key(x) ( kl->keys[(x)] )

#define greater(x,y) ( ustrcmp(key(x)->key, key(y)->key) > 0 )
#define swap(x,y) do { keyword *t=key(x); key(x)=key(y); key(y)=t; } while(0)

static void heap_add(keywordlist *kl, keyword *key) {
    int p;
    if (kl->nkeywords >= kl->size) {
	kl->size = kl->nkeywords + 128;
	kl->keys = resize(kl->keys, kl->size);
    }
    p = kl->nkeywords++;
    kl->keys[p] = key;
    while (heapparent(p) >= 0 && greater(p, heapparent(p))) {
	swap(p, heapparent(p));
	p = heapparent(p);
    }
}

static void heap_sort(keywordlist *kl) {
    int i, j;

    kl->size = kl->nkeywords;
    kl->keys = resize(kl->keys, kl->size);

    i = kl->nkeywords;
    while (i > 1) {
	i--;
	swap(0, i);		       /* put greatest at end */
	j = 0;
	while (1) {
	    int left = heaplchild(j), right = heaprchild(j);
	    if (left >= i || !greater(left, j))
		left = -1;
	    if (right >= i || !greater(right, j))
		right = -1;
	    if (left >= 0 && right >= 0) {
		if (greater(left, right))
		    right = -1;
		else
		    left = -1;
	    }
	    if (left >= 0) { swap(j, left); j = left; }
	    else if (right >= 0) { swap(j, right); j = right; }
	    else break;
	}
    }
    /* FIXME: check for duplicate keys; do what about them? */
}

keyword *kw_lookup(keywordlist *kl, wchar_t *str) {
    int i, j, k, cmp;

    i = -1;
    j = kl->nkeywords;
    while (j-i > 1) {
	k = (i+j)/2;
	cmp = ustrcmp(str, kl->keys[k]->key);
	if (cmp < 0)
	    j = k;
	else if (cmp > 0)
	    i = k;
	else
	    return kl->keys[k];
    }
    return NULL;
}

/*
 * This function reads through source form and collects the
 * keywords. They get collected in a heap, sorted by Unicode
 * collation, last at the top (so that we can Heapsort them when we
 * finish).
 */
keywordlist *get_keywords(paragraph *source) {
    int errors = FALSE;
    keywordlist *kl = mknew(keywordlist);
    numberstate *n = number_init();
    int prevpara = para_NotParaType;

    number_cfg(n, source);

    kl->nkeywords = 0;
    kl->size = 0;
    kl->keys = NULL;
    kl->nlooseends = kl->looseendssize = 0;
    kl->looseends = NULL;
    for (; source; source = source->next) {
	wchar_t *p, *q;
	p = q = source->keyword;

	/*
	 * Look for the section type override (`example',
	 * `question' or whatever - to replace `chapter' or
	 * `section' on a per-section basis).
	 */
	if (q) {
	    q = uadv(q);	       /* point q at the word beyond */
	    if (!*q) q = NULL;
	}
	
	/*
	 * Number the chapter / section / list-item / whatever.
	 */
	source->kwtext = number_mktext(n, source->type, source->aux, q,
				       prevpara, &source->kwtext2,
				       source->fpos, &errors);
	prevpara = source->type;

	if (p && *p) {
	    if (source->kwtext || source->type == para_Biblio) {
		keyword *kw = mknew(keyword);
		kw->key = p;
		kw->text = source->kwtext;
		kw->para = source;
		heap_add(kl, kw);
	    }
	} else {
	    if (kl->nlooseends >= kl->looseendssize) {
		kl->looseendssize = kl->nlooseends + 32;
		kl->looseends = resize(kl->looseends, kl->looseendssize);
	    }
	    kl->looseends[kl->nlooseends++] = source->kwtext;
	}
    }

    number_free(n);

    if (errors) {
	free_keywords(kl);
	return NULL;
    }

    heap_sort(kl);

    return kl;
}

void free_keywords(keywordlist *kl) {
    int i;
    while (kl->nlooseends)
	free_word_list(kl->looseends[--kl->nlooseends]);
    sfree(kl->looseends);
    for (i = 0; i < kl->nkeywords; i++) {
	if (kl->keys[i])
	    free_word_list(kl->keys[i]->text);
	sfree(kl->keys[i]);
    }
    sfree(kl->keys);
    sfree(kl);
}

void subst_keywords(paragraph *source, keywordlist *kl) {
    for (; source; source = source->next) {
	word *ptr;
	for (ptr = source->words; ptr; ptr = ptr->next) {
	    if (ptr->type == word_UpperXref ||
		ptr->type == word_LowerXref) {
		keyword *kw;
		word **endptr, *close, *subst;

		kw = kw_lookup(kl, ptr->text);
		if (!kw) {
		    error(err_nosuchkw, &ptr->fpos, ptr->text);
		    subst = NULL;
		} else
		    subst = dup_word_list(kw->text);

		if (subst && ptr->type == word_LowerXref &&
		    kw->para->type != para_Biblio &&
		    kw->para->type != para_BiblioCited)
		    ustrlow(subst->text);

		close = mknew(word);
		close->text = NULL;
		close->alt = NULL;
		close->type = word_XrefEnd;
		close->fpos = ptr->fpos;

		close->next = ptr->next;
		ptr->next = subst;

		for (endptr = &ptr->next; *endptr; endptr = &(*endptr)->next)
		    (*endptr)->fpos = ptr->fpos;

		*endptr = close;
		ptr = close;
	    }
	}
    }
}
