using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ZusiCLIProject.EntryPoints.ReihenschlussHelper;
using ZusiCLIProject.FileLibrary.Zusi3;
using KombiSchalterFunktion = ZusiCLIProject.FileLibrary.Zusi3.NativeMethods.KombiSchalterFunktion;

#if NETCOREAPP
namespace ZusiCLIProject.EntryPoints;
#else
namespace ZusiCLIProject.EntryPoints
{
#endif

public class Antrieb141
{
	// die Funktion sollte eine Messagebox starten, die als kurze Doku zur Funktion
	// der dll für den Fahrzeugbauer liefert
	public static void InfoEx(System.IntPtr callerOrNull, System.Reflection.Assembly assenbly, int id) 
	{
		MessageBox.Show("Modifizierter Reihenschlussantrieb mit einem speziell mit dem Knallen des Niederspannungsschaltwerkes der 141." + FahrzeugAntrieb141.BedienungSpezielleWerteBeschreibung, "Antrieb Baureihe 141");
	}
	// die dll kann mehrere Varianten enthalten, es muss mindestens 1 zurückgegeben werden
	public static int AnzahlVariantenEx(System.IntPtr callerOrNull, System.Reflection.Assembly assenbly, int id) { return 1; }
	// Für jede Variante soll ein kurzer String als Beschreibung zurückgegeben werden
	// Die strings werden im Fahrzeugeditor in die Combobox gefüllt
	// index beginnt bei 0 für die 1. Variante
	public static string VariantenNameEx(System.IntPtr callerOrNull, System.Reflection.Assembly assenbly, int id, int index) 
	{
		if (index == 0)
			return "BR 141 Standard";
		else
			return "error";
	}
	// Diese Funktion wird beim Start der Fahrt einmal aufgerufen, um der dll
	// mitzuteilen, welche der Varianten berechnet werden soll
	// index beginnt bei 0 für die 1. Variante
#if NETCOREAPP
	[System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
	public static NativeMethods.IFahrzeugAntrieb VarianteAktivierenEx(System.IntPtr callerOrNull, System.Reflection.Assembly assenbly, int id, int index) 
	{ 
		try
		{
			return new NativeMethods.FahrzeugAntriebCatcher(new FahrzeugAntrieb141(index));
		}
		catch(System.Exception ex)
		{
			MessageBox.Show("Antrieb konnte nicht initialisiert werden:\r\n" + ex.ToString(), "Antrieb Baureihe 141");
			return new NativeMethods.FahrzeugAntriebCatcher(null); //Damit Zusi nicht völlig abstürzt.
		}
	}
}
#if NETCOREAPP
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
public abstract class FahrzeugAntriebBasisfunktion : NativeMethods.IFahrzeugAntrieb
{
	public static AntriebsmodellElektrischReihenschluss LoadAntrieb()
	{
		var fzgName = "Antrieb141.fzg";
		var fileCandidate = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
		if (!System.IO.File.Exists(System.IO.Path.Combine(fileCandidate, fzgName))) //wenn sich diese DLL im /lib-Unterordner befindet, wollen wir die .fzg-Datei auch finden.
			fileCandidate = System.IO.Path.Combine(fileCandidate, "..");
		return AntriebsmodellElektrischReihenschluss.GetAntriebeByFileName(System.IO.Path.Combine(fileCandidate, fzgName)).Single();
	}
	public FahrzeugAntriebBasisfunktion(int index)
	{
		this.VarianteAktuell = index;
		this.Antriebsdaten = LoadAntrieb();
		this.Nachlaufsteuerung = new Nachlaufsteuerung(this.Antriebsdaten.Nachlaufsteuerung);
	}
	public const int IdKnallenAusloesen = 19;
	public const int IdKnallenVollume = 20;
	public static string BedienungSpezielleWerteBeschreibung
	{
		get
		{
			return "Spezielle IDs:\r\n" +
							"Knallen auslösen (steht 1 Tick auf 1): " + IdKnallenAusloesen + "\r\n" +
							"Lautstärkte Knallen (steht bis zum nächsten auslösen auf diesem Wert): " + IdKnallenVollume;
		}
	}
	protected int VarianteAktuell {get; set;}
	protected float ZugkraftAktuell {get; set;}
	protected float RischaAktuell { get; set; }
	public Nachlaufsteuerung Nachlaufsteuerung {get; private set;}
	public AntriebsmodellElektrischReihenschluss Antriebsdaten {get; private set;}
	protected float Speed {get; set;}
	
	// Antriebstyp gemäß TCP-Doku 0002 000A 008E 0001 0025 0001
	public abstract byte Antriebstyp { get; }
	
