/**
 * 
 */
package xhail.core.entities;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections4.iterators.ArrayIterator;

import xhail.core.Buildable;
import xhail.core.Config;
import xhail.core.Dialler;

/**
 * @author stefano
 *
 */
public class Answers implements Iterable<Answer> {

	public static class Builder implements Buildable<Answers> {

		private Set<Answer> answers = new HashSet<>();

		private Config config;

		private int count = 0;

		private Values values = null;

		public Builder(Config config) {
			if (null == config)
				throw new IllegalArgumentException("Illegal 'config' argument in Answers.Builder.Builder(Config): " + config);
			this.config = config;
		}
		
		@Override
		public Answers build() {
			return new Answers(this);
		}

		public Builder clear() {
			first = -1L;
			this.answers.clear();
			this.count = 0;
			this.values = null;
			return this;
		}

		public final boolean isMeaningful() {
			for (Answer answer : answers)
				if (answer.isMeaningful())
					return true;
			return false;
		}

		public Builder put(Values values, Answer answer) {
			if (null == values)
				throw new IllegalArgumentException("Illegal 'values' argument in Answers.Builder.putAnswer(Values, Answer): " + values);
			if (null == answer)
				throw new IllegalArgumentException("Illegal 'answer' argument in Answers.Builder.putAnswer(Values, Answer): " + answer);
			if (first < 0L)
				first = System.nanoTime();
			int order = null == this.values ? -1 : values.compareTo(this.values);
			if (order < 0) {
				this.answers.clear();
				this.values = values;
			}
			if (order <= 0)
				this.answers.add(answer);
			count += 1;
			return this;
		}

		public Builder remove(Values values, Answer answer) {
			if (null == values)
				throw new IllegalArgumentException("Illegal 'values' argument in Answers.Builder.removeAnswer(Values, Answer): " + values);
			if (null == answer)
				throw new IllegalArgumentException("Illegal 'answer' argument in Answers.Builder.removeAnswer(Values, Answer): " + answer);
			if (null != this.values && 0 == values.compareTo(this.values))
				if (this.answers.remove(answer))
					count -= 1;
			return this;
		}

		public final int size() {
			return answers.size();
		}

	}

	private static long abduction = 0L;
	private static long deduction = 0L;
	private static long first = -1L;
	private static long induction = 0L;
	private static long loading = -1L;
	private static final double NORMALIZER = 1_000_000_000.0;

	private static long start = -1L;

	public static final double getAbduction() {
		return abduction / NORMALIZER;
	}

	public static final double getDeduction() {
		return deduction / NORMALIZER;
	}

	public static final double getFirst() {
		if (first < 0L || start < 0L)
			return 0L;
		return (first - start) / NORMALIZER;
	}

	public static final double getInduction() {
		return induction / NORMALIZER;
	}

	public static final double getLoading() {
		if (loading < 0L || start < 0L)
			return 0L;
		return (loading - start) / NORMALIZER;
	}

	public static final double getNow() {
		if (start < 0L)
			return 0L;
		return (System.nanoTime() - start) / NORMALIZER;
	}

	public static final void loaded() {
		if (loading < 0L)
			loading = System.nanoTime();
	}

	public static final void started() {
		if (start < 0L)
			start = System.nanoTime();
	}

	public static Map.Entry<Values, Collection<Collection<String>>> timeAbduction(int iter, Dialler dialer) {
		if (iter < 0)
			throw new IllegalArgumentException("Illegal 'iter' argument in Answers.timeAbduction(int, Dialer): " + iter);
		if (null == dialer)
			throw new IllegalArgumentException("Illegal 'dialer' argument in Answers.timeAbduction(int, Dialer): " + dialer);
		long time = System.nanoTime();
		Map.Entry<Values, Collection<Collection<String>>> result = dialer.execute(iter);
		abduction += (System.nanoTime() - time);
		return result;
	}

	public static Hypothesis timeDeduction(Grounding grounding, Collection<String> output) {
		if (null == grounding)
			throw new IllegalArgumentException("Illegal 'grounding' argument in Answers.timeDeduction(Grounding, Collection<String>): " + grounding);
		if (null == output)
			throw new IllegalArgumentException("Illegal 'output' argument in Answers.timeDeduction(Grounding, Collection<String>): " + output);
		long time = System.nanoTime();
		Hypothesis result = new Hypothesis.Builder(grounding).parse(output).build();
		result.getHypotheses();
		deduction += (System.nanoTime() - time);
		return result;
	}

	public static Grounding timeDeduction(Problem problem, Collection<String> output) {
		if (null == problem)
			throw new IllegalArgumentException("Illegal 'problem' argument in Answers.timeDeduction(Problem, Collection<String>): " + problem);
		if (null == output)
			throw new IllegalArgumentException("Illegal 'output' argument in Answers.timeDeduction(Problem, Collection<String>): " + output);
		long time = System.nanoTime();
		Grounding result = new Grounding.Builder(problem).parse(output).build();
		result.getGeneralisation();
		deduction += (System.nanoTime() - time);
		return result;
	}

	public static Map.Entry<Values, Collection<Collection<String>>> timeInduction(int iter, Dialler dialer) {
		if (iter < 0)
			throw new IllegalArgumentException("Illegal 'iter' argument in Answers.timeInduction(int, Dialer): " + iter);
		if (null == dialer)
			throw new IllegalArgumentException("Illegal 'dialer' argument in Answers.timeInduction(Dialer): " + dialer);
		long time = System.nanoTime();
		Map.Entry<Values, Collection<Collection<String>>> result = dialer.execute(iter);
		induction += (System.nanoTime() - time);
		return result;
	}

	private final Answer[] answers;

	private final Config config;

	private final int count;

	private final Values values;

	private Answers(Builder builder) {
		if (null == builder)
			throw new IllegalArgumentException("Illegal 'builder' argument in Answers(Answers.Builder): " + builder);
		this.answers = builder.answers.toArray(new Answer[builder.answers.size()]);
		this.config = builder.config;
		this.count = builder.count;
		this.values = builder.values;
	}

	public final int count() {
		return count;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Answers other = (Answers) obj;
		if (!Arrays.equals(answers, other.answers))
			return false;
		if (values == null) {
			if (other.values != null)
				return false;
		} else if (!values.equals(other.values))
			return false;
		return true;
	}

	public final Answer getAnswer(int index) {
		if (index < 0 || index >= answers.length)
			throw new IndexOutOfBoundsException("Illegal 'index' argument in Answers.getAnswer(int): " + index);
		return answers[index];
	}

	public final Answer[] getAnswers() {
		return answers;
	}

	public final Config getConfig() {
		return config;
	}

	public final Values getValues() {
		return values;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(answers);
		result = prime * result + ((values == null) ? 0 : values.hashCode());
		return result;
	}

	public final boolean isEmpty() {
		return 0 == answers.length;
	}

	@Override
	public Iterator<Answer> iterator() {
		return new ArrayIterator<>(answers);
	}

	public final int size() {
		return answers.length;
	}

}