package analyzer;

import interfaces.TrainGUInterface.ProgressWatcher;

public class Trainer {
	private float[][] mInput;
	private float[][] mOutput;
	private float mLearningRate;
	private int mMaxIterations;
	private ProgressWatcher mProgressWatcher;
	private final float ACCEPTABLE_MSE = 0.3f;
	public static final int STATUS_PLAY = 1;
	public static final int STATUS_PAUSE = 2;
	public static final int STATUS_STOP = 3;
	private int mStatus = STATUS_STOP;
	private float mMaxRandomInterval = 2;
	private float mMinRandomInterval = -2;
	private int mHiddenNeurons = 3;

	public Trainer(float[][] input, float[][] output, int hiddenNeuronsNumber, float learningRate, int maxIterations, ProgressWatcher progressWatcher) {
		mInput = input;
		mOutput = output;
		// for(int i=0;i<mOutput.length;i++)
		// System.out.println(mOutput[i][0]+" "+mOutput[i][1]+" "+mOutput[i][2]);
		mHiddenNeurons = hiddenNeuronsNumber;
		mLearningRate = learningRate;
		mMaxIterations = maxIterations;
		mProgressWatcher = progressWatcher;
		mWeightH = new float[mHiddenNeurons][3];
		mWeightO = new float[6][mHiddenNeurons];
		mBestWeightH = new float[mHiddenNeurons][3];
		mBestWeightO = new float[6][mHiddenNeurons];
	}

	private float[][] mWeightH;
	private float[][] mWeightO;
	private float[][] mBestWeightH;
	private float[][] mBestWeightO;

	private int mCurrentIteration = 1;

	private float mBestMSE = Float.MAX_VALUE;

	public void startTraining() {
		if (mStatus == STATUS_STOP) {
			initializeWeightRandom(mWeightH);
			initializeWeightRandom(mWeightO);

			storeWeight(mWeightH, mBestWeightH);
			storeWeight(mWeightO, mBestWeightO);
		}

		mStatus = STATUS_PLAY;
		// System.out.println("yes");
		for (int n = mCurrentIteration; n <= mMaxIterations && mStatus == STATUS_PLAY; n++) {
			mCurrentIteration = n;
			// System.out.println(n);
			// for (int i = 0; i < mWeightO.length; i++) {
			// for (int j = 0; j < mWeightO[i].length; j++)
			// System.out.print(mWeightO[i][j] + " ");
			// System.out.println();
			// }
			// System.out.println();

			Backpropagation backpropagation;
			for (int i = 0; i < mInput.length; i++) {
				// System.out.println("y");
				backpropagation = new Backpropagation(mInput[i], mWeightH, mWeightO, mOutput[i], mLearningRate);
				backpropagation.applyBackpropagation();
				mWeightH = backpropagation.getWeightH();
				mWeightO = backpropagation.getWeightO();
			}

			// for (int x = 0; x < mWeightO.length; x++) {
			// for (int j = 0; j < mWeightO[x].length; j++)
			// System.out.print(mWeightO[x][j] + " ");
			// System.out.println();
			// }
			// System.out.println("________________________________________________");

			FeedForward feedForward;
			float MSE = 0;
			for (int i = 0; i < mInput.length; i++) {
				feedForward = new FeedForward(mInput[i], mWeightH, mWeightO, false);
				feedForward.applyFeedForward();
				// System.out.print(mInput[i][0]+" "+mInput[i][1]+" "+mInput[i][2]+"\t");
				float[] actualOutput = feedForward.getNetOutputO();

				float[] netErrorSegma = calculateNetErrorSegma(mOutput[i], actualOutput);
				MSE += claculateMeanSquareError(netErrorSegma);
				// System.out.print(mOutput[i][0]+" "+mOutput[i][1]+" "+mOutput[i][2]+"\t");
				// System.out.print(actualOutput[0]+" "+actualOutput[1]+" "+actualOutput[2]+" "+actualOutput[3]+"\t");
				// System.out.println();
			}
			// System.out.println();
			// System.out.println();
			MSE = MSE / mInput.length;

			if (MSE <= mBestMSE) {
				storeWeight(mWeightH, mBestWeightH);
				storeWeight(mWeightO, mBestWeightO);
				mBestMSE = MSE;
			}
			// System.out.println(MSE);
			// for (int i = 0; i < mWeightH.length; i++) {
			// for (int j = 0; j < mWeightH[i].length; j++)
			// System.out.println(mWeightH[i][j] + " ");
			// System.out.println();
			// }

			if (mProgressWatcher != null)
				mProgressWatcher.callback(mMaxIterations, mCurrentIteration);
		}

		if (mCurrentIteration == mMaxIterations)
			mStatus = STATUS_STOP;
	}

	private void initializeWeightRandom(float[][] weight) {
		for (int i = 0; i < weight.length; i++)
			for (int j = 0; j < weight[i].length; j++)
				weight[i][j] = randomInRange(mMinRandomInterval, mMaxRandomInterval);
	}

	public float getBestMSE() {
		return mBestMSE;
	}

	public float[][] getBestWeightH() {
		return mBestWeightH;
	}

	public float[][] getBestWeightO() {
		return mBestWeightO;
	}

	private void storeWeight(float[][] toBeStored, float[][] storageVariable) {
		for (int i = 0; i < toBeStored.length; i++)
			for (int j = 0; j < toBeStored[i].length; j++)
				storageVariable[i][j] = toBeStored[i][j];
	}

	private float[] calculateNetErrorSegma(float[] y, float[] o) {
		float[] netErrorSegma = new float[y.length];
		for (int i = 0; i < y.length; i++)
			netErrorSegma[i] = y[i] - o[i];

		return netErrorSegma;
	}

	private float claculateMeanSquareError(float[] Segma) {
		float MSE = 0;
		for (int i = 0; i < Segma.length; i++)
			MSE += Segma[i] * Segma[i];

		MSE = MSE / Segma.length; // or divide by 2

		return MSE;
	}

	public void pause() {
		mStatus = STATUS_PAUSE;
	}

	public void play() {
		mStatus = STATUS_PLAY;
	}

	public void stop() {
		mStatus = STATUS_STOP;

	}

	public int getStatus() {
		return mStatus;
	}

	public void setCallback(ProgressWatcher progressWatcher) {
		mProgressWatcher = progressWatcher;
	}

	public void removeCallback() {
		mProgressWatcher = null;
	}

	public static float randomInRange(float min, float max) {
		float rand = (float) (Math.random() < 0.5 ? ((1 - Math.random()) * (max - min) + min) : (Math.random() * (max - min) + min));
		// if (rand == 0 || (rand > max))
		// return randomInRange(min, max);
		// else
		return rand;
	}
}
