#include "mscHack.hpp"
#include <bitset>

//-----------------------------------------------------
// General Definition
//-----------------------------------------------------
#define nCHANNELS 3
#define SEMI (1.0f / 12.0f)

//-----------------------------------------------------
// Reverb Definition
//-----------------------------------------------------
#define REV_TAPS 16
#define REV_BUF_SIZE 0x4000
#define REV_BUF_MAX 0x3FFF

typedef struct
{
    float buf[REV_BUF_SIZE];
    unsigned int in;
    unsigned int out[REV_TAPS];
} REVERB_STRUCT;

//-----------------------------------------------------
// filter
//-----------------------------------------------------
enum FILTER_TYPES
{
    FILTER_OFF,
    FILTER_LP,
    FILTER_HP,
    FILTER_BP,
    FILTER_NT
};

typedef struct
{
    int type;
    float basef, q, f, qmod, fmod;
    float lp1, bp1;

} FILTER_STRUCT;

//-----------------------------------------------------
// Morph oscillator
//-----------------------------------------------------
#define nMORPH_WAVES 3

enum MOD_TYPES
{
    MOD_MORPH,
    MOD_LEVEL,
    MOD_DET1,
    MOD_DET2,
    nMODS
};

typedef struct
{
    float inc;
    float semi;
    float fval[nMODS];

    // FILTER_STRUCT filter;

} MORPH_OSC_STRUCT;

enum GLOBAL_OSCS
{
    GOSC_FILTER,
    GOSC_NOISE,
    nGLOBALOSCS
};

typedef struct
{
    float finc;
    float fval;

} GLOBAL_OSC_STRUCT;

//-----------------------------------------------------
// Module Definition
//
//-----------------------------------------------------
struct Dronez : Module
{
    enum ParamIds
    {
        PARAM_SPEED,
        nPARAMS
    };

    enum InputIds
    {
        IN_VOCT,
        IN_RANDTRIG,
        nINPUTS
    };

    enum OutputIds
    {
        OUT_L,
        OUT_R,
        nOUTPUTS
    };

    enum LightIds
    {
        nLIGHTS
    };

    enum FADE_STATE
    {
        FADE_IDLE,
        FADE_OUT,
        FADE_IN,
    };

    // Contructor
    Dronez()
    {
        config(nPARAMS, nINPUTS, nOUTPUTS, nLIGHTS);

        putseed((int)random::u32());

        std::vector<std::string> speedLabels;
        for (int i = 0; i < 9; ++i)
        {
            char strVal[10];
            snprintf(strVal, 10, "x%.2f", speeds[i]);
            speedLabels.emplace_back(strVal);
        }
        configSwitch(PARAM_SPEED, 0.0, 8.0, 4.0, "Morph speed", speedLabels);
        configInput(IN_RANDTRIG, "Seed Randomization");
        configInput(IN_VOCT, "Drone v/oct Pitch");
        configOutput(OUT_L, "Left Drone");
        configOutput(OUT_R, "Right Drone");

        BuildDrone();
    }

    std::string m_sLabel1, m_sLabel2;
    std::bitset<nCHANNELS> m_seedOn{};
    std::atomic<int> m_flashRandom{0};

    // oscillators
    MORPH_OSC_STRUCT m_osc[nCHANNELS] = {};
    EnvelopeData m_wav[nCHANNELS][nMORPH_WAVES] = {};
    EnvelopeData m_mod[nCHANNELS][nMODS] = {};
    EnvelopeData m_global[nGLOBALOSCS];

    GLOBAL_OSC_STRUCT m_GlobalOsc[nGLOBALOSCS] = {};

    float m_cuttoff = 0.5, m_rez = 0.5;
    float m_finc[nCHANNELS][nMODS] = {};

    FILTER_STRUCT m_filter = {};

    // reverb
    REVERB_STRUCT m_reverb = {};

    // random seed
    dsp::SchmittTrigger m_SchmitTrigRand;

    int m_Seed = 0;
    int m_FadeState = FADE_IN;
    float m_fFade = 0.0f;
    float speeds[9] = {0.01f, 0.1f, 0.50f, 0.75f, 1.0f, 1.5f, 2.0f, 4.0f, 8.0f};

