#ifndef __P_SCHEDULEEXTENSION__
#define __P_SCHEDULEEXTENSION__


#if !defined(AVR) && !defined(ESP32)
#error Platform is not supported
#endif

#include <MLLExtension.h>
         
//#define _PeterDebug				// allgemeiner Debug
//#define _PeterDebug1				// Entwicklungs-Debug mit einigen zusätzlichen Ausgaben. Muss zusätzlich zu _PeterDebug aktiviert werden.
//#define _PeterEntw1				// Zufalswert wird auf 0 gesetzt, so dass alle eingegebenen Schaltwerte sofort geschaltet werden. Nur zur Entwicklung.

#if defined(_PeterDebug1) && !defined(_PeterDebug)
#error Debugschalter falsch für P_Schedule
#endif

#ifdef _PeterDebug
	#ifdef __FlashStringHelper		// Dank Jürgen (jueff) gibt es jetzt keinen Kompilerfehler mehr mit Streaming. 
		#undef __FlashStringHelper
	#endif
	#include <Streaming.h> 	  // https://github.com/janelia-arduino/Streaming
#endif


/*
P_ScheduleExtension 
V 0.01 	17.05.2024	Test mit 5 Parametern (Dest1, TestN, enableCh (SI_1), start und end) Mit Zufallszeit fest im Programm (x_zufall = 15).
V 0.02 	20.05.2024	Debug-Ausgabe hinzu.
V 0.03 	20.05.2024	Zufallswert über Variable aus Prog-Gen. 
V 0.04	29.05.2024	Neue Version: Aufruf-Parameter:								
					<= nicht realisiert, Parameterart verworfen!
V 0.05	17.06.2024  Eingabe über Uhrzeit. Damit neue Parameter:	
						dstVar1				Erste Variable			Variable
						dstVarN				Letzte Variable			Variable
						enableCh			SI_1					Nummer der Enable Eingangs
						start_Std			Start-Stunde			0 - 23
						start_Min			Start-Minutes			0 - 59
						end_Std				Ende-Stunde				0 - 23
						end_Min				Ende.Minute				0 - 59
						zufall				Zufall-Tick				0 - 99
					Umrechnung der Uhrzeit in Ticks erledigt.
V 0.06	01.07.2024	Erste funktionierende REalisierung.
					Uhr2Tick Funktion geprüft. Ok!
					Einzelfunktion mit einer Variablen geprüft. Ok
V 0.07	04.07.2024	Bei mehreren Variablen werden nicht immer alle geschaltet. Sie werden direkt hintereinander geschaltet.
V 0.08	07.07.2024	Jetzt werden alle Varablen geschaltet. Aber immer noch schnell in Random-Art.
V 0.09	09.07.2024	SwitchVal Überlauf/negativer Wert wird jetzt umgerechnet, alledings in der gleichen DatState.
V 0.10	21.07.2024	Eingeschränkte Funktion: Mehrere Objekte schalten fast gleichzeitig (Haus1 ... Haus5).	
					Hinweis: Minimale Schaltzeit 1 Tick entspricht min. 3 Minuten!
V 0.11	25.08.2024	Umstellung auf neue Zeitrechnung. Mehrere Objekte werden aber fast gleichzeitig geschaltet.
V 0.12	28.08.2024	Prüfung der Schaltzeiten bei CalcSwitchVal, nicht befriedigend.
V 0.13	09.09.2024	Ist die Zeit zwischen ein und aus kleiner als der Zufallswert, wird 2/3 der Zeitdifferenz als Zufallswert verwendet.
V 0.14	16.10.2024	Debug erweitert. Ergebnis von CalcSwitchVal konnte > 512 sein, damit wurde eine Blockade ausgelöst.
					Probleme bei mehreren Varablen.
V 0.15	17.10.2024	Tests. Bei kurzer Schaltdifferenz wurde manchmal ein Schaltpunkt übersprungen.			
V 0.16	21.10.2024	Neuer Umrechnungsfaktor, wenn Zufall > Schaltdifferenz (Wert zwischen ein und aus bzw. aus und ein). 
					Beta-Version 1
V 0.17 	25.10.2024	Fehler bei der Abfrage nach Objekten (beginnen mit 0 nicht 1). Dadurch schalten 2 Objekte gleichzeitig.
					Debugausgabe, wenn Zufall programmtechnisch gekürzt wird.
					Beta-Version 2
V 0.18	05.01.2025	Debuging: Dank Jürgen (jueff) gibt es jetzt keinen Kompilerfehler mehr mit Streaming.
					Debuging: Wenn Zufall verkürzt wird, LED mit ausgeben.
					Variablendefinition aus MOdulen herausgenommen, da Probleme mit ESP32.

				
ToDo:

				
		
*/
#ifdef _PeterDebug
	const byte MyProgVersionHigh = 0;			// Debugausgabe der Version
	const byte MyProgVersionLow  = 18;			// Debugausgabe der Version
