/* Links between top-level syms and the exprs which reference them
 */

/*

	Copyright (C) 1991-2003 The National Gallery

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the Free Software Foundation, Inc.,
	51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

 */

/*

	These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/*
#define DEBUG
#define DEBUG_DIRTY
 */

#include "nip4.h"

void *
link_expr_destroy(LinkExpr *le)
{
	GSList **llinks = le->dynamic ?
		&le->link->dynamic_links : &le->link->static_links;
	GSList **elinks = le->dynamic ?
		&le->expr->dynamic_links : &le->expr->static_links;

#ifdef DEBUG
	printf("link_expr_destroy: removing expr ");
	symbol_name_scope_print(le->expr->sym);
	printf("referencing link->child = ");
	symbol_name_scope_print(le->link->child);
	printf("\n");
#endif /*DEBUG*/

	*llinks = slist_remove_all(*llinks, le);
	*elinks = slist_remove_all(*elinks, le);

	g_free(le);

	return NULL;
}

static LinkExpr *
link_expr_new(Link *link, Expr *expr, gboolean dynamic)
{
	GSList **llinks = dynamic ? &link->dynamic_links : &link->static_links;
	GSList **elinks = dynamic ? &expr->dynamic_links : &expr->static_links;
	LinkExpr *le;

	g_assert(expr_get_root_dynamic(expr)->sym == link->parent);

#ifdef DEBUG
	printf("link_expr_new: expr ");
	symbol_name_scope_print(expr->sym);
	printf(" references link->child = ");
	symbol_name_scope_print(link->child);
	printf("\n");
#endif /*DEBUG*/

	if (!(le = INEW(NULL, LinkExpr)))
		return NULL;

	le->link = link;
	le->expr = expr;
	le->count = 1;
	le->dynamic = dynamic;

	*llinks = g_slist_prepend(*llinks, le);
	*elinks = g_slist_prepend(*elinks, le);

	return le;
}

/* Make a new serial number.
 */
int
link_serial_new(void)
{
	static int serial = 0;

	return serial++;
}

/* Fwd ref.
 */
static void *symbol_dirty_set(Symbol *sym);

/* child has become dirty ... update parent's dirty count.
 */
static void *
link_dirty_child(Link *link)
{
	g_assert(link->parent->ndirtychildren >= 0);

	link->parent->ndirtychildren += 1;

	if (link->parent->ndirtychildren == 1)
		/* Parent had no dirty children ... it does now.
		 */
		symbol_dirty_set(link->parent);

	symbol_state_change(link->parent);

	return NULL;
}

/* link->parent no longer has link->child as a dirty child (cleaned or
 * removed) ... update counts.
 */
static void *
link_clean_child(Link *link)
{
	Symbol *parent = link->parent;

	/* One fewer dirty children!
	 */
	parent->ndirtychildren--;
	g_assert(parent->ndirtychildren >= 0);

	/* Have we just cleaned the last dirty child of link->parent? If we
	 * have and if link->parent has an error, clear the error so that
	 * link->parent gets a chance to recalc. The new value of
	 * link->child might fix the problem.
	 */
	if (parent->ndirtychildren == 0 &&
		parent->expr)
		expr_error_clear(parent->expr);

	symbol_state_change(parent);

	return NULL;
}

/* Junk a link.
 */
void *
link_destroy(Link *link)
{
#ifdef DEBUG
	printf("link_destroy: destroying link from ");
	symbol_name_scope_print(link->parent);
	printf("to ");
	symbol_name_scope_print(link->child);
	printf("\n");
#endif /*DEBUG*/

	if (link->child->dirty)
		(void) link_clean_child(link);

	link->parent->topchildren =
		slist_remove_all(link->parent->topchildren, link);
	link->child->topparents =
		slist_remove_all(link->child->topparents, link);
	slist_map(link->static_links,
		(SListMapFn) link_expr_destroy, NULL);
	slist_map(link->dynamic_links,
		(SListMapFn) link_expr_destroy, NULL);

	g_free(link);

	return NULL;
}

