
/*
 *    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.
 */


/*
 *		page.c
 */

#include "tex.h"
#include "heap.h"
#include "arith.h"
#include "str.h"
#include "token.h"
#include "tokenstack.h"
#include "tokenlists.h"
#include "eq.h"
#include "eqstack.h"
#include "evalstack.h"
#include "scan.h"
#include "expand.h"
#include "box.h"
#include "pack.h"
#include "par.h"
#include "math.h"
#include "dvi.h"
#include "print.h"
#include "error.h"
#include "page.h"

scal	best_height_plus_depth;
ptr		best_page_break;
scal	best_size;
scal	cur_page_depth;
val		insert_penalties;
ptr		last_glue = MAX_HALFWORD;
scal	last_kern;
val		last_penalty;
val		least_page_cost;
bool	output_active;
int		page_contents;
scal	page_max_depth;
ptr		page_tail;
scal	page_so_far[8];

#define	set_page_so_far_zero(P)	(page_so_far[P] = 0)
#define	set_height_zero(H)		(active_height[H] = 0)

ptr
prune_page_top (p)
	ptr		p;
{
	ptr		q;
	ptr		prev_p;

	prev_p = temp_head;
	link(temp_head) = p;
	while (p != NULL) {
		switch (type(p))
		{
		case HLIST_NODE:
		case VLIST_NODE:
		case RULE_NODE:
			q = new_skip_param(SPLIT_TOP_SKIP_CODE);
			link(prev_p) = q;
			link(q) = p;
			if (width(temp_ptr) > height(p))
				width(temp_ptr) -= height(p);
			else width(temp_ptr) = 0;
			p = NULL;
			break;

		case WHATSIT_NODE:
		case MARK_NODE:
		case INS_NODE:
			prev_p = p;
			p = link(prev_p);
			break;
		
		case GLUE_NODE:
		case KERN_NODE:
		case PENALTY_NODE:
			q = p;
			p = link(q);
			link(q) = NULL;
			link(prev_p) = p;
			flush_node_list(q);
			break;

		default:
			confusion("pruning");
			break;
		}
	}
	return (link(temp_head));
}

ptr
vert_break (p, h, d)
	ptr		p;
	scal	h;
	scal	d;
{
	val		b;
	ptr		q;
	ptr		r;
	int		t;
	val		pi;
	ptr		prev_p;
	scal	prev_dp;
	ptr		best_place;
	val		least_cost;

	prev_p = p;
	least_cost = AWFUL_BAD;
	do_all_six(set_height_zero);
	prev_dp = 0;
	loop {
		if (p == NULL)
			pi = EJECT_PENALTY;
		else {
			switch (type(p))
			{
			case HLIST_NODE:
			case VLIST_NODE:
			case RULE_NODE:
				cur_height = cur_height + prev_dp + height(p);
				prev_dp = depth(p);
				goto not_found;
				break;
			
			case WHATSIT_NODE:
				goto not_found;
				break;
			
			case GLUE_NODE:
				if (precedes_break(prev_p))
					pi = 0;
				else goto update_heights;
				break;
			
			case KERN_NODE:
				if (link(p) == NULL)
					t = PENALTY_NODE;
				else t = type(link(p));
				if (t == GLUE_NODE)
					pi = 0;
				else goto update_heights;
				break;
			
			case PENALTY_NODE:
				pi = penalty(p);
				break;
			
			case MARK_NODE:
			case INS_NODE:
				goto not_found;
				break;
			
			default:
				confusion("vertbreak");
				break;
			}
		}
		if (pi < INF_PENALTY) {
			if (cur_height < h) {
				if (active_height[3] != 0 ||
					active_height[4] != 0 ||
					active_height[5] != 0)
					b = 0;
				else b = badness(h - cur_height, active_height[2]);
			} else if (cur_height - h > active_height[6])
				b = AWFUL_BAD;
			else b = badness(cur_height - h, active_height[6]);
			if (b < AWFUL_BAD) {
				if (pi <= EJECT_PENALTY)
					b = pi;
				else if (b < INF_BAD)
					b += pi;
				else b = DEPLORABLE;
			}
			if (b <= least_cost) {
				best_place = p;
				least_cost = b;
				best_height_plus_depth = cur_height + prev_dp;
			}
			if (b == AWFUL_BAD || pi <= EJECT_PENALTY)
				return best_place;
		}
		if (type(p) < GLUE_NODE || type(p) > KERN_NODE)
			goto not_found;

	update_heights:
		if (type(p) == KERN_NODE)
			q = p;
		else {
			q = glue_ptr(p);
			active_height[2 + stretch_order(q)] += stretch(q);
			active_height[6] += shrink(q);
			if (shrink_order(q) != NORMAL && shrink(q) != 0) {
				print_err("Infinite glue shrinkage found in box being split");
				help_inf_shrink_box();
				error();
				r = new_spec(q);
				delete_glue_ref(q);
				shrink_order(r) = NORMAL;
				glue_ptr(p) = r;
			}
		}
		cur_height = cur_height + prev_dp + width(q);
		prev_dp = 0;

	not_found:
		if (prev_dp > d) {
			cur_height = cur_height + prev_dp - d;
			prev_dp = d;
		}
		prev_p = p;
		p = link(prev_p);
	}
}

