package com.quui.tm2;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.quui.tm2.util.TM2Logger;

/**
 * Synthesis of data and info into a model.
 * @param <D> The data type
 * @param <I> The info type
 * @author Fabian Steeg (fsteeg)
 */
public interface Synthesis<D extends Comparable<D> & Serializable, I extends Comparable<I> & Serializable> {

    /**
     * Run this synthesis using the given blackboard.
     * @param blackboard The blackboard to perform this synthesis on
     * @return The unchanged blackboard (TODO: should this be the model?)
     */
    Map<Class<? extends Agent<?, ?>>, List<Annotation<?>>> run(
            Map<Class<? extends Agent<?, ?>>, List<Annotation<?>>> blackboard);

    /** @return The agents supplying the data to synthesize with the info into a model */
    List<Agent<?, D>> data();

    /** @return The agents supplying the info to synthesize with the data into a model */
    List<Agent<?, I>> info();

    /**
     * @return The model that will be created based on the data and info
     *         TODO: should this be returned via run()?
     */
    Model<D, I> model();

    /**
     * Builder for an immutable synthesis.
     * @author Fabian Steeg (fsteeg)
     */
    public static final class Builder<D extends Comparable<D> & Serializable, I extends Comparable<I> & Serializable> {
        private List<Agent<?, D>> data = new ArrayList<Agent<?, D>>();
        private List<Agent<?, I>> info = new ArrayList<Agent<?, I>>();
        private final Model<D, I> model;

        /**
         * A builder for a synthesis of data and info into a model.
         * @param model The model to synthesize data and info into.
         */
        public Builder(final Model<D, I> model) {
            this.model = model;
        }

        /**
         * @return The immutable synthesis instance
         */
        public Synthesis<D, I> build() {
            return new ImmutableSynthesis<D, I>(this);
        }

        /**
         * @param agent An agent contributing to the synthesized data
         * @return The builder, for cascaded calls
         */
        public Builder<D, I> data(final Agent<?, D> agent) {
            data.add(agent);
            return this;
        }

        /**
         * @param agent An agent contributing to the synthesized info
         * @return The builder, for cascaded calls
         */
        public Builder<D, I> info(final Agent<?, I> agent) {
            info.add(agent);
            return this;
        }

    }

    /**
     * Package-private synthesis implementation, instantiated by the builder.
     * @author Fabian Steeg (fsteeg)
     * @param <D> The data type
     * @param <I> The info type
     */
    class ImmutableSynthesis<D extends Comparable<D> & Serializable, I extends Comparable<I> & Serializable>
            implements Synthesis<D, I> {
        private final List<Agent<?, D>> data;
        private final List<Agent<?, I>> info;
        private final Model<D, I> model;

        ImmutableSynthesis(final Synthesis.Builder<D, I> builder) {
            this.data = builder.data;
            this.info = builder.info;
            this.model = builder.model;
        }

        /**
         * {@inheritDoc}
         * @see com.quui.tm2.Synthesis#run(java.util.Map)
         */
        public Map<Class<? extends Agent<?, ?>>, List<Annotation<?>>> run(
                final Map<Class<? extends Agent<?, ?>>, List<Annotation<?>>> blackboard) {
            if (!canTrain(blackboard)) {
                return null;
            }
            TM2Logger.singleton().info("Running synthesis with blackboard: " + blackboard);

            for (int i = 0; i < data.size(); i++) {
                model.train(
                        ImmutableAnalysis.typedAnnotations(data.get(i), blackboard),
                        ImmutableAnalysis.typedAnnotations(info.get(i), blackboard));
            }

            return blackboard;

        }

        /**
         * {@inheritDoc}
         * @see com.quui.tm2.Synthesis#info()
         */
        public List<Agent<?, I>> info() {
            return Collections.unmodifiableList(info);
        }

        /**
         * {@inheritDoc}
         * @see com.quui.tm2.Synthesis#data()
         */
        public List<Agent<?, D>> data() {
            return Collections.unmodifiableList(data);
        }

        /**
         * {@inheritDoc}
         * @see com.quui.tm2.Synthesis#model()
         */
        public Model<D, I> model() {
            return model;
        }

        private boolean canTrain(
                final Map<Class<? extends Agent<?, ?>>, List<Annotation<?>>> blackboard) {
            for (int i = 0; i < data.size(); i++) {
                Agent<?, D> dat = data.get(i);
                Agent<?, I> inf = info.get(i);
                if (!(blackboard.containsKey(dat.getClass()) && blackboard.containsKey(inf.getClass()))) {
                    TM2Logger.singleton().debug("Not training with blackboard: " + blackboard);
                    return false;
                }
            }
            TM2Logger.singleton().debug("Training with blackboard: " + blackboard);
            return true;
        }

		@Override
		public Class<I> getInfoTypeClass() {
			return AnnotationReader.getAgentClass((Class<? extends Agent<?, I>>) info().get(0).getClass());
		}

		@Override
		public Class<D> getDataTypeClass() {
			return AnnotationReader.getAgentClass((Class<? extends Agent<?, D>>) data().get(0).getClass());
		}

    }

	  Class<I> getInfoTypeClass();

	  Class<D> getDataTypeClass();

}