    void putx(int x);
    void putf(float fval);

    int getseed(void);
    void putseed(int seed);

    void ChangeSeedPending(int seed);
    void BuildWave(int ch);
    void BuildDrone(void);

    void RandWave(EnvelopeData *pEnv, float min = 0.0f, float max = 1.0f);
    void RandPresetWaveAdjust(EnvelopeData *pEnv);

    // audio
    void ChangeFilterCutoff(FILTER_STRUCT *pf, float cutfreq);
    void processFilter(FILTER_STRUCT *pfilter, float *pIn);
    void processReverb(float In, float *pL, float *pR);

    // Overrides
    void JsonParams(bool bTo, json_t *root);
    void process(const ProcessArgs &args) override;
    json_t *dataToJson() override;
    void dataFromJson(json_t *rootJ) override;
    void onRandomize() override;
};

//-----------------------------------------------------
// Dronez_SeedButton
//-----------------------------------------------------
void Dronez_SeedButton(void *pClass, int id, bool bOn)
{
    Dronez *mymodule;
    mymodule = (Dronez *)pClass;

    mymodule->m_seedOn[id] = bOn;
    mymodule->ChangeSeedPending(mymodule->getseed());
}

//-----------------------------------------------------
// Dronez_RandButton
//-----------------------------------------------------
void Dronez_RandButton(void *pClass, int id, bool bOn)
{
    Dronez *mymodule;
    mymodule = (Dronez *)pClass;

    mymodule->m_flashRandom = 5;
    mymodule->ChangeSeedPending((int)random::u32());
}

//-----------------------------------------------------
// Procedure:   Widget
//
//-----------------------------------------------------

struct Dronez_Widget : ModuleWidget
{
    Label *m_pTextLabel{nullptr};
    Label *m_pTextLabel2{nullptr};
    MyLEDButton *m_pButtonSeed[32]{};
    MyLEDButton *m_pButtonRand{nullptr};

    int m_iLastSpeed{-1};

    Dronez_Widget(Dronez *module)
    {
        int i, x, y;

        setModule(module);

        // box.size = Vec( 15*5, 380 );
        setPanel(APP->window->loadSvg(asset::plugin(thePlugin, "res/Dronez.svg")));

        addInput(createInput<MyPortInSmall>(Vec(10, 20), module, Dronez::IN_VOCT));
        addInput(createInput<MyPortInSmall>(Vec(10, 241), module, Dronez::IN_RANDTRIG));

        // random button
        m_pButtonRand =
            new MyLEDButton(40, 238, 25, 25, 20.0, DWRGB(180, 180, 180), DWRGB(255, 0, 0),
                            MyLEDButton::TYPE_MOMENTARY, 0, module, Dronez_RandButton);
        addChild(m_pButtonRand);

        addOutput(createOutput<MyPortOutSmall>(Vec(48, 20), module, Dronez::OUT_L));
        addOutput(createOutput<MyPortOutSmall>(Vec(48, 45), module, Dronez::OUT_R));

        y = 95;
        x = 9;

        // module->lg.Open("c://users//mark//documents//rack//Dronez.txt");

        for (i = 31; i >= 0; i--)
        {
            m_pButtonSeed[i] =
                new MyLEDButton(x, y, 11, 11, 8.0, DWRGB(180, 180, 180), DWRGB(255, 255, 0),
                                MyLEDButton::TYPE_SWITCH, i, module, Dronez_SeedButton);
            addChild(m_pButtonSeed[i]);

            if (i % 4 == 0)
            {
                y += 15;
                x = 9;
            }
            else
            {
                x += 15;
            }
        }

        auto kb = createParam<MSCH_Widget_Knob1>(Vec(10, 280), module, Dronez::PARAM_SPEED);
        kb->set(DWRGB(255, 255, 0), DWRGB(0, 0, 0), 13.0f);
        addParam(kb);

        m_pTextLabel2 = new Label();
        m_pTextLabel2->box.pos = Vec(30, 280);
        m_pTextLabel2->text = "x1.00";
        addChild(m_pTextLabel2);

        m_pTextLabel = new Label();
        m_pTextLabel->box.pos = Vec(0, 213);
        m_pTextLabel->text = "----";
        addChild(m_pTextLabel);

        addChild(createWidget<ScrewSilver>(Vec(30, 0)));
        addChild(createWidget<ScrewSilver>(Vec(30, 365)));
    }

