import * as TSU from "@panyam/tsutils";
import { TimedEntity } from "./entity";
const ZERO = TSU.Num.Fraction.ZERO;
const ONE = TSU.Num.Fraction.ONE;
export class CycleCursor {
    constructor(cycle, barIndex = 0, beatIndex = 0, instance = 0) {
        this.cycle = cycle;
        this.barIndex = barIndex;
        this.beatIndex = beatIndex;
        this.instance = instance;
    }
    get next() {
        const currBar = this.cycle.bars[this.barIndex];
        const result = [
            [this.barIndex, this.beatIndex, this.instance],
            currBar.beatLengths[this.beatIndex],
        ];
        this.instance++;
        if (!currBar.beatCounts[this.beatIndex] || this.instance >= currBar.beatCounts[this.beatIndex]) {
            this.instance = 0;
            this.beatIndex++;
            if (this.beatIndex >= currBar.beatLengths.length) {
                this.beatIndex = 0;
                this.barIndex++;
                if (this.barIndex >= this.cycle.bars.length) {
                    this.barIndex = 0;
                }
            }
        }
        return result;
    }
    get prev() {
        const currBar = this.cycle.bars[this.barIndex];
        const result = [
            [this.barIndex, this.beatIndex, this.instance],
            currBar.beatLengths[this.beatIndex],
        ];
        this.instance--;
        if (this.instance < 0) {
            this.beatIndex--;
            if (this.beatIndex < 0) {
                this.barIndex--;
                if (this.barIndex < 0) {
                    this.barIndex = this.cycle.bars.length - 1;
                }
                this.beatIndex = this.cycle.bars[this.barIndex].beatCount - 1;
            }
            this.instance = (this.cycle.bars[this.barIndex].beatCounts[this.beatIndex] || 1) - 1;
        }
        return result;
    }
}
export class Bar extends TimedEntity {
    constructor(config = null) {
        super((config = config || {}));
        this.TYPE = "Bar";
        this.beatLengths = [];
        this.beatCounts = [];
        this.name = config.name || "";
        for (const bl of config.beatLengths || []) {
            if (typeof bl === "number") {
                this.beatLengths.push(TSU.Num.Frac(bl));
            }
            else {
                this.beatLengths.push(bl);
            }
        }
        for (const bc of config.beatCounts || []) {
            this.beatCounts.push(bc);
        }
        while (this.beatCounts.length < this.beatLengths.length) {
            this.beatCounts.push(1);
        }
    }
    debugValue() {
        return Object.assign(Object.assign({}, super.debugValue()), { name: name, beatLengths: this.beatLengths });
    }
    equals(another) {
        if (!super.equals(another))
            return false;
        if (this.beatLengths.length != another.beatLengths.length)
            return false;
        if (this.beatCounts.length != another.beatCounts.length)
            return false;
        for (let i = 0; i < this.beatLengths.length; i++) {
            if (!this.beatLengths[i].equals(another.beatLengths[i]))
                return false;
        }
        for (let i = 0; i < this.beatCounts.length; i++) {
            if (this.beatCounts[i] != another.beatCounts[i])
                return false;
        }
        return true;
    }
    copyTo(another) {
        super.copyTo(another);
        another.name = this.name;
        another.beatLengths = [...this.beatLengths];
        another.beatCounts = [...this.beatCounts];
    }
    instanceCount(beatIndex) {
        if (beatIndex > this.beatCounts.length) {
            return 1;
        }
        else {
            return this.beatCounts[beatIndex];
        }
    }
    get beatCount() {
        return this.beatLengths.length;
    }
    get totalBeatCount() {
        let out = 0;
        for (let i = 0; i < this.beatLengths.length; i++) {
            out += this.beatCounts[i] || 1;
        }
        return out;
    }
    get duration() {
        let total = ZERO;
        for (let i = 0; i < this.beatLengths.length; i++) {
            total = total.plus(this.beatLengths[i].timesNum(this.beatCounts[i] || 1));
        }
        return total;
    }
}
export class Cycle extends TimedEntity {
    constructor(config = null) {
        super((config = config || {}));
        this.TYPE = "Cycle";
        this.name = config.name || "";
        this.bars = config.bars || [];
    }
    debugValue() {
        return Object.assign(Object.assign({}, super.debugValue()), { name: name, bars: this.bars.map((p) => p.debugValue()) });
    }
    children() {
        return this.bars;
    }
    equals(another) {
        if (!super.equals(another)) {
            return false;
        }
        if (this.bars.length != another.bars.length)
            return false;
        for (let i = 0; i < this.bars.length; i++) {
            if (!this.bars[i].equals(another.bars[i]))
                return false;
        }
        return true;
    }
    getAtIndex(globalIndex) {
        let cycle = 0;
        while (globalIndex < 0) {
            globalIndex += this.totalBeatCount;
            cycle--;
        }
        if (globalIndex >= this.totalBeatCount) {
            cycle = Math.floor(globalIndex / this.totalBeatCount);
        }
        globalIndex = globalIndex % this.totalBeatCount;
        let offset = ZERO;
        for (let barIndex = 0; barIndex < this.bars.length; barIndex++) {
            const bar = this.bars[barIndex];
            if (globalIndex >= bar.totalBeatCount) {
                globalIndex -= bar.totalBeatCount;
                offset = offset.plus(bar.duration);
            }
            else {
                for (let beatIndex = 0; beatIndex < bar.beatCount; beatIndex++) {
                    const beatLength = bar.beatLengths[beatIndex];
                    const beatCount = bar.beatCounts[beatIndex] || 1;
                    if (globalIndex >= beatCount) {
                        globalIndex -= beatCount;
                        offset = offset.plus(beatLength.timesNum(beatCount));
                    }
                    else {
                        const instance = globalIndex;
                        return [cycle, [barIndex, beatIndex, instance], offset.plus(beatLength.timesNum(instance))];
                    }
                }
            }
        }
        throw new Error("Should not be here!");
    }
    getPosition(globalOffset) {
        const duration = this.duration;
        let cycleNum = 0;
        if (globalOffset.isLT(ZERO)) {
            while (globalOffset.isLT(ZERO)) {
                cycleNum--;
                globalOffset = globalOffset.plus(duration);
            }
        }
        else if (globalOffset.isGTE(duration)) {
            const realOffset = globalOffset.mod(duration);
            globalOffset = globalOffset.minus(realOffset).divby(duration);
            TSU.assert(globalOffset.isWhole);
            cycleNum = globalOffset.floor;
            globalOffset = realOffset;
        }
        let globalIndex = 0;
        for (let barIndex = 0; barIndex < this.bars.length; barIndex++) {
            const bar = this.bars[barIndex];
            const barDuration = bar.duration;
            if (globalOffset.isGTE(barDuration)) {
                globalOffset = globalOffset.minus(barDuration);
            }
            else {
                for (let beatIndex = 0; beatIndex < bar.beatCount; beatIndex++) {
                    const beatLength = bar.beatLengths[beatIndex];
                    const beatCount = bar.beatCounts[beatIndex] || 1;
                    for (let instance = 0; instance < beatCount; instance++, globalIndex++) {
                        if (globalOffset.isGTE(beatLength)) {
                            globalOffset = globalOffset.minus(beatLength);
                        }
                        else {
                            return [cycleNum, [barIndex, beatIndex, instance], globalOffset, globalIndex];
                        }
                    }
                }
            }
            globalIndex += bar.totalBeatCount;
        }
        throw new Error("Should not be here!");
    }
    *iterateBeats(startBar = 0, startBeat = 0, startInstance = 0) {
        let barIndex = startBar;
        let beatIndex = startBeat;
        let instanceIndex = startInstance;
        while (true) {
            const currBar = this.bars[barIndex];
            yield [[barIndex, beatIndex, instanceIndex], currBar.beatLengths[beatIndex]];
            instanceIndex++;
            if (!currBar.beatCounts[beatIndex] || instanceIndex >= currBar.beatCounts[beatIndex]) {
                instanceIndex = 0;
                beatIndex++;
                if (beatIndex >= currBar.beatLengths.length) {
                    beatIndex = 0;
                    barIndex++;
                    if (barIndex >= this.bars.length) {
                        barIndex = 0;
                    }
                }
            }
        }
    }
    copyTo(another) {
        super.copyTo(another);
        another.name = this.name;
        another.bars = this.bars.map((x) => x.clone());
    }
    get beatCount() {
        let out = 0;
        for (const bar of this.bars)
            out += bar.beatCount;
        return out;
    }
    get totalBeatCount() {
        let out = 0;
        for (const bar of this.bars)
            out += bar.totalBeatCount;
        return out;
    }
    get duration() {
        return this.bars.reduce((x, y) => x.plus(y.duration), ZERO);
    }
}
Cycle.DEFAULT = new Cycle({
    name: "Adi Thalam",
    bars: [
        new Bar({ name: "Laghu", beatLengths: [1, 1, 1, 1] }),
        new Bar({ name: "Dhrutam", beatLengths: [1, 1] }),
        new Bar({ name: "Dhrutam", beatLengths: [1, 1] }),
    ],
});
//# sourceMappingURL=cycle.js.map