package net.woodyfolsom.cs6601.p2;

import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import net.woodyfolsom.cs6601.p2.Ingredient.TYPE;
import dkohl.bayes.bayesnet.BayesNet;
import dkohl.bayes.builders.FoodNetBuilder;
import dkohl.bayes.inference.EnumerateAll;
import dkohl.bayes.probability.Assignment;
import dkohl.bayes.probability.ProbabilityAssignment;
import dkohl.bayes.probability.Variable;
import dkohl.util.RootMeanSquareError;

public class BayesChef {

	public static void main(String... args) {
		System.out.println("Reading recipe book.");
		RecipeBook recipeBook = RecipeBookReader.readRecipeBook(new File("data/long_recipebook.xml"));
		
		System.out.println("Reading survey data.");
		Survey survey = SurveyReader.readSurvey(new File("data/long_survey.xml"));
		
		new BayesChef().reportBestMeal(recipeBook,survey);
	}
	
	private void reportBestMeal(RecipeBook recipeBook, Survey survey) {
		int numRecipes = recipeBook.getSize();
		System.out.println("Read data for " + numRecipes + " recipes.");
		
		int numDiners = survey.getDinerCount();
		System.out.println("Read data for " + numDiners + " diner(s).");
		
		System.out.println("Creating Bayes net for survey dataset...");
		
		int numSurveyDishes = survey.getDishCount();
		int bayesNetIndexStart = 0;
		int bayesNetIndexEnd = numSurveyDishes / 2;
		
		int comparisonIndexStart = bayesNetIndexEnd;
		int comparisonIndexEnd = numSurveyDishes;
		
		//TODO change this to include min/max recipe indices for building the net
		BayesNet net = FoodNetBuilder.createDishNet(survey, recipeBook, bayesNetIndexStart, bayesNetIndexEnd);
		
		System.out.println("Querying Bayes net for predicted flavor preferences: ");
		//TODO change this to query actual flavor combos from latter half of survey
		double[] predictedRating = new double[numSurveyDishes - bayesNetIndexEnd];
		
		for (int i = comparisonIndexStart; i < comparisonIndexEnd; i++) {
			predictedRating[i - comparisonIndexStart] = printPreference(net, recipeBook.getRecipe(survey.getDish(i)));
		}
		
		System.out.println("Querying survey dataset for actual recipe flavor preferences: ");
		double[] actualRating = new double[predictedRating.length];
		
		for (int i = comparisonIndexStart; i < comparisonIndexEnd; i++) {
			actualRating[i - comparisonIndexStart] = survey.getAverageRating(i);
			System.out.println("Actual average rating for " + survey.getDish(i) + ": " + actualRating[i - comparisonIndexStart]);
		}
		
		RootMeanSquareError rmse = new RootMeanSquareError();
		for (int i = 0; i < actualRating.length; i++) {
			rmse.push(predictedRating[i], actualRating[i]);
		}
		
		System.out.println("Root Mean Squared Error (predicted vs. actual): " + rmse.error());
	}
	
	int printPreference(BayesNet net, Recipe recipe) {
		Set<TYPE> ingredientTypes = recipe.getIngredients().getTypes();
		
		List<ProbabilityAssignment> probs = EnumerateAll.enumerateAsk(new Variable(
				FoodNetBuilder.TASTE, FoodNetBuilder.RATING_DOMAIN),
				net, createQuery(ingredientTypes.toArray(new TYPE[ingredientTypes.size()])));
		
		double max_val = 0.0;
		String max_arg = "0";
		for (ProbabilityAssignment p : probs) {
			if (p.getProbability() > max_val) {
				max_val = p.getProbability();
				max_arg = p.getValue();
			}
		}

		System.out.println("TASTE for " + recipe.getName() + " " + ingredientTypes + " : " + max_arg + " " + max_val);
		return Integer.valueOf(max_arg);
	}
	
	int printPreference(BayesNet net, TYPE type) {
		List<ProbabilityAssignment> probs = EnumerateAll.enumerateAsk(new Variable(
				FoodNetBuilder.TASTE, FoodNetBuilder.RATING_DOMAIN),
				net, createQuery(type));
		double max_val = 0.0;
		String max_arg = "N/A";
		for (ProbabilityAssignment p : probs) {
			if (p.getProbability() > max_val) {
				max_val = p.getProbability();
				max_arg = p.getValue();
			}
		}

		System.out.println("TASTE " + type + ": " + max_arg + " " + max_val);
		return Integer.valueOf(max_arg);
	}
	
	List<Assignment> createQuery(TYPE... types) {
		List<Assignment> assignment = new LinkedList<Assignment>();
		for (TYPE type : types) {
		switch (type) {
		case PORK:
		case BEEF:
		case POTATO:
		case TOMATO:
		case GENERIC_NUTS:
			assignment.add(new Assignment(new Variable(type.toString(), FoodNetBuilder.DOMAIN),
					FoodNetBuilder.TRUE_VALUE));
			break;
		default:
		}
		}
		return assignment;
	}
}