    void step() override
    {
        auto az = dynamic_cast<Dronez *>(module);
        if (az)
        {
            if (m_iLastSpeed != (int)az->paramQuantities[Dronez::PARAM_SPEED]->getValue())
            {
                m_iLastSpeed = (int)az->paramQuantities[Dronez::PARAM_SPEED]->getValue();
                char strVal[10] = {};
                snprintf(strVal, 10, "x%.2f", az->speeds[m_iLastSpeed]);
                az->m_sLabel2 = strVal;
            }

            if (m_pTextLabel->text != az->m_sLabel1)
                m_pTextLabel->text = az->m_sLabel1;
            if (m_pTextLabel2->text != az->m_sLabel2)
                m_pTextLabel2->text = az->m_sLabel2;
            for (int i = 0; i < 32; i++)
            {
                m_pButtonSeed[i]->Set(az->m_seedOn[i]);
            }
            if (az->m_flashRandom > 0)
            {
                az->m_flashRandom--;
                m_pButtonRand->Set(true);
            }
            else
            {
                m_pButtonRand->Set(false);
            }
        }
        Widget::step();
    }
};

//-----------------------------------------------------
// Procedure: JsonParams
//
//-----------------------------------------------------
void Dronez::JsonParams(bool bTo, json_t *root) { JsonDataInt(bTo, "m_Seed", root, &m_Seed, 1); }

//-----------------------------------------------------
// Procedure: toJson
//
//-----------------------------------------------------
json_t *Dronez::dataToJson()
{
    json_t *root = json_object();

    if (!root)
        return NULL;

    JsonParams(TOJSON, root);

    return root;
}

//-----------------------------------------------------
// Procedure:   fromJson
//
//-----------------------------------------------------
void Dronez::dataFromJson(json_t *root)
{
    JsonParams(FROMJSON, root);

    ChangeSeedPending(m_Seed);
}

//-----------------------------------------------------
// Procedure:   onRandomize
//
//-----------------------------------------------------
void Dronez::onRandomize() { ChangeSeedPending((int)random::u32()); }

//-----------------------------------------------------
// Procedure:   getseed
//
//-----------------------------------------------------
int Dronez::getseed(void)
{
    int seed = 0, shift = 1;
    ;

    for (int i = 0; i < 32; i++)
    {
        if (m_seedOn[i])
            seed |= shift;

        shift <<= 1;
    }

    return seed;
}

//-----------------------------------------------------
// Procedure:   putseed
//
//-----------------------------------------------------
void Dronez::putseed(int seed)
{
    m_Seed = seed;

    init_rand(seed);
    putx(seed);

    for (int i = 0; i < 32; i++)
    {
        m_seedOn[i] = (bool)(seed & 1);
        seed >>= 1;
    }
}

//-----------------------------------------------------
// Procedure:   ChangeSeedPending
//
//-----------------------------------------------------
void Dronez::ChangeSeedPending(int seed)
{
    m_FadeState = FADE_OUT;
    putseed(seed);
}

//-----------------------------------------------------
// Procedure:   RandWave
//-----------------------------------------------------
void Dronez::RandWave(EnvelopeData *pEnv, float min, float max)
{
    int i;

    for (i = 0; i < ENVELOPE_HANDLES - 1; i++)
        pEnv->setVal(i, frand_mm(min, max));

    pEnv->setVal(i, pEnv->m_HandleVal[0]);
}

//-----------------------------------------------------
// Procedure:   RandPresetWaveAdjust
//-----------------------------------------------------
void Dronez::RandPresetWaveAdjust(EnvelopeData *pEnv)
{
    int i;
    float fval;

    if (frand_perc(25.0f))
    {
        RandWave(pEnv, 0.0f, 1.0f);
    }
    else
    {
        // pEnv->Preset( (int)frand_mm( 2.0f, 7.25f) );
        pEnv->Preset(EnvelopeData::PRESET_SIN);

        for (i = 0; i < ENVELOPE_HANDLES - 1; i++)
        {
            fval = clamp(pEnv->m_HandleVal[i] + frand_mm(-0.01f, 0.01f), -1.0f, 1.0f);
            pEnv->setVal(i, fval);
        }
    }
}