#endif
/*
Definition in InitDefs.h:
enum DayState_E : uint8_t	// --> don't change the sequence
    {
    Unknown = 0,
    SunSet  = 1,  	// 12 - 24 Uhr   
    SunRise = 2		// 0 Uhr - 12 Uhr Sonnenaufgang
	}
	
// Results generated by Get_Input()
#define INP_OFF            0b00000000
#define INP_ON             0b00000011
#define INP_TURNED_ON      0b00000001
#define INP_TURNED_OFF     0b00000010
#define INP_INIT_OFF       0b00000110
 Es ist NICHT moeglich den Ausgang einer Logic Funktion mehrfach zu verwenden weil dadurch
 Zustaende verloren gehen. Die Eingaenge haben 4 verschiedene Zustaende
   INP_OFF            B00000000
   INP_ON             B00000011
   INP_TURNED_ON      B00000001
   INP_TURNED_OFF     B00000010
                              ^
 Die Logic Funktion setzt nur das Letzte Bit.
 Der vorangegabgegangene Zustand wird im Bit mit der Wertigkeit 2 gespeichert.
 Dieses Bit wird am einde einer Updateschleife gesetzt.
 Wenn mehrere Logic Funktionen in die gleiche Variable schreiben, dann wird der Letzte Zustand
 nicht richtig gesetzt => INP_TURNED_ON / _OFF geht verloren ;-(
*/



// Defines für my_FirstLedStatus
	#define myLedOn 1					// LED-Status ist ein
	#define myLedOff 2					// LED-Status ist aus
	#define myLedUnKnow 0				// LED-Status ist unbekannt


class P_ScheduleExtension : public MLLExtension
{
private:
	uint8_t dstVar1;							// Übergabeparameter 1.LED
	uint8_t dstVarN;							// Übergabeparameter letzte LED
	uint8_t enableCh;							// Übergabeparameter SI_1
	uint8_t start_Std;							// Übergabeparameter Start-Stunde
	uint8_t start_Min;							// Übergabeparameter Start-Minute
	uint8_t end_Std;							// Übergabeparameter Ende-Stunde
	uint8_t end_Min;							// Übergabeparameter Ende-Minute
	uint8_t zufall;								// Übergabeparameter Zufall-Ticks
	
	uint16_t my_start;							// interner Startwert (Zeit umgerechnet)
	uint16_t my_end;							// interner Endwert (Zeit umgerechnet)
	uint8_t my_FirstLedStatus = myLedOff;   	// interner Status der ersten LED

	uint16_t SwitchVal = 0; 					// Zeit-Tick zum schalten
	uint8_t ToDoCnt = 0;						// Anzahl Elemente zum schalten
	uint16_t MyAktTime = 0;						// P_Schedule interner Timer, abgeleitet von Darkness & DayState
	uint16_t z_diff = 0;
	
	uint16_t NeuZufall = 999;					// Für Debugausgabe 'Zufall-Wert verkürzt'
	uint8_t OrgZufall = 0;						// Für Debugausgabe 'Zufall-Wert verkürzt'
	
