import { JavaObject, cast_java_lang_Object } from '../../java/lang/JavaObject';
import { KursblockungDynFachart, cast_de_nrw_schule_svws_core_kursblockung_KursblockungDynFachart } from '../../core/kursblockung/KursblockungDynFachart';
import { KursblockungDynStatistik, cast_de_nrw_schule_svws_core_kursblockung_KursblockungDynStatistik } from '../../core/kursblockung/KursblockungDynStatistik';
import { KursblockungStatic, cast_de_nrw_schule_svws_core_kursblockung_KursblockungStatic } from '../../core/kursblockung/KursblockungStatic';
import { KursblockungMatrix, cast_de_nrw_schule_svws_core_kursblockung_KursblockungMatrix } from '../../core/kursblockung/KursblockungMatrix';
import { KursblockungInputSchueler, cast_de_nrw_schule_svws_core_data_kursblockung_KursblockungInputSchueler } from '../../core/data/kursblockung/KursblockungInputSchueler';
import { KursblockungDynKurs, cast_de_nrw_schule_svws_core_kursblockung_KursblockungDynKurs } from '../../core/kursblockung/KursblockungDynKurs';
import { JavaString, cast_java_lang_String } from '../../java/lang/JavaString';
import { Vector, cast_java_util_Vector } from '../../java/util/Vector';
import { System, cast_java_lang_System } from '../../java/lang/System';
import { KursblockungOutputFachwahlZuKurs, cast_de_nrw_schule_svws_core_data_kursblockung_KursblockungOutputFachwahlZuKurs } from '../../core/data/kursblockung/KursblockungOutputFachwahlZuKurs';

export class KursblockungDynSchueler extends JavaObject {

	private readonly representation : String;

	private fachartArr : Array<KursblockungDynFachart>;

	private fachartZuGUI : Array<number>;

	private fachartZuKurs : Array<KursblockungDynKurs | null>;

	private fachartZuKursSaveS : Array<KursblockungDynKurs | null>;

	private fachartZuKursSaveK : Array<KursblockungDynKurs | null>;

	private readonly statistik : KursblockungDynStatistik;

	private nichtwahlen : number = 0;

	private readonly schieneBelegt : Array<boolean>;

	private static dummy : KursblockungMatrix = new KursblockungMatrix(0, 0);

	private matrix : KursblockungMatrix;


	/**
	 * Im Konstruktor wird {@code pSchueler} in ein Objekt dieser Klasse
	 * umgewandelt.
	 * 
	 * @param pStatistik      Referenz um die Nichtwahlen mitzuteilen.
	 * @param pSchueler       Die Schüler-Daten von der GUI/DB.
	 * @param pSchienenAnzahl Wir benötigt, um {@link #schieneBelegt} zu
	 *                        initialisieren.
	 */
	constructor(pSchueler : KursblockungInputSchueler, pStatistik : KursblockungDynStatistik, pSchienenAnzahl : number) {
		super();
		this.representation = pSchueler.representation;
		this.statistik = pStatistik;
		this.fachartArr = Array(0).fill(null);
		this.fachartZuGUI = Array(0).fill(0);
		this.fachartZuKurs = Array(0).fill(null);
		this.fachartZuKursSaveS = Array(0).fill(null);
		this.fachartZuKursSaveK = Array(0).fill(null);
		this.nichtwahlen = 0;
		this.schieneBelegt = Array(pSchienenAnzahl).fill(false);
		this.matrix = KursblockungDynSchueler.dummy;
	}

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

	/**
	 * Eine String-Darstellung des Schülers. Beinhaltet meistens den Vornamen, den
	 * Nachnamen, das Geburtsdatum und das Geschlecht.
	 * 
	 * @return Eine String-Darstellung des Schülers.
	 */
	gibRepresentation() : String {
		return this.representation;
	}

	/**
	 * Liefert die aktuelle Anzahl an Nichtwahlen.
	 * 
	 * @return Die aktuelle Anzahl an Nichtwahlen.
	 */
	gibNichtwahlen() : number {
		return this.nichtwahlen;
	}

	/**
	 * Liefert ein Array aller Facherten (= Fachwahlen) des Schülers.
	 * 
	 * @return Ein Array aller Facherten (= Fachwahlen) des Schülers.
	 */
	gibFacharten() : Array<KursblockungDynFachart> {
		return this.fachartArr;
	}

