
/*
 *    Copyright 1986, 1987 Pat Joseph Monardo. All rights reserved.
 *    Copying of this file is granted according to the provisions 
 *    specified in the file COPYING which must accompany this file.
 */


/*
 *		align.c
 */

#include "tex.h"
#include "cmds.h"
#include "heap.h"
#include "arith.h"
#include "eq.h"
#include "eqstack.h"
#include "hash.h"
#include "token.h"
#include "tokenstack.h"
#include "scan.h"
#include "evalstack.h"
#include "box.h"
#include "pack.h"
#include "math.h"
#include "mlist-hlist.h"
#include "error.h"
#include "align.h"

ptr		align_ptr;

ptr		cur_align;
ptr		cur_span;
ptr		cur_loop;
ptr		cur_head;
ptr		cur_tail;

push_alignment ()
{
	ptr		p;

	p = get_node(ALIGN_STACK_NODE_SIZE);
	link(p) = align_ptr;
	info(p) = cur_align;
	llink(p) = preamble;
	rlink(p) = cur_span;
	mem[p + 2].i = cur_loop;
	mem[p + 3].i = align_state;
	info(p + 4) = cur_head;
	link(p + 4) = cur_tail;
	align_ptr = p;
	cur_head = get_avail();
}

pop_alignment ()
{
	ptr		p;

	free_avail(cur_head);
	p = align_ptr;
	cur_tail = link(p + 4);
	cur_head = info(p + 4);
	align_state = mem[p + 3].i;
	cur_loop = mem[p + 2].i;
	cur_span = rlink(p);
	preamble = llink(p);
	cur_align = info(p);
	align_ptr = link(p);
	free_node(p, ALIGN_STACK_NODE_SIZE);
}

init_align ()
{
	ptr		p;
	ptr		save_cs_ptr;
	
	save_cs_ptr = cur_cs;
	push_alignment();
	align_state = -1000000;
	if (mode == MMODE && (tail != head || incompleat_noad != NULL)) {
		print_err("Improper ");
		print_esc("halign");
		print(" inside $$'s");
		help_display_align();
		error();
		flush_math();
	}
	push_nest();
	if (mode == MMODE) {
		mode = -VMODE;
		prev_depth = nest[nest_ptr - 2].aux_field;
	} else if (mode > 0)
		negate(mode);
	scan_spec();
	new_save_level(ALIGN_GROUP);
	preamble = NULL;
	cur_align = align_head;
	cur_loop = NULL;
	scanner_status = ALIGNING;
	warning_index = save_cs_ptr;
	align_state = -1000000;
	loop {
		link(cur_align) = new_param_glue(TAB_SKIP_CODE);
		cur_align = link(cur_align);
		if (cur_cmd == CAR_RET)
			break;
		p = align_tokens;
		token_link(p) = NULL;
		loop {
			get_preamble_token();
			if (cur_cmd == MAC_PARAM)
				break;
			if (cur_cmd <= CAR_RET &&
				cur_cmd >= TAB_MARK &&
				align_state == -1000000) {
				if (p == align_tokens &&
					cur_loop == NULL &&
					cur_cmd == TAB_MARK) {
					cur_loop = cur_align;
				} else {
					print_err("Missing # inserted in alignment preamble");
					help_preamble_missing();
					back_error();
					break;
				}
			} else if (cur_cmd != SPACER || p != align_tokens) {
				token_link(p) = new_token();
				p = token_link(p);
				token(p) = cur_tok;
			}
		}
		link(cur_align) = new_null_box();
		cur_align = link(cur_align);
		info(cur_align) = end_span;
		width(cur_align) = NULL_FLAG;
		u_part(cur_align) = token_link(align_tokens);
		p = align_tokens;
		token_link(p) = NULL;
		loop {
			get_preamble_token();
			if (cur_cmd <= CAR_RET &&
				cur_cmd >= TAB_MARK &&
				align_state == -1000000)
				break;
			if (cur_cmd == MAC_PARAM) {
				print_err("Only one # is allowed per tab");
				help_preamble_many();
				error();
				continue;
			}
			token_link(p) = new_token();
			p = token_link(p);
			token(p) = cur_tok;
		}
		token_link(p) = new_token();
		p = token_link(p);
		token(p) = END_TEMPLATE_TOKEN;
		v_part(cur_align) = token_link(align_tokens);
	}
	scanner_status = NORMAL;
	new_save_level(ALIGN_GROUP);
	if (every_cr != NULL)
		begin_token_list(every_cr, EVERY_CR_TEXT);
	align_peek();
}

