package io.openems.edge.predictor.lstm;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Arrays;

import org.junit.Test;

import io.openems.edge.predictor.lstm.common.HyperParameters;
import io.openems.edge.predictor.lstm.common.LstmPredictor;
import io.openems.edge.predictor.lstm.preprocessing.DataModification;

/**
 * Unit test class for the LstmPredictor model.
 * 
 * <p>
 * This class tests the behavior of the LstmPredictor for different types of
 * input patterns such as impulse, step, ramp, and exponential inputs. It
 * validates that the predictions generated by the LSTM model match the expected
 * outputs within a defined margin of error.
 * </p>
 * 
 * <p>
 * The model utilizes pre-defined hyperparameters and scaling factors to process
 * the input data. The test cases ensure that the scaling applied before the
 * prediction and scaled-back result after the prediction are consistent with
 * the hyperparameters.
 * </p>
 * 
 * <p>
 * The model trend data used in the test is also related to the specific scaling
 * factors defined in {@link HyperParameters}, and the tests confirm the proper
 * interaction between the input data and the model's trend.
 * </p>
 * 
 * 
 * 
 * <p>
 * The tests use the {@link DataModification#scale()} method to scale the input
 * data before prediction and {@link DataModification#scaleBack()} to reverse
 * the scaling after the prediction. The scaling ranges are provided by the
 * hyperparameters through {@link HyperParameters#getScalingMin()} and
 * {@link HyperParameters#getScalingMax()}.
 * </p>
 */
public class LstmPredictorTest {
	private static HyperParameters hyperParameters = new HyperParameters();

	private static ArrayList<ArrayList<Double>> modelTrend = new ArrayList<>(Arrays.asList(
			createList(0.30000000000000004, -0.10191832534531027, -0.19262844428679757, 0.016925024201681654),
			createList(-0.7999999999999999, -0.3142909416393413, -0.3341676120993015, -0.09089772222510135),
			createList(-0.4999999999999999, 0.051555896559209405, -0.11477687998526631, 0.10826117268571883),
			createList(-0.6, -1.449260711226437, -1.6789748520719996, -1.6707673970279129),
			createList(1.9000000000000004, 2.0276163313785935, 2.0457575003167086, 1.716902676376759),
			createList(0.09999999999999995, -0.40632251238009526, -0.2902480457595551, -0.21870167929155354),
			createList(-0.08825349909436375, -0.10024408682002699, -0.0891597522413061, -0.11174726093461877),
			createList(-0.2529282639641216, -0.24738024250988547, -0.18556978270548535, -0.2302537524898713)//
	));

	private static ArrayList<Double> createList(Double... values) {
		return new ArrayList<>(Arrays.asList(values));
	}

	@Test
	public void predictTest() {

		/*
		 * IMPULSE RESPONSE : impulses are the sudden change in consumption for a very
		 * short period of time
		 * 
		 * Example : When someone runs the electric drilling machine
		 * 
		 * When the change magnitude of data last indexed data is very high compared to
		 * other data in an array, model identifies it as an impulse.
		 * 
		 * Model will make a prediction negating the drastic change
		 * 
		 */
		double result;

		var impulseSimulation = new ArrayList<>(createList(50.0, 55.0, 55.0, 150.0));

		result = LstmPredictor.predict(//
				DataModification.scale(impulseSimulation, //
						hyperParameters.getScalingMin(), hyperParameters.getScalingMax()), //
				modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4),
				modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters);

		result = DataModification.scaleBack(result, //
				hyperParameters.getScalingMin(), //
				hyperParameters.getScalingMax());
		assertEquals(result, -4379.836081864531, 0.001);
	}

	@Test
	public void predictTest1() {
		double result;
		// STEP RESPONSE : Example: plugging in EV for charging
		var stepSimulation1 = new ArrayList<>(createList(55.0, 45.0, 150.0, 150.0));

		result = LstmPredictor.predict(//
				DataModification.scale(stepSimulation1, //
						hyperParameters.getScalingMin(), hyperParameters.getScalingMax()),
				modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4),
				modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters);

		result = DataModification.scaleBack(result, //
				hyperParameters.getScalingMin(), //
				hyperParameters.getScalingMax());

		assertEquals(result, -4382.945607343116, 0.001);
	}

	@Test
	public void predictTest2() {
		double result;

		var stepSimulation2 = new ArrayList<>(createList(45.0, 150.0, 150.0, 150.0));
		result = LstmPredictor.predict(//
				DataModification.scale(stepSimulation2, //
						hyperParameters.getScalingMin(), hyperParameters.getScalingMax()),
				modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4),
				modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters);
		result = DataModification.scaleBack(result, //
				hyperParameters.getScalingMin(), //
				hyperParameters.getScalingMax());

		assertEquals(result, -4380.577836686382, 0.0001);

	}

	@Test
	public void predictTest3() {
		double result;

		var stepSimulation3 = new ArrayList<>(createList(150.0, 150.0, 150.0, 150.0));
		result = LstmPredictor.predict(//
				DataModification.scale(stepSimulation3, hyperParameters.getScalingMin(),
						hyperParameters.getScalingMax()),
				modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4),
				modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters);
		result = DataModification.scaleBack(result, //
				hyperParameters.getScalingMin(), //
				hyperParameters.getScalingMax());

		assertEquals(result, -4391.590202508077, 0.001);

	}

	@Test
	public void predictTest4() {
		double result;

		// RESPONSE TO RAMP INPUT
		var rampInput = new ArrayList<>(createList(100.0, 200.0, 400.0, 800.0));
		result = LstmPredictor.predict(//
				DataModification.scale(rampInput, //
						hyperParameters.getScalingMin(), //
						hyperParameters.getScalingMax()),
				modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4),
				modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters);
		result = DataModification.scaleBack(result, //
				hyperParameters.getScalingMin(), //
				hyperParameters.getScalingMax());

		assertEquals(result, -4376.960088551304, 0.001);

	}

	@Test
	public void predictTest5() {
		double result;

		// RESPONSE to exponential input
		var expInput = new ArrayList<>(createList(20.0, 400.0, 160000.0, 3200000000.0));
		result = LstmPredictor.predict(//
				DataModification.scale(expInput, //
						hyperParameters.getScalingMin(), //
						hyperParameters.getScalingMax()),
				modelTrend.get(0), modelTrend.get(1), modelTrend.get(2), modelTrend.get(3), modelTrend.get(4),
				modelTrend.get(5), modelTrend.get(7), modelTrend.get(6), hyperParameters);
		result = DataModification.scaleBack(result, //
				hyperParameters.getScalingMin(), //
				hyperParameters.getScalingMax());

		assertEquals(result, -6666.666666666666, 0.001);

	}

}