	/**
	 * Setzt alle Facharten (=Fachwahlen) des Schülers.
	 * 
	 * @param pFacharten Die Facharten des Schülers.
	 * @param pIDs       Die zur Fachwahl zugehörige ID der GUI bzw. Datenbank.
	 */
	aktionSetzeFachartenUndIDs(pFacharten : Array<KursblockungDynFachart>, pIDs : Array<number>) : void {
		let nFacharten : number = pFacharten.length;
		this.fachartArr = pFacharten;
		this.fachartZuGUI = pIDs;
		this.fachartZuKurs = Array(nFacharten).fill(null);
		this.fachartZuKursSaveS = Array(nFacharten).fill(null);
		this.fachartZuKursSaveK = Array(nFacharten).fill(null);
		this.statistik.aktionNichtwahlenVeraendern(nFacharten);
		this.nichtwahlen = nFacharten;
		for (let i : number = 1; i < nFacharten; i++){
			for (let j : number = i; j >= 1; j--){
				let anzL : number = this.fachartArr[j - 1].gibKurseMax();
				let anzR : number = this.fachartArr[j].gibKurseMax();
				if (anzL > anzR) {
					let fL : KursblockungDynFachart = this.fachartArr[j - 1];
					let fR : KursblockungDynFachart = this.fachartArr[j];
					let valL : number = this.fachartZuGUI[j - 1];
					let valR : number = this.fachartZuGUI[j];
					this.fachartArr[j - 1] = fR;
					this.fachartArr[j] = fL;
					this.fachartZuGUI[j - 1] = valR;
					this.fachartZuGUI[j] = valL;
				}
			}
		}
		this.matrix = new KursblockungMatrix(nFacharten, this.schieneBelegt.length);
	}

	/**
	 * Speichert die aktuell belegten Kurse im Zustand S.
	 */
	aktionZustandSpeichernS() : void {
		System.arraycopy(this.fachartZuKurs, 0, this.fachartZuKursSaveS, 0, this.fachartZuKurs.length);
	}

	/**
	 * Speichert die aktuell belegten Kurse im Zustand K.
	 */
	aktionZustandSpeichernK() : void {
		System.arraycopy(this.fachartZuKurs, 0, this.fachartZuKursSaveK, 0, this.fachartZuKurs.length);
	}

	/**
	 * Entfernt zunächst den Schüler aus seinen aktuellen Kursen und setzt ihn dann
	 * in die Kurse, die zuvor im Zustand S gespeichert wurden.
	 */
	aktionZustandLadenS() : void {
		this.aktionWaehleKurse(this.fachartZuKursSaveS);
	}

	/**
	 * Entfernt zunächst den Schüler aus seinen aktuellen Kursen und setzt ihn dann
	 * in die Kurse, die zuvor im Zustand K gespeichert wurden.
	 */
	aktionZustandLadenK() : void {
		this.aktionWaehleKurse(this.fachartZuKursSaveK);
	}

	private aktionWaehleKurse(wahl : Array<KursblockungDynKurs | null>) : void {
		this.aktionKurseAlleEntfernen();
		for (let i : number = 0; i < this.fachartZuKurs.length; i++){
			let kurs : KursblockungDynKurs | null = wahl[i];
			if (kurs !== null) {
				this.aktionKursHinzufuegen(i, kurs);
			}
		}
	}

	/**
	 * Entfernt den Schüler aus seinen aktuell zugeordneten Kursen.
	 */
	aktionKurseAlleEntfernen() : void {
		for (let i : number = 0; i < this.fachartArr.length; i++){
			let kurs : KursblockungDynKurs | null = this.fachartZuKurs[i];
			if (kurs !== null) {
				this.aktionKursEntfernen(i, kurs);
			}
		}
	}

	/**
	 * Geht die Facharten durch (Facharten mit einer kleineren Kursanzahl zuerst)
	 * und geht dann pro Fachart alle Kurse durch (Kurse mit kleinerer Schüleranzahl
	 * zuerst). Falls der Kurs wählbar ist, wird der Schüler hinzugefügt und es geht
	 * weiter mit der nächsten Fachart. Ein Kurs ist wählbar, wenn nicht bereits ein
	 * Kurs zugeordnet wurde und die Schienen in den der Kurs sind frei sind.<br>
	 * 
	 * Falls der Paramter {@code pNurMultikurse} TRUE ist, dann werden nur
	 * Multikurse verteilt.
	 * 
	 * @param pNurMultikurse Falls TRUE ist, dann werden nur Multikurse verteilt.
	 */
	aktionKurseZufaelligVerteilen(pNurMultikurse : boolean) : void {
		let perm : Array<number> = KursblockungStatic.gibPermutation(this.fachartArr.length);
		for (let p : number = 0; p < this.fachartArr.length; p++){
			let i : number = perm[p];
			if (this.fachartZuKurs[i] !== null) {
				continue;
			}
			let fachart : KursblockungDynFachart = this.fachartArr[i];
			if (pNurMultikurse) {
				if (!fachart.gibHatMultikurs()) {
					continue;
				}
			}
			let kurse : Array<KursblockungDynKurs> = fachart.gibKurse();
			let perm2 : Array<number> = KursblockungStatic.gibPermutation(kurse.length);
			for (let p2 : number = 0; p2 < perm2.length; p2++){
				let i2 : number = p2;
				let kurs : KursblockungDynKurs = kurse[i2];
				let waehlbar : boolean = true;
				for (let nr of kurs.gibSchienenLage()) {
					if (this.schieneBelegt[nr]) {
						waehlbar = false;
					}
				}
				if (waehlbar) {
					this.aktionKursHinzufuegen(i, kurs);
					break;
				}
			}
		}
	}

