#include "Region.h"
#include "Helpers.h"
#include "absl/strings/str_replace.h"

bool sfz::Region::parseOpcode(const Opcode& opcode)
{
    switch (hash(opcode.opcode))
    {
    // Sound source: sample playback
    case hash("sample"): 
        sample = absl::StrReplaceAll(trim(opcode.value), {{"\\", "/"}});
    break;
    case hash("delay"): setValueFromOpcode(opcode, delay, Default::delayRange); break;
    case hash("delay_random"): setValueFromOpcode(opcode, delayRandom, Default::delayRange); break;
    case hash("offset"): setValueFromOpcode(opcode, offset, Default::offsetRange); break;
    case hash("offset_random"): setValueFromOpcode(opcode, offsetRandom, Default::offsetRange); break;
    case hash("end"): setValueFromOpcode(opcode, sampleEnd, Default::sampleEndRange); break;
    case hash("count"): setValueFromOpcode(opcode, sampleCount, Default::sampleCountRange); break;
    case hash("loopmode"):
    case hash("loop_mode"):
        switch(hash(opcode.value))
        {
            case hash("no_loop"):
                loopMode = SfzLoopMode::no_loop;
                break;
            case hash("one_shot"):
                loopMode = SfzLoopMode::one_shot;
                break;
            case hash("loop_continuous"):
                loopMode = SfzLoopMode::loop_continuous;
                break;
            case hash("loop_sustain"):
                loopMode = SfzLoopMode::loop_sustain;
                break;
            default:
                DBG("Unkown loop mode:" << std::string(opcode.value));
        }
        break;
    case hash("loopend"):
    case hash("loop_end"): setRangeEndFromOpcode(opcode, loopRange, Default::loopRange); break;
    case hash("loopstart"):
    case hash("loop_start"): setRangeStartFromOpcode(opcode, loopRange, Default::loopRange); break;

    // Instrument settings: voice lifecycle
    case hash("group"): setValueFromOpcode(opcode, group, Default::groupRange); break;
    case hash("offby"):
    case hash("off_by"): setValueFromOpcode(opcode, offBy, Default::groupRange); break;
    case hash("off_mode"):
        switch(hash(opcode.value))
        {
            case hash("fast"):
                offMode = SfzOffMode::fast;
                break;
            case hash("normal"):
                offMode = SfzOffMode::normal;
                break;
            default:
                DBG("Unkown off mode:" << std::string(opcode.value));
        }
        break;
    // Region logic: key mapping
    case hash("lokey"): setRangeStartFromOpcode(opcode, keyRange, Default::keyRange); break;
    case hash("hikey"): setRangeEndFromOpcode(opcode, keyRange, Default::keyRange); break;
    case hash("key"):
        setRangeStartFromOpcode(opcode, keyRange, Default::keyRange);
        setRangeEndFromOpcode(opcode, keyRange, Default::keyRange);
        setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange);
        break;
    case hash("lovel"): setRangeStartFromOpcode(opcode, velocityRange, Default::velocityRange); break;
    case hash("hivel"): setRangeEndFromOpcode(opcode, velocityRange, Default::velocityRange); break;
        
    // Region logic: MIDI conditions
    case hash("lochan"): setRangeStartFromOpcode(opcode, channelRange, Default::channelRange); break;
    case hash("hichan"): setRangeEndFromOpcode(opcode, channelRange, Default::channelRange); break;
    case hash("lobend"): setRangeStartFromOpcode(opcode, bendRange, Default::bendRange); break;
    case hash("hibend"): setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); break;
    case hash("locc"): 
        if (opcode.parameter) 
            setRangeStartFromOpcode(opcode, ccConditions[*opcode.parameter], Default::ccRange); 
        break;
    case hash("hicc"):
        if (opcode.parameter) 
            setRangeEndFromOpcode(opcode, ccConditions[*opcode.parameter], Default::ccRange); 
        break;
    case hash("sw_lokey"): setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange); break;
    case hash("sw_hikey"): setRangeEndFromOpcode(opcode, keyswitchRange, Default::keyRange); break;
    case hash("sw_last"): setValueFromOpcode(opcode, keyswitch, Default::keyRange); break;
    case hash("sw_down"): setValueFromOpcode(opcode, keyswitchDown, Default::keyRange); break;
    case hash("sw_up"): setValueFromOpcode(opcode, keyswitchUp, Default::keyRange); break;
    case hash("sw_previous"): setValueFromOpcode(opcode, previousNote, Default::keyRange); break;
    case hash("sw_vel"):
        switch(hash(opcode.value))
        {
            case hash("current"):
                velocityOverride = SfzVelocityOverride::current;
                break;
            case hash("previous"):
                velocityOverride = SfzVelocityOverride::previous;
                break;
            default:
                DBG("Unknown velocity mode: " << std::string(opcode.value));
        }
        break;
    // Region logic: internal conditions
    case hash("lochanaft"): setRangeStartFromOpcode(opcode, aftertouchRange, Default::aftertouchRange); break;
    case hash("hichanaft"): setRangeEndFromOpcode(opcode, aftertouchRange, Default::aftertouchRange); break;
    case hash("lobpm"): setRangeStartFromOpcode(opcode, bpmRange, Default::bpmRange); break;
    case hash("hibpm"): setRangeEndFromOpcode(opcode, bpmRange, Default::bpmRange); break;
    case hash("lorand"): setRangeStartFromOpcode(opcode, randRange, Default::randRange); break;
    case hash("hirand"): setRangeEndFromOpcode(opcode, randRange, Default::randRange); break;
    case hash("seq_length"): setValueFromOpcode(opcode, sequenceLength, Default::sequenceRange); break;
    case hash("seq_position"): setValueFromOpcode(opcode, sequencePosition, Default::sequenceRange); break;
    // Region logic: triggers
    case hash("trigger"):
        switch(hash(opcode.value))
        {
            case hash("attack"):
                trigger = SfzTrigger::attack;
                break;
            case hash("first"):
                trigger = SfzTrigger::first;
                break;
            case hash("legato"):
                trigger = SfzTrigger::legato;
                break;
            case hash("release"):
                trigger = SfzTrigger::release;
                break;
            case hash("release_key"):
                trigger = SfzTrigger::release_key;
                break;
            default:
                DBG("Unknown trigger mode: " << std::string(opcode.value));
        }
        break;
    case hash("on_locc"): 
        if (opcode.parameter) 
            setRangeStartFromOpcode(opcode, ccTriggers[*opcode.parameter], Default::ccRange); 
        break;
    case hash("on_hicc"):
        if (opcode.parameter) 
            setRangeEndFromOpcode(opcode, ccTriggers[*opcode.parameter], Default::ccRange); 
        break;

    // Performance parameters: amplifier
    case hash("volume"): setValueFromOpcode(opcode, volume, Default::volumeRange); break;
    // case hash("volume_oncc"): 
    //     if (opcode.parameter) 
    //         setValueFromOpcode(opcode, volumeCCTriggers[*opcode.parameter], Default::volumeCCRange); 
    //     break;
    case hash("amplitude"): setValueFromOpcode(opcode, amplitude, Default::amplitudeRange); break;
    case hash("amplitude_cc"):
    case hash("amplitude_oncc"): setCCPairFromOpcode(opcode, amplitudeCC, Default::amplitudeRange); break;
    case hash("pan"): setValueFromOpcode(opcode, pan, Default::panRange); break;
    case hash("pan_oncc"): setCCPairFromOpcode(opcode, panCC, Default::panCCRange); break;
    case hash("position"): setValueFromOpcode(opcode, position, Default::positionRange); break;
    case hash("position_oncc"): setCCPairFromOpcode(opcode, positionCC, Default::positionCCRange); break;
    case hash("width"): setValueFromOpcode(opcode, width, Default::widthRange); break;
    case hash("width_oncc"): setCCPairFromOpcode(opcode, widthCC, Default::widthCCRange); break;
    case hash("amp_keycenter"): setValueFromOpcode(opcode, ampKeycenter, Default::keyRange); break;
    case hash("amp_keytrack"): setValueFromOpcode(opcode, ampKeytrack, Default::ampKeytrackRange); break;
    case hash("amp_veltrack"): setValueFromOpcode(opcode, ampVeltrack, Default::ampVeltrackRange); break;
    case hash("amp_random"): setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange); break;
    case hash("amp_velcurve_"):
        if (opcode.parameter && Default::ccRange.containsWithEnd(*opcode.parameter))
        {
            if (auto value = readOpcode(opcode.value, Default::ampVelcurveRange); value)
                velocityPoints.emplace_back(*opcode.parameter, *value);
        }
        break;
    case hash("xfin_lokey"): setRangeStartFromOpcode(opcode, crossfadeKeyInRange, Default::keyRange); break;
    case hash("xfin_hikey"): setRangeEndFromOpcode(opcode, crossfadeKeyInRange, Default::keyRange); break;
    case hash("xfout_lokey"): setRangeStartFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange); break;
    case hash("xfout_hikey"): setRangeEndFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange); break;
    case hash("xfin_lovel"): setRangeStartFromOpcode(opcode, crossfadeVelInRange, Default::velocityRange); break;
    case hash("xfin_hivel"): setRangeEndFromOpcode(opcode, crossfadeVelInRange, Default::velocityRange); break;
    case hash("xfout_lovel"): setRangeStartFromOpcode(opcode, crossfadeVelOutRange, Default::velocityRange); break;
    case hash("xfout_hivel"): setRangeEndFromOpcode(opcode, crossfadeVelOutRange, Default::velocityRange); break;
    case hash("xf_keycurve"):
        switch (hash(opcode.value))
        {
        case hash("power"):
            crossfadeKeyCurve = SfzCrossfadeCurve::power;
            break;
        case hash("gain"):
            crossfadeKeyCurve = SfzCrossfadeCurve::gain;
            break;
        default:
            DBG("Unknown crossfade power curve: " << std::string(opcode.value));
        }
        break;
    case hash("xf_velcurve"):
        switch (hash(opcode.value))
        {
        case hash("power"):
            crossfadeVelCurve = SfzCrossfadeCurve::power;
            break;
        case hash("gain"):
            crossfadeVelCurve = SfzCrossfadeCurve::gain;
            break;
        default:
            DBG("Unknown crossfade power curve: " << std::string(opcode.value));
        }
        break;

    // Performance parameters: pitch
    case hash("pitch_keycenter"): setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange); break;
    case hash("pitch_keytrack"): setValueFromOpcode(opcode, pitchKeytrack, Default::pitchKeytrackRange); break;
    case hash("pitch_veltrack"): setValueFromOpcode(opcode, pitchVeltrack, Default::pitchVeltrackRange); break;
    case hash("pitch_random"): setValueFromOpcode(opcode, pitchRandom, Default::pitchRandomRange); break;
    case hash("transpose"): setValueFromOpcode(opcode, transpose, Default::transposeRange); break;
    case hash("tune"): setValueFromOpcode(opcode, tune, Default::tuneRange); break;

    // Amplitude Envelope
    case hash("ampeg_attack"): setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange); break;
    case hash("ampeg_decay"): setValueFromOpcode(opcode, amplitudeEG.decay, Default::egTimeRange); break;
    case hash("ampeg_delay"): setValueFromOpcode(opcode, amplitudeEG.delay, Default::egTimeRange); break;
    case hash("ampeg_hold"): setValueFromOpcode(opcode, amplitudeEG.hold, Default::egTimeRange); break;
    case hash("ampeg_release"): setValueFromOpcode(opcode, amplitudeEG.release, Default::egTimeRange); break;
    case hash("ampeg_start"): setValueFromOpcode(opcode, amplitudeEG.start, Default::egPercentRange); break;
    case hash("ampeg_sustain"): setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange); break;
    case hash("ampeg_vel2attack"): setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange); break;
    case hash("ampeg_vel2decay"): setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange); break;
    case hash("ampeg_vel2delay"): setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange); break;
    case hash("ampeg_vel2hold"): setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange); break;
    case hash("ampeg_vel2release"): setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange); break;
    case hash("ampeg_vel2sustain"): setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); break;
    case hash("ampeg_attack_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccAttack, Default::egOnCCTimeRange); break;
    case hash("ampeg_decay_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccDecay, Default::egOnCCTimeRange); break;
    case hash("ampeg_delay_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccDelay, Default::egOnCCTimeRange); break;
    case hash("ampeg_hold_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccHold, Default::egOnCCTimeRange); break;
    case hash("ampeg_release_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccRelease, Default::egOnCCTimeRange); break;
    case hash("ampeg_start_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccStart, Default::egOnCCPercentRange); break;
    case hash("ampeg_sustain_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange); break;
    // Ignored opcodes
    case hash("ampeg_depth"):
    case hash("ampeg_vel2depth"):
        break;
    default:
        return false;
    }

    return true;
}

bool sfz::Region::prepare()
{
    auto fileInformation = filePool.getFileInformation(sample);
    if (!fileInformation)
        return false;

    DBG("Sample " << sample << " information: " << fileInformation->end << "(" << fileInformation->loopBegin << "->" << fileInformation->loopEnd << ")");
    sampleEnd = std::min(sampleEnd, fileInformation->end);
    loopRange.shrinkIfSmaller(fileInformation->loopBegin, fileInformation->loopEnd);
    preloadedData = fileInformation->preloadedData;
    return true;
}

bool sfz::Region::isSwitchedOn() const noexcept
{
    return false;
}

bool sfz::Region::registerNoteOn(int channel, int noteNumber, uint8_t velocity, float randValue)
{
    return false;
}

bool sfz::Region::registerNoteOff(int channel, int noteNumber, uint8_t velocity, float randValue)
{
    return false;
}
bool sfz::Region::registerCC(int channel, int ccNumber, uint8_t ccValue)
{
    return false;
}
void sfz::Region::registerPitchWheel(int channel, int pitch)
{

}
void sfz::Region::registerAftertouch(int channel, uint8_t aftertouch)
{

}
void sfz::Region::registerTempo(float secondsPerQuarter)
{

}