/*
 * EvolpParser.java
 *
 * Copyright (C) 2006 - 2007 Martin Slota
 *
 * 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, or (at your option) any later
 * version.
 *
 * 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., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * History:
 * v0.1 (???): initial version
 * v0.1.5 (2007-01-01): initial implementation with tests and some documentation
 * v0.2 (2007-01-28):
 * - checked exceptions eliminated (see
 *   http://www.mindview.net/Etc/Discussions/CheckedExceptions)
 * v0.2.1 (2007-02-08):
 * - made reusable (setInput methods instead of constructors)
 * v0.2.2 (2007-03-05):
 * - test of parseEvolp
 * - docs written
 * 1.0.0 (2007-05-04):
 * - modified to reflect the change of a rule's body from a List to a Set
 */

package lp.parse;

import java.util.List;
import java.util.Set;
import lp.struct.LpAtom;
import lp.struct.LpConstant;
import lp.struct.LpLiteral;
import lp.struct.LpPredicate;
import lp.struct.LpRule;
import lp.struct.LpTerm;
import lp.struct.LpVariable;
import lp.unit.EvolpProgram;
import lp.unit.GeneralizedLogicProgram;
import lp.unit.LogicProgram;

import static lp.parse.LpTokenType.*;
import lp.struct.LpCompoundTerm;
import lp.struct.LpFunction;


/**
 * Inherits the basic behaviour from {@link LpParser} and overrides some parts
 * in order to parse the input according to this grammar:
 *
 *<pre>
 *Rule ---&gt; (Literal | InnerRule) DOT
 *InnerRule ---&gt; NormalRule | Constraint
 *OrdinaryRule ---&gt; Literal RULE_ARROW (Literal (COMMA Literal)*)?
 *Constraint ---&gt; RULE_ARROW Literal (COMMA Literal)*
 *Literal ---&gt; 'not'? Atom
 *Atom ---&gt; PredicateName Arguments?
 *PredicateName ---&gt; LOWERCASE_WORD
 *Arguments ---&gt; LEFT_PAREN Term (COMMA Term)* RIGHT_PAREN
 *Term ---&gt; LEFT_PAREN Term RIGHT_PAREN | Constant | Variable | CompoundTerm | InnerRule
 *Constant ---&gt; LOWERCASE_WORD
 *Variable ---&gt; UPPERCASE_WORD
 *CompoundTerm ---&gt; FunctionName Arguments
 *FunctionName ---&gt; LOWERCASE_WORD
 *</pre>
 *
 * In this grammar
 * <ul>
 * <li>Rule, InnerRule, OrdinaryRule, Constraint, Literal, Atom, PredicateName,
 * Arguments, Term, Constant, Variable, CompoundTerm and FunctionName are
 * non-terminals corresponding to the structure of the input</li>
 * <li>DOT, RULE_ARROW, COMMA, LEFT_PAREN, RIGHT_PAREN, LOWERCASE_WORD and
 * UPPERCASE_WORD are tokens as defined in {@link LpTokenType}</li>
 * <li>'not' is a LOWERCASE_WORD with a corresponding lexem "not"</li>
 * </ul>
 *
 * In addition, it offers the static method {@link #parseEvolp()} that parses
 * the given input as an EVOLP input, i.e. a sequence of logic program separated
 * from each other by "newEvents." facts. The first program in the sequence is
 * the base program an the others are events. For example on this input:
 *
 *<pre>
 *a.
 *newEvents.
 *not a.
 *newEvents.
 *a <- not b.
 *</pre>
 *
 * it would produce an EVOLP program with this the base program
 *
 *<pre>a.</pre>
 *
 * and these two events:
 *
 *<pre>not a.</pre>
 *<pre>a <- not b.</pre>
 *
 * @version 1.0.0
 * @see LpLexer
 * @see LpParser
 */
public class EvolpParser extends LpParser {
	/**
	 * The fact "newEvents." for separating parts of the EVOLP input.
	 */
	public static final LpRule SEP_RULE;
	
