//
// UBahnWagen.gs
//

include "Locomotive.gs"

class UBahnTriebwagen isclass Locomotive
{

	// Öffentliche Variablen:
	// ======================

// Betriebsvariablen

	public int Fahrtziel = 0;		// Fahrtziel entsprechend der Codierungsnummer wie im Driver-Command und den Dasy-/Zielfahrtsschildtexturen
	public int HeadlightsDir = 0;		// 1: Scheinwerfer, -1: Rücklichter, 0: aus
	public int Fahrpult = 0;		// Ist der Geschwindigkeitshebel aus der Neutralstellung bewegt? (Lüfter und Spitzenbeleuchtung an)
	public float pipepress = 500;		// Luftdruck in der Hauptleitung; 500 = gelöst.
	public float pipepress_target = 500;	// Zielluftdruck in der Hauptleitung
	public float pipeflow = 0;		// Aktuelle Hauptleitungsfließgeschwindigkeit
	public bool LDoor = false;		// Zustand der linken Türen
	public bool RDoor = false;		// Zustand der rechten Türen

	public bool DoorFreeL = false;		// Türfreigabe (während der Fahrt: Vorwahl, nach dem Halten: Freigabe)
	public bool DoorFreeR = false;

	public bool Stationsbremse = false;	// Stationsbremse (Festhaltebremse) um eine gewisse Menge Luft abzulassen

	public bool Zwangsbremse = false;	// Liegt eine Art von Zwangsbremsung vor?
	public float zwangsapply = 500;		// Auf welches Niveau wird die Zwangsbremsung die Leitung absenken?
	public float zwangsflow = 0;		// Und wie schnell?

	public float k1 = 0;			// Anfahrverzögerungsstellung (0 bis 1)
	public float k2 = 0;			// Hebelstellung (0 bis 1)
	public bool  freigabe = 0;		// Anfahrtsfreigabe

	public int kurve = 0;			// Fährt der Zug gerade durch eine schwache (1) oder starke (2) Kurve oder nicht (0) ?

	public bool automat = true;		// Ist der Automat eingeschaltet?
	public bool umformer = true;		// Ist der Umformer eingeschaltet?
	public bool federspeicher = false;	// Ist die Federspeicherbremse eingeschaltet?

// Physik- oder Betriebs-Konstanten


	public float intervall = 0.1;		// Wiederholungsintervall für Fahrphysik


	public float aBremsMax = -0.77;		// Bremsverzögerung bei E-Bremse auf maximaler Stufe
	public float aBremsMaxAir = -2.0;	// Bremsverzögerung der Luftbremse bei "full apply"
	public float aBremsMaxFeder = -1.0;	// Bremsverzögerung der Federspeicherbremse

	public float VMax = 72;			// Maximalgeschwindigkeit incl. Toleranz für Deuta-Klingel

	public float anfahr_vmax = 90;		// Maximal erreichbare Geschwindigkeit für t gegen Unendlich (in km/h)
	public float anfahr_amax = 1.0;		// Maximale Beschleunigung bei "Vollgas" aus dem Stand (in m/s²)
	public float anfahr_verz = 1;		// Verzögerung bis beim Anfahren die maximale Beschleunigung erreicht wird in s^-1

	public bool selbstAbfert = true;	// Der Zug wird vom Fahrer abgefertigt. Andernfalls kommen andere Ansagen und das A2 wird aktiviert.
	public float DoorCloseTime = 5.0;	// Türschließzeit bis Wagen freigegeben werden darf

	public float applyflow = 40;		// Druckabfall pro Sekunde bei normalem Bremsenziehen
	public float emergencyflow = 1000;	// Druckabfall pro Sekunde bei Notbremsung
	public float fullapply = 350;		// Höchstdruck für "full apply"
	public float stationapply = 450;	// Luftdruck für Stationsbremse

// Interne Variablen:

