/*
 *	Proof of concept CML1 constraint generator
 *	(c) 2001, Red Hat Inc
 *
 *	TODO:
 *	For each CHOICE set insert a constraint set of
 *		entry NOR { other options }
 *	Allow -o foo -a bar (put the or/and entry in the condition)
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
 *
 */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

/*
 *	We chain these together to list conditions applied to a rule block
 *	for an if or a subset rule
 */
struct condition
{
	struct condition *next;
	char *name1;
	int op;
#define OP_EQ				1
#define OP_NE				2
	char *name2;
};

/*
 *	Each actual ruleset we must apply is a constraint. They are linked
 *	by ->next for each constraint in this ruleset and chain to walk to
 *	the next ruleset.
 */
 
struct constraint
{
	int type;
	struct condition *link;
	struct constraint *next;		/* For stacks */
	struct constraint *chain;		/* For gluing multiple */
};

#define CONSTRAINT_GROUP_OR		1
#define CONSTRAINT_GROUP_AND		2
/*
 *	For these two cases the chain holds a list of conditions that
 *	are applied either logical and or logical or. The conditions may
 *	contain unparsed strings at this point
 */
#define CONSTRAINT_GROUP_NOTOR		3
#define CONSTRAINT_GROUP_NOTAND		4
/*
 *	As above but generated by inversions. This allows us to use the same
 *	condition chain and just copy the constraint.
 */
#define CONSTRAINT_SUBSET		5
/*
 *	The condition group is a list of entries whose value bounds the
 *	range of the target.
 */

/*
 *	Each CML1 variable we discover we add to this. The constraints
 *	is a chain of discovered constraints for this variable. Ctail holds
 *	the last constraint so that we can apply more nodes to it correctly
 *	when building an AND.
 */
 
struct variable
{
	char *tag;
	int type;
	struct variable *next;
	struct constraint *constraints;
	struct constraint *ctail;
};

/*
 *	Used to remember where in the constraint stack we are as we walk
 *	through layers of if conditionals.
 */
 
struct ifstack
{
	struct constraint *top;		/* Stack top below our entry */
	struct constraint *head, *tail;
	struct ifstack *next;
};

static struct variable *varlist;
static struct constraint *constraint_stack;
static struct ifstack *if_stack;

/*
 *	Add a constraint to the if stack. The code is designed to allow us
 *	to add a group of constraints here in case we need it in future
 */
 
static void push_ifconstraint(struct constraint *c)
{
	struct ifstack *i = (struct ifstack *)malloc(sizeof(struct ifstack));
	
	if(i==NULL)
	{	
		fprintf(stderr, "Out of memory.\n");
		exit(1);
	}
	i->head = c;
	/* Push us on the ifstack, add our constraint set */
	i->top = constraint_stack;
	c->next = constraint_stack;
	i->next = if_stack;
	constraint_stack = i->head;
	if_stack = i;
}

/*
 *	Rip the head off the constraint stack, used for fi
 */
  
static int pop_ifconstraint(void)
{
	if(if_stack)
	{
		constraint_stack = if_stack->top;
		if_stack = if_stack->next;
		return 0;
	}
	return -1;
}

/*
 *	Change the sense of the top of the if stack. Used for else
 */
 
static void invert_top_ifconstraint(void)
{
	if(if_stack)
	{
		if(if_stack->head->type <= 2)
			if_stack->head->type += 2;
		else
			if_stack->head->type -= 2;
	}
}

/*
 *	We have discovered a new declaration of a variable. We must attach
 *	the current if stack to it, and invert the order of the stack 
 *	as we go for neatness. We have to copy here but we don't have to
 *	copy the condition block
 */
 
static struct constraint *attach_constraint_stack(struct variable *v)
{
	struct constraint *c, *n, *p, *r;
		
	/*
	 *	We want most global rule first. We also have to copy here
	 *	as we will attach new subtrees
	 */

	c = constraint_stack;
	p = NULL;
	r = NULL;
	
	while(c)
	{
		n = malloc(sizeof(struct constraint));
		if(n==NULL)
		{
			fprintf(stderr, "Out of memory\n");
			exit(1);
		}
		*n = *c;
		/*
		 *	Link copy into our tree, and invert the order as we go
		 */
		n->next = p;
		n->chain = NULL;
		p = n;
		if(r==NULL)
			r = n;
		c=c->next;
	}
	
	/*
	 *	p is now the top of the chain (n is not always defined.. )
	 */
	
