import { JavaObject, cast_java_lang_Object } from '../../java/lang/JavaObject';
import { KursblockungInputKurs, cast_de_nrw_schule_svws_core_data_kursblockung_KursblockungInputKurs } from '../../core/data/kursblockung/KursblockungInputKurs';
import { JavaInteger, cast_java_lang_Integer } from '../../java/lang/JavaInteger';
import { KursblockungDynFachart, cast_de_nrw_schule_svws_core_kursblockung_KursblockungDynFachart } from '../../core/kursblockung/KursblockungDynFachart';
import { KursblockungOutputKursZuSchiene, cast_de_nrw_schule_svws_core_data_kursblockung_KursblockungOutputKursZuSchiene } from '../../core/data/kursblockung/KursblockungOutputKursZuSchiene';
import { LinkedCollection, cast_de_nrw_schule_svws_core_adt_collection_LinkedCollection } from '../../core/adt/collection/LinkedCollection';
import { KursblockungDynSchiene, cast_de_nrw_schule_svws_core_kursblockung_KursblockungDynSchiene } from '../../core/kursblockung/KursblockungDynSchiene';
import { JavaString, cast_java_lang_String } from '../../java/lang/JavaString';
import { Logger, cast_de_nrw_schule_svws_logger_Logger } from '../../logger/Logger';
import { Vector, cast_java_util_Vector } from '../../java/util/Vector';
import { System, cast_java_lang_System } from '../../java/lang/System';
import { LogLevel, cast_de_nrw_schule_svws_logger_LogLevel } from '../../logger/LogLevel';

export class KursblockungDynKurs extends JavaObject {

	private readonly guiID : number;

	private readonly representation : String;

	private readonly fachart : KursblockungDynFachart;

	private readonly schienenLage : Array<KursblockungDynSchiene>;

	private readonly schienenLageSaveS : Array<KursblockungDynSchiene>;

	private readonly schienenLageSaveK : Array<KursblockungDynSchiene>;

	private readonly schienenLageFixiert : number;

	private readonly schienenFrei : Array<KursblockungDynSchiene>;

	private readonly schienenFreiSaveS : Array<KursblockungDynSchiene>;

	private readonly schienenFreiSaveK : Array<KursblockungDynSchiene>;

	private schuelerAnz : number = 0;

	private readonly logger : Logger;


	/**
	 * Der Kurs wählt eine zufällige Schienenlage und fügt sich selbst den
	 * entsprechenden Schienen hinzu.
	 * 
	 * @param pSchienenLage        Ein Array aller Schienen, in denen der Kurs
	 *                             gerade liegt.
	 * @param pSchienenLageFixiert Anzahl an Schienen in {@code pSchienenLage}, die
	 *                             fixiert ist.
	 * @param pSchienenFrei        Ein Array aller Schienen, in die der Kurs
	 *                             wechseln könnte.
	 * @param iKurs                Alle Informationen zu diesem Kurs. Das Objekt
	 *                             stammt von der GUI.
	 * @param pFachart             Die zu diesem Kurs zugehörige Fachart.
	 * @param pLogger              Logger für Benutzerhinweise, Warnungen und
	 *                             Fehler.
	 */
	public constructor(pSchienenLage : Array<KursblockungDynSchiene>, pSchienenLageFixiert : number, pSchienenFrei : Array<KursblockungDynSchiene>, iKurs : KursblockungInputKurs, pFachart : KursblockungDynFachart, pLogger : Logger) {
		super();
		this.schienenLage = pSchienenLage;
		this.schienenLageFixiert = pSchienenLageFixiert;
		this.schienenFrei = pSchienenFrei;
		this.guiID = iKurs.id;
		this.representation = iKurs.representation;
		this.fachart = pFachart;
		this.schuelerAnz = 0;
		this.logger = pLogger;
		this.schienenLageSaveS = Array(this.schienenLage.length).fill(null);
		this.schienenLageSaveK = Array(this.schienenLage.length).fill(null);
		for (let i : number = 0; i < this.schienenLage.length; i++){
			this.schienenLageSaveS[i] = this.schienenLage[i];
			this.schienenLageSaveK[i] = this.schienenLage[i];
		}
		this.schienenFreiSaveS = Array(this.schienenFrei.length).fill(null);
		this.schienenFreiSaveK = Array(this.schienenFrei.length).fill(null);
		for (let i : number = 0; i < this.schienenFrei.length; i++){
			this.schienenFreiSaveS[i] = this.schienenFrei[i];
			this.schienenFreiSaveK[i] = this.schienenFrei[i];
		}
		for (let i : number = 0; i < this.schienenLage.length; i++){
			this.schienenLage[i].aktionKursHinzufuegen(this);
		}
	}

	public toString() : String {
		return this.representation;
	}

	/**
	 * Die Kurs-ID der GUI.
	 * 
	 * @return Die Kurs-ID der GUI.
	 */
	public gibID() : number {
		return this.guiID;
	}

