/* lp.c
 * Neil Moore <neil@cs.uky.edu>
 * 22 Oct 1999
 *
 * Part of lpsms.  See COPYRIGHT for copyright information.
 */
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <lp.h>
#include <lp_lists.h>
#include <lp_util.h>

#ifndef VERSION
#define VERSION "unknown"
#endif
#ifndef LPSMSVER
#define LPSMSVER "unknown"
#endif

#ifdef USE_REAL_A_AND_R_BOUNDS
static int a_bounds(struct lp *p, struct lp_atom *a);
static int r_bounds(struct lp *p, struct lp_rule *r);

/* Test whether a is a valid pointer to an atom in p.  This function returns
 * 1 for true, 0 for false.
 */
int a_bounds(struct lp *p, struct lp_atom *a) 
{
	if(p == NULL || a == NULL)
		return 0;	

	return(a >= p->atoms && a < &p->atoms[p->natoms]);
}


/* Test whether r is a valid pointer to a rule in p.  This function returns 1
 * for true, 0 for false.
 */
int r_bounds(struct lp *p, struct lp_rule *r) 
{
	if(p == NULL || r == NULL)
		return 0;
	
	return(r >= p->rules && r < &p->rules[p->nrules]);
}
#else /* !defined(USE_REAL_A_AND_R_BOUNDS) */
# define a_bounds(p,a) ((p) && (a) && (a)>=(p)->atoms && (a)<&((p)->atoms[(p)->natoms]))
# define r_bounds(p,r) ((p) && (r) && (r)>=(p)->rules && (r)<&((p)->rules[(p)->nrules]))
#endif

/* Return a string containing the version of liblp being used.  The
 * string is of the format "X.Y.Z", where X and Y may be one or two
 * digits, and Z may be from one to three digits.  X is the major
 * version, Y the minor version, and Z the revision.
 *
 * An increment in the revision number indicates a bugfix or other
 * cleanup; programs written for X.Y.Z should work under X.Y.W,
 * assuming they are not affected by any fixed (or introduced) bugs.
 *
 * An increment in the minor version number indicates new features or
 * similar changes.  A program written for X.Y.Z should work under
 * X.Y+1.W, although the reverse may not necessarily be true.  Note
 * that private functions with extern linkage (those whose names 
 * begin with an underscore) may change in incompatible ways between
 * minor versions, so you should not use them if you wish your program
 * to work in the future.
 *
 * An increment in the major version number indicates changes to
 * existing APIs, removal of features, changes in semantics, or
 * other such changes.  A program written for X.Y.Z may not run,
 * or even compile, with X+1.V.W.
 * 
 */
const char *lp_version(void) {
	return VERSION;
}

/* Return a string containing the version of lpsms being used.
 * lpsms (logic programming with stable model semantics) is a system
 * consisting of liblp and a number of programs which use it.  The
 * string is of the same format as that returned by lp_version(),
 * but changes in the lpsms version number do not necessarily
 * correspond to equivalent changes in the liblp version number.
 */
const char *lp_sms_version(void) {
	return LPSMSVER;
}

#ifdef USE_REAL_LP_APOS_AND_RPOS
/* Given a program p and an atom a, return the position of a within p.  For
 * example, lp_apos(&p, &p.atoms[3]) should return 3.  If the atom is not
 * within bounds, lp_apos aborts the program (if LP_DEBUG is defined) or
 * fails silently (if LP_DEBUG is not defined).
 */
int lp_apos(struct lp *p, struct lp_atom *a) 
{
	int pos;
	lp_assert(a != NULL);
	lp_assert(p != NULL);
	
	pos = a - p->atoms;
	
	lp_assert(pos >= 0 && pos < p->natoms);
	
	return pos;
}


/* Given a program p and a rule r, return the position of r within p.  For
 * example, lp_rpos(&p, &p.rules[3]) should return 3.  If the rule is not
 * within bounds, lp_rpos aborts the program (if LP_DEBUG is defined) or
 * fails silently (if LP_DEBUG is not defined).
 */
int lp_rpos(struct lp *p, struct lp_rule *r) 
{
	int pos;
	lp_assert(r != NULL);
	lp_assert(p != NULL);
	
	pos = r - p->rules;
	
	lp_assert(pos >= 0 && pos < p->nrules);
	
	return pos;
}
#endif /* USE_REAL_LP_APOS_AND_RPOS */