	uint8_t DstVar1;							// Hilfsvariable V0.18 
	uint8_t DstVarN;							// Hilfsvariable V0.18
	uint8_t Nr;									// Hilfsvariable V0.18
	uint8_t XZufall = 2;						// Hilfsvariable V0.18
	uint16_t XDiff = 0;							// Hilfsvariable V0.18
	uint8_t Inp = 0;							// Hilfsvariable V0.18
	uint8_t minuteRund = 0;						// Hilfsvariable V0.18
	uint16_t takt = 0;							// Hilfsvariable V0.18
	uint16_t tempSchalt = 0;					// Hilfsvariable V0.18
	
	public:
	// Constructor
	//-----------------------------------------------------------------------------------------------
  P_ScheduleExtension(uint8_t dstVar1, uint8_t dstVarN, uint8_t enableCh, uint8_t start_Std, uint8_t start_Min, uint8_t end_Std, uint8_t end_Min, uint8_t zufalltick )  
	//-----------------------------------------------------------------------------------------------
  {
	this->dstVar1 = dstVar1;
    this->dstVarN = dstVarN;
    this->enableCh = enableCh;
    this->start_Std = start_Std;
    this->end_Std = end_Std;
	this->start_Min = start_Min;
    this->end_Min = end_Min;
	this->zufall = zufalltick;
	OrgZufall = zufalltick;						// Damit im Debug der Originalwert angezeigt werden kann, wenn Zufall verkürzt wird.
	my_start = Uhr2Tick(start_Std, start_Min);	// Startzeit umrechnen Rückgabe 
	my_end = Uhr2Tick(end_Std, end_Min);		// Endzeit umrechnen  Rückgabe 

	// Ist der Zufallswert > als die Schaltzeit, so wird als Zufall durch 2.5 geteilt verwendet.
	// Sonst bleibt u.U. die LED dauernd an oder aus.
	if (my_start < my_end)
	{
		z_diff = (my_end - my_start);
		if (z_diff / 2 <= zufall)
		{
			zufall = (z_diff * 2) / 5;
			NeuZufall = zufall;
		}
		z_diff = ((my_start + 512) - my_end);
		if (z_diff / 2 <= zufall)
		{
			zufall = (z_diff * 2) / 5;
			NeuZufall = zufall;
		}	
	} else {
		z_diff = 512 - my_start;
		z_diff = z_diff + my_end;
		if (z_diff / 2 <= zufall)
		{
			zufall = (z_diff * 2) / 5;
			NeuZufall = zufall;
		}
	}
	my_FirstLedStatus = myLedOff;				// Aktueller LED-Status (an/aus) der ersten LED (dstVar1)
#ifdef _PeterDebug1
	zufall = 0;
	NeuZufall = 999;
#endif
  }

	private:
	
#ifdef _PeterDebug	
	uint8_t Delta_ToDoCnt = 0;					// Unnötige Ausgaben gleicher Tick's unterbinden.
	uint16_t Delta_MyAktTime = 0;				// Unnötige Ausgaben gleicher Tick's unterbinden.
	uint8_t Mitternacht_Flag = 0;				// Mitternacht ausgeben
	uint16_t Minutes ;
	
	//---------------
	void AktUhrzeit()
	//---------------
	// Debug-Ausgabe der aktuellen Zeit
	{
		if (Delta_MyAktTime != MyAktTime)
		{
			uint16_t Minutes = ((uint32_t)MyAktTime * 24*60) / 512;
			Serial << "P_S- " << "+Uhrzeit: " <<  _WIDTHZ(Minutes/60,2) << ":" <<  _WIDTHZ(Minutes%60,2) << " MyAktTime " << MyAktTime << " Darkness: " << Darkness << " DayState " << DayState << endl;
			Delta_MyAktTime = MyAktTime;
		}
	}
	
