/* simpmain.c
 * Neil Moore <neil@cs.uky.edu>
 * 24 Feb 2000
 *
 * Part of lpsms.  See COPYRIGHT for copyright information.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <lp.h>
#include <lp_smrd.h>
#include <lp_smpr.h>
#include <lp_util.h>
#include <lp_graph.h>

#ifndef VERSION
#define VERSION "unknown"
#endif

static void uniquify(struct lp *p);
static void rename_atoms(struct lp *p, int ren_dual);
static void handle_reqs(struct lp *p);
static void handle_pos_req(struct lp *p, struct lp_atom *a);
static void handle_neg_req(struct lp *p, struct lp_atom *a);
static void classify_rules(struct lp *p);
static void resize_rules(struct lp *p, int nrules);
static void resize_atoms(struct lp *p, int natoms);
static void usage(void);
static void version(void);

int main(int argc, char *argv[]) 
{
	char *infilename = NULL;
	char *outfilename = NULL;
	FILE *infile = NULL, *outfile = NULL;
	clock_t start, end;
	int verbosity=1;
	int timing=1;
	int do_uniq=0;
	int do_killing=1;
	int ren_dual=0;

	struct lp *prog = NULL;

	while (*++argv != NULL) {
		if(!strcmp(*argv, "-o") || !strcmp(*argv, "--output")) {
			++argv;
			if(*argv == NULL) {
				usage();
				return 1;
			}
			outfilename = *argv;
		} else if(!strncmp(*argv, "-o", 2)) {
			outfilename = *argv + 2;
		} else if(!strncmp(*argv, "--output=", 9)) {
			outfilename = *argv + 9;
		} else if(!strcmp(*argv, "-u") || !strcmp(*argv, "--uniquify")) {
			do_uniq = 1;
		} else if(!strcmp(*argv, "-U") || !strcmp(*argv, "--no-uniquify")) {
			do_uniq = 0;
		} else if(!strcmp(*argv, "-d") || !strcmp(*argv, "--rename-dual")) {
			ren_dual = 1;
		} else if(!strcmp(*argv, "-D") || !strcmp(*argv, "--no-rename-dual")) {
			ren_dual = 0;
		} else if(!strcmp(*argv, "-k") || !strcmp(*argv, "--killing-req")) {
			do_killing = 1;
		} else if(!strcmp(*argv, "-K") || !strcmp(*argv, "--no-killing-req")) {
			do_killing = 0;
		} else if(!strcmp(*argv, "-V") || !strcmp(*argv, "--verbose")) {
			verbosity = 2;
		} else if(!strcmp(*argv, "-m") || !strcmp(*argv, "--messages")) {
			verbosity = 1;
		} else if(!strcmp(*argv, "-q") || !strcmp(*argv, "--quiet")) {
			verbosity = 0;
		} else if(!strcmp(*argv, "-t") || !strcmp(*argv, "--timing")) {
			timing = 1;
		} else if(!strcmp(*argv, "-n") || !strcmp(*argv, "--no-time")) {
			timing = 0;
		} else if(!strcmp(*argv, "-v") || !strcmp(*argv, "--version")) {
			version();
			return 0;
		} else if(!strcmp(*argv, "-h") || !strcmp(*argv, "--help")) {
			usage();
			return 0;
		} else if(!strcmp(*argv, "-")) {
			infilename = NULL;
		} else if ((*argv)[0] == '-') {
			usage();
			return 1;
		} else {
			infilename = *argv;
		}		
	}

	if(verbosity > 1 && infilename)
		fprintf(stderr, "Opening %s for reading...\n", infilename);
	
	if(infilename == NULL) {
		infile = stdin;
	} else if((infile = fopen(infilename, "r")) == NULL) {
		if(verbosity > 0)
			fprintf(stderr, "cannot open %s: %s\n", infilename, strerror(errno));
		goto failure;
	}


	if(verbosity > 1 && outfilename)
		fprintf(stderr, "Opening %s for writing...\n", outfilename);
	
	if(outfilename == NULL) {
		outfile = stdout;
	} else if((outfile = fopen(outfilename, "w")) == NULL) {
		if(verbosity > 0)
			fprintf(stderr, "cannot open %s: %s\n", outfilename, strerror(errno));
		goto failure;
	}

	start=clock();
	prog = lp_malloc(sizeof(struct lp));
	
	if(verbosity > 1)
		fprintf(stderr, "Reading program...\n");
	if(lp_sm_read_file(infile, prog) < 0) {
		if(verbosity > 0) {
			const char *msg = lp_smrd_error();
			if(msg)
				fprintf(stderr, "%s\n", msg);
			fprintf(stderr, "error in input; output not generated\n");
		}
		goto failure;
	}

	if(verbosity > 1)
		fprintf(stderr, "Translating compute statement...\n");
	if(do_killing)
		handle_reqs(prog);
	if(do_uniq) {
		if(verbosity > 1)
			fprintf(stderr, "Uniquifying killing atoms...\n");
		classify_rules(prog);
		uniquify(prog);
	}
	if(verbosity > 1)
		fprintf(stderr, "Renaming atoms...\n");
	rename_atoms(prog, ren_dual);

	if(verbosity > 1)
		fprintf(stderr, "Outputting program...\n");
	lp_sm_print_program(outfile, prog);

	end = clock();
	if(timing)
		fprintf(stderr, "simplification: %f seconds\n", ((double)end - start)/ CLOCKS_PER_SEC);

	if(verbosity > 1)
		fprintf(stderr, "Cleaning up...\n");
	if (infile != stdin)
		fclose(infile);
	if(outfile != stdout)
		fclose(outfile);
	lp_free_lp(prog);
	return 0;
	
 failure:
	if(verbosity > 1)
		fprintf(stderr, "Cleaning up...\n");
	if(infile && infile != stdin)
		fclose(infile);
	if(outfile && outfile != stdout)
		fclose(outfile);
	if(prog)
		lp_free_lp(prog);
	return 1;
}


void classify_rules(struct lp *p) 
{
	int i, apos;

	/* appear[a]    Meaning for p->atoms[a]
	 * -------------------------------------------------
	 *   -1        Appears other than as a killing atom.
	 *    0        Has not yet appeared.
	 *    1        Has appeared only as a killing atom.
	 */
	int *appear = lp_calloc(p->natoms, sizeof(int));
	
	for(i=0; i<p->nrules; i++) {
		apos = lp_apos(p, p->rules[i].head);
		
		if(appear[apos] == -1)
			continue;
		
		if(lp_is_local_killing(p, &p->rules[i]))
			appear[apos] = 1;
		else
			appear[apos] = -1;
	}

	for(i=0; i<p->nrules; i++) {
		apos = lp_apos(p, p->rules[i].head);

		if(appear[apos] == 1)
			p->rules[i].type = LP_R_KILLING;
		else
			p->rules[i].type = LP_R_NORMAL;
	}
	free(appear);
}