	// ruft der Sim einmal pro Rechenschritt für das selbstgefahrene Fahrzeug und
	// für Fahrzeuge im Mehrfachtraktionsmodus im selbstgefahrenen Zug auf,
	// um die Führerstandsbedienung zu übertragen
	public virtual void Bedienung(NativeMethods.AntriebsRenderModus antriebsRenderModus, NativeMethods.ProtokollFstRef fstProtokoll, float sp)
	{
	}
	
	// ruft der Sim einmal pro Rechenschritt auf, dort sollen die Berechnungen erfolgen
	// wird nur für das selbstgefahrene Fahrzeug und für Fahrzeuge im
	// Mehrfachtraktionsmodus im selbstgefahrenen Zug aufgerufen
	public virtual void Berechnung(double deltaT, NativeMethods.AntriebsRenderModus antriebsRenderModus, NativeMethods.MehrfachtraktionsdatenRef mehrfachtraktionsdaten, bool HSGesperrt) 
	{ 
		Speed = mehrfachtraktionsdaten.Value.SpAkt;
		Nachlaufsteuerung.Berechnen(deltaT);
	}
	
	// das wird für Fahrzeuge einmal pro Rechenschritt aufgerufen, die im eigenen
	// Zug im Modus "eigener Tf" fahren und für alle Fahrzeuge im Autopilotmodus
	// es muss hier eine vereinfachte, schnelle Berechnung durchgeführt werden
	// komplexe Rechenmanöver können die Performance beeinflussen, da sehe viele Züge
	// mit diesem Modus im fahrplan unterwegs sein können
	public void VorspanntraktionsdatenSetzen(float sp, float sollLeistungNormiert)
	{
		ZugkraftAktuell = sollLeistungNormiert * Antriebsdaten.Kennfeld.KraftAblesen(System.Math.Abs(sp));
	}
	
	// wird vom Sim aufgerufen, wenn ein Nullstellungszwang durch das Antriebssystem
	// erfolgen muss
	public void Nullstellungszwang() { NullstellungszwangAktiv = true; }
	
	// Setter wird vom Sim aufgerufen, wenn sich der Modus des HS ändern soll
	// modus:  0: HS ein, 1: HS aus, 2: Spannung frisch an, 3: HS frisch ein
	public byte Hauptschalterzustand { get; set; }
	
	// die bei der angegebenen Geschwindigkeit max. mögliche Zugkraft
	public float GetMaxAntriebskraft(float sp) { return Antriebsdaten.Kennfeld.KraftAblesen(System.Math.Abs(sp)); }

	// div. Fahrgrößen
	public float Antriebskraft { get {return ZugkraftAktuell;} }
	public float AntriebskraftSollNormiert { get {return 0;} }
	public float Oberstrom { get; set; }
	public float Motorstrom { get; set; }
	public float Motorspannung { get; set; }
	public int Achsen { get {return Antriebsdaten.Antriebsachsen; } }
	public float SollZugkraft { get {return 0;} }
	public float Motordrehzahl { get {return 0;} }
	public float Motormoment { get {return 0;} }
	public float Motorlast { get {return 0;} }
	
	public float Fahrstufe { get {return Nachlaufsteuerung.Stufe;} }
	// C-Druck für Druckluftergänzungsbremse o.ä. soll Druckluft-Bremse überstimmen
	// -1 keine Vorgabe
	public float ZylSolldruck { get {return -1;} }
	public bool FahrstufeIst0 { get {return Nachlaufsteuerung.Stufe == 0; } }
	// muss true zurückgeben, wenn aus Sicht des Antriebsmodells ein
	// Nullstellungszwang aktiv ist
	public bool NullstellungszwangAktiv { get; set; }
	// muss true zurückgeben, wenn aus Sicht des Antriebsmodells eine
	// Hauptschalterauslösung nötig ist wegen unpassender Fahrleitungsspannung
	// Spannungsfaktor ist 1, wenn die Fahrleitungsspannung genau dem Sollwert entspricht
	public bool HasHauptschalterausloesungWgOLEnde(NativeMethods.Fahrleitungstyp fahrlTyp, float spannungsfaktor)
	{
		float unterpannungsausloesungFaktor = (10.0f / 15);
		return spannungsfaktor * fahrlTyp.GetSpannung() < unterpannungsausloesungFaktor * Antriebsdaten.Volt.GetSpannung();
	}
	// muss true zurückgeben, wenn aus Sicht des Antriebsmodells eine
	// Hauptschalterauslösung wg. Oberstromüberschreitung nötig ist.
	// für diesen Antrieb muss sich die dll selbst um die Auswirkungen kümmern
	// Die Funktion teilt das hier nur z.B. den anderen Einheiten einer Mehrfachtraktion mit
	private bool m_HasHauptschalterausloesungWgOberstrom = false;
	public bool HasHauptschalterausloesungWgOberstrom { get { return m_HasHauptschalterausloesungWgOberstrom; } set { m_HasHauptschalterausloesungWgOberstrom = true; Hauptschalterzustand = 1; } }
	//bool Hauptschalterstatus { get; } => Oben schon der Setter

