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

static void strikerules(struct lp *p, int *stricken, int *nhead,
			struct lp_rlist *rl, struct lp_alist **atoms);
static void strikebody(struct lp *p, int *stricken, int *bs, int *obs,
		       struct lp_rlist *rl, struct lp_alist **atoms);
static void start_fks(struct lp *p, int *stricken, int *pbs, int *nbs,
		      int *nhead, struct lp_alist **atoms);


/* Compute the Fitting-Kunen semantics of a logic program.  This is a
 * wrapper around _do_compute_fks.  This function returns an array of
 * (three-valued) truth values, one for each atom.
 *
 * The well-founded semantics (see lp_wfs.c) generally produces a model
 * better than (or equivalent to) that produced by the Fitting-Kunen
 * semantics; however, FKS can be computed in linear time on the size of
 * the program, while no linear method of computing WFS is known to exist.
 *
 */
int *lp_fks(struct lp *p) 
{
	int *model;
	model = lp_calloc(p->natoms, sizeof(int));
	lp_fks_rel(p,model);
	return model;
}

void lp_fks_rel(struct lp *p, int *model) 
{
	struct lp_rlist **pdepends, **ndepends;
	struct lp_alist *atoms = NULL;
	int *possize, *negsize, *nhead;
	int *stricken;
	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_compute_fks(p,&atoms,model,stricken,pdepends,ndepends,
		       possize,negsize,nhead);
	
	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(stricken);
	free(nhead);
	return;
}


/* Compute the Fitting-Kunen semantics of a logic program.  This is an
 * internal function intended for use by lp_fks() and lp_wfs().
 *
 */
void _do_compute_fks(struct lp *p, struct lp_alist **atoms, int *inmodel,
		    int *stricken, struct lp_rlist **pdepends,
		    struct lp_rlist **ndepends, int *pbs, int *nbs, int *nhead) 
{
	int val;
	int apos;
	
	start_fks(p, stricken, pbs, nbs, nhead, atoms);
	
	while(*atoms != NULL) {
		val = (*atoms)->value;
		apos = lp_apos(p,lp_al_pop(atoms));

		if(inmodel[apos] == LP_L_TRUE || (inmodel[apos] == LP_L_FALSE && val == LP_L_FALSE))
			continue;
		
		inmodel[apos] = val;
		
		if(val == LP_L_TRUE) {
			strikerules(p, stricken, nhead, ndepends[apos], atoms);
			strikebody(p, stricken, pbs, nbs, pdepends[apos], atoms);
		} else {
			strikerules(p, stricken, nhead, pdepends[apos], atoms);
			strikebody(p, stricken, nbs, pbs, ndepends[apos], atoms);
		}
	}
}


/* Begin computing FKS.  This function adds to the queue (atoms) those
 * atoms with known truth values---those with appear as the head of a
 * fact are marked false, and those which do not appear in any head are
 * marked true.
 *
 */
void start_fks(struct lp *p, int *stricken, int *pbs, int *nbs, int *nhead,
	       struct lp_alist **atoms)
{
	int i;
	
	for(i=0; i<p->nrules; i++) {
		if(pbs[i] + nbs[i] == 0 && !stricken[i]) {
			lp_al_add(atoms,p->rules[i].head);
			(*atoms)->value = LP_L_TRUE;
		}
	}

	for(i=0; i<p->natoms; i++) {
		if(nhead[i] == 0) {
			lp_al_add(atoms, &p->atoms[i]);
			(*atoms)->value = LP_L_FALSE;
		}
	}
}


/* Mark as stricken the rules in rl.  This also decrements the head count
 * for the atom in those rules' heads, possibly adding those atoms to the
 * queue.
 *
 */
void strikerules(struct lp *p, int *stricken, int *nhead, struct lp_rlist *rl,
		 struct lp_alist **atoms) 
{
	struct lp_rlist *j;
	
	for(j=rl; j!=NULL; j=j->next) {
		int rpos = lp_rpos(p, j->rule);
		
		if(stricken[rpos])
			continue;
		
		stricken[rpos] = 1;
		
		if(--nhead[lp_apos(p,j->rule->head)] == 0) {
			lp_al_add(atoms, j->rule->head);
			(*atoms)->value = LP_L_FALSE;
		}
	}
}


/* Decrement the body size of the rules in rl, possibly adding those rules'
 * heads to the queue.
 *
 */
void strikebody(struct lp *p, int *stricken, int *bs, int *obs, struct lp_rlist *rl,
		struct lp_alist **atoms) 
{
	struct lp_rlist *j;
	
	for(j=rl; j!=NULL; j=j->next) {
		int rpos = lp_rpos(p, j->rule);
		
		--bs[rpos];
		
		if((bs[rpos] + obs[rpos] == 0) && !stricken[rpos]) {
			lp_al_add(atoms, j->rule->head);
			(*atoms)->value = LP_L_TRUE;
		}
	}
}