void uniquify(struct lp *p) 
{
	int i, j, curr=0, orignatoms;
	int *nappears;

	nappears = lp_calloc(p->natoms, sizeof(int));

	for(i=0;i<p->nrules; i++)
		if(p->rules[i].type == LP_R_KILLING)
			nappears[lp_apos(p, p->rules[i].head)]++;
	

	orignatoms = p->natoms;
	
	for(i=0; i<p->nrules; i++) {
		if(p->rules[i].type == LP_R_KILLING) {
			/* 1 Apr 1999 */
			if(--nappears[lp_apos(p, p->rules[i].head)] == 0)
				continue;
			
			for(j=0; j<p->rules[i].nneg; j++) {
				if(p->rules[i].neg[j] == p->rules[i].head)
					p->rules[i].neg[j] = &p->atoms[p->natoms + curr];
			}
				p->rules[i].head = &p->atoms[p->natoms + curr++];
		}
	}

	resize_atoms(p, p->natoms + curr);

	for(i=orignatoms; i<p->natoms; i++)
		sprintf(p->atoms[i].name, "Gka%05d", i-orignatoms);

	free(nappears);
	return;
}


void handle_reqs(struct lp *p) 
{
	int i;
	if(p->nposreq > 0) {
		resize_atoms(p, p->natoms+1);
		
		strcpy(p->atoms[p->natoms-1].name, "Gpr00000");
	}
	
	
	for(i=0; i<p->nposreq; i++)
			handle_pos_req(p, p->posreq[i]);

	for(i=0; i<p->nnegreq; i++)
			handle_neg_req(p, p->negreq[i]);

	free(p->posreq);
	p->posreq = 0;
	p->nposreq = 0;
	free(p->negreq);
	p->negreq = 0;
	p->nnegreq = 0;
}

void handle_pos_req(struct lp *p, struct lp_atom *a)
{
	int i, j, k;
	int nrules;
	struct lp_rule *newrule;
	struct lp_rule *last;
	struct lp_rule *r;
	
	nrules = p->nrules;

	/* remove all rules with not(a) in the body.  Rule order is not
	 * preserved.
	 */
	for(i=0; i<nrules; i++) {
		r = &p->rules[i];
		for(j=0; j<r->nneg; j++) {
			if(r->neg[j] == a) {
				p->size -= (r->npos + r->nneg + 1);
				
				/* copy last rule into this one */
				last = &p->rules[p->nrules-1];
				r->head = last->head;
				r->npos = last->npos;
				r->nneg = last->nneg;
				r->type = last->type;
				
				for(k=0; k<last->nneg; k++)
					r->neg[k] = last->neg[k];
				for(k=0; k<last->npos; k++)
					r->pos[k] = last->pos[k];

				/* remove last rule */
				nrules--;
				i--;   /* reproecess changed rule */
				break; /* go to next rule */
			}
		}	
	}

	/* Leave room for new rule */
	if(nrules != p->nrules-1)
		resize_rules(p, nrules+1);
	newrule = &p->rules[p->nrules - 1];
	
	newrule->head = &p->atoms[p->natoms - 1];
	newrule->npos = 0;
	newrule->nneg = 2;
	newrule->neg[0] = a;
	/* p->natoms - 1 is the killing atom added by handle_reqs() */
	newrule->neg[1] = &p->atoms[p->natoms - 1];

	p->size += 3;
}