/* Initialise a program with a specified number of rules (initially with
 * empty bodies, and heads corresponding to atom 0) and atoms (initially
 * with empty names).  The program size should also be specified.  The
 * lp structure must already have been allocated.  The positive and
 * negative requirements are made empty, and the number of models to
 * generate is set to 0 (unlimited).
 */
void lp_init_lp(struct lp *p, int nrules, int natoms, int size) 
{
	lp_assert(p != NULL);
	lp_assert(size >= 0);
	
	p->atoms = lp_calloc(natoms, sizeof(struct lp_atom));
	p->rules = lp_calloc(nrules, sizeof(struct lp_rule));
	p->natoms = natoms;
	p->nrules = nrules;
	p->size = size;
	p->nposreq = 0;
	p->nnegreq = 0;
	p->posreq = NULL;
	p->negreq = NULL;
	p->nmodels = 0;
}


/* Free a program, its rules, its atoms, and its positive and negative
 * requirements lists.
 */
void lp_free_lp(struct lp *p)
{
	lp_assert(p != NULL);

	if(p->atoms)
		free(p->atoms);
	if(p->rules)
		free(p->rules);
	if(p->posreq)
		free(p->posreq);
	if(p->negreq)
		free(p->negreq);
	free(p);
}


/* Allocate a new program (using lp_malloc) and copy an existing program
 * into it.  Rules, atoms, size, number of models, and requirement lists
 * are copied.
 *
 * This function returns a pointer to the copy.
 */
struct lp *lp_copy_lp(struct lp *p) 
{
	struct lp *copy;
	int i,j;
	ptrdiff_t diff;

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

	for(i=0;i<p->nrules;i++) {
		struct lp_rule *s = &p->rules[i];
		struct lp_rule *d = &copy->rules[i];

		d->head = s->head + diff;
		d->npos = s->npos;
		d->nneg = s->nneg;
		
		for(j=0; j < s->npos; j++)
			d->pos[j] = s->pos[j] + diff;
		
		for(j=0; j < s->nneg; j++)
			d->neg[j] = s->neg[j] + diff;
	}

	return copy;
}


/* Allocate a new program (using lp_malloc) and copy most of an existing
 * program into it.  Atoms, size, number of models, and requirement lists
 * are copied.  This function may be used, for example, when computing the
 * reduct of a program---everything but the rules should be copied as-is.
 *
 * This function returns a pointer to the copy.
 */
struct lp *lp_copy_lp_basic(struct lp *p) {
	struct lp *copy;
	int i;
	ptrdiff_t diff;
	
	copy = lp_malloc(sizeof(struct lp));

	lp_init_lp(copy, p->nrules, p->natoms, p->size);
	
	copy->nposreq = p->nposreq;
	copy->nnegreq = p->nnegreq;
	copy->nmodels = p->nmodels;

	if(p->posreq == NULL)	
		copy->posreq = NULL;
	else
		copy->posreq = lp_malloc(p->nposreq * sizeof(struct lp_atom *));
	
	if(p->negreq == NULL)	
		copy->negreq = NULL;
	else
		copy->negreq = lp_malloc(p->nnegreq * sizeof(struct lp_atom *));

	diff = copy->atoms - p->atoms;
	
	for(i=0;i<p->natoms;i++)
		strncpy(copy->atoms[i].name, p->atoms[i].name, LP_ATOMLEN+1);

	for(i=0;i<p->nposreq;i++)
		copy->posreq[i] = p->posreq[i] + diff;
	
	for(i=0;i<p->nnegreq;i++)
		copy->negreq[i] = p->negreq[i] + diff;

	return copy;
}


/* Simplify a logic program.  Currently, no transformations are applied.
 * The simplified program is expected to behave exactly as the original.
 *
 * This function returns 1 if any changes were made, otherwise 0.
 */
int lp_simplify_lp(struct lp *p) 
{
	return 0;
}


/* Classify a rule as a fact, horn clause, killing rule, or local killing
 * rule.  See the functions below for precise definitions.  This function
 * returns an LP_R_* constant indicating the type of rule.  The `type'
 * field of the rule is *not* set.
 *
 * This functon performs the expensive (O(n) on the size of the program)
 * operation of testing globality of killing atoms.  If this is not desired,
 * see lp_classify_rule_local() below.
 */