	bool isFirstOrLast = true;		// Ist dies das erste oder letzte Fahrzeug im Verband?
	bool LastIsFirstOrLast = false;		// letzter Zustand dieser Variable.
	int LastFahrtziel = 0;			// letzter Zustand von "Fahrtziel"
	int temp = 0;
	int LastHeadlightsDir = 0;		// letzter Zustand von "HeadlightsDir"
	bool DunkelTestVar = false;		// Soll alle 0,3s geprüft werden, ob der Zug im Tunnel ist oder nicht?
	bool PhysicsOn = true;			// Soll die Physik simuliert werden? (Zug rollt an im Gefälle) Achtung: ist nur Zustandsanzeiger...

// Funktionen, die kleine logische oder mathematisch Probleme lösen

	int BoolToInt( bool value )
	{
		if( value )
			return 1;
		return 0;
	}

	public float exp(float x, float a, float eb)		// Näherungsweise a*exp(b*x)
	{
		int i;
		float temp = a;
		for( i=0;i<x;i++ )
			temp = temp * eb;
		return temp;
			
	}

// Funktionen, die entweder gar nicht oder nur primitv implementiert sind und in jedem Fahrzeug individuell mit Leben gefüllt werden:

	// Standardimplementierung der Deuta
	thread public void Deuta()
	{
		zwangsflow = applyflow;
		zwangsapply = stationapply;
		Zwangsbremse = true;
		Sleep( 3.0 );
		Zwangsbremse = false;
	}

	// Standardimplementierung für v(a)
	public float accel(float stufe)
	{
		float k2 = stufe / 5.0;
		float omega = anfahr_amax/(anfahr_vmax / 3.6);
		return k2*omega*((anfahr_vmax / 3.6) - Math.Fabs(GetVelocity()) );
	}

	// Standardimplementierung für omega (siehe Anfahrsteuerung)
	public float omegaFkt()
	{
		return anfahr_amax/(anfahr_vmax / 3.6);
	}

	// Standardimplementierung der Türöffnungsfunktion (links)
	public void OpenDoorsL()
	{
		SetMeshAnimationState("left-passenger-door", true);
	}

	// Standardimplementierung der Türöffnungsfunktion (rechts)
	public void OpenDoorsR()
	{
		SetMeshAnimationState("right-passenger-door", true);
	}

	// Standardimplementierung der Spitzenbeleuchtungsfunktion
	public void SetHeadlights( int dir )
	{

	}

	// Standardimplementierung der Türschließfunktion (links)
	thread public void CloseDoorsL()
	{
		SetMeshAnimationState("left-passenger-door", false);
	}

	// Standardimplementierung der Türschließfunktion (rechts)
	thread public void CloseDoorsR()
	{
		SetMeshAnimationState("right-passenger-door", false);
	}

	// Standardimplementierung der Helligkeitsfunktion:
	// thread public void DunkelAdjust( int mode ){}

	// Standardimplementierung für Ansage:
	public void Ansage(string text){}

	// Folgende Funktion schließt die Türen in Abhängigkeit der Seite, wo sie offen sind:
	thread public void CloseDoors()
	{
		if (LDoor)
		{
			Sleep( Math.Rand(0.0, 0.2) );
			CloseDoorsL();
			Sleep( DoorCloseTime );
			LDoor = false;
		}
		if (RDoor)
		{
			Sleep( Math.Rand(0.0, 0.2) );
			CloseDoorsR();
			Sleep( DoorCloseTime );
			RDoor = false;
		}
	}