ptr
vsplit (n, h)
	int		n;
	scal	h;
{
	ptr		p;
	ptr		q;
	ptr		v;

	v = box(n);
	if (split_first_mark != NULL) {
		delete_token_ref(split_first_mark);
		split_first_mark = NULL;
		delete_token_ref(split_bot_mark);
		split_bot_mark = NULL;
	}
	if (v == NULL)
		return NULL;
	if (type(v) != VLIST_NODE) {
		print_err("");
		print_esc("vsplit");
		print(" needs a ");
		print_esc("vbox");
		help_vsplit_vbox();
		error();
		return NULL;
	}
	q = vert_break(list_ptr(v), h, split_max_depth);
	p = list_ptr(v);
	if (p == q)
		list_ptr(v) = NULL;
	else {
		loop {
			if (type(p) == MARK_NODE) {
				if (split_first_mark == NULL) {
					split_first_mark = mark_ptr(p);
					split_bot_mark = split_first_mark;
					token_ref_count(split_first_mark) += 2;
				} else {
					delete_token_ref(split_bot_mark);
					split_bot_mark = mark_ptr(p);
					add_token_ref(split_bot_mark);
				}
			}
			if (link(p) == q) {
				link(p) = NULL;
				break;
			}
			p = link(p);
		}
	}
	q = prune_page_top(q);
	p = list_ptr(v);
	free_node(v, BOX_NODE_SIZE);
	if (q == NULL)
		box(n) = NULL;
	else box(n) = vpack(q, NATURAL);
	return (vpackage(p, h, EXACTLY, split_max_depth));
}

freeze_page_specs (s)
	int		s;
{
	page_contents = s;
	page_goal = vsize;
	page_max_depth = max_depth;
	page_depth = 0;
	do_all_six(set_page_so_far_zero);
	least_page_cost = AWFUL_BAD;
#ifdef STAT
	if (tracing_pages > 0) {
		begin_diagnostic();
		print_nl("%% goal height=");
		print_scaled(page_goal);
		print(", max depth=");
		print_scaled(page_max_depth);
		end_diagnostic(FALSE);
	}
#endif
}

box_error (n)
	int		n;
{
	error();
	begin_diagnostic();
	print_nl("The following box has been deleted:");
	show_box(box(n));
	end_diagnostic(TRUE);
	flush_node_list(box(n));
	box(n) = NULL;
}

ensure_vbox (n)
	int		n;
{
	ptr		p;

	p = box(n);
	if (p != NULL && type(p) == HLIST_NODE) {
		print_err("Insertions can only be added to a vbox");
		help_tut();
		box_error(n);
	}
}

print_plus (s, o)
	int		s;
	char*	o;
{
	if (page_so_far[s] != 0) {
		print(" plus ");
		print_scaled(page_so_far[s]);
		print(o);
	}
}