	// Zustand angeben für Luftpresser-Steuerung
	public bool HauptschalterDrin { get {return Hauptschalterzustand == 1; } }
	public bool DieselmotorLaeuft { get {return true;} }
}
#if NETCOREAPP
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
public class FahrzeugAntrieb141 : FahrzeugAntriebBasisfunktion
{
	public FahrzeugAntrieb141(int index) : base(index) 
	{
		Rng = new Random();
	}
	public override byte Antriebstyp { get {return 1;} }
	public override void Bedienung(NativeMethods.AntriebsRenderModus antriebsRenderModus, NativeMethods.ProtokollFstRef fstProtokoll, float sp)
	{
		base.Bedienung(antriebsRenderModus, fstProtokoll, sp);
		if ((fstProtokoll.Value.Rischa.Funktion == KombiSchalterFunktion.RischaR) || (fstProtokoll.Value.Rischa.Funktion == KombiSchalterFunktion.RischaV))
		{
			if (VarianteAktuell == 0)
			{
				if (fstProtokoll.Value.SchaltstufenSteller.Funktion == KombiSchalterFunktion.FahrstufeX)
					Nachlaufsteuerung.Zielstufe = (int)System.Math.Round(fstProtokoll.Value.SchaltstufenSteller.Parameter * Nachlaufsteuerung.Daten.AnzahlSchaltstufen);
			}
			else if (VarianteAktuell == 1)
			{
				if (fstProtokoll.Value.ZugkraftSteller.Funktion == KombiSchalterFunktion.ZugkraftX)
					Nachlaufsteuerung.Zielstufe = (int)System.Math.Round(fstProtokoll.Value.ZugkraftSteller.Parameter * Nachlaufsteuerung.Daten.AnzahlSchaltstufen);
			}
		}
		else
			Nachlaufsteuerung.Zielstufe = 0;

		if      (fstProtokoll.Value.Hauptschalter.Funktion == KombiSchalterFunktion.HauptschalterAus)
			Hauptschalterzustand = 1;
		else if (fstProtokoll.Value.Hauptschalter.Funktion == KombiSchalterFunktion.HauptschalterEin)
		{
			// nur HS einlegen, wenn das Schaltwerk abgelaufen ist und der Fahrschalter in 0 steht
			if (VarianteAktuell == 0)
			{
				if ((Nachlaufsteuerung.Stufe == 0) && fstProtokoll.Value.IsSchaltstufenStellerZero())
					Hauptschalterzustand = 0;
			}
			else if (VarianteAktuell == 1)
			{
				if ((Nachlaufsteuerung.Stufe == 0) && fstProtokoll.Value.IsZugkraftStellerZero())
					Hauptschalterzustand = 0;
			}
		}

		if (HauptschalterDrin)
			HasHauptschalterausloesungWgOberstrom = false;

		RischaAktuell = fstProtokoll.Value.RichschaFaktor();

		BedienungKnallen(antriebsRenderModus, fstProtokoll, sp);
	}
	public override void Berechnung(double deltaT, NativeMethods.AntriebsRenderModus antriebsRenderModus, NativeMethods.MehrfachtraktionsdatenRef mehrfachtraktionsdaten, bool HSGesperrt) 
	{
		base.Berechnung(deltaT, antriebsRenderModus, mehrfachtraktionsdaten, HSGesperrt);
		if (Hauptschalterzustand == 0 && !NullstellungszwangAktiv && !HSGesperrt)
			ZugkraftAktuell = Antriebsdaten.Kennfeld.GetStufenWert(Nachlaufsteuerung.Stufe) * Antriebsdaten.Kennfeld.KraftAblesen(System.Math.Abs(Speed));
		else
			ZugkraftAktuell = 0;

		ReihenschlussHelper.AntriebsmodellElektrischReihenschluss.MotorenWerte werte;
		if (Antriebsdaten.Reihenschlussmotor != null)
			werte = Antriebsdaten.GetWerteReihenschluss(ZugkraftAktuell, GetMaxAntriebskraft(mehrfachtraktionsdaten.Value.SpAkt),
				mehrfachtraktionsdaten.Value.SpAkt, mehrfachtraktionsdaten.Value.StreckenStromsystem.GetSpannung() * mehrfachtraktionsdaten.Value.FahrlSpannungsfaktor);
		else if (Antriebsdaten.Drehstrommotor != null)
			werte = Antriebsdaten.GetWerteDrehstrom(ZugkraftAktuell, GetMaxAntriebskraft(mehrfachtraktionsdaten.Value.SpAkt),
				mehrfachtraktionsdaten.Value.SpAkt, mehrfachtraktionsdaten.Value.StreckenStromsystem.GetSpannung() * mehrfachtraktionsdaten.Value.FahrlSpannungsfaktor);
		else
			throw new System.ArgumentOutOfRangeException();

		Oberstrom = werte.IOber;
		Motorstrom = werte.IMotor;
		Motorspannung = werte.UMotor;

		if (FahrstufeIst0)
			NullstellungszwangAktiv = false;

		if (mehrfachtraktionsdaten.Value.DruckHL < 3.0f)
			NullstellungszwangAktiv = true; //Wird erst bei nächsten Berechnen aktiv.

		// Hauptschalterauslösung bei Oberstrom mit indirekter Mitauslösung des Hauptschalters (Beispiel für C#-Technik)
		if (Oberstrom > Antriebsdaten.MaxOberstrom)
			HasHauptschalterausloesungWgOberstrom = true;

		// Unterpannungsauslösung als Abfragefunktion mit Auslösung des HS durch die Berechnung (Beispiel für C#-Technik)
		if (HasHauptschalterausloesungWgOLEnde(mehrfachtraktionsdaten.Value.StreckenStromsystem, mehrfachtraktionsdaten.Value.FahrlSpannungsfaktor))
			Hauptschalterzustand = 1;
	}
	public Random Rng {get; private set;}
	private int OldFahrstuffe {get; set;}
	private float OldSoundVollume {get; set;}
	private void BedienungKnallen(NativeMethods.AntriebsRenderModus antriebsRenderModus, NativeMethods.ProtokollFstRef fstProtokoll, float sp)
	{
		if (OldFahrstuffe == Nachlaufsteuerung.Stufe)
		{
			fstProtokoll.Value.SetFtdIntern(IdKnallenAusloesen, (float)0);
			fstProtokoll.Value.SetFtdIntern(IdKnallenVollume, (float)OldSoundVollume);
		}
		else
		{
			float rnd = ((float)Rng.Next(2, 100)) / 100.0f;
			if (Oberstrom < 0.5f)
				rnd = 0.3f;
			fstProtokoll.Value.SetFtdIntern(IdKnallenAusloesen, (float)1);
			fstProtokoll.Value.SetFtdIntern(IdKnallenVollume, (float)rnd);
			OldFahrstuffe = Nachlaufsteuerung.Stufe;
			OldSoundVollume = rnd;
		}
	}

}
public class Nachlaufsteuerung
{
	public Nachlaufsteuerung(AntriebsmodellElektrischReihenschluss.NachlaufsteuerungTyp daten)
	{
		Daten = daten;
	}
	public void Berechnen(double deltaT)
	{
		if (m_StufeAktuellFloat < Zielstufe)
		{
			m_StufeAktuellFloat = System.Math.Min(m_StufeAktuellFloat + deltaT * Daten.GeschwAuf, Zielstufe);
		}
		else if (m_StufeAktuellFloat > Zielstufe)
		{
			m_StufeAktuellFloat = System.Math.Max(m_StufeAktuellFloat - deltaT * Daten.GeschwAb, Zielstufe);
		}
	}
	public int Stufe { get { return (int)System.Math.Round(m_StufeAktuellFloat); } }
	public int Zielstufe { get; set; }
	public AntriebsmodellElektrischReihenschluss.NachlaufsteuerungTyp Daten { get; private set; }
	private double m_StufeAktuellFloat = 0;
}
public static class ProtokollFstExtMethods
{
	public static bool IsSchaltstufenStellerZero(this NativeMethods.ProtokollFst fstProtokoll)
	{
		return ((fstProtokoll.SchaltstufenSteller.Funktion == KombiSchalterFunktion.FahrstufeSchnellAus) ||
				((fstProtokoll.SchaltstufenSteller.Funktion == KombiSchalterFunktion.FahrstufeX) && (fstProtokoll.SchaltstufenSteller.Parameter == 0)));
	}
	public static bool IsZugkraftStellerZero(this NativeMethods.ProtokollFst fstProtokoll)
	{
		return ((fstProtokoll.ZugkraftSteller.Funktion == KombiSchalterFunktion.ZugkraftSchnellAus) ||
				((fstProtokoll.ZugkraftSteller.Funktion == KombiSchalterFunktion.ZugkraftX) && (fstProtokoll.ZugkraftSteller.Parameter == 0)));
	}
	public static float RichschaFaktor(this NativeMethods.ProtokollFst fstProtokoll)
	{
		if (fstProtokoll.Rischa.Funktion == KombiSchalterFunktion.RischaV)
			return 1;
		if (fstProtokoll.Rischa.Funktion == KombiSchalterFunktion.RischaR)
			return -1;
		return 0;
	}

}

#if NETCOREAPP
#else
}
#endif