/* Make a new link.
 */
static Link *
link_new(Symbol *child, Symbol *parent)
{
	Link *link;

	g_assert(is_top(parent) && is_top(child));
	g_assert(parent != child);

#ifdef DEBUG
	printf("link_new: making link from ");
	symbol_name_scope_print(parent);
	printf(" to ");
	symbol_name_scope_print(child);
	printf("\n");
#endif /*DEBUG*/

	if (!(link = INEW(NULL, Link)))
		return NULL;

	link->parent = parent;
	link->child = child;
	link->serial = 0;
	link->static_links = NULL;
	link->dynamic_links = NULL;

	parent->topchildren = g_slist_prepend(parent->topchildren, link);
	child->topparents = g_slist_prepend(child->topparents, link);

	/* If the new child is dirty, note it.
	 */
	if (child->dirty)
		link_dirty_child(link);

	return link;
}

static Link *
link_find_child_sub(Link *link, Symbol *child)
{
	if (link->child == child)
		return link;

	return NULL;
}

/* Look up connection between child and parent.
 */
static Link *
link_find_child(Symbol *child, Symbol *parent)
{
	return (Link *) slist_map(parent->topchildren,
		(SListMapFn) link_find_child_sub, child);
}

static void *
link_expr_find_expr_sub(LinkExpr *le, Expr *expr)
{
	if (le->expr == expr)
		return le;

	return NULL;
}

/* Look up a linkexpr by expr.
 */
static LinkExpr *
link_expr_find_expr(Link *link, Expr *expr, gboolean dynamic)
{
	GSList *links = dynamic ? link->dynamic_links : link->static_links;

	return (LinkExpr *) slist_map(links,
		(SListMapFn) link_expr_find_expr_sub, expr);
}

/* Add a reference from expr to child to the link graph.
 */
void *
link_add(Symbol *child, Expr *expr, gboolean dynamic)
{
	Expr *parent = expr_get_root_dynamic(expr);

	/* child can be a zombie toplevel which we should resolve to an enclosing
	 * table.
	 */
	if (child->type == SYM_ZOMBIE) {
		Symbol *new_sym;

		if ((new_sym = compile_resolve_top(child)))
			child = new_sym;
	}

#ifdef DEBUG
	printf("link_add: child = ");
	symbol_name_print(child);
	printf("; expr = ");
	expr_name_print(expr);
	printf("; dynamic = %s\n", bool_to_char(dynamic));
#endif /*DEBUG*/

	g_assert(parent);
	g_assert(parent->sym);
	g_assert(is_top(child) && is_top(parent->sym));
	g_assert(child != parent->sym);

	Link *link;
	LinkExpr *le;

	if (!(link = link_find_child(child, parent->sym))) {
		if (!(link = link_new(child, parent->sym)))
			return child;
	}

	if (!(le = link_expr_find_expr(link, expr, dynamic))) {
		if (!(le = link_expr_new(link, expr, dynamic)))
			return child;
	}
	else
		le->count++;

	return NULL;
}

/* Remove a ref from expr to child.
 */
void *
link_remove(Symbol *child, Expr *expr, gboolean dynamic)
{
	Symbol *parent = expr_get_root_dynamic(expr)->sym;
	Link *link = link_find_child(child, parent);
	LinkExpr *le = link_expr_find_expr(link, expr, dynamic);

	g_assert(is_top(parent) && is_top(child));
	g_assert(parent != child);
	g_assert(link);

	le->count--;
	if (le->count == 0) {
		if (link_expr_destroy(le))
			return child;
	}
	if (!link->static_links && !link->dynamic_links) {
		if (link_destroy(link))
			return child;
	}

	return NULL;
}

/* Is this a ref to a top-level? Add to link graph if it is.
 */
static void *
link_children_expr_sub(Symbol *child, Expr *expr)
{
	if (is_top(child)) {
		Expr *root = expr_get_root_dynamic(expr);

		/* Don't need to record recursive refs.
		 */
		if (root && root->sym && root->sym != child) {
			if (link_add(child, expr, FALSE))
				return child;
		}
	}

	return NULL;
}

