package dk.itu.mario.level.grammar;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import dk.itu.mario.level.LevelComponent;

public class LevelGrammarFactory {
	public static LevelGrammar createGrammar(File grammarFile) throws FileNotFoundException, IOException {
		FileInputStream fis = new FileInputStream(grammarFile);
		LevelGrammar levelGrammar = new LevelGrammar();
		try {
			InputStreamReader reader = new InputStreamReader(fis);
			BufferedReader buf = new BufferedReader(reader);
			String line;
			while ((line = buf.readLine()) != null) {
				line = line.trim(); // trim off newline/whitespace
				if (line.length() == 0 || line.startsWith("#")) {
					continue;
				}
				//System.out.println("Read: " + line);
				//split on whitespace
				String[] fields;
				if (line.startsWith("VAR")) {
					fields = line.split("\\s");
					if ("=".equals(fields[2]) && fields.length == 4) {
						levelGrammar.addVariable(new Variable(fields[1],LevelComponent.TYPE.valueOf(fields[3])));
					} else {
						throw new RuntimeException("Invalid VAR syntax: " + line);
					}
				} else if (line.startsWith("RULE")) {
					fields = line.split("->");
					String ruleName = fields[0].split("\\s")[1];
					//System.out.println("Rule name: " + ruleName);
					Variable lhs = levelGrammar.getVariable(ruleName);
					if (lhs == null) {
						throw new RuntimeException("LHS variable not found: " + ruleName);
					}
					Clause rhs = getClause(fields[1].trim(),levelGrammar);
					levelGrammar.addProductionRule(new ProductionRule(lhs,rhs));
				} else if (line.startsWith("START")) {
					fields = line.split("\\s");
					if ("=".equals(fields[1]) && fields.length == 3) {
						levelGrammar.setStart(fields[2]);
					} else {
						throw new RuntimeException("Invalid START syntax: " + line);
					}		
				} else {
					throw new RuntimeException("Unable to parse grammar file " + grammarFile.getName() + " due to invalid line: " + line);
				}
			}
			return levelGrammar;
		} finally {
			fis.close();
		}
	}
	
	private static Clause getClause(String clause, LevelGrammar grammar) {
		if (!clause.contains("|") && !clause.contains("+")) {
			return grammar.getVariable(clause);
		}
		int lBraceIndex = clause.indexOf("{");
		boolean isOrClause = false;
		int rBraceIndex = 0;
		
		double[] chances = {};
		
		if (lBraceIndex != -1) {
			isOrClause = true;
			rBraceIndex = clause.indexOf("}");
			//System.out.println("Read OR-clause probabilities from: " + lBraceIndex + " to " + rBraceIndex);
			String[] doubleFields = clause.substring(lBraceIndex+1,rBraceIndex).split(",");
			chances = new double[doubleFields.length];
			for (int i = 0; i < doubleFields.length; i++) {
				chances[i] = Double.valueOf(doubleFields[i]);
			}
		}
		
		String remainder = clause.substring(rBraceIndex);
		List<String> rhsClauseStrings = new ArrayList<String>();
		if (remainder.contains("(")) {
			int nextLeftIndex = -1;
			do {
				nextLeftIndex = remainder.indexOf("(", nextLeftIndex+1);
				if (nextLeftIndex != -1) {
					int nextRightIndex = remainder.indexOf(")", nextLeftIndex+1);
					if (nextRightIndex == -1) {
						throw new RuntimeException("Unmatched left '(' in clause");
					}
					rhsClauseStrings.add(remainder.substring(nextLeftIndex+1,nextRightIndex));
					nextLeftIndex = nextRightIndex;
				}
			} while (nextLeftIndex != -1);
		} else {
			isOrClause = remainder.contains("|");
			if (isOrClause) {
				for (String subclause : remainder.split("\\|")) {
					rhsClauseStrings.add(subclause.trim());
				}
			} else {
				for (String subclause : remainder.split("\\+")) {
					rhsClauseStrings.add(subclause.trim());
				}
			}
		}
		
		List<Clause> rhsClauses = new ArrayList<Clause>();
		
		for (String subclause : rhsClauseStrings) {
			rhsClauses.add(getClause(subclause,grammar));
		}
		
		if (isOrClause) {
			return new OrClause(chances, rhsClauses);
		} else {
			return new AndClause(rhsClauses);
		}
	}
	public static LevelGrammar createGrammar() {
		LevelGrammar grammar = new LevelGrammar();
		
		Variable v_START = new Variable("S", LevelComponent.TYPE.LEVEL);
		Variable v_LAND_SEGMENT = new Variable("HALF_LVL", LevelComponent.TYPE.LEVEL_SEGMENT);
		Variable v_LO_HI = new Variable("LO_HI", LevelComponent.TYPE.LO_HI);
		Variable v_HI_LO = new Variable("HI_LO", LevelComponent.TYPE.HI_LO);
		Variable v_LO_PATH = new Variable("LO_PATH", LevelComponent.TYPE.LO_PATH);
		Variable v_HI_PATH = new Variable("HI_PATH", LevelComponent.TYPE.HI_PATH);
		Variable v_lo_path = new Variable("lo_path", LevelComponent.TYPE.FLAT_LO);
		Variable v_hi_path = new Variable("hi_path", LevelComponent.TYPE.FLAT_HI);
		
		grammar.addVariable(v_START);
		grammar.addVariable(v_LAND_SEGMENT);
		grammar.addVariable(v_LO_HI);
		grammar.addVariable(v_HI_LO);
		grammar.addVariable(v_LO_PATH);
		grammar.addVariable(v_HI_PATH);
		
		grammar.addProductionRule(new ProductionRule(v_START,v_LAND_SEGMENT,v_LAND_SEGMENT));
		
		grammar.addProductionRule(
				new ProductionRule(v_LAND_SEGMENT,
						new OrClause( new double[] {0.25,0.65,0.10},
								new AndClause(v_LO_HI,v_HI_LO),
								v_LO_PATH,
								new AndClause(v_LAND_SEGMENT,v_LAND_SEGMENT)
						)
				)
		);
		
		grammar.addProductionRule(
				new ProductionRule(v_LO_HI, v_LO_PATH, v_HI_PATH)
		);
		
		grammar.addProductionRule(
				new ProductionRule(v_HI_LO, v_HI_PATH, v_LO_PATH)
		);
		
		grammar.addProductionRule(
				new ProductionRule(v_HI_PATH,
						new OrClause( new double[] {0.25,0.75},
								new AndClause(v_HI_PATH,v_HI_PATH),
								v_hi_path
						)
				)
		);
		
		grammar.addProductionRule(
				new ProductionRule(v_LO_PATH,
						new OrClause( new double[] {0.25,0.75},
								new AndClause(v_LO_PATH,v_LO_PATH),
								v_lo_path
						)
				)
		);
		
		grammar.setStart(v_START);
		
		return grammar;
	}
}