//-----------------------------------------------------
// Procedure:   BuildWave
//
//-----------------------------------------------------
float semis[8] = {(SEMI * 5.0f),  (SEMI * 12.0f), (SEMI * 17.0f), (SEMI * 24.0f),
                  (SEMI * 29.0f), (SEMI * 36.0f), (SEMI * 41.0f), (SEMI * 48.0f)};
void Dronez::BuildWave(int ch)
{
    if (srand() & 1)
    {
        m_osc[ch].semi = 0; // semis[ srand() & 0x7 ];
    }
    else if (srand() & 1)
    {
        m_osc[ch].semi = 1;
    }
    else
    {
        m_osc[ch].semi = 2;
    }

    // audio waves
    m_wav[ch][0].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_Audio, false, 1.0f);
    RandPresetWaveAdjust(&m_wav[ch][0]);
    // RandWave( &m_wav[ ch ][ 0 ], 0.0f, 1.0f );

    m_wav[ch][1].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_Audio, false, 1.0f);
    // RandPresetWaveAdjust( &m_wav[ ch ][ 1 ], -0.1f, 0.1f );
    m_wav[ch][1].Preset((int)frand_mm(2.0f, 7.2f));

    m_wav[ch][2].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_Audio, false, 1.0f);
    RandPresetWaveAdjust(&m_wav[ch][2]);
    // RandWave( &m_wav[ ch ][ 2 ], 0.0f, 1.0f );

    // modulation waveforms
    m_mod[ch][MOD_MORPH].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_n1to1, false, 1.0f);
    m_finc[ch][MOD_MORPH] = 1.0f / frand_mm(14.5f, 38.0f);
    RandWave(&m_mod[ch][MOD_MORPH], 0.3f, 0.7f);

    m_mod[ch][MOD_LEVEL].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_0to1, false, 1.0f);
    m_finc[ch][MOD_LEVEL] = 1.0f / frand_mm(14.5f, 38.0f);
    RandWave(&m_mod[ch][MOD_LEVEL], 0.1f, 0.4f);

    m_mod[ch][MOD_DET1].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_0to1, false, 1.0f);
    m_finc[ch][MOD_DET1] = 1.0f / frand_mm(14.5f, 38.0f);
    RandWave(&m_mod[ch][MOD_DET1], 0.01f, 0.1f);

    m_mod[ch][MOD_DET2].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_0to1, false, 1.0f);
    m_finc[ch][MOD_DET2] = 1.0f / frand_mm(14.5f, 38.0f);
    RandWave(&m_mod[ch][MOD_DET2], 0.01f, 0.1f);
}

//-----------------------------------------------------
// Procedure:   BuildDrone
//
//-----------------------------------------------------
void Dronez::BuildDrone()
{
    int i, ch;

    init_rand(m_Seed);

    for (ch = 0; ch < nCHANNELS; ch++)
    {
        BuildWave(ch);
    }

    m_global[GOSC_FILTER].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_0to1, false, 1.0f);
    m_GlobalOsc[GOSC_FILTER].finc = 1.0f / frand_mm(14.5f, 38.0f);
    RandWave(&m_global[GOSC_FILTER], 0.01f, 1.0f);
    // m_GlobalOsc[GOSC_FILTERi ].Env.Preset( EnvelopeData::PRESET_SIN );
    // RandPresetWaveAdjust( &m_GlobalOsc[GOSC_FILTER ].Env );

    m_global[GOSC_NOISE].Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_0to1, false, 1.0f);
    m_GlobalOsc[GOSC_NOISE].finc = 1.0f / frand_mm(14.5f, 38.0f);
    RandWave(&m_global[GOSC_NOISE], 0.01f, 0.3f);
    // m_GlobalOsc[GOSC_NOISE ].Env.Preset( EnvelopeData::PRESET_SIN );
    // RandPresetWaveAdjust( &m_GlobalOsc[ GOSC_NOISE ].Env );

    m_cuttoff = frand_mm(0.05f, 0.4f);
    m_rez = frand_mm(0.1f, 0.8f);

    //-----------------------------------------------------
    // Reverb
    for (i = 0; i < REV_TAPS; i++)
        m_reverb.out[i] =
            (m_reverb.in - (int)(APP->engine->getSampleRate() * frand_mm(0.01f, .1f))) &
            REV_BUF_MAX;
}