	if(p==NULL)
	{
		v->ctail = NULL;
		v->constraints = NULL;
		return NULL;
	}
	
	/*
	 *	Add the ruleset to the variable
	 */
	p->chain = v->constraints;
	v->constraints = p;
	v->ctail = r;
	return p;
}
	
/*
 *	Given a name find the variable
 */
static struct variable *find_variable(char *tag)
{
	struct variable *v = varlist;
	while(v != NULL)
	{
		if(strcmp(v->tag,tag)==0)
			return v;
		v = v->next;
	}
	return NULL;
}

/*
 *	Given a name add a variable, or if it exists add another rule chain
 *	to it
 */

static void add_variable(char *tag, int type)
{
	struct variable *v;
	v = find_variable(tag);
	
	if(v==NULL)
	{
		v = malloc(sizeof(*v)+strlen(tag)+1);
		v->tag = (char *)(v+1);
		strcpy(v->tag, tag);
		v->type = type;
		v->next = varlist;
		v->constraints = NULL;
		v->ctail = NULL;
		attach_constraint_stack(v);
		varlist = v;
	}
	else
	{
		/* 
		 *	A second way to set the variable exists, both must
		 *	be considered
		 */
		attach_constraint_stack(v);
	}
}

/*
 *	Add a condition to a constraint rule
 */

static struct condition *add_condition(struct constraint *c, char *name1, int op, char *name2)
{
	struct condition *cn = malloc(sizeof(*cn)+strlen(name1)+strlen(name2)+2);
	if(cn==NULL)
	{
		fprintf(stderr, "Out of memory.\n");
		exit(1);
	}
	cn->name1 = (char *)(cn+1);
	cn->name2 = cn->name1+strlen(name1)+1;
	strcpy(cn->name1, name1);
	strcpy(cn->name2, name2);
	cn->op = op;
	cn->next = c->link;
	c->link = cn;
	
}

/*
 *	Add a constraint to a variable. It is added to the tail of the
 *	current constraint set.
 */
 
static struct constraint *add_constraint(char *vname, int type)
{
	struct variable *v;
	struct constraint *c = malloc(sizeof(*c));
	
	v = find_variable(vname);
	if(v==NULL)
	{
		fprintf(stderr, "Undeclared variable '%s' used.\n", vname);
		return NULL;
	}
	c->type = type;
	c->link = NULL;
	c->next = NULL;
	c->chain = NULL;

	/*
	 *	Add the rule to the end of the current rule stack
	 */
	 
	if(v->ctail)
		v->ctail->next = c;
	else
		v->constraints = c;
	v->ctail = c;
}


/*
 *	Parser (this is taken from Milliways BBS). Either Alan Cox or
 *		Justin Mitchell wrote this bit but I don't remember whom..)
 */

#define MAX_ARGC	256

static void shuffle_up(char *bp)
{
	/* The strlen is right its the length of bp, which is the length
	   of bp+1 including the NUL */
	memmove(bp,bp+1,strlen(bp));
}

static int keywords(char *line, char **argvp[])
{
	char *ptr=line;
	int n=0;
	int quoted=0;
	int slashed=0;
	int squoted=0;
	static char *argv[MAX_ARGC];
	
	while(n<MAX_ARGC-1)
	{
		while(*ptr && isspace(*ptr))
			ptr++;
		if(*ptr==0)
			break;
		argv[n++]=ptr;
		while(*ptr && (!isspace(*ptr) || quoted || slashed || squoted))
		{
			if(slashed==1)
			{
				ptr++;
				slashed=0;
				continue;
			}
			if(*ptr=='\\')
			{
				slashed=1;
				shuffle_up(ptr);
				continue;
			}
			if(*ptr=='"')
			{
				quoted=1-quoted;
				shuffle_up(ptr);
				continue;
			}
			if(*ptr=='\'')
			{
				squoted=1-squoted;
				shuffle_up(ptr);
				continue;
			}
			ptr++;
		}
		if(*ptr)
			*ptr++=0;
	}
	argv[n]=NULL;
	*argvp=argv;
	return n;
}

 
/*
 *	Variable types from names
 */
 
static int vartype(char *x)
{
	if(strcmp(x, "bool")==0)
		return 0;
	if(strcmp(x, "tristate")==0)
		return 1;
	if(strcmp(x, "string")==0)
		return 2;
	if(strcmp(x, "int")==0)
		return 3;
	if(strcmp(x, "hex")==0)
		return 4;
	/* Type 5 is choice which can't occur this way */
	return -1;
}