	// Folgender Thread enthält diverse häufig zu aktualisierende Parameter (abhängig von intervall)
	thread void Cycle(void)
	{

		int i;

		while( 1 )
		{
			//Ist der Wagen der Erste oder der Letzte? Dann "ja", sonst "nein".

			isFirstOrLast = (GetMyTrain().GetVehicles()[0] == me) or (GetMyTrain().GetVehicles()[GetMyTrain().GetVehicles().size()-1] == me);

			// Falls sich Fahrtziel oder Reihenfolge im Verband verändert hat:
			if ((Fahrtziel != LastFahrtziel) or (isFirstOrLast != LastIsFirstOrLast))
			{
				// Falls nicht erstes oder letztes Fahrzeug wird das Zugzielschild gelöscht
				if (isFirstOrLast)
					temp = Fahrtziel;
				else
					temp = 0;

				SetFXTextureReplacement("zielfahrtschild", GetAsset ().FindAsset ("zielschild"), temp );

				LastFahrtziel = Fahrtziel;
				LastIsFirstOrLast = isFirstOrLast;
			}

// geht leider nicht, weil dann in besetzten Zügen alle DriverCommands gelöscht werden :-(
/*
			if(GetMyTrain().GetVehicles()[0] == me)
				if( GetMyTrain().GetActiveDriver() != null )
					if( GetMyTrain().GetActiveDriver().GetLocation() != me )
					{
						Schedule lastschedule = GetMyTrain().GetActiveDriver().schedule;
						GetMyTrain().GetActiveDriver().SetLocation( me );
						GetMyTrain().GetActiveDriver().schedule = lastschedule;
					}

*/

//##########################################################
/*
// Testabschnitt (Wagennummer innerhalb des Zuges wird in Form des Zielfahrtschildes dargestellt)

			for(i=0;i<GetMyTrain().GetVehicles().size();i++)
				if (GetMyTrain().GetVehicles()[i] == me)
					SetFXTextureReplacement("zielfahrtschild", GetAsset ().FindAsset ("zielschild"), i );	
					//SetFXNameText( "Number01" , cast<string>i);

*/
//##########################################################


			// Scheinwerfe / Rücklichter

			LastHeadlightsDir = HeadlightsDir;

			// Wenn die Geschwindigkeit 1m/s überschreitet, werden die Zugziellichter entsprechend der Richtung aktiviert

			if ((GetVelocity() > 1) and isFirstOrLast)
				HeadlightsDir = 1;
			else if ((GetVelocity() < -1) and isFirstOrLast)
				HeadlightsDir = -1;
			else if ( ! isFirstOrLast)
				HeadlightsDir = 0;
			else if (LastHeadlightsDir == 0)
				HeadlightsDir = -1;

//##########################################################
/*
// Testabschnitt (Wenn Automat an ist, leuchten die Scheinwerfer)

			if (automat)
				HeadlightsDir = 1;
*/
//##########################################################

			if (HeadlightsDir != LastHeadlightsDir)
				SetHeadlights( HeadlightsDir );

			// Wenn die Geschwindigkeit VMax überschreitet, wird die Deuta aktiviert:
			if ((Math.Fabs(GetVelocity()) * 3.6) > VMax )
				Deuta();

			// Fahrpult-Zustand wird zugewiesen:

			if( HeadlightsDir == 1 )
			{
				for (i=0;i<GetMyTrain().GetVehicles().size();i++)
					if( GetMyTrain().GetVehicles()[i].isclass(UBahnTriebwagen) )
						(cast<UBahnTriebwagen>GetMyTrain().GetVehicles()[i]).Fahrpult = 1;
			}

			//Fahrphysik, z.Zt.: E-Bremse & Luftbremse!

			float beschleunigung = 0;
			float bremsbeschleunigung;
			float stufe = GetEngineSetting( "throttle");

			// ********* Federspeicherbremsbeschleunigung *******
			if ( federspeicher )
				bremsbeschleunigung = aBremsMaxFeder * intervall;
			else
				bremsbeschleunigung = 0;

			// ************ E-Bremsbeschleunigung ***************

			if ((GetEngineSetting( "dynamic-brake") > 0.1) and ( Math.Fabs(GetVelocity()) > 1.0 ) and automat)
			{
				if (stufe > 4)
					stufe = 4;

				bremsbeschleunigung = bremsbeschleunigung + ( aBremsMax * stufe / 40 );
			}

			// ************ Luftbremsbeschleunigung ***************

			// Zuweisung der Hauptleitungsveränderungswerte durch den Bremshebel:

			if (Zwangsbremse)
			{
				pipepress_target = zwangsapply;
				pipeflow = - zwangsflow;
			}
			else if (GetEngineSetting( "train-lap-brake") == 2.0 and pipepress > 0)
			{
				pipepress_target = 0;
				pipeflow = - applyflow;
			}
			else if (GetEngineSetting( "train-lap-brake") == 4.0 and pipepress > 0)
			{
				pipepress_target = 0;
				pipeflow = - emergencyflow;
			}
			else if (Stationsbremse)
			{
				if ((pipepress < stationapply) or (! (GetEngineSetting( "train-lap-brake") == 3.0)))
				{
					Stationsbremse = false;
					pipeflow = 0;
				}
				else
				{
					pipepress_target = stationapply-5;
					pipeflow = - applyflow;
				}
			}
			else if (GetEngineSetting( "train-lap-brake") == 0.0 and pipepress < 500)
			{
				pipepress_target = 500;
				pipeflow = applyflow;
			}
			else
			{
				pipeflow = 0;
			}

			// Berechnung des Zustandes der Hauptleitung:

			if( pipeflow != 0 )
			{
				if (
					(
						(pipeflow > 0) and (pipepress_target - pipepress > (pipeflow * intervall))
					) or (
						(pipeflow < 0) and (pipepress - pipepress_target > (pipeflow * intervall))
					)
				)
					pipepress = pipepress + (pipeflow * intervall);
				else
					//pipepress = (pipepress + pipepress_target ) / 2.0;
					pipepress = pipepress_target;
			}

			// Berechnung der Verzögerung durch L-Bremsung:

			if (pipepress < 470)
			{
				float pressdif = (470-pipepress);
				if( pressdif > (470 - fullapply) )
					pressdif = (470 - fullapply);
				bremsbeschleunigung = bremsbeschleunigung +(pressdif/(470 - fullapply))*aBremsMaxAir*intervall;
			}

			// ************ Anfahrbeschleunigung ***************

			if ((GetEngineSetting( "dynamic-brake") < 0.1) and (stufe > 0))
				freigabe = true;
			else
				freigabe = false;

			// Darf der Strom freigegeben werden?

			if (LDoor or RDoor or (pipepress < 499) or (! automat) or federspeicher )
				freigabe = false;

			// Ermittlung von k1:

			if (! freigabe)
				k1 = 0.0;
			else
			{
				k1 = k1 + (anfahr_verz * intervall);
				if( k1 > 1)
					k1 = 1;
			}

			if (freigabe)

				beschleunigung = k1*accel(stufe)*intervall;

			if (GetEngineSetting("reverser") < 1.0)
				beschleunigung = -beschleunigung;

			// ******** resultierende Beschleunigung ***********

			// Berechnung der Richtung des Tfz:

			int dir = 0;
			if (GetVelocity() > 0.0)
				dir = 1;
			else if (GetVelocity() < 0.0)
				dir = -1;
			else
				dir = 0;

			// Falls dieser Triebwagen falschrum am Zug hängt, muss die Beschleunigung auch rückwärts angesetzt werden.

			if( ! GetDirectionRelativeToTrain() )
				dir = -dir;

			// Verändern der Geschwindigkeit:
			if(GetMyTrain().GetPhysicsModel() != 1)
			{
				if (Math.Fabs(GetVelocity()) > (   -Math.Fabs(beschleunigung)    - bremsbeschleunigung))
				{
					GetMyTrain().AddVelocity( (dir * bremsbeschleunigung + beschleunigung) / GetMyTrain().GetVehicles().size() );
					if (! PhysicsOn)
					{
						//GetMyTrain().EnablePhysics(true);
						PhysicsOn = true;
					}
				}
				else
				{
					GetMyTrain().SetVelocity( 0 );
					if (PhysicsOn)
					{
						//GetMyTrain().EnablePhysics(false);
						PhysicsOn = false;
					}
				}
			}

			Sleep( intervall );
		}
	}