print_totals ()
{
	print_scaled(page_total);
	print_plus(2, "");
	print_plus(3, "fil");
	print_plus(4, "fill");
	print_plus(5, "filll");
	if (page_shrink != 0) {
		print(" minus ");
		print_scaled(page_shrink);
	}
}

#ifdef STAT
show_split(n, w, q)
	int		n;
	scal	w;
	ptr		q;
{
	begin_diagnostic();
	print_nl("% split");
	print_int(n);
	print(" to ");
	print_scaled(w);
	print_char(',');
	print_scaled(best_height_plus_depth);
	print(" p=");
	if (q == NULL)
		print_int(EJECT_PENALTY);
	else if (type(q) == PENALTY_NODE)
		print_val(penalty(q));
	else print_char('0');
	end_diagnostic(FALSE);
}

show_page_stats (b, p, c)
	val		b;
	val		p;
	val		c;
{
	if (tracing_pages > 0) {
		begin_diagnostic();
		print_nl("%");
		print(" t=");
		print_totals();
		print(" g=");
		print_scaled(page_goal);
		print(" b=");
		if (b == AWFUL_BAD)
			print_char('*');
		else print_val(b);
		print(" p=");
		print_val(p);
		print(" c=");
		if (c == AWFUL_BAD)
			print_char('*');
		else print_val(c);
		if (c <= least_page_cost)
			print_char('#');
		end_diagnostic(FALSE);
	}
}
#endif