	/**
	 * Eine String-Darstellung des Kurses, z.B. 'D;GK1'.
	 * 
	 * @return Eine String-Darstellung des Kurses, z.B. 'D;GK1'.
	 */
	public gibRepresentation() : String {
		return this.representation;
	}

	/**
	 * Liefert die zum Kurs zugehörige Fachart.
	 * 
	 * @return Die zum Kurs zugehörige Fachart.
	 */
	public gibFachart() : KursblockungDynFachart {
		return this.fachart;
	}

	/**
	 * Liefert die aktuelle Anzahl an Schülern in diesem Kurs.
	 * 
	 * @return Die aktuelle Anzahl an Schülern in diesem Kurs.
	 */
	public gibSchuelerAnzahl() : number {
		return this.schuelerAnz;
	}

	/**
	 * Liefert {@code true} falls die Schienenlage des Kurses noch veränderbar ist.
	 * Wenn der Kurs komplett fixiert ist, oder so viele Schienen gesperrt sind,
	 * dass der Kurs keine Wahlmöglichkeit bezüglich seiner Schienen hat, dann ist
	 * das Resultat {@code false}
	 * 
	 * @return Liefert {@code true}, falls die Schienenlage des Kurses noch
	 *         veränderbar ist.
	 */
	public gibHatFreiheitsgrade() : boolean {
		return (this.schienenLageFixiert < this.schienenLage.length) && (this.schienenFrei.length > 0);
	}

	/**
	 * Liefert die aktuelle Schienenlage dieses Kurses.
	 * 
	 * @return Ein Array, das angibt, in welchen Schienen der Kurs ist. Die Werte
	 *         sind 0-indiziert.
	 */
	public gibSchienenLage() : Array<number> {
		let length : number = this.schienenLage.length;
		let lage : Array<number> = Array(length).fill(0);
		for (let i : number = 0; i < length; i++){
			lage[i] = this.schienenLage[i].gibNr();
		}
		return lage;
	}

	/**
	 * Liefert die Anzahl an Schienen, die dieser Kurs belegt.
	 * 
	 * @return Die Anzahl an Schienen, die dieser Kurs belegt.
	 */
	public gibSchienenAnzahl() : number {
		return this.schienenLage.length;
	}

	/**
	 * Speichert die aktuelle Lage der Schienen im Zustand S, um diese bei Bedarf
	 * mit der Methode {@link #aktionZustandLadenS} zu laden.
	 */
	public aktionZustandSpeichernS() : void {
		System.arraycopy(this.schienenLage, 0, this.schienenLageSaveS, 0, this.schienenLage.length);
		System.arraycopy(this.schienenFrei, 0, this.schienenFreiSaveS, 0, this.schienenFrei.length);
	}

	/**
	 * Speichert die aktuelle Lage der Schienen im Zustand K, um diese bei Bedarf
	 * mit der Methode {@link #aktionZustandLadenK} zu laden.
	 */
	public aktionZustandSpeichernK() : void {
		System.arraycopy(this.schienenLage, 0, this.schienenLageSaveK, 0, this.schienenLage.length);
		System.arraycopy(this.schienenFrei, 0, this.schienenFreiSaveK, 0, this.schienenFrei.length);
	}

	/**
	 * Lädt die zuvor mit der Methode {@link #aktionZustandSpeichernS} gespeicherte
	 * Lage der Schienen.
	 */
	public aktionZustandLadenS() : void {
		this.aktionSchienenLageEntfernen();
		System.arraycopy(this.schienenLageSaveS, 0, this.schienenLage, 0, this.schienenLage.length);
		System.arraycopy(this.schienenFreiSaveS, 0, this.schienenFrei, 0, this.schienenFrei.length);
		this.aktionSchienenLageHinzufuegen();
	}

	/**
	 * Lädt die zuvor mit der Methode {@link #aktionZustandSpeichernK} gespeicherte
	 * Lage der Schienen.
	 */
	public aktionZustandLadenK() : void {
		this.aktionSchienenLageEntfernen();
		System.arraycopy(this.schienenLageSaveK, 0, this.schienenLage, 0, this.schienenLage.length);
		System.arraycopy(this.schienenFreiSaveK, 0, this.schienenFrei, 0, this.schienenFrei.length);
		this.aktionSchienenLageHinzufuegen();
	}

	/**
	 * Verteilt den Kurs auf die Schienen zufällig.
	 */
	public aktionZufaelligVerteilen() : void {
		if (!this.gibHatFreiheitsgrade()) {
			return;
		}
		if (this.schuelerAnz > 0) {
			this.logger.log(LogLevel.ERROR, "Kurs.aktionZufaelligVerteilen: schuelerAnz > 0 (Ein Kurs mit SuS darf nicht verteilt werden)");
			return;
		}
		for (let i1 : number = this.schienenLageFixiert; i1 < this.schienenLage.length; i1++){
			let i2 : number = (Math.random() * this.schienenFrei.length) as number;
			let schiene1 : KursblockungDynSchiene = this.schienenLage[i1];
			let schiene2 : KursblockungDynSchiene = this.schienenFrei[i2];
			schiene1.aktionKursEntfernen(this);
			schiene2.aktionKursHinzufuegen(this);
			this.schienenLage[i1] = schiene2;
			this.schienenFrei[i2] = schiene1;
		}
	}