//-----------------------------------------------------
// Procedure:   putf
//
//-----------------------------------------------------
void Dronez::putf(float fval)
{
    char strVal[10] = {};

    snprintf(strVal, 10, "%.3f", fval);
    m_sLabel1 = strVal;
}

//-----------------------------------------------------
// Procedure:   putf
//
//-----------------------------------------------------
void Dronez::putx(int x)
{
    char strVal[10] = {};

    snprintf(strVal, 10, "%.8X", x);
    m_sLabel1 = strVal;
}

//-----------------------------------------------------
// Procedure:   ChangeFilterCutoff
//
//-----------------------------------------------------
void Dronez::ChangeFilterCutoff(FILTER_STRUCT *pf, float cutfreq)
{
    float fx, fx2, fx3, fx5, fx7;

    // clamp at 1.0 and 20/samplerate
    cutfreq = fmax(cutfreq, 20 / APP->engine->getSampleRate());
    cutfreq = fmin(cutfreq, 1.0);

    // calculate eq rez freq
    fx = 3.141592 * (cutfreq * 0.026315789473684210526315789473684) * 2 * 3.141592;
    fx2 = fx * fx;
    fx3 = fx2 * fx;
    fx5 = fx3 * fx2;
    fx7 = fx5 * fx2;

    pf->f = 2.0 * (fx - (fx3 * 0.16666666666666666666666666666667) +
                   (fx5 * 0.0083333333333333333333333333333333) -
                   (fx7 * 0.0001984126984126984126984126984127));
}

//-----------------------------------------------------
// Procedure:   Filter
//
//-----------------------------------------------------
#define MULTI (0.33333333333333333333333333333333f)
void Dronez::processFilter(FILTER_STRUCT *pf, float *pIn)
{
    float rez, hp1;
    float input, lowpass, bandpass, highpass;

    rez = 1.0 - m_rez;

    input = *pIn / AUDIO_MAX;

    input = input + 0.000000001;

    pf->lp1 = pf->lp1 + pf->f * pf->bp1;
    hp1 = input - pf->lp1 - rez * pf->bp1;
    pf->bp1 = pf->f * hp1 + pf->bp1;
    lowpass = pf->lp1;
    highpass = hp1;
    bandpass = pf->bp1;

    pf->lp1 = pf->lp1 + pf->f * pf->bp1;
    hp1 = input - pf->lp1 - rez * pf->bp1;
    pf->bp1 = pf->f * hp1 + pf->bp1;
    lowpass = lowpass + pf->lp1;
    highpass = highpass + hp1;
    bandpass = bandpass + pf->bp1;

    input = input - 0.000000001;

    pf->lp1 = pf->lp1 + pf->f * pf->bp1;
    hp1 = input - pf->lp1 - rez * pf->bp1;
    pf->bp1 = pf->f * hp1 + pf->bp1;

    lowpass = (lowpass + pf->lp1) * MULTI;
    highpass = (highpass + hp1) * MULTI;
    bandpass = (bandpass + pf->bp1) * MULTI;

    /*switch( pf->type )
    {
    case FILTER_LP:
        out = lowpass;
        break;
    case FILTER_HP:
        out  = highpass;
        break;
    case FILTER_BP:
        out  = bandpass;
        break;
    case FILTER_NT:
        out  = lowpass + highpass;
        break;
    default:
        return;
    }*/

    *pIn = lowpass * AUDIO_MAX;
}

