package moa.classifiers.JanStaniewicz;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

import javafx.collections.transformation.SortedList;
import moa.classifiers.AbstractClassifier;
import moa.classifiers.Classifier;
import moa.classifiers.JanStaniewicz.helpers.ChunkInfo;
import moa.classifiers.JanStaniewicz.helpers.InstMahalanobisDist;
import moa.classifiers.JanStaniewicz.helpers.Mahalanobis;
import moa.classifiers.JanStaniewicz.helpers.OversamplingMethod;
import moa.core.DoubleVector;
import moa.core.Example;
import moa.core.InstanceExample;
import moa.core.Measurement;
import moa.core.MiscUtils;
import moa.options.ClassOption;

import com.github.javacliparser.FloatOption;
import com.github.javacliparser.StringOption;
import com.github.javacliparser.IntOption;
import com.github.javacliparser.MultiChoiceOption;

//import weka.core.Instance;
import com.yahoo.labs.samoa.instances.Instance;

public class SERA extends AbstractOversamplingClassifier {

	public SERA() {
		this(new Mahalanobis(MinorityClass, postBalanceRatioOption.getValue()));
	}

	public SERA(OversamplingMethod method) {
		super(method);
	}

	static double MinorityClass = 1.0;

	public moa.options.ClassOption baseLearnerOption = new ClassOption("baseLearner", 'l', "Classifier to train.", Classifier.class,
			"trees.HoeffdingTree");

	public IntOption chunkSizeOption = new IntOption("chunkSize", 'c',
			"The number of examples after which the learning proces should apear", 100, 1, Integer.MAX_VALUE);

	public FloatOption costFactorOption = new FloatOption("costFactor", 'k',
			"The cost factor k used for biased bagging", 1.0, 1, Float.MAX_VALUE);

	public IntOption ensembleSizeOption = new IntOption("ensembleSize", 's', "The number of models in the bag.", 10, 1,
			Integer.MAX_VALUE);

	public static FloatOption postBalanceRatioOption = new FloatOption("postBalanceRatio", 'f',
			"The ratio between positive and negattive samples after balancing", 0.5, 0.001, Float.MAX_VALUE);

	protected Classifier[] ensemble;

	private int counter;

	private int chunkNum;

	private ArrayList<Instance> chunk;

	private double lastClassRatio = 0.5;

	@Override
	public double[] getVotesForInstance(Instance inst) {
		DoubleVector combinedVote = new DoubleVector();
		for (int i = 0; i < this.ensemble.length; i++) {
			DoubleVector vote = new DoubleVector(this.ensemble[i].getVotesForInstance((com.yahoo.labs.samoa.instances.Instance) inst));
			if (vote.sumOfValues() > 0.0) {
				vote.normalize();
				combinedVote.addValues(vote);
			}
		}
		return combinedVote.getArrayRef();
	}

	@Override
	public void resetLearningImpl() {
		this.ensemble = new Classifier[this.ensembleSizeOption.getValue()];
		Classifier baseLearner = (Classifier) getPreparedClassOption(this.baseLearnerOption);
		baseLearner.resetLearning();
		for (int i = 0; i < this.ensemble.length; i++) {
			this.ensemble[i] = baseLearner.copy();
		}
		counter = 0;
		chunkNum = 1;
		chunk = new ArrayList();
	}

	@Override
	public void trainOnInstanceImpl(Instance inst) {
		counter++;
		if (counter % chunkSizeOption.getValue() == 0) {
			TrainClassifier();
			counter = 0;
			chunkNum++;
		} else
			chunk.add((Instance) inst);
	}

	private void TrainClassifier() {
		ChunkInfo info = new ChunkInfo(chunk, chunkNum, lastClassRatio);
		int minorCount = countMinorityExamplesInChunk();
		int numberOfSamples = (int) Math
				.floor((double) chunkSizeOption.getValue() * (postBalanceRatioOption.getValue() - lastClassRatio));
		ArrayList<Instance> LearningSet = new ArrayList(Oversample(info, numberOfSamples));

		for (Instance inst : LearningSet) {

			for (int i = 0; i < this.ensemble.length; i++) {
				int k;
				if (inst.classValue() == MinorityClass) // Zakadam e 1.0 to
				{
					k = MiscUtils.poisson(costFactorOption.getValue(), this.classifierRandom);
				} else
					k = MiscUtils.poisson(1.0, this.classifierRandom);
				if (k > 0) {
					Instance weightedInst = (Instance) inst.copy();
					weightedInst.setWeight(inst.weight() * k);
					this.ensemble[i].trainOnInstance(weightedInst);
				}
			}
		}
		lastClassRatio = (double) minorCount / chunkSizeOption.getValue();

	}

	private int countMinorityExamplesInChunk() {
		int minorCount = 0;
		for (Instance inst : chunk)
			if (inst.classValue() == MinorityClass) {
				minorCount++;
			}
		return minorCount;
	}
	
	@Override
	protected Measurement[] getModelMeasurementsImpl() {
		return null;
	}

	@Override
	public void getModelDescription(StringBuilder out, int indent) {

	}

	@Override
	public boolean isRandomizable() {
		return true;
	}

}