get_preamble_token ()
{
restart:
	get_token();
	while (cur_chr == SPAN_CODE && cur_cmd == TAB_MARK) {
		get_token();
		if (cur_cmd > MAX_COMMAND) {
			expand();
			get_token();
		}
	}
	if (cur_cmd == ASSIGN_GLUE && cur_chr == GLUE_BASE + TAB_SKIP_CODE) {
		scan_optional_equals();
		scan_glue(GLUE_VAL);
		if (global_defs > 0)
			geq_define(GLUE_BASE + TAB_SKIP_CODE, GLUE_REF, (ptr) cur_val);
		else eq_define(GLUE_BASE + TAB_SKIP_CODE, GLUE_REF, (ptr) cur_val);
		goto restart;
	}
}

align_peek ()
{
restart:
	align_state = 1000000;
	get_nbx_token();
	if (cur_cmd == NO_ALIGN) {
		scan_left_brace();
		new_save_level(NO_ALIGN_GROUP);
		if (mode == -VMODE)
			normal_paragraph();
	} else if (cur_cmd == RIGHT_BRACE)
		fin_align();
	else if (cur_cmd == CAR_RET && cur_chr == CR_CR_CODE)
		goto restart;
	else {
		init_row();
		init_col();
	}
}

init_row()
{
	push_nest();
	mode = (-HMODE - VMODE) - mode;
	aux = 0;
	tail_append(new_glue(glue_ptr(preamble)));
	subtype(tail) = TAB_SKIP_CODE + 1;
	cur_align = link(preamble);
	cur_tail = cur_head;
	init_span(cur_align);
}

init_span (p)
	ptr		p;
{
	push_nest();
	if (mode == -HMODE)
		space_factor = 1000;
	else {
		prev_depth = IGNORE_DEPTH;
		normal_paragraph();
	}
	cur_span = p;
}

init_col ()
{
	extra_info(cur_align) = cur_cmd;
	if (cur_cmd == OMIT)
		align_state = 0;
	else {
		back_input();
		begin_token_list((ptr) u_part(cur_align), (qword) U_TEMPLATE);
	}
}

bool
fin_col ()
{
	hword	n;
	gord	o;
	ptr		p;
	ptr		q;
	ptr		r;
	ptr		s;
	ptr		u;
	scal	w;

	q = link(cur_align);
	if (cur_align == NULL || q == NULL)
		confusion("endv");
	p = link(q);
	if (p == NULL && extra_info(cur_align) < CR_CODE) {
		if (cur_loop != NULL) {
			link(q) = new_null_box();
			p = link(q);
			info(p) = end_span;
			width(p) = NULL_FLAG;
			cur_loop = link(cur_loop);
			q = align_tokens;
			r = u_part(cur_loop);
			while (r != NULL) {
				token_link(q) = new_token();
				q = token_link(q);
				token(q) = token(r);
				r = token_link(r);
			}
			token_link(q) = NULL;
			u_part(p) = token_link(align_tokens);
			q = align_tokens;
			r = v_part(cur_loop);
			while (r != NULL) {
				token_link(q) = new_token();
				q = token_link(q);
				token(q) = token(r);
				r = token_link(r);
			}
			token_link(q) = NULL;
			v_part(p) = token_link(align_tokens);
			cur_loop = link(cur_loop);
			link(p) = new_glue(glue_ptr(cur_loop));
		} else {
			print_err("Extra alignment tab has been changed to ");
			print_esc("cr");
			help_align_apply();
			extra_info(cur_align) = CR_CODE;
			error();
		}
	}
	if (extra_info(cur_align) != SPAN_CODE) {
		unsave();
		new_save_level(ALIGN_GROUP);
		if (mode == -HMODE) {
			adjust_tail = cur_tail;
			u = hpack(link(head), NATURAL);
			w = width(u);
			cur_tail = adjust_tail;
			adjust_tail = NULL;
		} else {
			u = vpackage(link(head), NATURAL, 0L);
			w = height(u);
		}
		n = MIN_QUARTERWORD;
		if (cur_span != cur_align) {
			q = cur_span;
			do	{
				incr(n);
				q = link(link(q));
			} while (q != cur_align);
			if (n > MAX_QUARTERWORD)
				confusion("256 spans");
			q = cur_span;
			while (link(info(q)) < n)
				q = info(q);
			if (link(info(q)) > n) {
				s = get_node(SPAN_NODE_SIZE);
				info(s) = info(q);
				link(s) = n;
				info(q) = s;
				width(s) = w;
			} else if (width(info(q)) < w)
				width(info(q)) = w;
		} else if (w > width(cur_align))
			width(cur_align) = w;
		type(u) = UNSET_NODE;
		span_count(u) = n;
		get_stretch_order();
		glue_order(u) = o;
		glue_stretch(u) = total_stretch[o];
		get_shrink_order();
		glue_sign(u) = o;
		glue_shrink(u) = total_shrink[o];
		pop_nest();
		link(tail) = u;
		tail = u;
		tail_append(new_glue(glue_ptr(link(cur_align))));
		subtype(tail) = TAB_SKIP_CODE + 1;
		if (extra_info(cur_align) >= CR_CODE)
			return TRUE;
		init_span(p);
	}
	align_state = 1000000;
	get_nbx_token();
	cur_align = p;
	init_col();
	return FALSE;
}

