// musical_division.cpp — see musical_division.h. Pure; standard library only.

#include "core/instrument/note/musical_division.h"

#include <algorithm>
#include <cmath>

namespace reasampler::instrument::note {
namespace {

double modifierFactor(DivisionModifier m) {
    switch (m) {
        case DivisionModifier::Dotted:  return 1.5;
        case DivisionModifier::Triplet: return 2.0 / 3.0;
        case DivisionModifier::Straight: break;
    }
    return 1.0;
}

int clampExponent(int quarterExponent) {
    return (std::max)(kMinQuarterExponent, (std::min)(kMaxQuarterExponent, quarterExponent));
}

// The underlying type is unsigned, so an out-of-enum byte can only be too large.
DivisionModifier clampModifier(DivisionModifier m) {
    return static_cast<int>(m) < kModifierCount ? m : DivisionModifier::Straight;
}

}  // namespace

bool operator==(Division a, Division b) {
    return a.quarterExponent() == b.quarterExponent() && a.modifier() == b.modifier();
}

bool operator!=(Division a, Division b) { return !(a == b); }

Division makeDivision(int quarterExponent, DivisionModifier modifier) {
    return Division(static_cast<std::int8_t>(clampExponent(quarterExponent)),
                    clampModifier(modifier));
}

double divisionBeats(Division d) {
    return std::ldexp(1.0, d.quarterExponent()) * modifierFactor(d.modifier());
}

Division divisionAt(int index) {
    const int clamped = (std::max)(0, (std::min)(kDivisionCount - 1, index));
    return makeDivision(kMinQuarterExponent + clamped / kModifierCount,
                        static_cast<DivisionModifier>(clamped % kModifierCount));
}

int divisionIndex(Division d) {
    return (d.quarterExponent() - kMinQuarterExponent) * kModifierCount
           + static_cast<int>(d.modifier());
}

std::string divisionLabel(Division d) {
    const int e = d.quarterExponent();
    // Both branches meet at e == 2 ("1/1"): a division's written form is its length in
    // whole notes, which is 2^(e-2).
    std::string label = e <= 2 ? "1/" + std::to_string(1 << (2 - e))
                               : std::to_string(1 << (e - 2)) + "/1";
    if (d.modifier() == DivisionModifier::Dotted) label += '.';
    else if (d.modifier() == DivisionModifier::Triplet) label += 't';
    return label;
}

}  // namespace reasampler::instrument::note