int lp_classify_rule(struct lp *p, struct lp_rule *r) 
{
	/* assertions are made by the individual functions */

	if(lp_is_fact(p,r))
		return LP_R_FACT;
	else if(lp_is_horn(p,r))
		return LP_R_HORN;
	else if(lp_is_killing(p,r))
		return LP_R_KILLING;
	else if(lp_is_local_killing(p,r))
		return LP_R_LOCAL_KILLING;
	else
		return LP_R_NORMAL;
}


/* Classify a rule as a fact, horn clause, or local killing rule.  See the
 * functions below for precise definitions.  This function returns an LP_R_*
 * constant indicating the type of rule.  The `type' field of the rule is
 * *not* set.
 *
 * This functon does not test for globality of killing atoms, so a rule
 * identified as LP_R_LOCAL_KILLING may not necessarily be a killing rule.
 * In reality, it is quite rare that a local killing default not be a
 * global killing default.
 */
int lp_classify_rule_local(struct lp *p, struct lp_rule *r) 
{
	/* assertions are made by the individual functions */

	if(lp_is_fact(p,r))
		return LP_R_FACT;
	else if(lp_is_horn(p,r))
		return LP_R_HORN;
	else if(lp_is_local_killing(p,r))
		return LP_R_LOCAL_KILLING;
	else
		return LP_R_NORMAL;
}


/* Test whether a rule is a fact.  Facts have empty bodies.  Returns 1 for
 * true, 0 for false.
 */
int lp_is_fact(struct lp *p, struct lp_rule *r) 
{
	lp_assert(r != NULL);
	lp_assert(p != NULL);
	lp_assert(r_bounds(p,r));
	lp_assert(a_bounds(p,r->head));
	
	return (r->npos == 0 && r->nneg == 0);
}


/* Test whether a rule is a horn clause.  Horn clauses have no negative
 * literals in their bodies.  Returns 1 for true, 0 for false.
 */
int lp_is_horn(struct lp *p, struct lp_rule *r) 
{
	lp_assert(r != NULL);
	lp_assert(p != NULL);
	lp_assert(r_bounds(p,r));
	lp_assert(a_bounds(p,r->head));

	return (r->nneg == 0);
}


/* Test whether a rule is a (local) killing rule.  Local killing rules are
 * of the form:
 *     f :- a1, . . . , an, not b1, . . . , not bm, not f.
 *
 * where none of ai are the same as f.  Returns 1 for true, 0 for false.
 */
int lp_is_local_killing(struct lp *p, struct lp_rule *r) 
{
	int i;

	lp_assert(r != NULL);
	lp_assert(p != NULL);
	lp_assert(r_bounds(p,r));
	lp_assert(a_bounds(p,r->head));
	
	for(i=0; i<r->npos; i++)
		if(r->pos[i] == r->head)
			return 0;


	for(i=0; i<r->nneg; i++)
		if(r->neg[i] == r->head)
			return 1;

	return 0;
}


/* Test whether a rule is a (global) killing rule.  A rule r is a killing
 * rule if and only if:
 *  1. r is a local killing rule (see above)
 *  2. r's head does not appear as the head of any rule which is not
 *     a local killing rule.
 *
 * This function returns 1 for true, 0 for false.
 */
int lp_is_killing(struct lp *p, struct lp_rule *r) 
{
	int i;

	lp_assert(r != NULL);
	lp_assert(p != NULL);
	lp_assert(r_bounds(p,r));
	lp_assert(a_bounds(p,r->head));
	
	if(!lp_is_local_killing(p,r))
		return 0;

	for(i=0; i<p->nrules; i++)
		if(p->rules[i].head == r->head && !lp_is_local_killing(p,&p->rules[i]))
			return 0;

	return 1;
}


/* Compute reverse dependencies in this program.  This function returns
 * an array of rule lists, one list per atom.  Each list contains
 * (pointers to) the rules on which this atom directly depends.  An atom
 * a directly depends on a rule r iff a is r's head.
 *
 * This function allocates memory for the array and for the lists with
 * lp_malloc().
 *
 */
struct lp_rlist **lp_headof(struct lp *p)
{
	struct lp_rlist **result;
	int i;
	int apos;
	struct lp_rule *rule;
	
	result = lp_calloc(p->natoms, sizeof(struct lp_rlist *));
	
	for(i=0; i<p->nrules; i++) {
		rule = &p->rules[i];
		apos = lp_apos(p, rule->head);
		lp_rl_add(&result[apos],rule);
	}
	return result;
}


