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

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

static int kwcmp(void *av, void *bv)
{
    const keyword *a = (const keyword *)av;
    const keyword *b = (const keyword *)bv;
    return ustrcmp(a->key, b->key);
}

static int kwfind(void *av, void *bv)
{
    wchar_t *a = (wchar_t *)av;
    const keyword *b = (const keyword *)bv;
    return ustrcmp(a, b->key);
}

keyword *kw_lookup(keywordlist *kl, wchar_t *str) {
    return find234(kl->keys, str, kwfind);
}

/*
 * 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 = snew(keywordlist);
    numberstate *n = number_init();
    int prevpara = para_NotParaType;

    number_cfg(n, source);

    kl->size = 0;
    kl->keys = newtree234(kwcmp);
    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.
	 * This also sets up the `parent', `child' and `sibling'
	 * links.
	 */
	source->kwtext = number_mktext(n, source, q, &prevpara, &errors);

	if (p && *p) {
	    if (source->kwtext || source->type == para_Biblio) {
		keyword *kw, *ret;

		kw = snew(keyword);
		kw->key = p;
		kw->text = source->kwtext;
		kw->para = source;
		ret = add234(kl->keys, kw);
		if (ret != kw) {
		    error(err_multikw, &source->fpos, &ret->para->fpos, p);
		    sfree(kw);
		    /* FIXME: what happens to kw->text? Does it leak? */
		}
	    }
	} else {
	    if (kl->nlooseends >= kl->looseendssize) {
		kl->looseendssize = kl->nlooseends + 32;
		kl->looseends = sresize(kl->looseends, kl->looseendssize,
					word *);
	    }
	    kl->looseends[kl->nlooseends++] = source->kwtext;
	}
    }

    number_free(n);

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

    return kl;
}

void free_keywords(keywordlist *kl) {
    keyword *kw;
    while (kl->nlooseends)
	free_word_list(kl->looseends[--kl->nlooseends]);
    sfree(kl->looseends);
    while ( (kw = index234(kl->keys, 0)) != NULL) {
        delpos234(kl->keys, 0);
	free_word_list(kw->text);
	sfree(kw);
    }
    freetree234(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 = snew(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;
	    }
	}
    }
}