/* Fwd.
 */
static void *link_children(Symbol *child, Symbol *parent);

/* Add any refs to top-level syms within this local to the
 * top-level sym we are within.
 */
static void *
link_children_expr(Expr *expr, Symbol *parent)
{
	if (expr->compile) {
		Compile *compile = expr->compile;

		/* Add refs which local makes directly.
		 */
		if (slist_map(compile->children,
				(SListMapFn) link_children_expr_sub, expr))
			return expr;

		/* ... and recurse for sub-children.
		 */
		(void) icontainer_map(ICONTAINER(compile),
			(icontainer_map_fn) link_children, parent, NULL);
	}

	return NULL;
}

/* Add any refs to top-level syms within this local to the
 * top-level sym we are within.
 */
static void *
link_children(Symbol *child, Symbol *parent)
{
	if (child->expr) {
		if (link_children_expr(child->expr, parent))
			return child;
	}

	return NULL;
}

/* row is editing sym's value ... add any dependancies the user has included
 * there.
 */
static void *
link_row(Model *model, Symbol *parent)
{
	if (!IS_ROW(model) || !ROW(model)->expr)
		return NULL;

	/* Add any stuff in this row.
	 */
	return link_children_expr(ROW(model)->expr, parent);
}

static void *
symbol_ndirty_sub(Link *link, int *nd)
{
	if (link->child->dirty)
		*nd += 1;

	return NULL;
}

/* Count the number of dirty children. Used to generate initial leaf counts
 * and for assert() checking.
 */
int
symbol_ndirty(Symbol *sym)
{
	int nd = 0;

	(void) slist_map(sym->topchildren,
		(SListMapFn) symbol_ndirty_sub, &nd);

	return nd;
}

/* Fix a leaf count.
 */
void *
symbol_fix_counts(Symbol *sym)
{
#ifdef DEBUG
	int old_count = sym->ndirtychildren;
#endif /*DEBUG*/

	sym->ndirtychildren = symbol_ndirty(sym);

#ifdef DEBUG
	g_assert(sym->ndirtychildren == old_count);
#endif /*DEBUG*/

	symbol_state_change(sym);

	return NULL;
}

/* Junk all old links, static + dynamic.
 */
void
symbol_link_destroy(Symbol *sym)
{
	(void) slist_map(sym->topchildren, (SListMapFn) link_destroy, NULL);
}

/* Scan a symbol, remaking all the links.
 */
void
symbol_link_build(Symbol *sym)
{
	g_assert(is_top(sym));

	/* Make static links for our expr and all subexprs. If this symbol
	 * is being edited, get stuff from the edited value.
	 */
	if (sym->expr) {
		if (sym->expr->row)
			(void) icontainer_map_all(
				ICONTAINER(sym->expr->row),
				(icontainer_map_fn) link_row, sym);
		else
			(void) link_children_expr(sym->expr, sym);
	}

#ifdef DEBUG
	printf("symbol_link_build: ");
	symbol_name_print(sym);
	printf("\n");
	int indent = 0;
	dump_links(sym, &indent);
#endif /*DEBUG*/
}

static void *
link_dirty_set_sub(LinkExpr *le, int serial)
{
	return expr_dirty(le->expr, serial);
}

/* Mark exprs in parent dirty. These may be sub exprs, so parent is not
 * necessarily going to be symbol_dirty_set() ... eg. A2 may be displaying an
 * instance of class "fred", and we might have edited one of A2's members to
 * refer to A1 ... but A2 depends on A1, fred does not.
 */
static void *
link_dirty_set(Link *link, int serial)
{
	/* Mark exprs in parent dirty.
	 */
	if (slist_map(link->static_links,
			(SListMapFn) link_dirty_set_sub, GINT_TO_POINTER(serial)) ||
		slist_map(link->dynamic_links,
			(SListMapFn) link_dirty_set_sub, GINT_TO_POINTER(serial)))
		return link;

	return NULL;
}

