package storagesim;

import java.time.LocalDate;
import java.time.temporal.IsoFields;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.stream.Collectors;

import lib.OpenSimplexNoise;
import lib.SQLConnection;

/**
 * Runs a loop with a strict interval to simulate time passing
 * 
 * @author Manuel
 */

public class Simulation {

	boolean running;

	public LocalDate currentTime;
	
	OpenSimplexNoise noise;

	final float STEPS_PER_SECOND = 1 / 30f;
	final int MAX_STEPS = 1;
	final int TOLERANCE = 200;

	public Simulation() {
		this.running = false;

		this.currentTime = LocalDate.now();
		//this.currentTime = LocalDate.parse("2020-02-06");
		
		this.noise = new OpenSimplexNoise();
	}

	/**
	 * Starts and contains the loop
	 * 
	 * @author Manuel
	 */
	public void run(LocalDate time) {
		// initialise timing values
		double interval = 1000f / (STEPS_PER_SECOND);
		double previousMillis = System.currentTimeMillis() - interval;

		this.currentTime = time;
		
		do {
			// update current time
			double currentMillis = System.currentTimeMillis();
			double deltaMillis = currentMillis - previousMillis;

			// run code if enough time has passed
			int steps = 0;
			while (deltaMillis >= interval && steps < MAX_STEPS) {
				// --------------------
				// this is the code that is executed in the specified interval
				SQLConnection db = new SQLConnection();
				System.out.println("Date: " + this.currentTime.toString());
				simulateSales(db);
				sellItems(db);
				disposeItems(db);
				receiveOrders(db);
				sortStorage(db);
				checkStorage(db);
				sortStatistics(db);
				db.close();
				this.currentTime.plusDays(1);
				// this.running = false;
				// --------------------
				steps++;
				deltaMillis -= interval;
			}
			if (steps > 0) {
				// only change time if any code was executed
				previousMillis = currentMillis;
			}
		} while (this.running);
	}

	/**
	 * Removes all Items sold in the last time frame
	 * 
	 * @author Johann
	 */
	public void sellItems(SQLConnection db) {
		// Liste der verkauften waren wird erstellt
		ArrayList<HashMap<String, String>> Verkauft = db.queryToMap(
				"SELECT * FROM t_statistik WHERE `Woche` = " + this.currentTime.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR));
		int MengePalette; // Zwischenspeicher für die Menge der Items auf einer Palette

