package ac.essex.gp.ooechs.novelty1;

import ac.essex.ooechs.imaging.commons.signal.SignalDefinition;
import ac.essex.ooechs.imaging.commons.signal.Signal;
import ac.essex.ooechs.imaging.commons.signal.SignalProcessor;
import ac.essex.ooechs.imaging.commons.PixelLoader;
import ac.essex.gp.individuals.Individual;
import ac.essex.gp.problems.DataStack;

import java.util.Vector;

/**
 * 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 {

    protected Vector<SignalDefinition> signalDefinitions;

    protected Vector<NovelClassifier> classifiers;

    public NoveltyDetector(Vector<SignalDefinition> signalDefinitions) {

        this.signalDefinitions = signalDefinitions;

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

    }

    public void addClassifier(Individual ind, int signalID, double weight)  {
        classifiers.add(new NovelClassifier(ind, signalID, weight));
    }

    public double execute(PixelLoader image) {

        Signal[] signals = new Signal[signalDefinitions.size()];

        // generate the signals
        for (int i = 0; i < signalDefinitions.size(); i++) {
            SignalDefinition signalDefinition = signalDefinitions.elementAt(i);
            signals[i] = new Signal(signalDefinition, image);
        }

        double score = 0;
        double totalWeight = 0;

        for (int i = 0; i < classifiers.size(); i++) {
            NovelClassifier novelClassifier = classifiers.elementAt(i);
            if (novelClassifier.execute(signals[novelClassifier.signalID])) {
                score += novelClassifier.weight;
            }
            totalWeight += novelClassifier.weight;
        }

        return score / totalWeight;

    }

    protected class NovelClassifier {

        protected Individual individual;
        protected int signalID;
        protected double weight = 1;

        /**
         * @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, int signalID, double weight) {
            this.individual = individual;
            this.signalID = signalID;
            this.weight = weight;

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

        }

        public boolean execute(Signal s) {

            // generate signals from the image as required
            NovelGPProblem.currentSignal = new SignalProcessor(s);

            DataStack data = new DataStack();

            return individual.execute(data) > 0;

        }

    }

}