//-----------------------------------------------------
// Procedure:   processReverb
//
//-----------------------------------------------------
void Dronez::processReverb(float In, float *pL, float *pR)
{
    float fl = 0, fr = 0, rin;

    for (int i = 0; i < REV_TAPS; i++)
    {
        rin = m_reverb.buf[m_reverb.out[i]++] * 0.2f;
        m_reverb.out[i] &= REV_BUF_MAX;

        if (i < (REV_TAPS / 2))
            fl += rin;
        else
            fr += rin;
    }

    m_reverb.buf[m_reverb.in++] = In;
    m_reverb.in &= REV_BUF_MAX;

    *pL = (In * .3) + fl;
    *pR = (In * .3) + fr;
}

//-----------------------------------------------------
// Procedure:   step
//
//-----------------------------------------------------
void Dronez::process(const ProcessArgs &args)
{
    float out = 0.0f, In = 0.0f, outL = 0.0f, outR = 0.0f, fmorph[nCHANNELS], fcv = 0.0f;
    int ch, i, wv;

    // randomize trigger
    if (m_SchmitTrigRand.process(inputs[IN_RANDTRIG].getNormalVoltage(0.0f)))
    {
        m_flashRandom = 5;
        ChangeSeedPending((int)random::u32());
    }

    switch (m_FadeState)
    {
    case FADE_OUT:
        m_fFade -= 0.00005f;
        if (m_fFade <= 0.0f)
        {
            m_fFade = 0.0f;
            BuildDrone();
            m_FadeState = FADE_IN;
        }
        break;
    case FADE_IN:
        m_fFade += 0.00005f;
        if (m_fFade >= 1.0f)
        {
            m_fFade = 1.0f;
            m_FadeState = FADE_IDLE;
        }
        break;
    case FADE_IDLE:
    default:
        break;
    }

    for (i = 0; i < nGLOBALOSCS; i++)
    {
        m_global[i].m_Clock.syncInc =
            m_GlobalOsc[i].finc * speeds[(int)params[PARAM_SPEED].getValue()];
        m_GlobalOsc[i].fval = m_global[i].procStep(false, false);
    }

    // process oscillators
    for (ch = 0; ch < nCHANNELS; ch++)
    {
        // process modulation waves
        for (i = 0; i < nMODS; i++)
        {
            m_mod[ch][i].m_Clock.syncInc =
                m_finc[ch][i] * speeds[(int)params[PARAM_SPEED].getValue()];
            m_osc[ch].fval[i] = m_mod[ch][i].procStep(false, false);
        }

        // wav morph modulation
        memset(fmorph, 0, sizeof(fmorph));

        fcv = m_osc[ch].fval[MOD_MORPH];
        fmorph[1] = 1.0 - fabs(fcv);

        // left wave
        if (fcv <= 0.0f)
        {
            fmorph[0] = -fcv;
        }
        else
        {
            fmorph[2] = fcv;
        }

        In = 0.0f;

        // get wave audio
        for (wv = 0; wv < nMORPH_WAVES; wv++)
        {
            m_wav[ch][wv].m_Clock.syncInc =
                32.7032f *
                clamp(powf(2.0f, clamp(inputs[IN_VOCT].getNormalVoltage(3.0f) + m_osc[ch].semi,
                                       0.0f, VOCT_MAX)),
                      0.0f, 4186.01f);

            if (wv == 0)
            {
                m_wav[ch][wv].m_Clock.syncInc += m_osc[ch].fval[MOD_DET1];
            }
            else if (wv == 2)
            {
                m_wav[ch][wv].m_Clock.syncInc += m_osc[ch].fval[MOD_DET2];
            }

            In += m_wav[ch][wv].procStep(false, false) * fmorph[wv];
        }

        if (wv != 1)
            out += In * m_osc[ch].fval[MOD_LEVEL];
        else
            out += In;
    }

    if (frand_perc(75.0f))
        out += frand_mm(-1.0f, 1.0f) * m_GlobalOsc[GOSC_NOISE].fval;

    // filter
    ChangeFilterCutoff(&m_filter, m_cuttoff * m_GlobalOsc[GOSC_FILTER].fval);
    processFilter(&m_filter, &out);

    out *= m_fFade;

    processReverb(out, &outL, &outR);

    outputs[OUT_L].setVoltage(outL);
    outputs[OUT_R].setVoltage(outR);
}

Model *modelDronez = createModel<Dronez, Dronez_Widget>("Dronez");