	//---------------
	void SchaltzeitInfo(uint8_t xLed, uint8_t xAnAus, uint8_t XZufall)
	//---------------
	// Debug-Ausgabe der aktuellen Zeit
	{
		if (Delta_MyAktTime != MyAktTime)
		{
			uint16_t Minutes = ((uint32_t)MyAktTime * 24*60) / 512;
			if (xAnAus == myLedOn)
			{
				Serial << "P_S- " << "++Schaltzeit: " <<  _WIDTHZ(Minutes/60,2) << ":" <<  _WIDTHZ(Minutes%60,2) << " Soll Ein: " <<  _WIDTHZ(start_Std,2) << ":" << _WIDTHZ(start_Min,2) <<  " Zufall: " << zufall <<  " XZufall: " << XZufall << " -- MyAktTime " << MyAktTime << " Darkness: " << Darkness << " DayState " << DayState << " - LED: " << xLed << " Zustand: "  << xAnAus  << endl;
			} else {
				Serial << "P_S- " << "--Schaltzeit: " <<  _WIDTHZ(Minutes/60,2) << ":" <<  _WIDTHZ(Minutes%60,2) << " Soll Aus: " <<  _WIDTHZ(end_Std,2) << ":" << _WIDTHZ(end_Min,2) <<  " Zufall: " << zufall <<  " XZufall: " << XZufall << " -- MyAktTime " << MyAktTime << " Darkness: " << Darkness << " DayState " << DayState << " - LED: " << xLed << " Zustand: "  << xAnAus << endl;
			}
			Delta_MyAktTime = MyAktTime;
		}
	}
	
	//--------------------------------------------------
	void SchaltzeitUhrzeit(uint16_t SZeit, String ctext)
	//--------------------------------------------------
	// Debug-Ausgabe Schaltzeit (derzeit nicht verwendet)
	{
		uint16_t Minutes = (SZeit * 24*60) / 512;
		Serial << "P_S- " << "-Schaltzeit " << ctext << ": " << _WIDTHZ(Minutes/60,2) << ":" <<  _WIDTHZ(Minutes%60,2) << " MyAktTime " << MyAktTime << " Darkness: " << Darkness << endl;
	}
#endif
	
	
	//--------------------------------------
	uint8_t P_RundenMinTakt(uint8_t Minuten)			
	//--------------------------------------
	// Teile Minuten durch 3 und runde.
	// Das Runden der Minuten geschieht aus Speicherplatz-Gründen sehr einfach.
	{
		//uint8_t minuteRund = 0;
		minuteRund = Minuten/3;
		if((minuteRund % 3) >= 2) minuteRund++;
		return minuteRund;
 	}
	
	//--------------------------------------------------
	uint16_t Uhr2Tick(uint8_t x_stunde, uint8_t x_minute)
	//--------------------------------------------------
	// Umrechnung der Uhrzeit in MLL-Ticks 
	{
		//uint16_t takt = 0;
		takt = x_stunde * 21;
		if (x_stunde > 2) takt ++;
		if (x_stunde > 6) takt ++;
		if (x_stunde > 10) takt ++;
		if (x_stunde > 12) takt ++;
		if (x_stunde > 13) takt ++;
		if (x_stunde > 17) takt ++;
		if (x_stunde > 21) takt ++;
		takt = takt + P_RundenMinTakt(x_minute);
		if (x_minute > 29) takt ++;
		return takt;
	}
	
	//--------------------------------------
	void CalcSwitchVal(uint8_t LedIs_OnOff)
	//--------------------------------------
	// Schaltzeit durch "Zufall" ermitteln. 
	// Es werden 512 hinzugezählt, um einen negativen Übelauf (Schaltezit - Zufall) zu umgehen.
	// Nach der Rundung werden diese wieder abgezogen. Ist das Ergebnis < 512 ist es der gerechnete neue Schaltwert.
	{
		//uint16_t tempSchalt = 0;
		if (LedIs_OnOff == myLedOff)				// LED ist Off  -- Start wird berechnet
		{
			SwitchVal = random(512 + my_start - zufall, 512 + my_start + zufall);
		} 
		else 
		{									// LED ist On -- Ende wird berechnet
			SwitchVal = random(512 + my_end - zufall, 512 + my_end + zufall);
		}
#ifdef _PeterDebug1	
		Serial << "SwitchVal nach Random (1): " << SwitchVal << endl;
#endif
		if (SwitchVal >= 512)
		{
#ifdef _PeterDebug1	
			Serial << "SwitchVal > 512 (2): " << SwitchVal << endl;
#endif
			SwitchVal = SwitchVal - 512;
			if (SwitchVal >= 512)
			{
				SwitchVal = SwitchVal - 512;
			}
			if (SwitchVal == 0)
			{
				SwitchVal = 1;
			}
#ifdef _PeterDebug1	
			Serial << "SwitchVal nach Abzug (3): " << SwitchVal << endl;
#endif
		}
		else
		{
			SwitchVal = SwitchVal;		// Unnötig, nur zur Klarheit.
#ifdef _PeterDebug1	
			Serial << "SwitchVal ohne And (4): " << SwitchVal << endl;
#endif
		}
#ifdef _PeterDebug1
		Serial << "P_S- Switchval: " << SwitchVal << " Start: " << my_start << " Ende: " << my_end <<  " Zufall: " << zufall << "  Led is going (1=off, 2=on): " << LedIs_OnOff << " LED: " << dstVar1 << endl;
#endif 
	}
	

