/*
 *
 *  This file is part of
 *	MakeIndex - A formatter and format independent index processor
 *
 *  Copyright (C) 1998-2011 by the TeX Live project.
 *  Copyright (C) 1989 by Chen & Harrison International Systems, Inc.
 *  Copyright (C) 1988 by Olivetti Research Center
 *  Copyright (C) 1987 by Regents of the University of California
 *
 *  Author:
 *	Pehong Chen
 *	Chen & Harrison International Systems, Inc.
 *	Palo Alto, California
 *	USA
 *
 *  Contributors:
 *	Please refer to the CONTRIB file that comes with this release
 *	for a list of people who have contributed to this and/or previous
 *	release(s) of MakeIndex.
 *
 *  All rights reserved by the copyright holders.  See the copyright
 *  notice distributed with this software for a complete description of
 *  the conditions under which it is made available.
 *
 */

#include    "mkind.h"
#include    "genind.h"

#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif

static FIELD_PTR curr = NULL;
static FIELD_PTR prev = NULL;
static FIELD_PTR begin = NULL;
static FIELD_PTR the_end = NULL;
static FIELD_PTR range_ptr;
static int level = 0;
static int prev_level = 0;
static char *encap = NULL;
static char *prev_encap = NULL;
static int in_range = FALSE;
static int encap_range = FALSE;
static char buff[2 * ARGUMENT_MAX];
static char line[2 * ARGUMENT_MAX];	/* output buffer */
static int ind_lc = 0;			/* overall line count */
static int ind_ec = 0;			/* erroneous line count */
static int ind_indent;

static	void	flush_line (int print);
static	void	insert_page (void);
static	int	make_entry (int n);
static	void	make_item (const char* term);
static  unsigned char first_letter (char* term);
static	void	new_entry (void);
static	void	old_entry (void);
static	int	page_diff (struct KFIELD *a,struct KFIELD *b);
static	void	put_header (int let);
static	void	wrap_line (int print);

void
gen_ind(void)
{
    int     n;
    int     tmp_lc;

    MESSAGE1("Generating output file %s...", ind_fn);
    PUT(preamble);
    ind_lc += prelen;
    if (init_page)
	insert_page();

    /* reset counters for putting out dots */
    idx_dc = 0;
    for (n = 0; n < idx_gt; n++) {
	if (idx_key[n]->type != DUPLICATE)
	    if (make_entry(n)) {
		IDX_DOT(DOT_MAX);
	    }
    }
    tmp_lc = ind_lc;
    if (in_range) {
	curr = range_ptr;
	IND_ERROR1("Unmatched range opening operator %c.\n", idx_ropen);
    }
    prev = curr;
    flush_line(TRUE);
    PUT(delim_t);
    PUT(postamble);
    tmp_lc = ind_lc + postlen;
    if (ind_ec == 1) {
	DONE(tmp_lc, "lines written", ind_ec, "warning");
    } else {
	DONE(tmp_lc, "lines written", ind_ec, "warnings");
    }
}


static int
make_entry(int n)
{
    int     let;

    /* determine current and previous pointer */
    prev = curr;
    curr = idx_key[n];
    /* check if current entry is in range */
    if ((*curr->encap == idx_ropen) || (*curr->encap == idx_rclose))
	encap = &(curr->encap[1]);
    else
	encap = curr->encap;

    /* determine the current nesting level */
    if (n == 0) {
	prev_level = level = 0;
	let = *curr->sf[0];
	put_header(let);
	make_item(NIL);
    } else {
	prev_level = level;
	for (level = 0; level < FIELD_MAX; level++)
	    if (STRNEQ(curr->sf[level], prev->sf[level]) ||
		STRNEQ(curr->af[level], prev->af[level]))
		break;
	if (level < FIELD_MAX)
	    new_entry();

        /* Repeat test from just below to see if we are already in an
           open range.  If so, we don't want to output anything.  (It
           ends up being output as an erroneous \(.  See
           tests/nested-range-bb.tex.)  */           
	else if (! (*curr->encap == idx_ropen && in_range)) {
	    old_entry();
	}
    }

    if (*curr->encap == idx_ropen)
	if (in_range) {
	    IND_ERROR1("Extra range opening operator %c.\n", idx_ropen);
	} else {
	    in_range = TRUE;
	    range_ptr = curr;
	}
    else if (*curr->encap == idx_rclose)
	if (in_range) {
	    in_range = FALSE;
	    if (STRNEQ(&(curr->encap[1]), "") &&
		STRNEQ(prev_encap, &(curr->encap[1]))) {
IND_ERROR1("Range closing operator has an inconsistent encapsulator %s.\n",
			  &(curr->encap[1]));
	    }
	} else {
	    IND_ERROR1("Unmatched range closing operator %c.\n", idx_rclose);
	}
    else if ((*curr->encap != NUL) &&
	     STRNEQ(curr->encap, prev_encap) && in_range)
	IND_ERROR1("Inconsistent page encapsulator %s within range.\n",
		  curr->encap);
    return (1);
}