		for (HashMap<String, String> Artikel : Verkauft) {
			int VerkaufteID = Integer.parseInt(Artikel.get("f_Artikel_ID")); // ID des momentan Bearbeiteten Produktes

			// Alle Paletten werden zwischengespeichert
			ArrayList<HashMap<String, String>> Paletten = db.queryToMap("SELECT * FROM t_mindesthaltbarkeit WHERE `f_ProduktID` = " + VerkaufteID + " ORDER BY Menge");
			
			int Zähler = 0; // in einer späteren for() schleife wird ein weiterer Zähler benötigt

			for (int VerkaufteMenge = Integer.parseInt(Artikel.get("Menge")); VerkaufteMenge > 0; Zähler++) {
				if (Paletten.size() > Zähler) {
					// Der Rest auf einer Palette an Items
					MengePalette = Integer.parseInt(Paletten.get(Zähler).get("Menge"));
					
					if (VerkaufteMenge <= MengePalette) // Wenn mehr auf der einen Palette ist als verkauft wurde
					{
						MengePalette -= VerkaufteMenge;
						VerkaufteMenge = 0; // Alles wurde entfernt und die Palette wurde neu beschrieben
					} else // Wenn mehr verkauft wurde als auf der einen Palette war
					{
						VerkaufteMenge -= MengePalette;
						// Die Palette wird gelert, und die Variable neu beschrieben, es wird die
						// nächste Palette zu rate gezogen
						MengePalette = 0;
					}
					db.execute("UPDATE t_mindesthaltbarkeit SET `Menge` = " + MengePalette
							+ " WHERE t_mindesthaltbarkeit.`ID` = " + Paletten.get(Zähler).get("ID"));
					db.execute("UPDATE t_lager SET `Menge` = " + MengePalette
							+ " WHERE t_lager.`Paletten_ID` = " + Paletten.get(Zähler).get("ID"));
				} else {
					break;
				}
			}
		}
	}

	/**
	 * Disposes of all items that have expired
	 * 
	 * @author Johann
	 */
	public void disposeItems(SQLConnection db) {
		ArrayList<HashMap<String, String>> Waren = db.queryToMap("SELECT t_mindesthaltbarkeit.ID, t_mindesthaltbarkeit.Haltbarkeit, t_mindesthaltbarkeit.Menge, t_artikel.Name FROM t_mindesthaltbarkeit INNER JOIN t_artikel ON t_mindesthaltbarkeit.f_ProduktID = t_artikel.ID");
		// Waren liste wird erstellt

		// Nach Produkten die verworfen werden müssen suchen

		for (HashMap<String, String> Ware : Waren) {
			LocalDate Datum2 = LocalDate.parse(Ware.get("Haltbarkeit")); // Das DAtum des wahrscheinlich nichtmehr
																			// Haltbaren abfragen

			// Compare
			if (this.currentTime.isAfter(Datum2)) // Abfagen ob das Item abgelaufen ist
			{
				int ID = Integer.parseInt(Ware.get("ID")); // Speichern welches Produkt abgelaufen ist
				
				System.out.printf("Eine Pallette mit %s %s ist abgelaufen.\n", Ware.get("Menge"), Ware.get("Name"));
				
				db.execute("UPDATE t_mindesthaltbarkeit SET `Menge` = '0' WHERE t_mindesthaltbarkeit.`ID` = "
						+ ID); // produkt verwerfen
				db.execute("UPDATE t_lager SET `Menge` = '0' WHERE t_lager.`Paletten_ID` = "
						+ ID);
			}
		}

		// Leere Paletten wegwerfen

		Waren = db.queryToMap("SELECT * FROM t_mindesthaltbarkeit"); // Liste mit allen Palette wird aufgerufen

		for (HashMap<String, String> Ware : Waren) {
			if (Integer.parseInt(Ware.get("Menge")) == 0) // Wenn eine Palette leer ist
			{
				db.execute("DELETE FROM t_mindesthaltbarkeit WHERE t_mindesthaltbarkeit.`ID` = " + Ware.get("ID")); // Palette
																													// wir
																													// geleert
			}
		}

		// Leere Paletten in der Haupttabelle werden verworfen

		Waren = db.queryToMap("SELECT * FROM t_lager"); // Liste der Waren in der Haupttabelle wird erstellt

		for (HashMap<String, String> Ware : Waren) {
			if (Integer.parseInt(Ware.get("Menge")) == 0) // Leere Paletten werden gesucht
			{
				// leere Palette wird gelöscht
				db.execute("DELETE FROM t_lager WHERE t_lager.`Lagerplatz` = " + Ware.get("Lagerplatz"));
			}
		}
	}

	/**
	 * Receive orders and puts them into storage
	 * 
	 * @author Johann, Manuel
	 */
	public void receiveOrders(SQLConnection db) {
		ArrayList<HashMap<String, String>> Angekommen = db.queryToMap(
				"SELECT t_bestellungen.ID, f_Artikel_ID, Einkaufspreis, Ankunftsdatum, Menge, Marke, Name FROM t_bestellungen " +
				"INNER JOIN t_artikel ON t_bestellungen.f_Artikel_ID = t_artikel.ID " + 
				"WHERE Ankunftsdatum <= ('" + this.currentTime.toString() + "')");
		ArrayList<HashMap<String, String>> Artikel = db
				.queryToMap("SELECT ID, `MHK in Tagen`, Anzahl_pro_Palette FROM t_artikel");
		ArrayList<HashMap<String, String>> Haltbarkeit;

		LocalDate HaltbarBis;
		int MHKinTagen;

		for (HashMap<String, String> row : Angekommen) {
			int Artikel_ID = Integer.parseInt(row.get("f_Artikel_ID"));

			MHKinTagen = Integer.parseInt(Artikel.get(Artikel_ID - 1).get("MHK in Tagen"));
			if (MHKinTagen >= 0) {
				HaltbarBis = this.currentTime.plusDays(MHKinTagen);
			} else {
				HaltbarBis = LocalDate.parse("9999-12-31");
			}

			int PalettenMenge = Integer.parseInt(Artikel.get(Artikel_ID - 1).get("Anzahl_pro_Palette"));
			int BestellMenge = Integer.parseInt(row.get("Menge"));

			while (BestellMenge > 0) {
				int Menge = BestellMenge > PalettenMenge ? PalettenMenge : BestellMenge;
				BestellMenge -= Menge;

				db.execute("INSERT INTO t_mindesthaltbarkeit SET f_ProduktID = " + row.get("f_Artikel_ID")
						+ ", Haltbarkeit = ('" + HaltbarBis.toString() + "')" + ", Menge = " + Menge);

				Haltbarkeit = db.queryToMap("SELECT ID, f_ProduktID, Haltbarkeit, Menge FROM t_mindesthaltbarkeit");
				String palettenID = Haltbarkeit.get(Haltbarkeit.size()-1).get("ID") ;
				int lagerplatz = findStorageSpace(db);
				
				db.execute("INSERT INTO t_lager SET Lagerplatz = " + lagerplatz + ", Paletten_ID = " + palettenID + ", f_Artikel_ID = "
						+ row.get("f_Artikel_ID") + ", Ankunft = ('" + row.get("Ankunftsdatum") + "')" + ", Menge = "
						+ Menge);
			}
			
			db.execute("DELETE FROM t_bestellungen WHERE ID = " + row.get("ID"));
		}
		
		while (Angekommen.size() > 0) {
			BWLHelper.Bestellungen(db, this.currentTime, Angekommen, "Pascal");
		}
	}

	/**
	 * Goes through all available storage locations and finds the first empty in
	 * line
	 * 
	 * @author Manuel
	 */
	public int findStorageSpace(SQLConnection db) {
		ArrayList<HashMap<String, String>> Lagerplätze = db.queryToMap("SELECT Lagerplatz FROM t_lager");
		ArrayList<Integer> BelegteLagerplätze = new ArrayList<>(
				Lagerplätze.stream().filter((map) -> map.containsKey("Lagerplatz"))
						.map(map -> Integer.parseInt(map.get("Lagerplatz"))).collect(Collectors.toList()));
		BelegteLagerplätze.sort((a, b) -> Integer.compare(a, b));
		int val = 1;
		for (int i = 0; i < BelegteLagerplätze.size(); i++) {
			if (val < BelegteLagerplätze.get(i)) {
				break;
			} else {
				val = BelegteLagerplätze.get(i) + 1;
			}
		}
		return val;
	}

	/**
	 * Sorts storage to group products by type and removes gaps
	 * 
	 * @author Johann
	 */
	public void sortStorage(SQLConnection db) {
		ArrayList<HashMap<String, String>> products = db.queryToMap("SELECT ID FROM t_artikel");
		int Lagerplatz = 1;

		ArrayList<HashMap<String, String>> Waren = db.queryToMap("SELECT * FROM t_lager");
		ArrayList<HashMap<String, String>> Waren_unverändert = db.queryToMap("SELECT * FROM t_lager");
		String[][] Waren_sortiert = new String[Waren.size()][5];
		
		for (HashMap<String, String> product : products) {
			int productID = Integer.parseInt(product.get("ID"));
			
			Waren = db.queryToMap("SELECT * FROM t_lager WHERE f_Artikel_ID = " + productID);

			if (Waren.size() != 0) {
				for (HashMap<String, String> Ware : Waren) {
					Waren_sortiert[Lagerplatz - 1][0] = Integer.toString(Lagerplatz);
					Waren_sortiert[Lagerplatz - 1][1] = Ware.get("Paletten_ID");
					Waren_sortiert[Lagerplatz - 1][2] = Ware.get("f_Artikel_ID");
					Waren_sortiert[Lagerplatz - 1][3] = Ware.get("Ankunft");
					Waren_sortiert[Lagerplatz - 1][4] = Ware.get("Menge");
					Lagerplatz++;
				}
			}
		}

		for (int u = 0; u <= Waren_sortiert.length - 1; u++) {
			db.execute("UPDATE `t_lager` SET `Lagerplatz` = " + Waren_sortiert[u][0] + ", Paletten_ID = " + Waren_sortiert[u][1] + ", `f_Artikel_ID` = "
					+ Waren_sortiert[u][2] + ", `Ankunft` = ('" + Waren_sortiert[u][3] + "')" + ", `Menge` = "
					+ Waren_sortiert[u][4] + " WHERE `t_lager`.`Lagerplatz` = "
					+ Waren_unverändert.get(u).get("Lagerplatz"));
		}
		
		System.out.println("Lager wurde sortiert.");
	}

	/**
	 * Check if items have to be restocked and order them accordingly
	 * 
	 * @author Pascal
	 */
	public void checkStorage(SQLConnection db) {
		ArrayList<HashMap<String, String>> produkte = db.queryToMap("SELECT * FROM t_artikel");
		for (HashMap<String, String> produkt : produkte) {
			int produktID = Integer.parseInt(produkt.get("ID"));
			
			int sollbestand = Integer.parseInt(produkt.get("Sollbestand"));
			
			int ZinsenProzent = 8;
			
			if (sollbestand > 0) {
				boolean sicherheitsbestand = produkt.get("Sicherheitsbestand") == "1";
				
				BWLHelper.sys2(db, this.currentTime, produktID, 7, sollbestand, ZinsenProzent, sicherheitsbestand);
			} else {
				BWLHelper.sys1(db, this.currentTime, produktID, ZinsenProzent);
			}
		}
		
		BWLHelper.lagerStatistik(db, this.currentTime.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR));
	}
	
	/**
	 * Summarises sales statistics in extra table
	 * 
	 * @author Johann
	 */
	public void sortStatistics(SQLConnection db) {
		ArrayList<HashMap<String, String>> products = db.queryToMap("SELECT ID FROM t_artikel");
		ArrayList<HashMap<String, String>> Verkaufszahlen;
		int KWoche = currentTime.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
		int Verkauft;
		
		db.execute("TRUNCATE t_verkaufszahlen");
		
		for(int n = 1; 	n < KWoche; n++)
		{
			Verkaufszahlen  = db.queryToMap("SELECT * FROM `t_statistik` WHERE `Woche` = " + n);
			if(Verkaufszahlen.size() > 0)
			{
				for (HashMap<String, String> product : products) {
					int productID = Integer.parseInt(product.get("ID"));
					
					Verkauft = 0;
					
					Verkaufszahlen = db.queryToMap("SELECT * FROM `t_statistik` WHERE `f_Artikel_ID` = " + productID + " AND `Woche` = " + n);
					if(Verkaufszahlen.size() > 0)
					{
						for(int u = 0; u < Verkaufszahlen.size(); u++)
						{
							Verkauft += Integer.parseInt(Verkaufszahlen.get(u).get("Menge"));
						}
						db.execute("INSERT INTO `t_verkaufszahlen` (`ID`, `f_Artikel_ID`, `Woche`, `Menge`, `Verkaufspreis`) VALUES (NULL, '" + productID + "', '" + n + "', '" + Verkauft + "', '" + Verkaufszahlen.get(0).get("Verkaufspreis") + "')");								
					}
				}
			}
		}
	}
	
	/**
	 * Simulate sales through random noise
	 * 
	 * @author Manuel
	 */
	public void simulateSales(SQLConnection db) {
		ArrayList<HashMap<String, String>> products = db.queryToMap("SELECT ID, Verkaufspreis, Anzahl_pro_Palette FROM t_artikel");
		Random r = new Random();
		
		for (HashMap<String, String> product : products) {
			int productID = Integer.parseInt(product.get("ID"));
			int variance = Integer.parseInt(product.get("Anzahl_pro_Palette"));
			
			double stepSize = 52f * (r.nextDouble() + 0.00001) * 4;
			double val = (1 + this.noise.eval(productID * 100, this.currentTime.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR) / stepSize)) / 20.0;
			int sales = (int)(val * variance) + 1;
			
			//TODO somewhere we should check if we have enough of a product in storage
			
			db.execute("INSERT INTO t_statistik SET f_Artikel_ID = " + productID + 
					", Woche = " + this.currentTime.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR) + 
					", Menge = " + sales + 
					", Verkaufspreis = " + product.get("Verkaufspreis"));
		}
	}
}