void handle_neg_req(struct lp *p, struct lp_atom *a)
{
	int i, j, k;
	int nrules;
	struct lp_rule *r;
	struct lp_rule *last;
	
	nrules = p->nrules;

	/* remove all rules with a in the body.  Rule order is not
	 * preserved.
	 */
	for(i=0; i<nrules; i++) {
		r = &p->rules[i];
		for(j=0; j<r->npos; j++) {
			if(r->pos[j] == a) {
				p->size -= (r->npos + r->nneg + 1);
				
				/* copy last rule into this one */
				last = &p->rules[p->nrules-1];
				r->head = last->head;
				r->npos = last->npos;
				r->nneg = last->nneg;
				r->type = last->type;
				
				for(k=0; k<last->nneg; k++)
					r->neg[k] = last->neg[k];
				for(k=0; k<last->npos; k++)
					r->pos[k] = last->pos[k];

				/* remove last rule */
				nrules--;
				i--;   /* reprocess changed rule */
				break; /* go to next rule */
			}
		}	
	}

	if(nrules != p->nrules)
		resize_rules(p, nrules);
	
	for(i=0; i<p->nrules; i++) {
		r = &p->rules[i];
		/* The next two steps must be performed in this order */ 
		
		/* Eliminate not(a) from rule bodies */
		for(j=0; j<r->nneg; j++)
			if(r->neg[j] == a) {
				/* j-- to reprocess this literal */
				r->neg[j--] = r->neg[--r->nneg];
				p->size--;
			}
		/* Add not(a) to the body of rules with head a */
		if(r->head == a) {
			r->neg[r->nneg++] = a;
			p->size++;
		}
	}
}

void resize_rules(struct lp *p, int nrules)
{
	p->nrules = nrules;
	p->rules = lp_realloc(p->rules, sizeof(struct lp_rule) * nrules);
}


void resize_atoms(struct lp *p, int natoms) 
{
	int i,j;
	int diff;
	struct lp_atom *origbase = p->atoms;
		
	p->natoms = natoms;
	p->atoms = lp_realloc(p->atoms, sizeof(struct lp_atom) * natoms);

	diff = p->atoms - origbase;
	if(diff == 0)
		return;
		
	for(i=0; i<p->nrules; i++) {
		p->rules[i].head += diff;
		for(j=0; j<p->rules[i].nneg; j++)
			p->rules[i].neg[j] += diff;
		
		for(j=0; j<p->rules[i].npos; j++)
			p->rules[i].pos[j] += diff;
	}
	for(i=0; i<p->nposreq; i++)
		p->posreq[i] += diff;
	for(i=0; i<p->nnegreq; i++)
		p->negreq[i] += diff;
}

void rename_atoms(struct lp *prog, int ren_dual) {
	int i;

	/* Rename &false (generated by lparse to encode global constraints)
	 * to False.
	 */
	for(i=0; i<prog->natoms; i++)
		if(!strcmp(prog->atoms[i].name, "&false"))
			strcpy(prog->atoms[i].name, "False");

	if(!ren_dual)
		return;

	for(i=0; i<prog->natoms; i++) {
		char *n = prog->atoms[i].name;
		char *p;
		int len = strlen(n);

		p = strchr(n, '(');
		if(p)
			--p;
		else
			p = n + len-1;

		if(*p == '\'') {
			for(; p>n; --p) {
				*p = *(p-1);
			}
			*p = '!';
		}
		
	}
}
/* XXX: explain -k/-K in manpage */
void usage(void) 
{
	printf("usage: simplify [options] [filename]\n");
	printf("Make killing atoms in a logic program unique.\n\n");
	
	printf(" -o, --output=file    output goes to file (defaults to stdout)\n");
	printf(" -u, --uniquify       make killing atoms unique\n");
	printf(" -U, --no-uniquify    do not make killing atoms unique (default)\n");
	printf(" -d, --rename-dual    rename atoms of the form a' or a'(X) to !aor !a(X)\n");
	printf(" -D, --no-rename-dual do not rename atoms of the form a' or a'(X)\n");
	printf(" -k, --killing-req    turn requirements into killing rules (default)\n");
	printf(" -K, --no-killing-req do not turn requirements into killing rules\n");
	printf(" -t, --time           print timing information to stderr (default)\n");
	printf(" -n, --no-time        do not print timing information\n");
	printf(" -V, --verbose        print additional information to stderr\n");
	printf(" -m, --messages       print status messages to stderr (default)\n");
	printf(" -q, --quiet          do not print information (other than timing)\n");
	printf(" -v, --version        print version information to stdout\n");
	printf(" -h, --help           display this message\n\n");
	
	printf("Input (in smodels format) is read from filename (defaults to stdin).\n");
}

void version(void) 
{
	printf("simplify %s, from lpsms %s, using liblp %s\n", VERSION, lp_sms_version(), lp_version());
}