/* Compute dependencies in this program.  This function returns an array 
 * of rule lists, one list per atom.  Each list contains (pointers to) the
 * rules which directly depend on that atom.  A rule r directly depends on
 * an atom a iff a appears in r's body.
 *
 * This function allocates memory for the array and for the lists with
 * lp_malloc().
 *
 */
struct lp_rlist **lp_depends(struct lp *p)
{
	struct lp_rlist **result;
	int i, j;
	int apos;
	struct lp_rule *rule;
	
	result = lp_calloc(p->natoms, sizeof(struct lp_rlist *));
	
	for(i=0; i<p->nrules; i++) {
		rule = &p->rules[i];
		
		for(j=0; j<rule->npos; j++) {
			apos = lp_apos(p, rule->pos[j]);
			lp_rl_add(&result[apos],rule);
		}
		for(j=0; j<rule->nneg; j++) {
			apos = lp_apos(p, rule->neg[j]);
			lp_rl_add(&result[apos],rule);
		}
	}
	return result;
}


/* Compute positive dependencies in this program.  This function returns an
 * array of rule lists, one list per atom.  Each list contains (pointers
 * to) the rules which directly depend positively on that atom.  A rule r
 * directly depends positively on an atom a iff a appears positively in r's
 * body.
 *
 * This function allocates memory for the array and for the lists with
 * lp_malloc().
 *
 */
struct lp_rlist **lp_posdepends(struct lp *p)
{
	struct lp_rlist **result;
	int i, j;
	int apos;
	struct lp_rule *rule;
	
	result = lp_calloc(p->natoms, sizeof(struct lp_rlist *));
	
	for(i=0; i<p->nrules; i++) {
		rule = &p->rules[i];
		
		for(j=0; j<rule->npos; j++) {
			apos = lp_apos(p, rule->pos[j]);
			lp_rl_add(&result[apos],rule);
		}
	}
	return result;
}


/* Compute the negative dependencies of this rule.  This function returns
 * an array of rule lists, one list per atom.  Each list contains (pointers
 * to) the rules which directly depend negatively on that atom.  A rule r
 * directly depends negatively on an atom a iff a appears negatively in r's
 * body.
 *
 * This function allocates memory for the array and for the lists with
 * lp_malloc().
 *
 */
struct lp_rlist **lp_negdepends(struct lp *p)
{
	struct lp_rlist **result;
	int i, j;
	int apos;
	struct lp_rule *rule;
	
	result = lp_calloc(p->natoms, sizeof(struct lp_rlist *));
	
	for(i=0; i<p->nrules; i++) {
		rule = &p->rules[i];
		
		for(j=0; j<rule->nneg; j++) {
			apos = lp_apos(p, rule->neg[j]);
			lp_rl_add(&result[apos],rule);
		}
	}
	return result;
}


/* Count the number of times each atom appears as a rule head.  Returns
 * an array of integers, one for each atom.
 *
 * This function allocates memory for the array with lp_malloc().
 *
 */
int *lp_count_head(struct lp *p)
{
	int *result;
	int i;
	
	result = lp_calloc(p->natoms, sizeof(int));
	
	for(i=0; i<p->nrules; i++)
		result[lp_apos(p,p->rules[i].head)]++;

	return result;
}


/* Count the size of the body of each rule.  Returns an array of integers,
 * one for each rule.
 *
 * This function allocates memory for the array with lp_malloc().
 *
 */
int *lp_bodysizes(struct lp *p)
{
	int *result;
	int i;
	
	result = lp_malloc(sizeof(int) * p->nrules);
	
	for(i=0; i<p->nrules; i++)
		result[i] = p->rules[i].npos + p->rules[i].nneg;

	return result;
}


/* Count the size of the positive body of each rule.  Returns an array of
 * integers, one for each rule.
 *
 * This function allocates memory for the array with lp_malloc().
 *
 */
int *lp_possizes(struct lp *p)
{
	int *result;
	int i;
	
	result = lp_malloc(sizeof(int) * p->nrules);
	
	for(i=0; i<p->nrules; i++)
		result[i] = p->rules[i].npos;

	return result;
}


/* Count the size of the negative body of each rule.  Returns an array of
 * integers, one for each rule.
 *
 * This function allocates memory for the array with lp_malloc().
 *
 */
int *lp_negsizes(struct lp *p)
{
	int *result;
	int i;
	
	result = lp_malloc(sizeof(int) * p->nrules);
	
	for(i=0; i<p->nrules; i++)
		result[i] = p->rules[i].nneg;

	return result;
}
