"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Role = exports.Line = exports.Group = exports.Note = exports.Syllable = exports.Literal = exports.Space = exports.Rest = exports.Marker = exports.LeafAtom = exports.Atom = exports.AtomType = exports.ONE = exports.ZERO = void 0;
const TSU = __importStar(require("@panyam/tsutils"));
const entity_1 = require("./entity");
exports.ZERO = TSU.Num.Fraction.ZERO;
exports.ONE = TSU.Num.Fraction.ONE;
var AtomType;
(function (AtomType) {
    AtomType["NOTE"] = "Note";
    AtomType["LITERAL"] = "Literal";
    AtomType["SYLLABLE"] = "Syllable";
    AtomType["SPACE"] = "Space";
    AtomType["GROUP"] = "Group";
    AtomType["LABEL"] = "Label";
    AtomType["REST"] = "Rest";
    AtomType["MARKER"] = "Marker";
})(AtomType || (exports.AtomType = AtomType = {}));
class Atom extends entity_1.TimedEntity {
    constructor(duration = exports.ONE) {
        super();
        this.TYPE = "Atom";
        this.nextSibling = null;
        this.prevSibling = null;
        this.parentGroup = null;
        this.isContinuation = false;
        this._duration = duration || exports.ONE;
    }
    debugValue() {
        const out = super.debugValue();
        if (!this.duration.isOne) {
            out.duration = this.duration.factorized.toString();
        }
        if (this.isContinuation) {
            out.isContinuation = true;
        }
        if ((this.markersBefore || []).length > 0) {
            out.mbef = this.markersBefore.map((m) => m.debugValue());
        }
        if ((this.markersAfter || []).length > 0) {
            out.maft = this.markersAfter.map((m) => m.debugValue());
        }
        return out;
    }
    copyTo(another) {
        super.copyTo(another);
        another._duration = new TSU.Num.Fraction(this.duration.num, this.duration.den);
    }
    get duration() {
        return this._duration;
    }
    set duration(d) {
        this._duration = d;
    }
}
exports.Atom = Atom;
class LeafAtom extends Atom {
    constructor() {
        super(...arguments);
        this.TYPE = "LeafAtom";
        this.beforeRest = false;
    }
    splitAt(duration) {
        if (this.duration.cmp(duration) > 0) {
            const spillOver = this.createSpilloverSpace(this.duration.minus(duration));
            spillOver.isContinuation = true;
            this.duration = duration;
            return spillOver;
        }
        return null;
    }
    createSpilloverSpace(duration) {
        return new Space(duration);
    }
    debugValue() {
        return this.beforeRest ? Object.assign(Object.assign({}, super.debugValue()), { beforeRest: true }) : super.debugValue();
    }
}
exports.LeafAtom = LeafAtom;
class Marker extends entity_1.Entity {
    constructor(text, isBefore = true) {
        super();
        this.text = text;
        this.isBefore = isBefore;
        this.TYPE = "Marker";
    }
    debugValue() {
        return Object.assign(Object.assign({}, super.debugValue()), { text: this.text, before: this.isBefore });
    }
    toString() {
        return `Marker(${this.text}-${this.isBefore})`;
    }
}
exports.Marker = Marker;
class Rest extends LeafAtom {
    constructor() {
        super(exports.ZERO);
        this.TYPE = "Rest";
    }
}
exports.Rest = Rest;
class Space extends LeafAtom {
    constructor(duration = exports.ONE, isSilent = false) {
        super(duration);
        this.TYPE = "Space";
        this.isSilent = false;
        this.isSilent = isSilent;
    }
    debugValue() {
        return Object.assign(Object.assign({}, super.debugValue()), { isSilent: this.isSilent });
    }
    toString() {
        return `Space(${this.duration}-${this.isSilent})`;
    }
    copyTo(another) {
        super.copyTo(another);
        another.isSilent = this.isSilent;
    }
    equals(another) {
        return super.equals(another) && this.isSilent == another.isSilent;
    }
    createSpilloverSpace(duration) {
        const out = super.createSpilloverSpace(duration);
        out.isSilent = this.isSilent;
        return out;
    }
}
exports.Space = Space;
class Literal extends LeafAtom {
    constructor(value, duration = exports.ONE) {
        super(duration);
        this.value = value;
        this.TYPE = "Literal";
        this.embelishments = [];
    }
    debugValue() {
        const out = Object.assign(Object.assign({}, super.debugValue()), { value: this.value });
        if (this.embelishments.length > 0) {
            out.embs = this.embelishments.map((e) => ("debugValue" in e ? e.debugValue() : e));
        }
        return out;
    }
    toString() {
        return `Lit(${this.duration}-${this.value})`;
    }
    equals(another) {
        return super.equals(another) && this.value == another.value;
    }
    copyTo(another) {
        super.copyTo(another);
        another.value = this.value;
    }
}
exports.Literal = Literal;
class Syllable extends Literal {
    constructor() {
        super(...arguments);
        this.TYPE = "Syllable";
    }
    static fromLit(lit) {
        if (lit.TYPE == AtomType.SYLLABLE)
            return lit;
        const out = new Syllable(lit.value, lit.duration);
        out.embelishments = lit.embelishments;
        out.beforeRest = lit.beforeRest;
        return out;
    }
    toString() {
        return `Syll(${this.duration}-${this.value})`;
    }
}
exports.Syllable = Syllable;
class Note extends Literal {
    constructor(value, duration = exports.ONE, octave = 0, shift = 0) {
        super(value, duration);
        this.TYPE = "Note";
        this.octave = 0;
        this.shift = 0;
        this.octave = octave;
        this.shift = shift;
    }
    static fromLit(lit) {
        if (lit.TYPE == AtomType.NOTE)
            return lit;
        const out = new Note(lit.value, lit.duration);
        out.embelishments = lit.embelishments;
        out.beforeRest = lit.beforeRest;
        return out;
    }
    debugValue() {
        const out = Object.assign({}, super.debugValue());
        if (this.octave != 0)
            out.octave = this.octave;
        if (this.shift != 0)
            out.shift = this.shift;
        return out;
    }
    toString() {
        return `Note(${this.duration}-${this.value}-${this.octave})`;
    }
    equals(another) {
        return super.equals(another) && this.octave == another.octave && this.shift == another.shift;
    }
    copyTo(another) {
        super.copyTo(another);
        another.octave = this.octave;
        another.shift = this.shift;
    }
}
exports.Note = Note;
class Group extends Atom {
    constructor(...atoms) {
        super(atoms.length == 0 ? exports.ZERO : exports.ONE);
        this.TYPE = "Group";
        this.durationIsMultiplier = false;
        this.atoms = new TSU.Lists.ValueList();
        this.addAtoms(false, ...atoms);
    }
    equals(another, expect = false) {
        if (!super.equals(another))
            return false;
        return this.atoms.equals(another.atoms, (a1, a2) => a1.equals(a2));
    }
    copyTo(another) {
        super.copyTo(another);
        another.durationIsMultiplier = this.durationIsMultiplier;
        this.atoms.forEach((atom) => another.atoms.add(atom.clone()));
    }
    get duration() {
        if (this.durationIsMultiplier) {
            return this.totalChildDuration.divby(this._duration);
        }
        else {
            return this._duration;
        }
    }
    setDurationAsMultiplier(asMultiplier = true) {
        this.durationIsMultiplier = asMultiplier;
        return this;
    }
    setDuration(d, asMultiplier = false) {
        this._duration = d;
        this.durationIsMultiplier = asMultiplier;
        return this;
    }
    debugValue() {
        const out = Object.assign(Object.assign({}, super.debugValue()), { atoms: Array.from(this.atoms.values(), (a) => a.debugValue()) });
        if (this.durationIsMultiplier)
            out.durationIsMultiplier = true;
        return out;
    }
    splitAt(requiredDuration) {
        if (this.duration.isLTE(requiredDuration) || requiredDuration.isLTE(exports.ZERO)) {
            return null;
        }
        const targetGroup = new Group();
        if (this.durationIsMultiplier) {
            targetGroup.durationIsMultiplier = true;
            targetGroup._duration = this._duration;
        }
        let remainingDur = this.duration;
        const totalChildDuration = this.totalChildDuration;
        const durationFactor = this.durationIsMultiplier
            ? exports.ONE.divby(this._duration)
            : this._duration.divby(totalChildDuration, true);
        while (remainingDur.isGT(requiredDuration) && this.atoms.last) {
            const lastChild = this.atoms.last;
            const childDuration = lastChild.duration.times(durationFactor);
            const newDuration = remainingDur.minus(childDuration);
            if (newDuration.isGTE(requiredDuration)) {
                this.removeAtoms(true, lastChild);
                targetGroup.insertAtomsAt(targetGroup.atoms.first, true, lastChild);
                if (newDuration.equals(requiredDuration)) {
                    return targetGroup;
                }
            }
            else {
                const newRequiredDur = requiredDuration.minus(newDuration, true).divby(durationFactor, true);
                const spillOver = lastChild.splitAt(newRequiredDur);
                if (spillOver == null) {
                    throw new Error("Spill over cannot be null here");
                }
                if (!this.durationIsMultiplier) {
                    this._duration = requiredDuration;
                }
                else {
                    if (this._duration.isZero)
                        throw new Error("How can this be?");
                }
                spillOver.isContinuation = true;
                targetGroup.insertAtomsAt(targetGroup.atoms.first, true, spillOver);
                return targetGroup;
            }
            remainingDur = newDuration;
        }
        return targetGroup;
    }
    get totalChildDuration() {
        let out = exports.ZERO;
        this.atoms.forEach((atom) => (out = out.plus(atom.duration)));
        return out;
    }
    insertAtomsAt(beforeAtom, adjustDuration = false, ...atoms) {
        adjustDuration = adjustDuration && !this.durationIsMultiplier;
        const oldChildDuration = adjustDuration ? this.totalChildDuration : exports.ONE;
        for (const atom of atoms) {
            if (atom.parentGroup != null) {
                if (atom.parentGroup != this) {
                    throw new Error("Atom belongs to another parent. Remove it first");
                }
                atom.parentGroup.removeAtoms(false, atom);
            }
            if (atom.TYPE == AtomType.REST) {
                const last = this.atoms.last;
                if (last && last.TYPE != AtomType.GROUP && last.TYPE != AtomType.LABEL) {
                    last.beforeRest = true;
                }
            }
            else {
                atom.parentGroup = this;
                this.atoms.add(atom, beforeAtom);
            }
        }
        if (adjustDuration) {
            if (this._duration.isZero) {
                if (this.durationIsMultiplier)
                    throw new Error("How can this be?");
                this._duration = this.totalChildDuration;
            }
            else {
                const scaleFactor = this.totalChildDuration.divby(oldChildDuration);
                this._duration = this._duration.times(scaleFactor, true);
            }
        }
        return this;
    }
    addAtoms(adjustDuration = false, ...atoms) {
        return this.insertAtomsAt(null, adjustDuration, ...atoms);
    }
    removeAtoms(adjustDuration = false, ...atoms) {
        adjustDuration = adjustDuration && !this.durationIsMultiplier;
        const oldChildDuration = adjustDuration ? this.totalChildDuration : exports.ONE;
        for (const atom of atoms) {
            if (atom.parentGroup == this) {
                this.atoms.remove(atom);
                atom.parentGroup = null;
            }
            else if (atom.parentGroup != null) {
                throw new Error("Atom cannot be removed as it does not belong to this group");
            }
        }
        if (adjustDuration) {
            if (this._duration.isZero) {
                if (this.durationIsMultiplier)
                    throw new Error("How can this be?");
                this._duration = this.totalChildDuration;
            }
            else {
                const scaleFactor = this.totalChildDuration.divby(oldChildDuration);
                this._duration = this._duration.times(scaleFactor, true);
            }
        }
        return this;
    }
}
exports.Group = Group;
class Line extends entity_1.Entity {
    constructor() {
        super(...arguments);
        this.TYPE = "Line";
        this.offset = exports.ZERO;
        this.roles = [];
        this.marginText = "";
    }
    indexOfRole(name) {
        for (let i = 0; i < this.roles.length; i++) {
            if (this.roles[i].name == name)
                return i;
        }
        return -1;
    }
    get isEmpty() {
        for (const r of this.roles)
            if (!r.isEmpty)
                return false;
        return true;
    }
    debugValue() {
        const out = Object.assign(Object.assign({}, super.debugValue()), { roles: this.roles.map((r) => r.debugValue()) });
        if (!this.offset.isZero) {
            out.offset = this.offset.toString();
        }
        return out;
    }
    copyTo(another) {
        super.copyTo(another);
        another.roles = this.roles.map((r) => r.clone());
    }
    addAtoms(roleName, defaultToNotes, ...atoms) {
        const role = this.ensureRole(roleName, defaultToNotes);
        role.addAtoms(...atoms);
        return this;
    }
    ensureRole(roleName, defaultToNotes) {
        let ri = this.roles.findIndex((r) => r.name == roleName);
        if (ri < 0) {
            ri = this.roles.length;
            const role = new Role(this, roleName);
            role.defaultToNotes = defaultToNotes;
            this.roles.push(role);
        }
        return this.roles[ri];
    }
    get duration() {
        let max = exports.ZERO;
        for (const role of this.roles) {
            max = TSU.Num.Fraction.max(role.duration, max);
        }
        return max;
    }
}
exports.Line = Line;
class Role extends entity_1.Entity {
    constructor(line, name) {
        super();
        this.line = line;
        this.name = name;
        this.TYPE = "Role";
        this.defaultToNotes = true;
        this.atoms = [];
    }
    get isEmpty() {
        return this.atoms.length == 0;
    }
    debugValue() {
        return { name: this.name, atoms: this.atoms.map((a) => a.debugValue()) };
    }
    addAtoms(...atoms) {
        let last = null;
        for (const atom of atoms) {
            if (atom.TYPE == AtomType.REST) {
                if (last && last.TYPE != AtomType.GROUP && last.TYPE != AtomType.LABEL) {
                    last.beforeRest = true;
                }
            }
            else {
                this.atoms.push(atom);
            }
            last = atom;
        }
    }
    copyTo(another) {
        another.addAtoms(...this.atoms);
    }
    get duration() {
        return this.atoms.reduce((a, b) => a.plus(b.duration), exports.ZERO);
    }
}
exports.Role = Role;
//# sourceMappingURL=core.js.map