package it.cavallium.warppi.math.rules;

import it.cavallium.warppi.Platform;
import it.cavallium.warppi.Platform.ConsoleUtils;
import it.cavallium.warppi.WarpPI;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.functions.Expression;
import it.cavallium.warppi.math.functions.Variable;
import it.cavallium.warppi.math.functions.Variable.V_TYPE;
import it.cavallium.warppi.math.rules.dsl.DslAggregateException;
import it.cavallium.warppi.math.rules.dsl.RulesDsl;
import it.cavallium.warppi.math.rules.dsl.errorutils.DslFilesException;
import it.cavallium.warppi.math.rules.functions.*;
import it.cavallium.warppi.math.solver.MathSolver;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;

import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Stream;

public class RulesManager {

	public static ObjectArrayList<Rule>[] rules;

	private RulesManager() {
	}

	@SuppressWarnings({"unchecked"})
	public static void initialize() {
		WarpPI.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", "Loading the rules");
		RulesManager.rules = new ObjectArrayList[RuleType.values().length];
		for (final RuleType val : RuleType.values()) {
			RulesManager.rules[val.ordinal()] = new ObjectArrayList<>();
		}

		loadBuiltinRules();

		try {
			loadDslRules();
		} catch (IOException | DslFilesException e) {
			e.printStackTrace();
			if (e instanceof DslFilesException) {
				System.err.println();
				System.err.print(((DslFilesException) e).format());
			}
			WarpPI.getPlatform().exit(1);
		}
	}

	private static void loadBuiltinRules() {
		Stream.of(
			new DivisionRule(),
			new EmptyNumberRule(),
			new ExpressionRule(),
			new JokeRule(),
			new MultiplicationRule(),
			new NegativeRule(),
			new NumberRule(),
			new PowerRule(),
			new RootRule(),
			new RootSquareRule(),
			new SubtractionRule(),
			new SumRule(),
			new SumSubtractionRule(),
			new VariableRule()
		).forEach(RulesManager::addRule);
	}

	private static void loadDslRules() throws IOException, DslFilesException {
		final Platform platform = WarpPI.getPlatform();

		final DslFilesException fileErrors = new DslFilesException();
		for (final String path : platform.getRuleFilePaths()) {
			platform.getConsoleUtils().out().println(
				ConsoleUtils.OUTPUTLEVEL_NODEBUG,
				"RulesManager",
				"Found DSL rules file: " + path
			);

			final String source;
			try (final InputStream resource = platform.getPlatformStorage().getResourceStream(path)) {
				source = platform.getPlatformStorage().read(resource);
			}

			try {
				// This loop used to be written as RulesDsl.makeRules(source).forEach(RulesManager::addRule),
				// but the forEach method hangs on TeaVM.
				for (Rule rule : RulesDsl.makeRules(source)) {
					addRule(rule);
				}
			} catch (DslAggregateException e) {
				fileErrors.addFileErrors(path, source, e.getErrors());
			}
		}

		if (fileErrors.hasErrors()) {
			throw fileErrors;
		}
	}

	public static void warmUp() throws Error, InterruptedException {
		ObjectArrayList<Function> uselessResult = null;
		boolean uselessVariable = false;
		for (final RuleType val : RuleType.values()) {
			final ObjectArrayList<Rule> ruleList = RulesManager.rules[val.ordinal()];
			for (final Rule rule : ruleList) {
				String ruleName = "<null>";
				try {
					ruleName = rule.getRuleName();
					final ObjectArrayList<Function> uselessResult2 = rule.execute(RulesManager.generateUselessExpression());
					uselessVariable = (uselessResult == null ? new ObjectArrayList<>() : uselessResult).equals(uselessResult2);
					uselessResult = uselessResult2;
				} catch (final Exception e) {
					if (uselessVariable || true) {
						System.err.println("Exception thrown by rule '" + ruleName + "'!");
						e.printStackTrace();
					}
				}
			}
		}
		try {
			new MathSolver(RulesManager.generateUselessExpression()).solveAllSteps();
		} catch (InterruptedException | Error e) {
			e.printStackTrace();
		}
	}

	private static Function generateUselessExpression() {
		final MathContext mc = new MathContext();
		Function expr = new Expression(mc);
		expr = expr.setParameter(0, new Variable(mc, 'x', V_TYPE.VARIABLE));
		return expr;
	}

	public static void addRule(final Rule rule) {
		RulesManager.rules[rule.getRuleType().ordinal()].add(rule);
		WarpPI.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", rule.getRuleName(), "Loaded as " + rule.getRuleType() + " rule");
	}
}
