/* lp_wfs.c
 * Neil Moore <neil@cs.uky.edu>
 * 12 May 1999
 *
 * Part of liblp.  See COPYRIGHT for copyright information.
 */
#include <string.h>
#include <stddef.h>
#include <lp.h>
#include <lp_lists.h>
#include <lp_util.h>
#include <lp_fks.h>
#include <lp_lm.h>
#include <lp_wfs.h>

static int *do_fks_lm(struct lp *p, int *inmodel, int *stricken,
		      struct lp_rlist **depends, int *pbs);


/* Compute the well-founded semantics of a program.  This function uses
 * alternating Fitting-Kunen semantics and least model computation.  It
 * returns an array of (three-valued) truth values, one per atom.
 */
int *lp_wfs(struct lp *p) 
{
	int *model = lp_calloc(p->natoms, sizeof(int));
	lp_wfs_rel(p, model);
	return model;
}


/* Compute the well-founded semantics of a program using the specified
 * method.  LP_WFS_FKS requests alternating Fitting-Kunen semantics and
 * least model computation; LP_WFS_AFP requests alternating fixpoint
 * (of the Gamma operator) computation.  This function returns an array
 * of (three-valued) truth values, one per atom.
 *
 */
int *lp_wfs_m(struct lp *p, int method) 
{
	switch(method) {
	    case LP_WFS_FKS:
		return lp_wfs(p);
	    case LP_WFS_AFP:
		return lp_wfs_afp(p);
	    default:
		return NULL;
	}
}


/* Compute the well-founded semantics of a program by alternating Fitting-
 * Kunen semantics with least-model computation.  This method is, in
 * general, faster than the alternating-fixpoint-of-Gamma method.  This
 * function computes the well-founded semantics relative to a given model,
 * an array of p->natoms (3-valued) truth values. It modifies the model
 * parameter.
 */
void lp_wfs_rel(struct lp *p, int *model) 
{
	struct lp_rlist **pdepends, **ndepends;
	int *possize, *negsize, *nhead, *pbs2;
	struct lp_alist *atoms = NULL;
	int *stricken, *inlm;
	int found_one;
	int i;

	pdepends = lp_posdepends(p);
	ndepends = lp_negdepends(p);	
	possize = lp_possizes(p);
	negsize = lp_negsizes(p);
	nhead = lp_count_head(p);	

	stricken = lp_calloc(p->nrules, sizeof(int));	
	for(i=0;i<p->natoms;i++) {
		if(model[i] == LP_L_UNKNOWN)
			continue;
		
		lp_al_add(&atoms,&p->atoms[i]);
		atoms->value = model[i];
		model[i] = LP_L_UNKNOWN;
	}
	
	do {
		_do_compute_fks(p,&atoms,model,stricken,pdepends,ndepends,
				possize,negsize,nhead);
		
		pbs2=lp_copy_array(possize, p->nrules);
		inlm = do_fks_lm(p,model,stricken,pdepends,pbs2);
		free(pbs2);
		
		found_one = 0;
		for(i=0; i<p->nrules; i++) {
			int headpos = lp_apos(p,p->rules[i].head);
			
			if(!stricken[i] && model[headpos] == LP_L_UNKNOWN &&
			   !inlm[headpos]) {
				lp_al_add(&atoms, &p->atoms[headpos]);
				atoms->value = LP_L_FALSE;
				found_one = 1;
			}
		}

		free(inlm);
	} while(found_one);

	for(i=0; i<p->natoms; i++) {
		lp_rl_free(pdepends[i]);
		lp_rl_free(ndepends[i]);
	}
	free(pdepends);
	free(ndepends);
	free(possize);
	free(negsize);
	free(nhead);
	free(stricken);
}


/* Compute the well-founded semantics of a program by finding the alternating
 * fixpoints of Gamma.  This function returns an array of p->natoms (3-valued)
 * truth values.
 */