build_page ()
{
	val		b;
	val		c;
	scal	h;
	int		n;
	ptr		p;
	ptr		q;
	ptr		r;
	scal	w;
	val		pi;
	scal	delta;
	
	if (link(contrib_head) == NULL || output_active)
		return;
	do { 
		p = link(contrib_head);
		if (last_glue != MAX_HALFWORD)
			delete_glue_ref(last_glue);
		last_penalty = 0;
		last_kern = 0;
		if (type(p) == GLUE_NODE) {
			last_glue = glue_ptr(p);
			add_glue_ref(last_glue);
		} else {
			last_glue = MAX_HALFWORD;
			if (type(p) == PENALTY_NODE)
				last_penalty = penalty(p);
			else if (type(p) == KERN_NODE)
				last_kern = width(p);
		}
		switch (type(p))
		{
		case HLIST_NODE:
		case VLIST_NODE:
		case RULE_NODE:
			if (page_contents < BOX_THERE) {
				if (page_contents == EMPTY)
					freeze_page_specs(BOX_THERE);
				else page_contents = BOX_THERE;
				q = new_skip_param(TOP_SKIP_CODE);
				link(q) = p;
				if (width(temp_ptr) > height(p))
					width(temp_ptr) -= height(p);
				else width(temp_ptr) = 0;
				link(q) = p;
				link(contrib_head) = q;
				continue;
			} else {
				page_total = page_total + page_depth + height(p);
				page_depth = depth(p);
				goto contribute;
			}
			break;
		
		case WHATSIT_NODE:
			goto contribute;
			break;
		
		case GLUE_NODE:
			if (page_contents < BOX_THERE)
				goto done;
			else if (precedes_break(page_tail))
				pi = 0;
			else goto update_heights;
			break;
		
		case KERN_NODE:
			if (page_contents < BOX_THERE)
				goto done;
			else if (link(p) == NULL)
				return;
			else if (type(link(p)) == GLUE_NODE)
				pi = 0;
			else goto update_heights;
			break;
		
		case PENALTY_NODE:
			if (page_contents < BOX_THERE)
				goto done;
			else pi = penalty(p);
			break;

		case MARK_NODE:
			goto contribute;
			break;

		case INS_NODE:
			if (page_contents == EMPTY)
				freeze_page_specs(INSERTS_ONLY);
			n = subtype(p);
			r = page_ins_head;
			while (n >= subtype(link(r)))
				r = link(r);
			if (subtype(r) != n) {
				q = get_node(PAGE_INS_NODE_SIZE);
				link(q) = link(r);
				link(r) = q;
				r = q;
				subtype(r) = qi(n);
				type(r) = INSERTING;
				ensure_vbox(n);
				if (box(n) == NULL)
					height(r) = 0;
				else height(r) = height(box(n)) + depth(box(n));
				best_ins_ptr(r) = NULL;
				q = skip(n);
				if (count(n) == 1000)
					h = height(r);
				else h = x_over_n(height(r), 1000L) * count(n);
				page_goal = page_goal - h - width(q);
				page_so_far[2 + stretch_order(q)] += stretch(q);
				page_shrink += shrink(q);
				if (shrink_order(q) != NORMAL && shrink(q) != 0) {
					print_err("Infinite glue shrinkage inserted from ");
					print_esc("skip");
					print_int(n);
					help_inf_shrink_ins();
					error();
				}
			}
			if (type(r) == SPLIT_UP)
				insert_penalties += float_cost(p);
			else {
				last_ins_ptr(r) = p;
				delta = page_goal - page_total - page_depth + page_shrink;
				if (count(n) == 1000)
					h = height(p);
				else h = x_over_n(height(p), 1000L) * count(n);
				if ((h <= 0 || h <= delta) &&
					height(p) + height(r) <= dimen(n)) {
					page_goal -= h;
					height(r) += height(p);
				} else {
					if (count(n) <= 0)
						w = MAX_DIMEN;
					else {
						w = page_goal - page_total - page_depth;
						if (count(n) != 1000) 
							w = x_over_n(w, count(n)) * 1000;
					}
					if (w > dimen(n) - height(r))
						w = dimen(n) - height(r);
					q = vert_break(ins_ptr(p), w, depth(p));
					height(r) += best_height_plus_depth;
#ifdef STAT
					show_split(n, w, q);
#endif
					if (count(n) != 1000)
						best_height_plus_depth =
							x_over_n(best_height_plus_depth, 1000L) * count(n);
					page_goal -= best_height_plus_depth;
					type(r) = SPLIT_UP;
					broken_ptr(r) = q;
					broken_ins(r) = p;
					if (q == NULL)
						insert_penalties += EJECT_PENALTY;
					else if (type(q) == PENALTY_NODE)
						insert_penalties += penalty(q);
				}
			}
			goto contribute;
			break;
		
		default:
			confusion("page");
			break;
		}
		if (pi < INF_PENALTY) {
			if (page_total < page_goal) {
				if (page_so_far[3] != 0 ||
					page_so_far[4] != 0 ||
					page_so_far[5] != 0)
					b = 0;
				else b = badness(page_goal - page_total, page_so_far[2]);
			} else if (page_total - page_goal > page_shrink)
				b = AWFUL_BAD;
			else b = badness(page_total - page_goal, page_shrink);
			if (b < AWFUL_BAD) {
				if (pi <= EJECT_PENALTY)
					c = pi;
				else if (b < INF_BAD)
					c = b + pi + insert_penalties;
				else c = DEPLORABLE;
			} else c = b;
			if (insert_penalties >= 10000)
				c = AWFUL_BAD;
#ifdef STAT
			show_page_stats(b, pi, c);
#endif
			if (c <= least_page_cost) {
				best_page_break = p;
				best_size = page_goal;
				least_page_cost = c;
				r = link(page_ins_head);
				while (r != page_ins_head) {
					best_ins_ptr(r) = last_ins_ptr(r);
					r = link(r);
				}
			}
			if (c == AWFUL_BAD || pi <= EJECT_PENALTY) {
				fire_up(p);
				if (output_active) return;
				continue;
			}
		}
		if (type(p) < GLUE_NODE || type(p) > KERN_NODE)
			goto contribute;
		
	update_heights:
		if (type(p) == KERN_NODE)
			q = p;
		else {
			q = glue_ptr(p);
			page_so_far[2 + stretch_order(q)] += stretch(q);
			page_shrink += shrink(q);
			if (shrink_order(q) != NORMAL && shrink(q) != 0) {
				print_err("Infinite glue shrinkage found on current page");
				help_inf_shrink_page();
				error();
				r = new_spec(q);
				shrink_order(r) = NORMAL;
				delete_glue_ref(q);
				glue_ptr(p) = r;
			}
		}
		page_total = page_total + page_depth + width(q);
		page_depth = 0;

	contribute:
		if (page_depth > page_max_depth) {
			page_total = page_total + page_depth - page_max_depth;
			page_depth = page_max_depth;
		}
		link(page_tail) = p;
		page_tail = p;
		link(contrib_head) = link(p);
		link(p) = NULL;
		continue;

	done:
		link(contrib_head) = link(p);
		link(p) = NULL;
		flush_node_list(p);
	} while (link(contrib_head) != NULL);
	if (nest_ptr == 0)
		tail = contrib_head;
	else contrib_tail = contrib_head;
}