static void do_parse_error(char *file, char *buf, int line, char *str)
{
	fprintf(stderr, "%s", buf);
	fprintf(stderr, "%s at line %d of file %s.\n", str, line, file);
}

#define parse_error(a)	do_parse_error(name, errbuf, errline, a)

static int multigets(char *buf, int len, FILE *fp)
{
	char *dp=buf;
	int copied = 0;
	while(fgets(dp, len, fp)!=NULL)
	{
		char *ep = dp+strlen(dp)-2;
		copied=1;
		if(ep < dp || strncmp(ep, "\\\n", 2))
			break;
		len -= (ep-dp);
		dp = ep;
	}
	if(!copied)
		return 0;
	return 1;
}

static int parse_triple(char *name, char *errbuf, int errline, struct constraint *c, int *argc, char ***argv)
{
	int op;
	if(strcmp((*argv)[1], "=")==0)
		op = OP_EQ;
	else if(strcmp((*argv)[1], "!=")==0)
		op = OP_NE;
	else
	{
		parse_error("'==' or '!=' expected");
		op = OP_EQ;
	}
	add_condition(c, (*argv)[0], op, (*argv)[2]);
	*argc-=3;
	*argv+=3;
}

static struct constraint *parse_if_constraint(char *name, char *errbuf, int errline, int argc, char *argv[])
{
	int and = -1;
	int invert = 0;
	struct constraint *c = malloc(sizeof(*c));
	if(c==NULL)
	{
		fprintf(stderr, "Out of memory\n");
		exit(1);
	}
	
	c->link = NULL;
	c->type = 0;
	c->next = NULL;
	c->chain = NULL;
	
	if(argc < 3)
	{
		parse_error("'[' expected");
		return NULL;
	}
	
	
	if(strcmp(argv[1],"["))
	{
		parse_error("'[' expected");
		return NULL;
	}
	argv+=2;
	argc-=2;
	if(strcmp(argv[0], "!")==0)
	{
		argv++;
		argc++;
		invert=1;
	}
	while(argc>0)
	{
		if(*argv[0]==']')
			break;
		if(argc<3)
		{
			parse_error("Expected condition");
			goto bad;
		}
		if(parse_triple(name, errbuf, errline, c, &argc, &argv)==-1)
			break;
		if(*argv[0]==']')
			break;
		if(argc==0)
		{
			parse_error("']' expected");
			return NULL;
		}
		if(strcmp(argv[0], "-o")==0)
		{
			if(and==1)
			{
				parse_error("Mixing -o and -a is not permitted");
				goto bad;
			}
			and=0;
		}
		else if(strcmp(argv[0], "-a")==0)
		{
			if(and==0)
			{
				parse_error("Mixing -o and -a is not permitted");
				goto bad;
			}
			and=1;
		}
		else
		{
			parse_error("'];' '-o' or '-a' expected");
			goto bad;
		}
		argc--;
		argv++;
	}
done:
	if(and==1 || and==-1)
		c->type = CONSTRAINT_GROUP_AND;
	else
		c->type = CONSTRAINT_GROUP_OR;
	if(invert)
		c->type += 2;
	return c;
bad:
	c->link = NULL;
	goto done;
}