	static {
		LpAtom sepAtom = LpAtom.getInstance(
				LpPredicate.getInstance("newEvents", 0), null);
		LpLiteral sepLit = sepAtom.getPositiveLiteral();
		SEP_RULE = new LpRule(sepLit, null);
	}
	
	/**
	 * Creates a new {@code EvolpParser} instance that uses a new
	 * {@link LpLexer} instance to tokenize the character input before parsing.
	 */
	public EvolpParser() {
		super();
	}
	
	/**
	 * Creates a new {@code EvolpParser} instance that uses {@code lexer} to
	 * tokenize the character input before parsing.
	 *
	 * @param lexer the {@code LpLexer} instance used to tokenize the
	 * character input before parsing
	 */
	public EvolpParser(LpLexer lexer) {
		super(lexer);
	}
	
	/**
	 * Parses the given input as an EVOLP program , i.e. a sequence of logic
	 * programs separated from each other by "newEvents." facts. The first
	 * program in the sequence is the base program an the others are events.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O error occurs while tokenizing the input
	 * @throws LpParserException if the input doesn't match the expression
	 * (Rule* EOF) (Rule is a non-terminal from the grammar in class
	 * description)
	 * @see #parseRule()
	 */
	public EvolpProgram parseEvolp() {
		EvolpProgram result = new EvolpProgram();
		LogicProgram p = new GeneralizedLogicProgram();
		int i = 0;
		while (hasMoreTokens()) {
			LpRule r = parseRule();
			if (SEP_RULE.equals(r)) {
				if (i == 0) {
					result.setBaseProgram(p);
				} else {
					result.addEvent(p);
				}
				p = new GeneralizedLogicProgram();
				i++;
			} else {
				p.add(r);
			}
		}
		if (i == 0) {
			result.setBaseProgram(p);
		} else {
			result.addEvent(p);
		}
		return result;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public LpTerm parseTerm() {
		// skip leading left parenthesis
		int parenCount = 0;
		while (getLexer().getTokenType() == LEFT_PAREN) {
			match(LEFT_PAREN);
			parenCount++;
		}
		
		LpTerm result;
		
		if (getLexer().getTokenType() == UPPERCASE_WORD) {
			// we have a variable
			result = LpVariable.getInstance(getLexer().getLexem());
			nextToken();
		} else {
			LpTokenType type = getLexer().getTokenType();
			if (type == LOWERCASE_WORD) {
				// the input must begin with a literal (head of the rule that
				// will be returned as a term) or a constant or a compound term
				String name = getLexer().getLexem();
				nextToken();
				boolean positive = true;
				// check if it wasn't a default literal
				if (getLexer().getTokenType() == LOWERCASE_WORD
						&& "not".equals(name)) {
					positive = false;
					name = getLexer().getLexem();
					nextToken();
				}
				
				// parse arguments of the compound term/literal
				List<LpTerm> arguments = parseArguments();
				
				// if there was a "not", it must be a rule (the arrow is
				// required)
				if (!positive)
					expect(RULE_ARROW);
				
				if (getLexer().getTokenType() == RULE_ARROW) {
					// parse the body and return the appropriate rule
					Set<LpLiteral> body = parseRuleBody(false);
					// ignore a traling dot
					if (getLexer().getTokenType() == DOT)
						match(DOT);
					LpLiteral head = LpAtom.getInstance(
							LpPredicate.getInstance(name, arguments),
							arguments).getLiteral(positive);
					result = new LpRule(head, body);
				} else {
					// return the term parsed until now
					if (arguments == null) {
						result = LpConstant.getInstance(name);
					} else {
						result = LpCompoundTerm.getInstance(
								LpFunction.getInstance(name, arguments),
								arguments);
					}
				}
				
			} else {
				// the term must be a constraint
				result = parseOnlyRule();
				// ignore a traling dot
				if (getLexer().getTokenType() == DOT)
					match(DOT);
			}
		}
		
		// skip trailing right parenthesis (the same number as leading left
		// parenthesis)
		for (int i = 0; i < parenCount; i++) {
			match(RIGHT_PAREN);
		}
		
		return result;
	}
}