package dkohl.bayes.builders;

import java.util.HashSet;

import net.woodyfolsom.cs6601.p2.Diner;
import net.woodyfolsom.cs6601.p2.Ingredient.TYPE;
import net.woodyfolsom.cs6601.p2.Ingredients;
import net.woodyfolsom.cs6601.p2.Recipe;
import net.woodyfolsom.cs6601.p2.RecipeBook;
import net.woodyfolsom.cs6601.p2.Survey;
import dkohl.bayes.bayesnet.BayesNet;
import dkohl.bayes.estimation.MaximumLikelihoodEstimation;
import dkohl.bayes.example.builders.FoodExampleBuilder;
import dkohl.bayes.probability.Assignment;
import dkohl.bayes.probability.Probability;
import dkohl.bayes.probability.Variable;
import dkohl.bayes.probability.distribution.ContinousDistribution;
import dkohl.bayes.probability.distribution.ProbabilityDistribution;
import dkohl.bayes.probability.distribution.ProbabilityTable;
import dkohl.bayes.statistic.DataPoint;
import dkohl.bayes.statistic.DataSet;
import dkohl.onthology.Ontology;

public class FoodNetBuilder {
	public static final String TASTE = "Taste";
	public static final String SOMEONE_VEGETARIAN = "Vegetarian";
	public static final String CONTAINS_MEAT = "Meat";
	public static final String CONTAINS_VEGETABLE = "Vegetable";
	public static final String CONTAINS_BEEF = TYPE.BEEF.toString();
	public static final String CONTAINS_PORK = TYPE.PORK.toString();
	public static final String CONTAINS_TOMATOS = TYPE.TOMATO.toString();
	public static final String CONTAINS_POTATOS = TYPE.POTATO.toString();

	public static final String TRUE_VALUE = "true";
	public static final String FALSE_VALUE = "false";

	public static final String DOMAIN[] = { TRUE_VALUE, FALSE_VALUE };

	public static final String RATING_DOMAIN[] = { "1", "2", "3", "4", "5",
			"6", "7", "8", "9", "10" };

	private static final String[] VARIABLES = { SOMEONE_VEGETARIAN,
			CONTAINS_BEEF, CONTAINS_MEAT, CONTAINS_PORK, CONTAINS_POTATOS,
			CONTAINS_TOMATOS, CONTAINS_VEGETABLE, TASTE };

	private static final String[] OBSERVED = { CONTAINS_BEEF, CONTAINS_PORK,
			CONTAINS_POTATOS, CONTAINS_TOMATOS, };

	public static Ontology createOntology() {
		HashSet<String> classes = new HashSet<String>();
		
		classes.add(CONTAINS_MEAT);
		classes.add(CONTAINS_VEGETABLE);
		
		Ontology onthology = new Ontology(classes);
		
		onthology.define(CONTAINS_PORK, CONTAINS_MEAT);
		onthology.define(CONTAINS_BEEF, CONTAINS_MEAT);

		onthology.define(CONTAINS_TOMATOS, CONTAINS_VEGETABLE);
		onthology.define(CONTAINS_POTATOS, CONTAINS_VEGETABLE);
		return onthology;
	}

	private static Assignment build(String varible, String value) {
		return new Assignment(new Variable(varible, DOMAIN), value);
	}

	public static DataPoint normalize(DataPoint point, Ontology onto) {
		// resolve onthology
		DataPoint normPoint = new DataPoint(point);
		for (String key : point.keySet()) {
			if (onto.getInheritance().containsKey(key)) {
				normPoint
						.add(build(onto.getInheritance().get(key), TRUE_VALUE));
			}
		}

		// implement closed world assumption
		// everything unknown is false
		for (String variable : VARIABLES) {
			if (!normPoint.containsKey(variable)) {
				normPoint.add(build(variable, FALSE_VALUE));
			}
		}
		return normPoint;
	}

	public static DataSet getSurveyDataSet(Survey survey, RecipeBook recipeBook) {
		DataSet data = FoodExampleBuilder.examples();
		Ontology onto = createOntology();
		
		int nDishes = survey.getDishCount();
		for (int dinerIndex = 0; dinerIndex < survey.getDinerCount(); dinerIndex++) {
			Diner diner = survey.getDiner(dinerIndex);
			for (int dishIndex = 0; dishIndex < nDishes; dishIndex++) {
				data.add(normalize(createDataPoint(recipeBook, survey.getDish(dishIndex), diner.getRating(dishIndex)),onto));
			}
		}
		return data;
	}
	