static void
make_item(const char *term)
{
    int     i;

    if (level > prev_level) {
	/* ascending level */
	if (*curr->af[level] == NUL)
	    sprintf(line, "%s%s%s", term, item_u[level], curr->sf[level]);
	else
	    sprintf(line, "%s%s%s", term, item_u[level], curr->af[level]);
	ind_lc += ilen_u[level];
    } else {
	/* same or descending level */
	if (*curr->af[level] == NUL)
	    sprintf(line, "%s%s%s", term, item_r[level], curr->sf[level]);
	else
	    sprintf(line, "%s%s%s", term, item_r[level], curr->af[level]);
	ind_lc += ilen_r[level];
    }

    i = level + 1;
    while (i < FIELD_MAX && *curr->sf[i] != NUL) {
	PUT(line);
	if (*curr->af[i] == NUL)
	    sprintf(line, "%s%s", item_x[i], curr->sf[i]);
	else
	    sprintf(line, "%s%s", item_x[i], curr->af[i]);
	ind_lc += ilen_x[i];
	level = i;		/* Added at 2.11 <brosig@gmdzi.gmd.de> */
	i++;
    }

    ind_indent = 0;
    strcat(line, delim_p[level]);
    SAVE;
}

static unsigned char
first_letter(char *term)
{
    if (thai_sort)
        return strchr("\340\341\342\343\344", term[0]) ? term[1] : term[0];

    return TOLOWER(term[0]);
}

static void
new_entry(void)
{
    int let = -1; /* see comment below */
    FIELD_PTR ptr;
#ifdef HAVE_SETLOCALE
    char *prev_locale;
    prev_locale = setlocale(LC_CTYPE, NULL);
    setlocale(LC_CTYPE, "");
#endif

    if (in_range) {
	ptr = curr;
	curr = range_ptr;
	IND_ERROR1("Unmatched range opening operator %c.\n", idx_ropen);
	in_range = FALSE;
	curr = ptr;
    }
    flush_line(TRUE);

    /* beginning of a new group? */

    if (((curr->group != ALPHA) && (curr->group != prev->group) &&
	 (prev->group == SYMBOL)) ||
	((curr->group == ALPHA) &&
	 ((unsigned char)(let = first_letter(curr->sf[0])) 
	  != first_letter(prev->sf[0]))) ||
	(german_sort &&
	 (curr->group != ALPHA) && (prev->group == ALPHA))) {
	PUT(delim_t);
	PUT(group_skip);
	ind_lc += skiplen;
	/* beginning of a new letter? */
	/* Although we may use let unassigned here, it doesn't matter,
	   because put_header will not use its arg except in case
	   ALPHA, when it is assigned in the midst of condition above.
	   Let's not perturb the logic, just initialize it (above) to
	   avoid the warning.  */
	put_header(let);
	make_item(NIL);
    } else
	make_item(delim_t);
#ifdef HAVE_SETLOCALE
    setlocale(LC_CTYPE, prev_locale);
#endif
}


static void
old_entry(void)
{
    int     diff;

    /* current entry identical to previous one: append pages */
    diff = page_diff(the_end, curr);

    if ((prev->type == curr->type) && (diff != -1) &&
	(((diff == 0) && (prev_encap != NULL) && STREQ(encap, prev_encap)) ||
	 (merge_page && (diff == 1) &&
	  (prev_encap != NULL) && STREQ(encap, prev_encap)) ||
	 in_range)) {
	the_end = curr;
	/* extract in-range encaps out */
	if (in_range &&
	    (*curr->encap != NUL) &&
	    (*curr->encap != idx_rclose) &&
	    STRNEQ(curr->encap, prev_encap)) {
	    sprintf(buff, "%s%s%s%s%s", encap_p, curr->encap,
		    encap_i, curr->lpg, encap_s);
	    wrap_line(FALSE);
	}
	if (in_range)
	    encap_range = TRUE;
    } else {
	flush_line(FALSE);
	if ((diff == 0) && (prev->type == curr->type)) {
IND_ERROR(
"Conflicting entries: multiple encaps for the same page under same key.\n");
	} else if (in_range && (prev->type != curr->type)) {
IND_ERROR(
"Illegal range formation: starting & ending pages are of different types.\n");
	} else if (in_range && (diff == -1)) {
IND_ERROR(
"Illegal range formation: starting & ending pages cross chap/sec breaks.\n");
	}
	SAVE;
    }
}


