package it.cavallium.warppi.math;

import java.util.Arrays;
import java.util.List;

import it.cavallium.warppi.math.rules.Rule;
import it.cavallium.warppi.util.Error;
import it.cavallium.warppi.util.Utils;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;

public abstract class FunctionDynamic implements Function {
	private boolean simplified;
	
	public FunctionDynamic(final MathContext root) {
		this.root = root;
		functions = new Function[] {};
	}

	public FunctionDynamic(final Function[] values) {
		if (values.length > 0) {
			root = values[0].getMathContext();
		} else {
			throw new NullPointerException("Nessun elemento nell'array. Impossibile ricavare il nodo root");
		}
		functions = values;
	}

	public FunctionDynamic(final MathContext root, final Function[] values) {
		this.root = root;
		functions = values;
	}

	protected final MathContext root;
	protected Function[] functions;

	public Function[] getParameters() {
		return Arrays.copyOf(functions, functions.length);
	}

	public FunctionDynamic setParameters(final List<Function> value) {
		final FunctionDynamic f = clone();
		final int vsize = value.size();
		final Function[] tmp = new Function[vsize];
		for (int i = 0; i < vsize; i++) {
			tmp[i] = value.get(i);
		}
		f.functions = tmp;
		return f;
	}

	public FunctionDynamic setParameters(final Function[] value) {
		final FunctionDynamic f = clone();
		f.functions = value;
		return f;
	}

	@Override
	public Function getParameter(final int index) {
		return functions[index];
	}

	@Override
	public FunctionDynamic setParameter(final int index, final Function value) {
		final FunctionDynamic f = clone();
		f.functions[index] = value;
		return f;
	}

	public FunctionDynamic appendParameter(final Function value) {
		final FunctionDynamic f = clone();
		f.functions = Arrays.copyOf(f.functions, f.functions.length + 1);
		f.functions[f.functions.length - 1] = value;
		return f;
	}

	/**
	 * Retrieve the current number of parameters.
	 *
	 * @return The number of parameters.
	 */
	public int getParametersLength() {
		return functions.length;
	}

	public FunctionDynamic setParametersLength(final int length) {
		final FunctionDynamic f = clone();
		f.functions = Arrays.copyOf(functions, length);
		return f;
	}

	@Override
	public final ObjectArrayList<Function> simplify(final Rule rule) throws Error, InterruptedException {
		final Function[] fncs = getParameters();
		if (Thread.interrupted()) {
			throw new InterruptedException();
		}
		final ObjectArrayList<Function> result = new ObjectArrayList<>();

		final ObjectArrayList<ObjectArrayList<Function>> ln = new ObjectArrayList<>();
		boolean alreadySolved = true;
		for (final Function fnc : fncs) {
			final ObjectArrayList<Function> l = new ObjectArrayList<>();
			if (Thread.interrupted()) {
				throw new InterruptedException();
			}
			final ObjectArrayList<Function> simplifiedFnc = fnc.simplify(rule);
			if (simplifiedFnc == null) {
				l.add(fnc);
			} else {
				l.addAll(simplifiedFnc);
				alreadySolved = false;
			}
			ln.add(l);
		}

		if (alreadySolved) {
			return rule.execute(this);
		}

		final Function[][] results = Utils.joinFunctionsResults(ln);

		for (final Function[] f : results) {
			result.add(this.setParameters(f));
		}

		return result;
	}

	@Override
	public MathContext getMathContext() {
		return root;
	}

	@Override
	public abstract FunctionDynamic clone();

	@Override
	public int hashCode() {
		return Arrays.hashCode(functions);
	}

	@Override
	public abstract boolean equals(Object o);
}