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

//-----------------------------------------------------
// General Definition
//-----------------------------------------------------
#define nCHANNELS 2

//-----------------------------------------------------
// 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 2

enum MOD_TYPES
{
    MOD_LEVEL,
    MOD_REZ,
    nMODS
};

#define nBITS 2048

typedef struct
{
    int state;
    int baud;
    float finc;
    float fcount;
    float fout;
    int bits[nBITS];

} DIGITAL_OSC_STRUCT;

#define OSC2_NOTES 5

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

    enum InputIds
    {
        IN_RANDTRIG,
        IN_GATE,
        nINPUTS
    };

    enum OutputIds
    {
        OUT,
        nOUTPUTS
    };

    enum LightIds
    {
        nLIGHTS
    };

    enum FADE_STATE
    {
        FADE_IDLE,
        FADE_OUT,
        FADE_IN,
    };

    // Contructor
    Alienz()
    {
        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_GATE, "Signal Activation Gate");
        configOutput(OUT, "Waveform");

        BuildDrone();
    }

    // osc
    DIGITAL_OSC_STRUCT m_bitosc = {};

    EnvelopeData m_osc2;
    float m_osc2notes[OSC2_NOTES];
    float m_osc2freq;

    // modulation envelopes
    EnvelopeData m_mod[nCHANNELS][nMODS] = {};
    float m_fval[nCHANNELS][nMODS] = {};
    float m_finc[nCHANNELS][nMODS] = {};

    FILTER_STRUCT m_filter[nCHANNELS] = {};

    // random seed
    dsp::SchmittTrigger m_SchmitTrigRand;

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

    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(int ch, float f);
    void processFilter(int ch, float *pIn);

    // 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;
};

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

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

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

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

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

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

    int m_iLastSpeed{-1};

    Alienz_Widget(Alienz *module)
    {
        int i, x, y;

        setModule(module);

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

        addInput(createInput<MyPortInSmall>(Vec(10, 20), module, Alienz::IN_GATE));
        addInput(createInput<MyPortInSmall>(Vec(10, 241), module, Alienz::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, Alienz_RandButton);
        addChild(m_pButtonRand);

        addOutput(createOutput<MyPortOutSmall>(Vec(48, 20), module, Alienz::OUT));

        y = 95;
        x = 9;

        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, Alienz_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, Alienz::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<Alienz *>(module);
        if (az)
        {
            if (m_iLastSpeed != (int)az->paramQuantities[Alienz::PARAM_SPEED]->getValue())
            {
                m_iLastSpeed = (int)az->paramQuantities[Alienz::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 Alienz::JsonParams(bool bTo, json_t *root) { JsonDataInt(bTo, "m_Seed", root, &m_Seed, 1); }

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

    if (!root)
        return NULL;

    JsonParams(TOJSON, root);

    return root;
}

//-----------------------------------------------------
// Procedure:   fromJson
//
//-----------------------------------------------------
void Alienz::dataFromJson(json_t *root)
{
    // char strVal[ 10 ] = {};

    JsonParams(FROMJSON, root);

    ChangeSeedPending(m_Seed);

    // sprintf( strVal, "x%.2f", speeds[ (int)params[ PARAM_SPEED ].value ] );
    // m_pTextLabel2->text = strVal;
}

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

//-----------------------------------------------------
// Procedure:   getseed
//
//-----------------------------------------------------
int Alienz::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 Alienz::putseed(int seed)
{
    m_Seed = seed;

    init_rand(seed);
    putx(seed);

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

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

//-----------------------------------------------------
// Procedure:   RandWave
//-----------------------------------------------------
void Alienz::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 Alienz::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
//
//-----------------------------------------------------
void Alienz::BuildWave(int ch)
{
    // modulation waveforms
    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);

    if (ch == 0)
        RandWave(&m_mod[ch][MOD_LEVEL], 0.8f, 0.9f);
    else
        RandWave(&m_mod[ch][MOD_LEVEL], 0.1f, 0.4f);

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

//-----------------------------------------------------
// Procedure:   BuildDrone
//
//-----------------------------------------------------
int bauds[4] = {2400, 4800, 9600, 19200};
void Alienz::BuildDrone()
{
    int ch, i;
    int byte;
    int bit;

    init_rand(m_Seed);

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

        ChangeFilterCutoff(ch, frand_mm(0.1, 0.5));
    }

    // set up osc
    m_bitosc.baud = bauds[srand() & 3];

    m_bitosc.finc = APP->engine->getSampleRate() / frand_mm(100.0f, 400.0f);

    m_bitosc.fcount = 0.0f;

    m_bitosc.fout = 0.0f;

    for (i = 0; i < nBITS; i += 8)
    {
        byte = (int)frand_mm((float)0x20, (float)0x80);

        for (bit = 0; bit < 8; bit++)
        {
            m_bitosc.bits[i + bit] = (byte >> bit) & 1;
        }
    }

    // osc 2
    m_osc2.Init(EnvelopeData::MODE_LOOP, EnvelopeData::RANGE_n1to1, false, 1.0f);
    m_osc2.Preset(EnvelopeData::PRESET_TRI_FULL);

    for (i = 0; i < OSC2_NOTES; i++)
        m_osc2notes[i] = frand_mm(3.0f, 6.0f);

    m_osc2freq = APP->engine->getSampleRate() / frand_mm(60.0f, 90.0f);
}

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

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

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

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

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

    pf = &m_filter[ch];

    cutfreq = f;

    // 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 Alienz::processFilter(int ch, float *pIn)
{
    float rez, hp1;
    float input, lowpass, bandpass, highpass;
    FILTER_STRUCT *pf;

    rez = 1.0 - m_fval[ch][MOD_REZ];

    pf = &m_filter[ch];

    input = *pIn;

    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;
}

//-----------------------------------------------------
// Procedure:   step
//
//-----------------------------------------------------
void Alienz::process(const ProcessArgs &args)
{
    float In = 0.0f, fout;
    int i, ch;
    static int bcount = 0, note = 0, fcount = 0;

    // 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.0005f;
        if (m_fFade <= 0.0f)
        {
            m_fFade = 0.0f;
            BuildDrone();
            m_FadeState = FADE_IN;
        }
        break;
    case FADE_IN:
        m_fFade += 0.0005f;
        if (m_fFade >= 1.0f)
        {
            m_fFade = 1.0f;
            m_FadeState = FADE_IDLE;
        }
        break;
    case FADE_IDLE:
    default:
        break;
    }

    if (inputs[IN_GATE].isConnected())
    {
        if (inputs[IN_GATE].value < 0.000001)
        {
            outputs[OUT].setVoltage(0.0f);
            return;
        }
    }

    // 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_fval[ch][i] = m_mod[ch][i].procStep(false, false);
        }
    }

    m_bitosc.fcount -= 1.0f;

    // change
    if (m_bitosc.fcount <= 0.0f)
    {
        m_bitosc.fcount += m_bitosc.finc;

        if (m_bitosc.state == 2)
            m_bitosc.state = m_bitosc.bits[(bcount++) & (nBITS - 1)];
        else
            m_bitosc.state = 2;

        switch (m_bitosc.state)
        {
        case 0:
            m_bitosc.fout = -0.7f;
            break;
        case 1:
            m_bitosc.fout = 0.7f;
            break;
        case 2:
            m_bitosc.fout = 0.0f;
            break;
        }
    }

    In = m_bitosc.fout;
    processFilter(0, &In);

    // osc2
    fcount -= 1.0f;

    // change
    if (fcount <= 0.0f)
    {
        fcount += m_osc2freq;

        if (++note >= OSC2_NOTES)
            note = 0;

        m_osc2.m_Clock.syncInc = 32.7032f * clamp(powf(2.0f, m_osc2notes[note]), 0.0f, 4186.01f);
    }

    fout = m_osc2.procStep(false, false);

    processFilter(1, &fout);

    fout *= AUDIO_MAX * m_fval[1][MOD_LEVEL];

    fout += In * m_fval[0][MOD_LEVEL] * AUDIO_MAX;

    outputs[OUT].setVoltage(fout * m_fFade);
}

Model *modelAlienz = createModel<Alienz, Alienz_Widget>("Alienz");
