/* lp_smrd.c
 * Neil Moore <neil@cs.uky.edu>
 *  1 Sep 1999
 *
 * Part of liblp.  See COPYRIGHT for copyright information.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <lp.h>
#include <lp_util.h>
#include <lp_smrd.h>

static int sm_read_rule(FILE *f, struct lp *prog, struct lp_rule *rule,
			int lineno);
static int sm_read_atom(FILE *f, struct lp *prog, struct lp_atom *atom,
			int lineno);
static int sm_read_compute(FILE *f, struct lp *prog, int lineno);

static int sm_read_array(FILE *f, struct lp *p, struct lp_atom ***aptr,
			 int per_line);
static int sm_read_array_maxsize(FILE *f, struct lp *p, struct lp_atom **array,
			      int max, int per_line);
static int do_read_array(FILE *f, struct lp *p, struct lp_atom **array,
			 int num, int (*get)(FILE *));

static char *errmsg=0;
static int err_alloc=0;

/* Read a file (in smodels 1 format).  This function assumes that memory for
 * prog has already been allocated.  In the event of an error, this function
 * prints a message to stderr and returns nonzero.  Otherwise, it returns
 * zero.
 *
 */
int lp_sm_read_file(FILE *f, struct lp *prog)
{
	int natoms, nrules, size, line=3, i, len;

	lp_assert(prog != NULL);
	
	if((natoms = lp_getnumline(f)) < 0) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		len = strlen("0: expected a single number")+1;
		errmsg = lp_malloc(len);
		strncpy(errmsg, "0: expected a single number", len);
		return -1;
	}
	if((nrules = lp_getnumline(f)) < 0) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		len = strlen("1: expected a single number")+1;
		errmsg = lp_malloc(len);
		strncpy(errmsg, "1: expected a single number", len);
		return -1;
	}
	if((size = lp_getnumline(f)) < 0) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		len = strlen("2: expected a single number")+1;
		errmsg = lp_malloc(len);
		strncpy(errmsg, "2: expected a single number", len);
		return -1;
	}

	lp_init_lp(prog, nrules, natoms, size);
	
	/* read rules */
	for(i=0; i<nrules; i++, line++)
		if(sm_read_rule(f, prog, &prog->rules[i], line) < 0)
			goto failure;

	/* read atoms */
	for(i=0; i<natoms; i++, line++)
		if(sm_read_atom(f, prog, &prog->atoms[i], line) < 0)
			goto failure;

	/* read compute statement; since it is the last thing in the file,
	 * we don't need to update line.
	 */
	if(sm_read_compute(f, prog, line) < 0) {
		return -1;
	}
	
	return 0;
  failure:
	free(prog->atoms);
	free(prog->rules);
	if(prog->posreq)
		free(prog->posreq);
	if(prog->negreq)
		free(prog->negreq);
	return -1;
}


/* Read a rule in smodels 1 format.  If an error occurs, sm_read_rule()
 * prints an error to stderr and returns nonzero; otherwise, it returns
 * zero.
 *
 */