fire_up (c)
	ptr		c;
{
	int		n;
	ptr		p;
	ptr		q;
	ptr		r;
	ptr		s;
	bool	wait;
	ptr		prev_p;
	scal	save_vfuzz;
	val		save_vbadness;
	ptr		save_split_top_skip;

	if (type(best_page_break) == PENALTY_NODE) {
		geq_word_define(INT_BASE+OUTPUT_PENALTY_CODE, penalty(best_page_break));
		penalty(best_page_break) = INF_PENALTY;
	} else geq_word_define(INT_BASE+OUTPUT_PENALTY_CODE, INF_PENALTY);
	if (bot_mark != NULL) {
		if (top_mark != NULL)
			delete_token_ref(top_mark);
		top_mark = bot_mark;
		add_token_ref(top_mark);
		delete_token_ref(first_mark);
		first_mark = NULL;
	}
	if (c == best_page_break)
		best_page_break = NULL;
	if (box(255) != NULL) {
		print_err("");
		print_esc("box");
		print("255 is not void");
		help_box_255();
		box_error(255);
	}
	insert_penalties = 0;
	save_split_top_skip = split_top_skip;
	r = link(page_ins_head);
	while (r != page_ins_head) {
		if (best_ins_ptr(r) != NULL) {
			n = qo(subtype(r));
			ensure_vbox(n);
			if (box(n) == NULL)
				box(n) = new_null_box();
			p = box(n) + LIST_OFFSET;
			while (link(p) != NULL)
				p = link(p);
			last_ins_ptr(r) = p;
		}
		r = link(r);
	}
	q = hold_head;
	link(q) = NULL;
	prev_p = page_head;
	p = link(prev_p);
	while (p != best_page_break) {
		if (type(p) == INS_NODE) {
			r = link(page_ins_head);
			while (subtype(r) != subtype(p))
				r = link(r);
			if (best_ins_ptr(r) == NULL)
				wait = TRUE;
			else {
				wait = FALSE;
				s = ins_ptr(p);
				link(last_ins_ptr(r)) = s;
				s = last_ins_ptr(r);
				if (best_ins_ptr(r) == p) {
					if (type(r) == SPLIT_UP &&
						broken_ins(r) == p &&
						broken_ptr(r) != NULL) {
						while (link(s) != broken_ptr(r))
							s = link(s);
						split_top_skip = split_top_ptr(p);
						ins_ptr(p) = prune_page_top(broken_ptr(r));
						if (ins_ptr(p) != NULL) {
							temp_ptr = vpack(ins_ptr(p), NATURAL);
							height(p) = height(temp_ptr) + depth(temp_ptr);
							free_node(temp_ptr, BOX_NODE_SIZE);
							wait = TRUE;
						}
						link(s) = NULL;
					}
					best_ins_ptr(r) = NULL;
					n = qo(subtype(r));
					temp_ptr = list_ptr(box(n));
					free_node(box(n), BOX_NODE_SIZE);
					box(n) = vpack(temp_ptr, NATURAL);
				} else {
					while (link(s) != NULL)
						s = link(s);
					last_ins_ptr(r) = s;
				}
			}
			link(prev_p) = link(p);
			link(p) = NULL;
			if (wait) {
				link(q) = p;
				q = p;
				incr(insert_penalties);
			} else {
				delete_glue_ref(split_top_ptr(p));
				free_node(p, INS_NODE_SIZE);
			}
			p = prev_p;
		} else if (type(p) == MARK_NODE) {
			if (first_mark == NULL) {
				first_mark = mark_ptr(p);
				add_token_ref(first_mark);
			}
			if (bot_mark != NULL)
				delete_token_ref(bot_mark);
			bot_mark = mark_ptr(p);
			add_token_ref(bot_mark);
		}
		prev_p = p;
		p = link(prev_p);
	}
	split_top_skip = save_split_top_skip;
	if (p != NULL) {
		if (link(contrib_head) == NULL) {
			if (nest_ptr == 0)
				tail = page_tail;
			else contrib_tail = page_tail;
		}
		link(page_tail) = link(contrib_head);
		link(contrib_head) = p;
		link(prev_p) = NULL;
	}
	save_vbadness = vbadness;
	vbadness = INF_BAD;
	save_vfuzz = vfuzz;
	vfuzz = MAX_DIMEN;
	box(255) = vpackage(link(page_head), best_size, EXACTLY, page_max_depth);
	vbadness = save_vbadness;
	vfuzz = save_vfuzz;
	if (last_glue != MAX_HALFWORD)
		delete_glue_ref(last_glue);
	start_new_page();
	if (q != hold_head) {
		link(page_head) = link(hold_head);
		page_tail = q;
	}
	r = link(page_ins_head);
	while (r != page_ins_head) {
		q = link(r);
		free_node(r, PAGE_INS_NODE_SIZE);
		r = q;
	}
	link(page_ins_head) = page_ins_head;
	if (top_mark != NULL && first_mark == NULL) {
		first_mark = top_mark;
		add_token_ref(top_mark);
	}
	if (output_routine != NULL) {
		if (dead_cycles >= max_dead_cycles) {
			print_err("Output loop---");
			print_int(dead_cycles);
			print(" consecutive dead cycles");
			help_dead_cycles();
			error();
		} else {
			output_active = TRUE;
			incr(dead_cycles);
			push_nest();
			mode = -VMODE;
			prev_depth = IGNORE_DEPTH;
			mode_line = -line;
			begin_token_list(output_routine, OUTPUT_TEXT);
			new_save_level(OUTPUT_GROUP);
			normal_paragraph();
			scan_left_brace();
			return;
		}
	}
	if (link(page_head) != NULL) {
		if (link(contrib_head) == NULL) {
			if (nest_ptr == 0) tail = page_tail;
			else contrib_tail = page_tail;
		} else link(page_tail) = link(contrib_head);
		link(contrib_head) = link(page_head);
		link(page_head) = NULL;
		page_tail = page_head;
	}
	ship_out(box(255));
	box(255) = NULL;
}