int *lp_wfs_afp(struct lp *p)
{
	int *gfp, *lfp, *model;
	int *result;
	int i;
	
	model = lp_calloc(p->natoms, sizeof(int));
	
	while(1) {
		/* compute \Gamma^2 */
		gfp = lp_Gamma(p, model);
		lfp = lp_Gamma(p, gfp);
		
		/* detect fixpoint */
		if(lp_eq_array(lfp, model, p->natoms)) {
			free(model);
			break;
		}
		
		free(gfp);
		free(model);
		model = lfp;
	}

	result = lp_malloc(sizeof(int) * p->natoms);
	
	for(i=0; i<p->natoms; i++) {
		if(gfp[i] == 0)
			result[i] = LP_L_FALSE;
		else if(lfp[i] == 1)
			result[i] = LP_L_TRUE;
		else
			result[i] = LP_L_UNKNOWN;
	}
	
	free(lfp);
	free(gfp);
	return result;
}


/* Compute the Gamma operator of a set with respect to a particular model.
 * Gamma(p,m) is defined as the least model of the reduct of p with respect
 * to m.  This function returns an array of truth values (0 or 1), one for
 * each atom, indicating which atoms are in Gamma(p,M).
 *
 */
int *lp_Gamma(struct lp *p, int *M) 
{
	struct lp *r;
	int *result;

	r = lp_reduct(p, M);
	result = lp_lm(r);
	lp_free_lp(r);
	return result;
}


/* Compute the reduct of a program p with respect to a model M.  M is an
 * array of truth values (0 or 1).  The reduct of P with respect to M,
 * written $P^M$, is the program generated by:
 *  - removing each rule with `not X' in the body, where X is in M.
 *  - removing all negative literals from all remaining rule bodies.
 *
 * This function does not take into account any positive and negative
 * requirements.
 *
 */
struct lp *lp_reduct(struct lp *p, int *M)
{
	struct lp *red;
	int i,j,d_i;
	int killed;
	ptrdiff_t diff; /* Save many calls to lp_apos() */

	red = lp_copy_lp_basic(p);
	diff = red->atoms - p->atoms;

	for(i=0,d_i=0;i < p->nrules;i++) {
		struct lp_rule *s = &p->rules[i];
		struct lp_rule *d = &red->rules[d_i];
		
		d->head = s->head + diff;
		
		d->npos = s->npos;
		d->nneg = 0;
		
		for(j=0; j < s->npos; j++)
			d->pos[j] = s->pos[j] + diff;

		killed=0;
		/* for each negated literal not X:
		 *  - eliminate the rule if X \in M
		 *  - eliminate the literal if X \notin M
		 */
		for(j=0; j < s->nneg; j++)
			if(M[lp_apos(p,s->neg[j])]) {
				killed=1;
				red->size -= (s->npos + 1);
				break;
			}

		red->size -= s->nneg;

		if(!killed)
			d_i++;
	}
	red->rules = lp_realloc(red->rules, sizeof(struct lp_rule) * d_i);
	red->nrules = d_i;
	
	return red;
}


/* Compute the least model of a program, given certain information about
 * stricken rules and body size.  This is an internal function for use
 * by lp_wfs_fks.
 *
 */
int *do_fks_lm(struct lp *p, int *inmodel, int *stricken,
	       struct lp_rlist **depends, int *pbs)
{
	struct lp_rlist *j;
	int *inlm;
	struct lp_atom **stack;
	int i;
	int sp=0;
	

	inlm = lp_calloc(p->natoms, sizeof(int));
	stack = lp_malloc(sizeof(int) * p->size);
	
	for(i=0; i<p->nrules; i++)
		if(pbs[i] == 0 && !stricken[i] &&
		   !inmodel[lp_apos(p,p->rules[i].head)])
			stack[sp++] = p->rules[i].head;

	while(sp != 0) {
		int apos = lp_apos(p,stack[--sp]);

		if(inmodel[apos] || inlm[apos])
			continue;
		inlm[apos] = 1;

		for(j=depends[apos]; j!=NULL; j=j->next) {
			int rpos = lp_rpos(p, j->rule);
			if(--pbs[rpos] == 0 && !stricken[rpos])
				stack[sp++] = j->rule->head;
		}
	}
	free(stack);
	return inlm;
}