int sm_read_rule(FILE *f, struct lp *prog, struct lp_rule *rule, int lineno)
{
	int nneg, npos;
	int head;

	lp_assert(rule != NULL);
	lp_assert(prog != NULL);
	lp_assert(rule >= prog->rules && rule < &prog->rules[prog->nrules]);
	
	/* First, get the head */
	if((head = lp_getnum(f)) < 0) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: bad line")+1);
		/* mod 1_000_000_000 to prevent overflow */
		sprintf(errmsg, "%d: bad line", lineno % 1000000000);
		return -1;
	}
	if(head < 1 || head > prog->natoms) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: head atom (999999999) out of bounds")+1);
		sprintf(errmsg, "%d: head atom (%d) out of bounds\n", lineno % 1000000000,
		        head % 1000000000);
		return -1;
	}

	/* valid head */
	rule->head = &prog->atoms[--head];

	nneg = sm_read_array_maxsize(f, prog, rule->neg, LP_MAXNEG, 0);

	switch(nneg) {
	    case -1:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: bad line")+1);
		sprintf(errmsg, "%d: bad line", lineno % 1000000000);
		return -1;
	    case -2:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: too many body atoms")+1);
		sprintf(errmsg, "%d: too many body atoms", lineno % 1000000000);
		return -1;
	    case -3:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: body atom out of bound")+1);
		sprintf(errmsg, "%d: body atom out of bound", lineno % 1000000000);
		return -1;
	    default:
		rule->nneg = nneg;
	}

	npos = sm_read_array_maxsize(f, prog, rule->pos, LP_MAXPOS, 0);

	switch(npos) {
	    case -1:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: bad line")+1);
		sprintf(errmsg, "%d: bad line", lineno % 1000000000);
		return -1;
	    case -2:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: too many body atoms")+1);
		sprintf(errmsg, "%d: too many body atoms", lineno % 1000000000);
		return -1;
	    case -3:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: body atom out of bound")+1);
		sprintf(errmsg, "%d: body atom out of bound", lineno % 1000000000);
		return -1;
	    default:
		rule->npos = npos;
	}
	
	/* make sure we have no trailing garbage, and remove the
         * newline from stdin */

	if(lp_finishline(f) < 0) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: trailing garbage")+1);
		sprintf(errmsg, "%d: trailing garbage", lineno % 1000000000);
		return -1;
	}

	return 0;
}


/* Read an atom name from f.  In the event of an error, this function prints
 * an error message to stderr and returns nonzero; otherwise, it returns
 * zero.
 *
 */
int sm_read_atom(FILE *f, struct lp *prog, struct lp_atom *atom, int lineno)
{
	int len;
	char buff[LP_ATOMLEN+1];

	lp_assert(atom != NULL);
	lp_assert(prog != NULL);
	lp_assert(atom >= prog->atoms && atom < &prog->atoms[prog->natoms]);
	
	fgets(buff, LP_ATOMLEN + 1, f);

	len = strlen(buff);

	if(buff[len-1] != '\n') {
		if(feof(f)) {
			if(err_alloc)
				free(errmsg);
			err_alloc=1;
			errmsg = lp_malloc(strlen("999999999: premature EOF")+1);
			sprintf(errmsg, "%d: premature EOF", lineno % 1000000000);
		} else {
			if(err_alloc)
				free(errmsg);
			err_alloc=1;
			errmsg = lp_malloc(strlen("999999999: atom name too long")+1);
			sprintf(errmsg, "%d: atom name too long", lineno % 1000000000);
		}
		return -1;
	}

	buff[--len] = '\0';
	
	strncpy(atom->name, buff, len);
	atom->name[len] = '\0';
	
	return 0;
}


/* Read the smodels compute statement from f.  In the event of an error,
 * this function prints an error message to stderr and returns nonzero;
 * otherwise, it returns zero.
 *
 */