fin_row ()
{
	ptr		p;

	if (mode == -HMODE) {
		p = hpack(link(head), NATURAL);
		pop_nest();
		append_to_vlist(p);
		if(cur_head != cur_tail) {
			link(tail) = link(cur_head);
			tail = cur_tail;
		}
	} else {
		p = vpack(link(head), NATURAL);
		pop_nest();
		link(tail) = p;
		tail = p;
		space_factor = 1000;
	}
	type(p) = UNSET_NODE;
	glue_stretch(p) = 0;
	if (every_cr != NULL)
		begin_token_list(every_cr, EVERY_CR_TEXT);
	align_peek();
}

fin_align ()
{
	hword	n;
	scal	o;
	ptr		p;
	ptr		q;
	ptr		r;
	ptr		s;
	scal	t;
	ptr		u;
	ptr		v;
	scal	w;
	scal	rule_save;

	if (cur_group != ALIGN_GROUP)
		confusion("align1");
	unsave();
	if (cur_group != ALIGN_GROUP)
		confusion("align0");
	unsave();
	if (nest[nest_ptr - 1].mode_field == MMODE)
		o = display_indent;
	else o = 0;
	q = link(preamble);
	do {
		flush_list((ptr) u_part(q));
		flush_list((ptr) v_part(q));
		p = link(link(q));
		if (width(q) == NULL_FLAG) {
			width(q) = 0;
			r = link(q);
			s = glue_ptr(r);
			if (s != zero_glue) {
				add_glue_ref(zero_glue);
				delete_glue_ref(s);
				glue_ptr(r) = zero_glue;
			}
		}
		if (info(q) != end_span) {
			t = width(q) + width(glue_ptr(link(q)));
			r = info(q);
			s = end_span;
			info(s) = p;
			n = MIN_QUARTERWORD + 1;
			do {	
				width(r) -= t;
				u = info(r);
				while (link(r) > n) {
					s = info(s);
					n = link(info(s)) + 1;
				}
				if (link(r) < n) {
					info(r) = info(s);
					info(s) = r;
					decr(link(r));
					s = r;
				} else {
					if (width(r) > width(info(s)))
						width(info(s)) = width(r);
					free_node(r, SPAN_NODE_SIZE);
				}
				r = u;
			} while (r != end_span);
		}
		type(q) = UNSET_NODE;
		span_count(q) = MIN_QUARTERWORD;
		height(q) = 0;
		depth(q) = 0;
		glue_order(q) = NORMAL;
		glue_sign(q) = NORMAL;
		glue_stretch(q) = 0;
		glue_shrink(q) = 0;
		q = p;
	} while (q != NULL);
	save_ptr -= 2;
	pack_begin_line = -mode_line;
	if (mode == -VMODE) {
		rule_save = overfull_rule;
		overfull_rule = 0;
		p = hpack(preamble, saved(1), (int) saved(0));
		overfull_rule = rule_save;
	} else {
		q = link(preamble);
		do	{
			height(q) = width(q);
			width(q) = 0;
			q = link(link(q));
		} while (q != NULL);
		p = vpackage(preamble, saved(1), (int) saved(0), MAX_DIMEN);
		q = link(preamble);
		do	{
			width(q) = height(q);
			height(q) = 0;
			q = link(link(q));
		} while (q != NULL);
	}
	pack_begin_line = 0;
	for (q = link(head); q != NULL; q = link(q)) {
		if (type(q) == UNSET_NODE) {
			if (mode == -VMODE) {
				type(q) = HLIST_NODE;
				width(q) = width(p);
			} else {
				type(q) = VLIST_NODE;
				height(q) = height(p);
			}
			glue_order(q) = glue_order(p);
			glue_sign(q) = glue_sign(p);
			glue_set(q) = glue_set(p);
			shift_amount(q) = o;
			r = link(list_ptr(q));
			s = link(list_ptr(p)); 
			do	{
				n = span_count(r); 
				t = width(s);
				w = t;
				u = hold_head;
				while (n > MIN_QUARTERWORD) {
					decr(n);
					s = link(s);
					v = glue_ptr(s);
					link(u) = new_glue(v);
					u = link(u);
					subtype(u) = TAB_SKIP_CODE + 1;
					t += width(v);
					if (glue_sign(p) == STRETCHING)
						if (stretch_order(v) == glue_order(p))
							t += round(glue_set(p) * stretch(v));
					else if (glue_sign(p) == SHRINKING)
						if (shrink_order(v) == glue_order(p))
							t -= round(glue_set(p) * shrink(v));
					s = link(s);
					link(u) = new_null_box();
					u = link(u);
					t += width(s);
					if (mode == -VMODE)
						width(u) = width(s);
					else {
						type(u) = VLIST_NODE;
						height(u) = width(s);
					}
				}
				if (mode == -VMODE) {
					height(r) = height(q);
					depth(r) = depth(q);
					if (t == width(r)) {
						glue_sign(r) = NORMAL;
						glue_order(r) = NORMAL;
						glue_set(r) = 0.0;
					} else if (t > width(r)) {
						glue_sign(r) = STRETCHING;
						if (glue_stretch(r) == 0)
							glue_set(r) = 0.0;
						else glue_set(r) =
								(float) (t - width(r)) / glue_stretch(r);
					} else {
						glue_order(r) = glue_sign(r);
						glue_sign(r) = SHRINKING;
						if (glue_shrink(r) == 0)
							glue_set(r) = 0.0;
						else if (glue_order(r) == NORMAL &&
								width(r) - t > glue_shrink(r))
							glue_set(r) = 1.0;
						else glue_set(r) =
							(float)(width(r) - t) / glue_shrink(r);
					}
					width(r) = w;
					type(r) = HLIST_NODE;
				} else {
					width(r) = width(q);
					if (t == height(r)) {
						glue_sign(r) = NORMAL;
						glue_order(r) = NORMAL;
						glue_set(r) = 0.0;
					} else if (t > height(r)) {
						glue_sign(r) = STRETCHING;
						if (glue_stretch(r) == 0)
							glue_set(r) = 0.0;
						else glue_set(r) = 	
								(float) (t - height(r)) / glue_stretch(r);
					} else {
						glue_order(r) = glue_sign(r);
						glue_sign(r) = SHRINKING;
						if (glue_shrink(r) == 0)
							glue_set(r) = 0.0;
						else if (glue_order(r) == NORMAL &&
								height(r) - t > glue_shrink(r))
							glue_set(r) = 1.0;
						else glue_set(r) = 
								(float) (height(r) - t) / glue_shrink(r);
					}
					height(r) = w;
					type(r) = VLIST_NODE;
				} 
				shift_amount(r) = 0; 
				if (u != hold_head) {
					link(u) = link(r);
					link(r) = link(hold_head);
					r = u;
				}
				r = link(link(r));
				s = link(link(s));
			} while (r != NULL);
		} else if (type(q) == RULE_NODE) {
			if (is_running(width(q)))
				width(q) = width(p);
			if (is_running(height(q)))
				height(q) = height(p);
			if (is_running(depth(q)))
				depth(q) = depth(p);
		}
	}
	flush_node_list(p);
	pop_alignment();
	t = aux;
	p = link(head);
	q = tail;
	pop_nest();
	if (mode == MMODE) {
		do_assignments();
		if (cur_cmd != MATH_SHIFT) {
			print_err("Missing $$ inserted");
			help_display_align();
			back_error();
		} else {	
			get_x_token();
			if (cur_cmd != MATH_SHIFT) {
				print_err("Display math should end with $$");
				help_display();
				back_error();
			}
		}
		pop_nest();
		tail_append(new_penalty(pre_display_penalty));
		tail_append(new_param_glue(ABOVE_DISPLAY_SKIP_CODE));
		link(tail) = p;
		if (p != NULL)
			tail = q;
		tail_append(new_penalty(post_display_penalty));
		tail_append(new_param_glue(BELOW_DISPLAY_SKIP_CODE));
		prev_depth = t;
		resume_after_display();
	} else {	
		aux = t;
		link(tail) = p;
		if (p != NULL)
			tail = q;
		if (mode == VMODE)
			build_page();
	}
}

/*
 *	Help text
 */

help_display_align ()
{
	help3("Displays can use special alignments (like \\eqalignno)",
	"only if nothing but the alignment itself is between $$'s.",
	"So I've deleted the formulas that preceded this alignment.");
}

help_preamble_missing ()
{
	help3("There should be exactly one # between &'s, when an",
	"\\halign or \\valign is being set up. In this case you had",
	"none, so I've put one in; maybe that will work.");
}

help_preamble_many ()
{
	help3("There should be exactly one # between &'s, when an",
	"\\halign or \\valign is being set up. In this case you had",
	"more than one, so I'm ignoring all but the first.");
}

help_align_apply ()
{
	help3("You have given more \\span or & marks than there were",
	"in the preamble to the \\halign or \\valign now in progress.",
	"So I'll assume that you meant to type \\cr instead.");
}

help_display ()
{
	help2("The `$' that I just saw supposedly matches a previous `$$'.",
	"So I shall assume that you typed `$$' both times.");
}