static int
page_diff(FIELD_PTR a,FIELD_PTR b)
{
    short   i;

    if (a->count != b->count)
	return (-1);
    for (i = 0; i < a->count - 1; i++)
	if (a->npg[i] != b->npg[i])
	    return (-1);
    return (b->npg[b->count - 1] - a->npg[a->count - 1]);
}

static void
put_header(int let)
{
    if (headings_flag)
    {
	PUT(heading_pre);
	ind_lc += headprelen;
	switch (curr->group)
	{
	case SYMBOL:
	    if (headings_flag > 0)
	    {
		PUT(symhead_pos);
	    }
	    else
	    {
		PUT(symhead_neg);
	    }
	    break;
	case ALPHA:
	    if (headings_flag > 0)
	    {
		let = TOUPPER(let);
		PUTC(let);
	    }
	    else
	    {
		let = TOLOWER(let);
		PUTC(let);
	    }
	    break;
	default:
	    if (headings_flag > 0)
	    {
		PUT(numhead_pos);
	    }
	    else
	    {
		PUT(numhead_neg);
	    }
	    break;
	}
	PUT(heading_suf);
	ind_lc += headsuflen;
    }
}


/* changes for 2.12 (May 20, 1993) by Julian Reschke (jr@ms.maus.de):
   Use keywords suffix_2p, suffix_3p or suffix_mp for one, two or
   multiple page ranges (when defined) */

static void
flush_line(int print)
{
    char    tmp[sizeof(buff)];

    if (page_diff(begin, the_end) != 0)
	if (encap_range || (page_diff(begin, prev) > (*suffix_2p ? 0 : 1)))
	{
		int diff = page_diff(begin, the_end);
		
		if ((diff == 1) && *suffix_2p)
		    sprintf(buff, "%s%s", begin->lpg, suffix_2p);
		else if ((diff == 2) && *suffix_3p)
		    sprintf(buff, "%s%s", begin->lpg, suffix_3p);
		else if ((diff >= 2) && *suffix_mp)
		    sprintf(buff, "%s%s", begin->lpg, suffix_mp);
		else
		    sprintf(buff, "%s%s%s", begin->lpg, delim_r, the_end->lpg);

	    encap_range = FALSE;
	}
	else
	   	sprintf(buff, "%s%s%s", begin->lpg, delim_n, the_end->lpg);
    else
    {
	encap_range = FALSE; /* might be true from page range on same page */
	strcpy(buff, begin->lpg);
    }

    if (*prev_encap != NUL)
    {
	strcpy(tmp, buff);
	sprintf(buff, "%s%s%s%s%s",
		encap_p, prev_encap, encap_i, tmp, encap_s);
    }
    wrap_line(print);
}

static void
wrap_line(int print)
{
    int     len;

    len = strlen(line) + strlen(buff) + ind_indent;
    if (print) {
	if (len > linemax) {
	    PUTLN(line);
	    PUT(indent_space);
	    ind_indent = indent_length;
	} else
	    PUT(line);
	PUT(buff);
    } else {
	if (len > linemax) {
	    PUTLN(line);
	    sprintf(line, "%s%s%s", indent_space, buff, delim_n);
	    ind_indent = indent_length;
	} else {
	    strcat(buff, delim_n);
	    strcat(line, buff);
	}
    }
}


static void
insert_page(void)
{
    int     i = 0;
    int     j = 0;
    int     page = 0;

    if (even_odd >= 0) {
	/* find the rightmost digit */
	while (pageno[i++] != NUL);
	j = --i;
	/* find the leftmost digit */
	while (isdigit((unsigned char)pageno[--i]) && i > 0);
	if (!isdigit((unsigned char)pageno[i]))
	    i++;
	/* convert page from literal to numeric */
	page = strtoint(&pageno[i]) + 1;
	/* check even-odd numbering */
	if (((even_odd == 1) && (page % 2 == 0)) ||
	    ((even_odd == 2) && (page % 2 == 1)))
	    page++;
	pageno[j + 1] = NUL;
	/* convert page back to literal */
	while (page >= 10) {
	    pageno[j--] = TOASCII(page % 10);
	    page = page / 10;
	}
	pageno[j] = TOASCII(page);
	if (i < j) {
	    while (pageno[j] != NUL)
		pageno[i++] = pageno[j++];
	    pageno[i] = NUL;
	}
    }
    PUT(setpage_open);
    PUT(pageno);
    PUT(setpage_close);
    ind_lc += setpagelen;
}