	/**
	 * Setzt die Lage des Kurses auf die in der Liste übergebenen Schienen.
	 * 
	 * @param pSchienenWahl Die Schienen (0-indiziert), in denen der Kurs liegen
	 *                      soll.
	 */
	public aktionVerteileAufSchienen(pSchienenWahl : LinkedCollection<Number>) : void {
		for (let iLage : number = this.schienenLageFixiert; iLage < this.schienenLage.length; iLage++){
			let schieneL : KursblockungDynSchiene = this.schienenLage[iLage];
			if (pSchienenWahl.contains(schieneL.gibNr())) {
				continue;
			}
			for (let iFrei : number = 0; iFrei < this.schienenFrei.length; iFrei++){
				let schieneF : KursblockungDynSchiene = this.schienenFrei[iFrei];
				if (pSchienenWahl.contains(schieneF.gibNr())) {
					schieneL.aktionKursEntfernen(this);
					schieneF.aktionKursHinzufuegen(this);
					this.schienenFrei[iFrei] = schieneL;
					this.schienenLage[iLage] = schieneF;
					break;
				}
			}
		}
	}

	/**
	 * Speichert die aktuelle Kurs-Schiene-Zuordnung im Vector
	 * {@code vKursZuSchiene}.
	 * 
	 * @param vKursZuSchiene Diesem Vektor wird die aktuelle Kurszuordnung
	 *                       hinzugefügt.
	 */
	public aktionOutputErzeugen(vKursZuSchiene : Vector<KursblockungOutputKursZuSchiene>) : void {
		let kursZuSchiene : KursblockungOutputKursZuSchiene = new KursblockungOutputKursZuSchiene();
		kursZuSchiene.kurs = this.guiID;
		kursZuSchiene.schienen = this.gibSchienenLage();
		vKursZuSchiene.add(kursZuSchiene);
	}

	/**
	 * Entfernt einen Schüler aus diesem Kurs.
	 */
	public aktionSchuelerEntfernen() : void {
		this.fachart.aktionKursdifferenzEntfernen();
		this.schuelerAnz--;
		this.fachart.aktionSchuelerWurdeEntfernt();
		this.fachart.aktionKursdifferenzHinzufuegen();
	}

	/**
	 * Fügt einen Schüler diesem Kurs hinzu.
	 */
	public aktionSchuelerHinzufügen() : void {
		this.fachart.aktionKursdifferenzEntfernen();
		this.schuelerAnz++;
		this.fachart.aktionSchuelerWurdeHinzugefuegt();
		this.fachart.aktionKursdifferenzHinzufuegen();
	}

	/**
	 * Liefert TRUE, wenn die Schiene für den Kurs gesperrt wurde.
	 * 
	 * @param pSchiene Die Schiene nach der gefragt wurde.
	 * 
	 * @return TRUE, wenn die Schiene für den Kurs gesperrt wurde.
	 */
	public gibIstSchieneGesperrt(pSchiene : number) : boolean {
		for (let s of this.schienenLage) {
			if (s.gibNr() === pSchiene) {
				return false;
			}
		}
		for (let s of this.schienenFrei) {
			if (s.gibNr() === pSchiene) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Liefert TRUE, wenn die Schiene für den Kurs fixiert wurde.
	 * 
	 * @param pSchiene Die Schiene nach der gefragt wurde.
	 * 
	 * @return TRUE, wenn die Schiene für den Kurs fixiert wurde.
	 */
	public gibIstSchieneFixiert(pSchiene : number) : boolean {
		for (let iLage : number = 0; iLage < this.schienenLageFixiert; iLage++){
			if (this.schienenLage[iLage].gibNr() === pSchiene) {
				return true;
			}
		}
		return false;
	}

	private aktionSchienenLageHinzufuegen() : void {
		for (let i : number = 0; i < this.schienenLage.length; i++){
			this.schienenLage[i].aktionKursHinzufuegen(this);
		}
	}

	private aktionSchienenLageEntfernen() : void {
		for (let i : number = 0; i < this.schienenLage.length; i++){
			this.schienenLage[i].aktionKursEntfernen(this);
		}
	}

	isTranspiledInstanceOf(name : string): boolean {
		return ['de.nrw.schule.svws.core.kursblockung.KursblockungDynKurs'].includes(name);
	}

}

export function cast_de_nrw_schule_svws_core_kursblockung_KursblockungDynKurs(obj : unknown) : KursblockungDynKurs {
	return obj as KursblockungDynKurs;
}