	/**
	 * Verteilt alle Kurse die über genau 1 Schiene gehen mit Hilfe eines Matching
	 * Algorithmus.
	 */
	aktionKurseMitBipartiteMatchingVerteilen() : void {
		let data : Array<Array<number>> = this.matrix.getMatrix();
		for (let r : number = 0; r < this.fachartArr.length; r++){
			for (let c : number = 0; c < this.schieneBelegt.length; c++){
				data[r][c] = 0;
			}
			if (this.fachartZuKurs[r] !== null) {
				continue;
			}
			if (this.fachartArr[r].gibHatMultikurs()) {
				continue;
			}
			for (let kurs of this.fachartArr[r].gibKurse()) {
				for (let nr of kurs.gibSchienenLage()) {
					if (!this.schieneBelegt[nr]) {
						data[r][nr] = 1;
					}
				}
			}
		}
		let r2c : Array<number> = this.matrix.gibMaximalesBipartitesMatching(true);
		for (let r : number = 0; r < this.fachartArr.length; r++){
			if (this.fachartZuKurs[r] !== null) {
				continue;
			}
			let c : number = r2c[r];
			if (c === -1) {
				continue;
			}
			let kursGefunden : KursblockungDynKurs | null = null;
			for (let kurs of this.fachartArr[r].gibKurse()) {
				for (let nr of kurs.gibSchienenLage()) {
					if ((nr === c) && (kursGefunden === null)) {
						kursGefunden = kurs;
					}
				}
			}
			if (kursGefunden !== null) 
				this.aktionKursHinzufuegen(r, kursGefunden);
		}
	}

	/**
	 * Erzeugt pro Fachwahl ein Objekt des Typs
	 * {@link KursblockungOutputFachwahlZuKurs} und fügt es dem Vector
	 * {@code vFachwahlZuKurs} hinzu. Die GUI kann daraus die
	 * Schüler-Zu-Kurs-Zuordnungen rekonstruiern.
	 * 
	 * @param vFachwahlZuKurs Fügt diesem Vector pro Fachwahl ein Objekt des Typs
	 *                        {@link KursblockungOutputFachwahlZuKurs} hinzu.
	 */
	aktionOutputsErzeugen(vFachwahlZuKurs : Vector<KursblockungOutputFachwahlZuKurs>) : void {
		for (let i : number = 0; i < this.fachartArr.length; i++){
			let fachwahlZuKurs : KursblockungOutputFachwahlZuKurs = new KursblockungOutputFachwahlZuKurs();
			fachwahlZuKurs.fachwahl = this.fachartZuGUI[i];
			let tmpKurs : KursblockungDynKurs | null = this.fachartZuKurs[i];
			fachwahlZuKurs.kurs = (tmpKurs === null) ? -1 : tmpKurs.gibID();
			vFachwahlZuKurs.add(fachwahlZuKurs);
		}
	}

	/**
	 * Liefert TRUE, falls der Schüler mindestens einen Multikurs hat. Ein Multikurs
	 * ist ein Kurs, der über mehr als eine Schiene geht.
	 * 
	 * @return TRUE, falls der Schüler mindestens einen Multikurs hat.
	 */
	gibHatMultikurs() : boolean {
		for (let fachart of this.fachartArr) {
			if (fachart.gibHatMultikurs()) {
				return true;
			}
		}
		return false;
	}

	private aktionKursHinzufuegen(fachartIndex : number, kurs : KursblockungDynKurs) : void {
		kurs.aktionSchuelerHinzufügen();
		this.statistik.aktionNichtwahlenVeraendern(-1);
		this.nichtwahlen--;
		for (let nr of kurs.gibSchienenLage()) {
			if (this.schieneBelegt[nr]) {
				console.log(JSON.stringify("FEHLER: Schienen-Doppelbelegung! " + this.representation.valueOf()));
			}
			this.schieneBelegt[nr] = true;
		}
		this.fachartZuKurs[fachartIndex] = kurs;
	}

	private aktionKursEntfernen(fachartIndex : number, kurs : KursblockungDynKurs) : void {
		kurs.aktionSchuelerEntfernen();
		this.statistik.aktionNichtwahlenVeraendern(+1);
		this.nichtwahlen++;
		for (let nr of kurs.gibSchienenLage()) {
			if (!this.schieneBelegt[nr]) {
				console.log(JSON.stringify("FEHLER: Kurs ist gar nicht in Schiene ! " + this.representation.valueOf()));
			}
			this.schieneBelegt[nr] = false;
		}
		this.fachartZuKurs[fachartIndex] = null;
	}

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

}

export function cast_de_nrw_schule_svws_core_kursblockung_KursblockungDynSchueler(obj : unknown) : KursblockungDynSchueler {
	return obj as KursblockungDynSchueler;
}