	// Dieser Thread enthält den Algorithmus, ob jener Wagen im Tunnel ist oder nicht:

	/*

	thread void DunkelTest(void)
	{

		MapObject nextObject = null;
		int before = -2, behind = -2;
		int oldbehind, oldbefore;
		bool oneTimeDone = true;

		while( DunkelTestVar or oneTimeDone )
		{

			oldbehind = behind;
			oldbefore = before;

			oneTimeDone = false;
			GSTrackSearch thesearch = BeginTrackSearch(true);
			nextObject = thesearch.SearchNext();
			before = -1;
			behind = -1;
			while( nextObject != null )
			{
				if (nextObject.GetName() == "Tunnel1")
				{
					before = 1;
					break;
				}
				else if (nextObject.GetName() == "Tunnel0")
				{
					before = 0;
					break;
				}
				nextObject = thesearch.SearchNext();
			}
			thesearch = BeginTrackSearch(false);
			nextObject = thesearch.SearchNext();
			while( nextObject != null )
			{
				if (nextObject.GetName() == "Tunnel1")
				{
					behind = 1;
					break;
				}
				else if (nextObject.GetName() == "Tunnel0")
				{
					behind = 0;
					break;
				}
				nextObject = thesearch.SearchNext();
			}

			if ((behind != oldbehind) or (before != oldbefore))
			{
				if (Math.Fabs(GetVelocity()) > 1)
				{
					switch( behind )
					{
						case 0 :
							if ( before != 1 )
								SetMeshVisible("intunnel", false, 5.0 / Math.Fabs(GetVelocity())   );
							break;
						case 1 :
							if ( before != 0 )
								SetMeshVisible("intunnel", true, 15.0 / Math.Fabs(GetVelocity())   );
							break;
						default:
							if ( before == 0 )
								SetMeshVisible("intunnel", false, 5.0 / Math.Fabs(GetVelocity())   );
							else if ( before == 1 )
								SetMeshVisible("intunnel", true, 15.0 / Math.Fabs(GetVelocity())   );
					}
				}
			}
			Sleep( 0.4 );
		}
	}

	void DunkelEnter(Message msg)
	{

		string TriggerName = (cast<MeshObject>msg.src).GetName();

		if (TriggerName == "Tunnel0")
		{
			DunkelTestVar = true;
			DunkelTest();
		}
	}

	void DunkelLeave( Message msg )
	{

		string TriggerName = (cast<MeshObject>msg.src).GetName();

		if (TriggerName == "Tunnel0")
		{
			DunkelTestVar = false;
		}
	}

	*/

