import { Voice } from "../VoiceData";
import { Fraction } from "../../Common/DataObjects";
import { PlaybackEntry } from "./PlaybackEntry";

export class VoicePlaybackData {
    private parentVoice: Voice;
    private readonly playbackEntries: { enrolledTimestamp: Fraction, playbackEntry: PlaybackEntry }[] = [];
    private readonly dueEntries: { enrolledTimestamp: Fraction, playbackEntry: PlaybackEntry }[] = [];
    private nextEntryTimestamp: Fraction;

    /**
     * Holds the list of all (enrolled) playback entries for a certain voice within a part in the musicsheet.
     * The list is sorted by the enrolled timestamp and a playback entry can be in the list more than once (due to repetitions)
     */
    constructor (voice: Voice, playbackEntries: { enrolledTimestamp: Fraction, playbackEntry: PlaybackEntry}[]) {
        this.parentVoice = voice;
        this.playbackEntries = playbackEntries;
    }

    public get ParentVoice(): Voice {
        return this.parentVoice;
    }

    public get PlaybackEntries(): { enrolledTimestamp: Fraction, playbackEntry: PlaybackEntry }[] {
        return this.playbackEntries;
    }

    /** the index of the current entry to play */
    public playbackIndex: number;

    /** holds all playback entries that shall or should have been played */
    public get DueEntries(): { enrolledTimestamp: Fraction, playbackEntry: PlaybackEntry }[] {
        return this.dueEntries;
    }

    /** the timestamp of the next entry which needs to be awaiten */
    public get NextEntryTimestamp(): Fraction {
        return this.nextEntryTimestamp;
    }

    public isAudible(): boolean {
        return this.ParentVoice.Audible;
    }

    public reset(absoluteTimestamp: Fraction): Fraction {
        this.playbackIndex = 0;
        this.nextEntryTimestamp = undefined;
        this.dueEntries.clear();

        if (!this.ParentVoice.Audible) {
            return undefined;
        }

        for (let i: number = this.playbackIndex; i < this.playbackEntries.length; i++) {
            const entry: { enrolledTimestamp: Fraction, playbackEntry: PlaybackEntry } = this.playbackEntries[i];

            if (entry.playbackEntry.ParentVoiceEntry.ParentSourceStaffEntry.AbsoluteTimestamp.RealValue > absoluteTimestamp.RealValue) {
                this.nextEntryTimestamp = entry.enrolledTimestamp;
                this.playbackIndex = Math.max(0, i - 1);

                break;
            }
        }

        return this.nextEntryTimestamp;
    }

    public update(enrolledTimestamp: Fraction): Fraction {
        this.nextEntryTimestamp = undefined;
        this.dueEntries.clear();
        if (!this.ParentVoice.Audible) {
            return undefined;
        }

        for (let i: number = this.playbackIndex; i < this.playbackEntries.length; i++) {
            const entry: { enrolledTimestamp: Fraction, playbackEntry: PlaybackEntry } = this.playbackEntries[i];

            if (entry.enrolledTimestamp.RealValue > enrolledTimestamp.RealValue) {
                this.nextEntryTimestamp = entry.enrolledTimestamp;
                this.playbackIndex = i;
                break;
            }
            this.dueEntries.push(entry);
        }

        if (this.nextEntryTimestamp === undefined) {
            if (this.playbackEntries.length > 0) {
                const lastEntry: {enrolledTimestamp: Fraction, playbackEntry: PlaybackEntry} = this.playbackEntries.last();
                const lastEntryEndTs: Fraction = Fraction.plus(lastEntry.enrolledTimestamp, lastEntry.playbackEntry.Length);
                if (lastEntryEndTs.RealValue > enrolledTimestamp.RealValue) {
                    this.nextEntryTimestamp = lastEntryEndTs;
                }
            }

            this.playbackIndex = Number.MAX_SAFE_INTEGER;
        }

        return this.nextEntryTimestamp;
    }

}
