package analyzer;

import java.util.Random;

public class Backpropagation {
	private float[][] mWeightO;
	private float[][] mWeightH;
	private float[] mX;
	private float[] mY;
	private ActivationFunction activationFunction;
	private boolean isCorrectState = false;
	private float mLearningRate = 0.7f; // 0.3~0.7

	private float[][] mUpdatedWeightO;
	private float[][] mUpdatedWeightH;

	private float mMSE;

	public Backpropagation(float[] x, float[][] weightH, float[][] weightO, float[] y, float learningRate) {
		mWeightO = weightO;
		mWeightH = weightH;
		mX = x;
		mY = y;
		mLearningRate = learningRate;
		activationFunction = new ActivationFunction();

		mUpdatedWeightH = new float[weightH.length][weightH[0].length];
		mUpdatedWeightO = new float[weightO.length][weightO[0].length];
	}

	public void setX(float[] x) {
		mX = x;
	}

	public float[][] getWeightO() {
		if (isCorrectState)
			return mWeightO;
		else
			return null;
	}

	public float[][] getWeightH() {
		if (isCorrectState)
			return mWeightH;
		else
			return null;
	}

	public void applyBackpropagation() {
		FeedForward feedForward = new FeedForward(mX, mWeightH, mWeightO, false);
		feedForward.applyFeedForward();

		float[] o = feedForward.getNetOutputO();
		// for (int i = 0; i < o.length; i++)
		// System.out.print(o[i] + "\t");
		// System.out.println();

		float[] netErrorSegmaK = calculateNetErrorSegma(mY, feedForward.getNetOutputO());

		// for (int i = 0; i < netErrorSegmaK.length; i++)
		// System.out.print(netErrorSegmaK[i] + "\t");
		// System.out.println();
		// System.out.println();

		float[] cumulativeErrorSegmaO = calculateCumulativeErrorSegmaO(feedForward.getNetOutputO(), netErrorSegmaK);

		// for (int i = 0; i < cumulativeErrorSegmaO.length; i++)
		// System.out.print(cumulativeErrorSegmaO[i] + "\t");
		// System.out.println();
		// System.out.println();

		updateOutputLayerWeight(cumulativeErrorSegmaO, feedForward.getNetOutputI());

		// for (int i = 0; i < mUpdatedWeightO.length; i++) {
		// for (int j = 0; j < mUpdatedWeightO[i].length; j++)
		// System.out.print(mUpdatedWeightO[i][j] + " ");
		// System.out.println();
		// }
		// System.out.println();
		// System.out.println("_______");

		float[] cumulativeErrorSegmaH = calculateCumulativeErrorSegmaH(feedForward.getNetOutputI(), netErrorSegmaK);

		// for (int i = 0; i < cumulativeErrorSegmaH.length; i++)
		// System.out.print(cumulativeErrorSegmaH[i] + "\t");
		// System.out.println();
		// System.out.println();

		updateHiddenLayerWeight(cumulativeErrorSegmaH, mX);

		claculateMeanSquareError(netErrorSegmaK);

		updateWeights();

		isCorrectState = true;

		// 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();
		// System.out.println();
		//
		// for (int i = 0; i < mWeightH.length; i++) {
		// for (int j = 0; j < mWeightH[i].length; j++)
		// System.out.print(mWeightH[i][j] + " ");
		// System.out.println();
		// }

		// System.out.println(mMSE);
	}

	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[] calculateCumulativeErrorSegmaO(float[] netOutputO, float[] netErrorSegma) {
		float[] activatedNetInput = activationFunction.applySigmoidDervative(netOutputO);

		float[] cumulativeErrorSegmaO = new float[netOutputO.length];

		for (int i = 0; i < activatedNetInput.length; i++) {
			cumulativeErrorSegmaO[i] = activatedNetInput[i] * netErrorSegma[i];
		}

		return cumulativeErrorSegmaO;
	}

	private void updateOutputLayerWeight(float[] cumulativeErrorSegmaO, float[] netOutputI) {
		for (int i = 0; i < mUpdatedWeightO.length; i++)
			for (int j = 0; j < mUpdatedWeightO[i].length; j++) {
				mUpdatedWeightO[i][j] = mWeightO[i][j] + mLearningRate * cumulativeErrorSegmaO[i] * netOutputI[j];
			}
	}

	private float[] calculateCumulativeErrorSegmaH(float[] netInputH, float[] netErrorSegma) {
		float[] activatedNetInput = activationFunction.applySigmoidDervative(netInputH);
		// for(int i=0;i<netInputH.length;i++)
		// System.out.println(netInputH[i]);
		// System.out.println();
		// System.out.println();
		float[] cumulativeErrorSegmaH = new float[netInputH.length];

		for (int i = 0; i < activatedNetInput.length; i++) {
			float cumulativeMulti = 0;
			for (int j = 0; j < netErrorSegma.length; j++)
				cumulativeMulti += netErrorSegma[j] * mWeightO[j][i];

			cumulativeErrorSegmaH[i] = activatedNetInput[i] * cumulativeMulti;
		}

		return cumulativeErrorSegmaH;
	}

	private void updateHiddenLayerWeight(float[] cumulativeErrorSegmaH, float[] x) {
		for (int i = 0; i < mUpdatedWeightH.length; i++)
			for (int j = 0; j < mUpdatedWeightH[i].length; j++) {
				mUpdatedWeightH[i][j] = mWeightH[i][j] + mLearningRate * cumulativeErrorSegmaH[i] * x[j];
			}
	}

	private void 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

		mMSE = MSE;
	}

	private void updateWeights() {
		mWeightH = mUpdatedWeightH;
		mWeightO = mUpdatedWeightO;

		mUpdatedWeightH = new float[mWeightH.length][mWeightH[0].length];
		mUpdatedWeightO = new float[mWeightO.length][mWeightO[0].length];
	}

	public static class NeuralDenormalizer extends Random {

		public NeuralDenormalizer(long scaleFactor) {
			super(scaleFactor);
		}

		public int scale(int factor) {
			return nextInt(factor);
		}
	}
}
