package ac.essex.gp.ooechs.novelty2;

import ac.essex.gp.individuals.Individual;
import ac.essex.gp.problems.DataStack;
import ac.essex.gp.training.TrainingImage;
import ac.essex.gp.nodes.imaging.features.dissociateddipoles.DissociatedDipole;

import java.util.Vector;
import java.io.*;

/**
 * A novelty detector takes an input, and produces from it a number of signals. Each
 * signal is tested against one or more classifiers, with the outputs of each used to
 * vote on how "normal" an object is. If the object's normality falls below a certain
 * threshold then the object is classified as "new".
 *
 * @author Olly Oechsle, University of Essex, Date: 21-Apr-2008
 * @version 1.0
 */
public class NoveltyDetector implements Serializable {

    protected Vector<NoveltyDetector.NovelClassifier> classifiers;

    /**
     * Saves the individual to disk in serialised form. This is primarily
     * to allow the JavaWriter output to be compared with the actual individual
     * in a debugging environment.
     */
    public void save(File f){
        try {
            System.out.println("Saving File");
            FileOutputStream fos = new FileOutputStream(f);
            ObjectOutputStream out = new ObjectOutputStream(fos);
            out.writeObject(this);
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Loads a serialised individual from disk. Mainly used for debugging the
     * Java Writer.
     */
    public static NoveltyDetector load(File f) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(f);
        ObjectInputStream in = new ObjectInputStream(fis);
        NoveltyDetector individual = (NoveltyDetector) in.readObject();
        in.close();
        return individual;
    }

    public NoveltyDetector() {

        this.classifiers = new Vector<NoveltyDetector.NovelClassifier>(10);

    }

    public void addClassifier(Individual ind, double weight, int x, int y, int left, int right, int top, int bottom)  {
        classifiers.add(new NoveltyDetector.NovelClassifier(ind, weight, x, y, left, right, top, bottom));
    }

    public double execute(TrainingImage image) {

        double score = 0;

        for (int i = 0; i < classifiers.size(); i++) {
            NoveltyDetector.NovelClassifier novelClassifier = classifiers.elementAt(i);
            if (novelClassifier.execute(image)) {
                score += novelClassifier.weight;
            }
        }

        return score;

    }

    protected class NovelClassifier implements Serializable {

        protected Individual individual;
        protected double weight = 1;
        protected int x, y;
        protected int left, right, top, bottom;

        /**
         * @param individual The individual evolved using GP
         * @param signalID The signal to use
         * @param weight The weight associated with the classifier
         */
        public NovelClassifier(Individual individual, double weight, int x, int y, int left, int right, int top, int bottom) {
            this.individual = individual;
            this.weight = weight;
            this.x = x;
            this.y = y;

            this.left = left;
            this.right = right;
            this.top = top;
            this.bottom = bottom;

            //System.out.println(individual.toJava());

        }

        public boolean execute(TrainingImage t) {

            // generate signals from the image as required
            DataStack data = new DataStack();

            data.setImage(t);
            data.setX(x);
            data.setY(y);

            DissociatedDipole.left = left;
            DissociatedDipole.right = right;
            DissociatedDipole.top = top;
            DissociatedDipole.bottom = bottom;

            double result = individual.execute(data);
            // cleverly supports both thresholding and boolean expressions.
            return result > 0 && result < 2;

        }

    }

}