/* Walk the link graph, marking stuff for recomputation ... link->child has
 * changed, mark link->parent dirty.
 */
static void *
link_dirty_walk(Link *link, int serial)
{
	/* Have we walked down this link before?
	 */
	if (link->serial == serial)
		return NULL;
	link->serial = serial;

	/* Mark all exprs in parent dirty.
	 */
	return link_dirty_set(link, serial);
}

/* A symbol has changed ... walk the link graph, marking stuff dirty as
 * required. We don't mark this sym dirty.
 */
void *
symbol_dirty_intrans(Symbol *sym, int serial)
{
	g_assert(is_top(sym));

	return slist_map(sym->topparents,
		(SListMapFn) link_dirty_walk, GINT_TO_POINTER(serial));
}

static void *
symbol_dirty_set(Symbol *sym)
{
	g_assert(is_top(sym));

	/* Clear error, to make sure we will recomp it.
	 */
	if (sym->expr)
		expr_error_clear(sym->expr);

	if (!sym->dirty) {
#ifdef DEBUG_DIRTY
		printf("symbol_dirty_set: ");
		symbol_name_print(sym);
		printf("(%p)\n", sym);
#endif /*DEBUG_DIRTY*/

		/* Change of state.
		 */
		sym->dirty = TRUE;

		/* Update dirty counts on our parents.
		 */
		(void) slist_map(sym->topparents,
			(SListMapFn) link_dirty_child, NULL);

		/* Note change in leaf set and display.
		 */
		symbol_state_change(sym);
	}

	return NULL;
}

/* ... mark this one as well.
 */
void *
symbol_dirty(Symbol *sym, int serial)
{
	g_assert(is_top(sym));

	symbol_dirty_set(sym);

	return symbol_dirty_intrans(sym, serial);
}

void *
link_dirty_total(Link *link, int serial)
{
	static int recursion_depth = 0;

	/* Entering: note new recursion.
	 */
	if (recursion_depth++ > 1000) {
		error_top(_("Circular dependency"));
		error_sub(_("circular dependency detected near symbol \"%s\""),
			IOBJECT(link->parent)->name);
		recursion_depth = 0;
		return link;
	}

	/* Mark this sub-tree as dirty.
	 */
	symbol_dirty(link->child, serial);

	/* ... and repeat for any parents.
	 */
	if (link->child->type != SYM_ZOMBIE)
		if (slist_map(link->child->topchildren,
				(SListMapFn) link_dirty_total,
				GINT_TO_POINTER(serial)))
			return link;

	/* Pop recursion measure.
	 */
	recursion_depth--;

	return NULL;
}

/* As above, but mark children as dirty as well. Used by force recalc to make
 * sure that everything is completely rebuilt. Be careful of cycles!
 */
void *
symbol_dirty_total(Symbol *sym, int serial)
{
	if (sym->type == SYM_ZOMBIE)
		return NULL;

	/* No children: just mark this sub-tree as dirty.
	 */
	if (!sym->topchildren && symbol_dirty(sym, serial))
		return sym;

	if (slist_map(sym->topchildren,
			(SListMapFn) link_dirty_total, GINT_TO_POINTER(serial)))
		return sym;

	return NULL;
}

/* Mark a symbol as clean. Knock down the leaf count of the things which refer
 * to us ... one of them may turn into a leaf as a result.
 */
void *
symbol_dirty_clear(Symbol *sym)
{
	g_assert(is_top(sym));

	if (sym->dirty) {
#ifdef DEBUG_DIRTY
		printf("symbol_dirty_clear: ");
		symbol_name_print(sym);
		printf("(%p)\n", sym);
#endif /*DEBUG_DIRTY*/

		/* Change of state.
		 */
		sym->dirty = FALSE;
		symbol_state_change(sym);

		/* Update dirty counts on our parents.
		 */
		(void) slist_map(sym->topparents,
			(SListMapFn) link_clean_child, NULL);
	}

	return NULL;
}