/*
 *	Help text
 */

help_tut ()
{
	help3("Tut tut: You're trying to \\insert into a",
	"\\box register that now contains an \\hbox.",
	"Proceed, and I'll discard its present contents.");
}

help_vsplit_vbox ()
{
	help2("The box you are trying to split is an \\hbox.",
	"I can't split such boxes, so I'll leave it alone.");
}

help_inf_shrink_ins ()
{
	help3("The correction glue for page breaking with insertions",
	"must have finite shrinkability. But you may proceed,",
	"since the offensive shrinkability has been made finite.");
}

help_inf_shrink_box ()
{
	help4("The box you are \\vsplitting contains some infinitely",
	"shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'.",
	"Such glue doesn't belong there; but you can safely proceed,",
	"since the offensive shrinkability has been made finite.");
}

help_inf_shrink_page ()
{
	help4("The page about to be output contains some infinitely ",
	"shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'.",
	"Such glue doesn't belong there; but you can safely proceed,",
	"since the offensive shrinkability has been made finite.");
}

help_box_255 ()
{
	help2("You shouldn't use \\box255 except in output routines.",
	"Proceed, and I'll discard its present contents.");
}

help_dead_cycles ()
{
	help3("I've concluded that your \\output is awry; it never does a",
	"\\shipout, so I'm shipping \box255 out myself. Next time",
	"increase \\maxdeadcycles if you want me to be more patient!");
}