	public static DataPoint createDataPoint(RecipeBook recipeBook, String recipeName, int weight) {
		DataPoint point = new DataPoint();
		
		Recipe recipe = recipeBook.getRecipe(recipeName);
		Ingredients ingredients = recipe.getIngredients();
		
		if (ingredients.contains(TYPE.BEEF)) {
			point.add(build(CONTAINS_BEEF, TRUE_VALUE));
		}
		if (ingredients.contains(TYPE.PORK)) {
			point.add(build(CONTAINS_PORK, TRUE_VALUE));
		}
		if (ingredients.contains(TYPE.POTATO)) {
			point.add(build(CONTAINS_POTATOS, TRUE_VALUE));
		}
		if (ingredients.contains(TYPE.TOMATO)) {
			point.add(build(CONTAINS_TOMATOS, TRUE_VALUE));
		}
		
		point.add(build(TASTE, "" + weight));
		
		return point;
	}

	public static ProbabilityDistribution vegi() {
		String names[] = { SOMEONE_VEGETARIAN };
		ProbabilityTable table = new ProbabilityTable(names);
		table.setProbabilityForAssignment("true;", new Probability(0));
		table.setProbabilityForAssignment("false;", new Probability(1));
		return table;
	}

	public static ProbabilityDistribution beef() {
		String names[] = { CONTAINS_MEAT, CONTAINS_BEEF };
		ProbabilityTable table = new ProbabilityTable(names);
		return table;
	}

	public static ProbabilityDistribution pork() {
		String names[] = { CONTAINS_MEAT, CONTAINS_PORK };
		ProbabilityTable table = new ProbabilityTable(names);
		return table;
	}

	public static ProbabilityDistribution meet() {
		String names[] = { SOMEONE_VEGETARIAN, CONTAINS_MEAT };
		ProbabilityTable table = new ProbabilityTable(names);
		return table;
	}

	public static ProbabilityDistribution tomatos() {
		String names[] = { CONTAINS_VEGETABLE, CONTAINS_TOMATOS };
		ProbabilityTable table = new ProbabilityTable(names);
		return table;
	}

	public static ProbabilityDistribution potatos() {
		String names[] = { CONTAINS_VEGETABLE, CONTAINS_POTATOS };
		ProbabilityTable table = new ProbabilityTable(names);
		return table;
	}

	public static ProbabilityDistribution vegetables() {
		String names[] = { CONTAINS_VEGETABLE, SOMEONE_VEGETARIAN };
		ProbabilityTable table = new ProbabilityTable(names);
		return table;
	}

	public static ProbabilityDistribution taste() {
		String names[] = { TASTE, CONTAINS_BEEF, CONTAINS_PORK,
				CONTAINS_POTATOS, CONTAINS_TOMATOS };
		ContinousDistribution distribution = new ContinousDistribution(names, 0);
		return distribution;
	}

	public static BayesNet createDishNet(Survey survey, RecipeBook recipeBook) {
		BayesNet net = new BayesNet(VARIABLES);
		
		net.setDistribution(new Variable(SOMEONE_VEGETARIAN, DOMAIN), vegi());
		net.setDistribution(new Variable(CONTAINS_MEAT, DOMAIN), meet());
		net.setDistribution(new Variable(CONTAINS_VEGETABLE, DOMAIN),
				vegetables());
		net.setDistribution(new Variable(CONTAINS_BEEF, DOMAIN), beef());
		net.setDistribution(new Variable(CONTAINS_PORK, DOMAIN), pork());
		net.setDistribution(new Variable(CONTAINS_POTATOS, DOMAIN), potatos());
		net.setDistribution(new Variable(CONTAINS_TOMATOS, DOMAIN), tomatos());
		net.setDistribution(new Variable(TASTE, RATING_DOMAIN), taste());

		Ontology ontology = createOntology();
		for (String category : ontology.getClasses()) {
			net.connect(category, SOMEONE_VEGETARIAN);
		}

		for (String thing : OBSERVED) {
			net.connect(thing, ontology.getInheritance().get(thing));
			net.connect(TASTE, thing);
		}

		DataSet dataSet = getSurveyDataSet(survey, recipeBook);
		
		for (String category : ontology.getClasses()) {
			MaximumLikelihoodEstimation.estimate(dataSet, net, category);
			for (String thing : ontology.getClasses2thing().get(category)) {
				MaximumLikelihoodEstimation.estimate(dataSet, net, thing);
			}
		}

		MaximumLikelihoodEstimation.estimate(dataSet, net, TASTE);
		ContinousDistribution distribturion = (ContinousDistribution) net
				.getNodes().get(TASTE);
		
		distribturion.estimate();

		return net;
	}
}