int sm_read_compute(FILE *f, struct lp *prog, int lineno) 
{
	char buff[5];
	int nposreq, nnegreq, nmodels, len;

	lp_assert(prog != NULL);

	/* get positive requirements */
	fgets(buff, 4, f);
	if(feof(f) || strcmp(buff, "B+\n")) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: B+ expected")+1);
		sprintf(errmsg, "%d: B+ expected", lineno % 1000000000);
		return -1;
	}

	lineno++;

	prog->posreq = NULL;
	nposreq = sm_read_array(f, prog, &prog->posreq, 1);

	switch(nposreq) {
	    case -1:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("bad line near 999999999")+1);
		sprintf(errmsg, "bad line near %d", lineno % 1000000000);
		return -1;
	    case -2:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		len = strlen("CANNOT HAPPEN: nposreq too large")+1;
		errmsg = lp_malloc(len);
		strncpy(errmsg, "CANNOT HAPPEN: nposreq too large", len);
		return -1;
	    case -3:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("positive requirement out of bounds near 999999999")+1);
		sprintf(errmsg, "positive requirement out of bounds near %d", lineno % 1000000000);
		return -1;
	    default:
		prog->nposreq = nposreq;
	}
	
	lineno += nposreq + 1;

	/* get negative requirements */
	
	fgets(buff, 4, f);
	if(feof(f) || strcmp(buff, "B-\n")) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: B- expected")+1);
		sprintf(errmsg, "%d: B- expected", lineno % 1000000000);
		return -1;
	}

	lineno++;

	prog->negreq = NULL;
	nnegreq = sm_read_array(f, prog, &prog->negreq, 1);

	switch(nnegreq) {
	    case -1:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("bad line near 999999999")+1);
		sprintf(errmsg, "bad line near %d", lineno % 1000000000);
		return -1;
	    case -2:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		len = strlen("CANNOT HAPPEN: nnegreq too large")+1;
		errmsg = lp_malloc(len);
		strncpy(errmsg, "CANNOT HAPPEN: nnegreq too large", len);
		return -1;		
	    case -3:
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("negative requirement out of bounds near 999999999")+1);
		sprintf(errmsg, "negative requirement out of bounds near %d", lineno % 1000000000);
		return -1;
	    default:
		prog->nnegreq = nnegreq;
	}
	
	lineno += nnegreq + 1;

	if((nmodels=lp_getnumline(f)) < 0) {
		if(err_alloc)
			free(errmsg);
		err_alloc=1;
		errmsg = lp_malloc(strlen("999999999: number of extensions expected")+1);
		sprintf(errmsg, "%d: number of extensions expected", lineno % 1000000000);
		return -1;
	}

	prog->nmodels = nmodels;
	
	return 0;
}


/* Read an array from a file.  The array should be an integer indicating the
 * array size, followed by that many integers.  If per_line is true, each
 * integer is expected to appear on its own line.  Memory for the array is
 * allocated with lp_malloc().  This function returns the number of elements
 * read, or a negative number if there is an error.
 *
 */
int sm_read_array(FILE *f, struct lp *p, struct lp_atom ***arrayptr, int per_line) 
{
	int num;

	int (*get)(FILE *) = per_line ? lp_getnumline : lp_getnum;

	lp_assert(p != NULL);
	lp_assert(arrayptr != NULL);
	
	if((num = get(f)) < 0)
		return -1;

	if(num != 0)
		*arrayptr = lp_calloc(num, sizeof(struct lp_atom *));

	return do_read_array(f, p, *arrayptr, num, get);
}


/* Read an array from a file, with a specified maximum size.  The array
 * should be an integer indicating the array size, followed by that many
 * integers.  If per_line is true, each integer is expected to appear on
 * its own line.  This function returns the number of elements read, or
 * a negative number if there is an error.
 *
 */
int sm_read_array_maxsize(FILE *f, struct lp *p, struct lp_atom **array, int max, int per_line) 
{
	int num;

	int (*get)(FILE *) = per_line ? lp_getnumline : lp_getnum;

	lp_assert(p != NULL);
	lp_assert(array != NULL);
	
	if((num = get(f)) < 0)
		return -1;

	if(max > 0 && num > max)
		return -2;

	return do_read_array(f, p, array, num, get);
}


/* Read a specified number of elements into an array.  (*get)() is used to
 * read individual elements.  This function returns the number of elements
 * read, or a negative number if there is an error.
 *
 */
int do_read_array(FILE *f, struct lp *p, struct lp_atom **array, int num, int (*get)(FILE *)) 
{
	int i, curr;
	
	for(i=0; i<num; i++) {
		if((curr=get(f)) < 0)
			return -1;

		if(curr < 1 || curr > p->natoms)
			return -3;
		
		array[i] = &p->atoms[--curr];
	}

	return num;
}


/* Return the error message generated by the last call to lp_sm_read_file.
 */
const char *lp_smrd_error(void) 
{
	return errmsg;
}

