import { MusicSheet } from "../MusicSheet";
import { PartListEntry } from "../MusicSource/PartListEntry";
import { Repetition } from "../MusicSource/Repetition";
import { Fraction } from "../../Common/DataObjects/Fraction";
import { MusicPartManagerIterator } from "./MusicPartManagerIterator";

export class MusicPartManager /*implements ISelectionListener*/ {
    constructor(musicSheet: MusicSheet) {
        this.musicSheet = musicSheet;
    }
    private parts: PartListEntry[];
    private timestamps: TimestampTransform[];
    private musicSheet: MusicSheet;
    private sheetStart: Fraction;
    private sheetEnd: Fraction;

    /**
     * This method is called from CoreContainer when the user changes a Repetitions's userNumberOfRepetitions.
     */
    public reInit(): void {
        this.init();
    }

    /**
     * Main initialize method for MusicPartManager.
     */
    public init(): void {
        this.parts = this.musicSheet.Repetitions.slice(); // slice=arrayCopy
        this.sheetStart = this.musicSheet.SelectionStart = new Fraction(0, 1);
        this.sheetEnd = this.musicSheet.SelectionEnd = this.musicSheet.SheetEndTimestamp;
        this.calcMapping();
    }
    public getCurrentRepetitionTimestampTransform(curEnrolledTimestamp: Fraction): TimestampTransform {
        let curTransform: TimestampTransform = undefined;
        for (let i: number = this.timestamps.length - 1; i >= 0; i--) {
            curTransform = this.timestamps[i];
            if (curEnrolledTimestamp.gte(curTransform.from)) {
                return curTransform;
            }
        }
        return this.timestamps[0];
    }
    public absoluteEnrolledToSheetTimestamp(timestamp: Fraction): Fraction {
        if (this.timestamps.length === 0) {
            return timestamp;
        }
        const transform: TimestampTransform = this.getCurrentRepetitionTimestampTransform(timestamp);
        return Fraction.plus(timestamp, Fraction.minus(transform.to, transform.from)); // FIXME
    }
    public get Parts(): PartListEntry[] {
        return this.parts;
    }
    public get MusicSheet(): MusicSheet {
        return this.musicSheet;
    }
    public getIterator(start?: Fraction): MusicPartManagerIterator {
        if (!start) {
            return new MusicPartManagerIterator(this.musicSheet, this.musicSheet.SelectionStart, this.musicSheet.SelectionEnd);
        }
        return new MusicPartManagerIterator(this.musicSheet, start, undefined);
    }
    public setSelectionStart(beginning: Fraction): void {
        this.musicSheet.SelectionStart = beginning;
        this.musicSheet.SelectionEnd = undefined;
    }
    public setSelectionRange(start: Fraction, end: Fraction): void {
        this.musicSheet.SelectionStart = start ?? this.sheetStart;
        this.musicSheet.SelectionEnd = end ?? this.sheetEnd;
    }
    private calcMapping(): void {
        this.timestamps = [];
        const iterator: MusicPartManagerIterator = this.getIterator();
        let currentRepetition: Repetition = iterator.CurrentRepetition;
        let curTimestampTransform: TimestampTransform = new TimestampTransform(
            iterator.CurrentEnrolledTimestamp.clone(),
            iterator.CurrentSourceTimestamp.clone(),
            undefined,
            0
        );
        this.timestamps.push(curTimestampTransform);
        while (!iterator.EndReached) {
            if (iterator.JumpOccurred || currentRepetition !== iterator.CurrentRepetition) {
                currentRepetition = iterator.CurrentRepetition;
                // if we are still in the same repetition but in a different repetition run, we remember
                // that we have to jump backwards at this position
                if (iterator.backJumpOccurred) {
                    const jumpRep: Repetition = iterator.JumpResponsibleRepetition;
                    curTimestampTransform.nextBackJump = iterator.CurrentEnrolledTimestamp;
                    curTimestampTransform.curRepetition = jumpRep;
                    curTimestampTransform.curRepetitionIteration = iterator.CurrentJumpResponsibleRepetitionIterationBeforeJump;
                    for (let i: number = this.timestamps.length - 2; i >= 0; i--) {
                        if (jumpRep.AbsoluteTimestamp.gt(this.timestamps[i].to) || this.timestamps[i].curRepetition) {
                            break;
                        }
                        this.timestamps[i].nextBackJump = curTimestampTransform.nextBackJump;
                        this.timestamps[i].curRepetition = jumpRep;
                        this.timestamps[i].curRepetitionIteration = curTimestampTransform.curRepetitionIteration;
                    }
                }
                curTimestampTransform = new TimestampTransform(
                    iterator.CurrentEnrolledTimestamp.clone(),
                    iterator.CurrentSourceTimestamp.clone(),
                    undefined,
                    0
                );
                this.timestamps.push(curTimestampTransform);
            }
            iterator.moveToNext();
        }
    }
}


export class TimestampTransform {
    constructor(sourceTimestamp: Fraction, enrolledTimestamp: Fraction, repetition: Repetition, curRepetitionIteration: number) {
        this.from = sourceTimestamp;
        this.to = enrolledTimestamp;
        this.curRepetition = repetition;
        this.curRepetitionIteration = curRepetitionIteration;
        this.nextBackJump = undefined;
        this.nextForwardJump = undefined;
    }
    public from: Fraction;
    public to: Fraction;
    public nextBackJump: Fraction;
    public nextForwardJump: Fraction;
    public curRepetition: Repetition;
    public curRepetitionIteration: number;
}