static void parse_file(char *name)
{
	char buf[2048];
	char errbuf[2048];
	FILE *fp = fopen(name, "r");
	char *p;
	int errline = 0;
	struct constraint *c;
	
	if(fp==NULL)
	{
		perror(name);
		return;
	}
	while(multigets(buf, 2048, fp))
	{
		char **argv;
		int argc;
		p = buf;

		errline++;
		strcpy(errbuf,buf);
		
		argc = keywords(buf, &argv);
		
		if(argc==0)
			continue;
			
		if(*argv[0]=='#')
			continue;
		if(strcmp(argv[0], "comment")==0)
			continue;
		if(strcmp(argv[0], "mainmenu_option")==0)
			continue;
		if(strcmp(argv[0], "mainmenu_name")==0)
			continue;
		if(strcmp(argv[0], "endmenu")==0)
			continue;
		if(strcmp(argv[0], "source")==0)
		{
			if(argc!=2)
			{
				parse_error("source requires file name");
				continue;
			}
			parse_file(argv[1]);
			continue;
		}
		if(strcmp(argv[0], "bool")==0 || strcmp(argv[0], "tristate")==0)
		{
			int v = vartype(argv[0]);
			if(argc!=3)
				parse_error("prompt and name expected");
			else
				add_variable(argv[2], v);
			continue;
		}
		if(vartype(argv[0])!=-1)
		{
			int v = vartype(argv[0]);
			if(argc!=4)
				parse_error("prompt, name and default expected");
			else
				add_variable(argv[2], v);
			continue;
		}
		if(strncmp(argv[0], "dep_", 4)==0)
		{
			int v=vartype(argv[0] + 4);
			int i;
			if(argc<4)
				parse_error("prompt, name and symbols expected");
			else
			{
				add_variable(argv[2], v);
				c = add_constraint(argv[2], CONSTRAINT_SUBSET);
				for(i=3;argv[i];i++)
					add_condition(c, argv[i], 0, "");
			}
			continue;
		}
		if(strncmp(argv[0], "define_", 7)==0)
		{
			int v=vartype(argv[0] + 7);
			if(argc!=3)
				parse_error("name, value and symbol expected");
			else
			{
				add_variable(argv[1], v);
				c = add_constraint(argv[1], CONSTRAINT_SUBSET);
				add_condition(c, argv[2], 0, "");
				
			}
			continue;
		}
		if(strcmp(argv[0], "if")==0)
		{
			struct constraint *c = 	parse_if_constraint(name, errbuf, errline, argc, argv);
			if(c)
				push_ifconstraint(c);
			continue;
		}
		if(strcmp(argv[0], "else")==0)
		{
			invert_top_ifconstraint();
			continue;
		}
		if(strcmp(argv[0], "fi")==0)
		{
			if(pop_ifconstraint()<0)
				parse_error("unbalanced 'fi'");
			continue;
		}
		if(strcmp(argv[0], "then")==0)
		{	
			/* We hit a then on a new line, thats fine.. skip */
			continue;
		}
		if(strcmp(argv[0], "unset")==0)
		{	
			/* This isnt an official CML op but its abused by the
			   Alpha port */
			if(argc<2)
				parse_error("unset expects one variable name");
			continue;
		}
		if(strcmp(argv[0], "choice")==0)
		{
//			add_variable(argv[1], 5);
			continue;
		}
		parse_error("unknown command");
	}
	
	fclose(fp);
}

static void dump_condition(int type, struct condition *cn)
{
	int doall = 1;
	
	if(type == CONSTRAINT_SUBSET)
		doall = 0;
		
	printf("        %s", cn->name1);
	if(doall)
	{
		char *opstr="???";
		if(cn->op==OP_EQ)
			opstr="=";
		if(cn->op==OP_NE)
			opstr="!=";
		printf("%s %s", opstr, cn->name2);
	}
	printf("\n");
}

static void dump_constraint(struct constraint *c)
{
	struct condition *cp;
		printf("{\n");
	while(c!=NULL)
	{
		cp=c->link;
		if(cp!=NULL)
		{
			printf("    {\n");
			switch(c->type)
			{
				case CONSTRAINT_GROUP_OR:
					printf("    One of:\n");
					break;
				case CONSTRAINT_GROUP_AND:
					printf("    All of:\n");
					break;
				case CONSTRAINT_GROUP_NOTOR:
					printf("    None of:\n");
					break;
				case CONSTRAINT_GROUP_NOTAND:
					printf("    Not all of:\n");
					break;	
				case CONSTRAINT_SUBSET:	
					printf("    Subset of:\n");
					break;
				default:
					printf("BAD RULE!!\n");
			}
			while(cp!=NULL)
			{
				dump_condition(c->type, cp);
				cp = cp->next;
			}
			printf("    }\n");
		}			
		c=c->next;
		if(c)
			printf("    AND\n");
	}		
	printf("}\n");
}

static void dump(void)
{
	struct variable *v = varlist;
	while(v!=NULL)
	{
		struct constraint *c;
		printf("Variable %s: \n", v->tag);
		c=v->constraints;
		while(c!=NULL)
		{
			dump_constraint(c);
			c=c->chain;
			if(c)
				printf("OR\n");
		}
		v=v->next;
	}
}

int main(int argc, char *argv[])
{
	if(chdir("/usr/src/LINUS/LINUX2.4/linux.ac")==-1)
		perror("chdir");
	if(argv[1])
	{
		parse_file(argv[1]);
	}
	else
	{
		fprintf(stderr, "%s [file]\n", argv[0]);
		exit(1);
	}
	dump();
	return 0;
}