	void Init(void)
	{
		inherited();
		//DunkelTest();
		Cycle();
//		AddHandler(me.GetMyTrain(),"Trigger","Enter","DunkelEnter");
//		AddHandler(me.GetMyTrain(),"Trigger","Leave","DunkelLeave");
/* Zufall:	if (Math.Rand(0, 4 ) == 0)
			automat = false;
		if (Math.Rand(0, 4 ) == 0)
			federspeicher = true;
		if (Math.Rand(0, 4 ) == 0)
			umformer = false;
*/
		automat = true;
		federspeicher = false;
		umformer = true;
		
	}

	public void SetProperties(Soup soup)
	{
		inherited(soup);
		Fahrtziel = soup.GetNamedTagAsInt("UBahnTriebwagen.Fahrtziel");
		if (soup.GetNamedTagAsInt("UBahnTriebwagen.automat") == 1)
			automat = false;
		else if (soup.GetNamedTagAsInt("UBahnTriebwagen.automat") == 2)
			automat = true;
		if (soup.GetNamedTagAsInt("UBahnTriebwagen.federspeicher") == 1)
			federspeicher = false;
		else if (soup.GetNamedTagAsInt("UBahnTriebwagen.federspeicher") == 2)
			federspeicher = true;
		if (soup.GetNamedTagAsInt("UBahnTriebwagen.umformer") == 1)
			umformer = false;
		else if (soup.GetNamedTagAsInt("UBahnTriebwagen.umformer") == 2)
			umformer = true;
	}

	public Soup GetProperties(void)
	{
		Soup soup = inherited();
		soup.SetNamedTag("UBahnTriebwagen.Fahrtziel", Fahrtziel);
		soup.SetNamedTag("UBahnTriebwagen.automat", BoolToInt(automat) + 1);
		soup.SetNamedTag("UBahnTriebwagen.federspeicher", BoolToInt(federspeicher) + 1);
		soup.SetNamedTag("UBahnTriebwagen.umformer", BoolToInt(umformer) + 1);

		return soup;
	}

};