	//------------------------------------------
	void Proc_ScheduleMyTime(MobaLedLib_C& mobaLedLib)
	//------------------------------------------
	// Neue MLL-Zeitrechnung aus Darkness & DayState ->  0/511 = Mitternacht, 256 = 12  Uhr Mittag
	// Wird regelmässig über LOOP aufgerufen.
	{
		if (DayState == SunSet)		// 12 - 24 Uhr		Daystate == 2	Darkness 0 - 255 	MyAktTime 257 - 512
		{
			MyAktTime = Darkness + 256;
		} else {					// 0 - 12 Uhr		DayState == 1	Darkness 255 - 0	MyAktTime 0 - 256
			MyAktTime = 256 - Darkness;
		}
//		AktUhrzeit(); //############
	}


	//------------------------------------------
	void Proc_Schedule(MobaLedLib_C& mobaLedLib)
	//------------------------------------------
	{
/*		uint8_t DstVar1;
		uint8_t DstVarN;
		uint8_t Nr;
		uint8_t XZufall = 2;
		uint16_t XDiff;
		uint8_t Inp = mobaLedLib.Get_Input(enableCh); 		// Input LED-Nummer ????
	*/
		Inp = mobaLedLib.Get_Input(enableCh); 		// Input LED-Nummer ????
		if (Inp_Is_On(Inp))
		{
			if (ToDoCnt == 0)  
			{
				DstVar1 = dstVar1;
				DstVarN = dstVarN;
				for (uint8_t DstVar = DstVar1; DstVar <= DstVarN; DstVar ++)	// Zählen der aktiven LED's
				{
						ToDoCnt ++;
				}
#ifdef _PeterDebug1				
				Serial << "P_S- " << "ToDoCnt auf " << ToDoCnt <<  " gesetzt." << endl;
#endif
				CalcSwitchVal(my_FirstLedStatus);
			}
			if (ToDoCnt > 0)  
			{
				DstVar1 = dstVar1;
				DstVarN = dstVarN;
				if (DstVarN > 0)							// Alt war 1, die Objekte fangen aber bei 0 an. 24.10.24
				{
					XZufall = zufall / DstVarN;				// Sind mehrere Objekte vorhanden, so wird der geforderte Zufallswert duch die Anzahl der Objekte 
															// geteilt. Das ist dann die Zeitdifferenz, indem die Objekte geschaltet werden.
					if (my_end > my_start)					// my_start: 8:00 (170) , my_end: 10:00 (213) = 43
					{
						XDiff = my_end - my_start;
					}
					else	// my_end < my_start			// my_start: 10:00 (213), my_end: 8:00 (170) = 
					{
						XDiff = my_start - my_end;
						XDiff = XDiff * -1;
					}
					if (XDiff <= (zufall * 2))
					{
						XZufall = 1;
					}
					if (XDiff <= DstVarN)
					{
						XZufall = 0;
					}	
				}
				else
				{
					XZufall = 0;							// Bei nur einem Objekt ist die Berechnung nicht notwendig.
				}
						
				Nr = random8(ToDoCnt);
				if (my_FirstLedStatus == myLedOff)
				{
					for (uint8_t DstVar = DstVar1; DstVar <= DstVarN; DstVar ++)
					{
						if ((MyAktTime == SwitchVal))
						{
							if (mobaLedLib.Get_Input(DstVar) == INP_OFF || mobaLedLib.Get_Input(DstVar) == INP_TURNED_OFF)
							{
								if (Nr-- == 0)
								{
#ifdef _PeterDebug
									SchaltzeitInfo(DstVar, myLedOn, XZufall);
#endif		
									mobaLedLib.Set_Input(DstVar,1);
									ToDoCnt --;
									if (ToDoCnt > 0)
									{
										SwitchVal = SwitchVal + XZufall;
										if (SwitchVal > 511)
										{
											SwitchVal = XZufall;
										}
									}
								} 
							} 
						}
					}
				}
				else 
				{
					for (uint8_t DstVar = DstVar1; DstVar <= DstVarN; DstVar ++)
					{
						if ((MyAktTime == SwitchVal))
						{
							if (mobaLedLib.Get_Input(DstVar) == INP_ON || mobaLedLib.Get_Input(DstVar) == INP_TURNED_ON)
							{
								if (Nr-- == 0)
								{
#ifdef _PeterDebug
									SchaltzeitInfo(DstVar, myLedOff, XZufall);
#endif
									mobaLedLib.Set_Input(DstVar,0);
									ToDoCnt --;
									if (ToDoCnt > 0)
									{
										SwitchVal = SwitchVal + XZufall;
										if (SwitchVal > 511)
										{
											SwitchVal = XZufall;
										}
									}
								} 
							}	
						}
					}
				}
				if (ToDoCnt == 0)
				{
					if (my_FirstLedStatus == myLedOff)
					{
						my_FirstLedStatus = myLedOn ;
					}
					else
					{
						my_FirstLedStatus = myLedOff ;
					}
				}
#ifdef _PeterDebug1		
				if (Delta_ToDoCnt != ToDoCnt)
				{
					Serial << "P_S- " << "ToDoCnt steht jetzt auf " << ToDoCnt << "  my_FirstLedStatus (1=on, 2=off): " << my_FirstLedStatus << endl;
					Delta_ToDoCnt = ToDoCnt;
				}
#endif					
			}
			
		}
	}

 
	public:

	//----------------------------------
	void setup(MobaLedLib_C& mobaLedLib) // Setup der Funktion
	//----------------------------------
	{
    // hier kommt der Code hin, der beim Starten ausgeführt wird
	
#ifdef _PeterDebug
	Mitternacht_Flag = 0;
	Serial << "P_S- P_Schedule Version " << MyProgVersionHigh << "." << MyProgVersionLow <<  endl;
	if (NeuZufall < 999)
	{
		Serial << "P-S- Zufall-Wert für LED: " << dstVar1 << " verkürzt von " << OrgZufall << " auf " << NeuZufall <<  " Ticks" << endl; // 0.18 01.01.2025
	}
#endif
#ifdef _PeterEntw1
	Serial << "Zufall per '_PeterEntw1' auf 0 gesetzt!" << endl;
#endif
	}


	//----------------------------------
	void loop(MobaLedLib_C& mobaLedLib) // Zyklischer Aufruf der Funktion
	//----------------------------------
	{
    // hier kommt der Code hin, der regelmässig ausgeführt wird
		if (mobaLedLib.Trigger20fps > 0)
		{
			Proc_ScheduleMyTime(mobaLedLib);	// Aktuelle Zeit rechnen für P_Schedule-Zeit
			Proc_Schedule(mobaLedLib);			// P_Schedule ausführen
		}
/*		//Funktionsprüfung Uhr2Tick
		uint8_t xStd = 0;
		uint8_t xMin = 0;
		uint16_t xy = 0;
		for (xStd = 0; xStd < 24; xStd++)
  		{	
			for (xMin = 0; xMin < 60; xMin++)
			{
				xy = Uhr2Tick(xStd,xMin);
				Serial << "P_S- " << "Uhrzeit: " << xStd << ":" << xMin << "  Tick " << xy  << endl;
			}
		}	
*/
#ifdef _PeterDebug
		if (MyAktTime == 1)
		{
			if (Mitternacht_Flag == 0)
			{
				Serial << "Mitternacht!" << endl;
				Mitternacht_Flag = 1;
			}	
		}
		if (MyAktTime > 1)
		{
			Mitternacht_Flag = 0;
		}
#endif	
	}
};
#endif