#include "DaisyDuino.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <MIDI.h>

#include "leds.h"
#include "potentiometers.h"
#include "buttons.h"
#include "looper.h"
#include "env_follower.h"
#include "helpers.h"
#include "luts.h"
#include "ui.h"

MIDI_CREATE_DEFAULT_INSTANCE();
#define BUFFER_LENGTH_SECONDS 5

#define DEBUGMODE

static const size_t buffer_length = 48000 * BUFFER_LENGTH_SECONDS;
static float DSY_SDRAM_BSS buffer[buffer_length];
static float DSY_SDRAM_BSS buffer_b[buffer_length];
static float DSY_SDRAM_BSS buffer_c[buffer_length];
static float DSY_SDRAM_BSS buffer_d[buffer_length];
static float DSY_SDRAM_BSS buffer_e[buffer_length];


// Create instances of audio stuff
atoav::Looper looper_a, looper_b, looper_c, looper_d, looper_e;
static atoav::EnvelopeFollower input_envelope_follower;
DSY_SDRAM_BSS ReverbSc reverb;
static Compressor compressor;
Oscillator lfo;
static SampleHold sample_and_hold;
static WhiteNoise noise;
static Metro tick;
static Easer easer;

// Initialize Buttons  
Button button_1 = Button(D7);
Button button_2 = Button(D8);
Button button_3 = Button(D9);
Button button_4 = Button(D10);
Button button_5 = Button(D13);
Button button_6 = Button(D27);

// Initialize Potentiometers
Potentiometer pot_1 = Potentiometer(A0);
Potentiometer pot_2 = Potentiometer(A1);
Potentiometer pot_3 = Potentiometer(A3);
Potentiometer pot_4 = Potentiometer(A2);
Potentiometer pot_5 = Potentiometer(A4);
Potentiometer pot_6 = Potentiometer(A5);
Potentiometer pot_7 = Potentiometer(A6);

// RGB LED               R    G    B
RGBLed rgb_led = RGBLed(A10, A11, A11);


// OLED Display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1   //   QT-PY / XIAO
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// User Interface
Ui ui;

// Daisy
DaisyHardware hw;

// Variables for the Audio-Callback
size_t num_channels;
float blocksize;
float drywetmix = 0.0f;
float volume = 1.0f;
float reverbmix = 0.0f;
float lfo_amount = 0.0f;
float pitch_val = 0.5f;
float midi_pitch_offset = 0.0f;

float reverb_tone = 15000.0f;
float reverb_decay = 0.95f;
float lfo_speed = 8.0f;
int lfo_kind = 0;

float pressure = 0.0f;

// Actual audio-processing is orchestrated here
void AudioCallback(float **in, float **out, size_t size) {
  float output = 0.0f;
  float out1, out2;
  reverb.SetFeedback(reverb_decay);
  reverb.SetLpFreq(reverb_tone);
  lfo.SetFreq(lfo_speed);
  tick.SetFreq(lfo_speed);
  
  // Iterate through the samples in the buffer
  for (size_t i = 0; i < size; i++) {
    uint8_t trig = tick.Process();
    float lfo_value = lfo.Process();
    float noise_value = noise.Process();

    float rand = easer.Process(
      sample_and_hold.Process(
        trig, 
        noise_value * 5.0f, 
        sample_and_hold.MODE_SAMPLE_HOLD
    ));
    if (ui.rec_source == REC_SOURCE_NOISE) {
     ui.activeLooper()->Record(noise_value * 0.7f);
    }
    // When the metro ticks, trigger the envelope to start.
    float random_amount = (lfo_amount -0.5f) * 2.0;
    if (trig) {
      // Random LFO
      if (lfo_kind == 1) {
        ui.activeLooper()->addToPlayhead(rand * random_amount * 48000.0f);
        ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset);
      }
    }

    // Add the LFO to the signal if it is active
    if (lfo_kind == 0) {
      ui.activeLooper()->setPlaybackSpeed(pitch_val + lfo_value * lfo_amount + midi_pitch_offset);
    } else {
      ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset);
    }
    

    float looper_out;
    
    if (ui.rec_source == REC_SOURCE_PRE) {
     ui.activeLooper()->Record(in[1][i]);
    }
    
    // Process the input envelope
    input_envelope_follower.Process(in[1][i]);

    // Process the Looper
    switch (ui.buffer_summing_mode) {
      case BUFFER_SUM_MODE_SOLO:
        // Only play active looper
        looper_out = saturate(ui.activeLooper()->Process());
        break;
      case BUFFER_SUM_MODE_SUM:
        // Sum all loopers
        looper_out = looper_a.Process();
        looper_out += looper_b.Process();
        looper_out += looper_c.Process();
        looper_out += looper_d.Process();
        looper_out += looper_e.Process();
        looper_out = saturate(looper_out);
        break;
      case BUFFER_SUM_MODE_RING:
        // Sum all loopers and ringmodulate with input
        looper_out = looper_a.Process();
        looper_out += looper_b.Process();
        looper_out += looper_c.Process();
        looper_out += looper_d.Process();
        looper_out += looper_e.Process();
        float deadbanded_input;
        if (in[1][i] < 0.0f) {
          deadbanded_input = min(0.0f, in[1][i] + 0.05f);
        } else {
          deadbanded_input = max(0.0f, in[1][i] - 0.05f);
        }
        looper_out *= deadbanded_input*2.0f;
        looper_out = saturate(looper_out);
        break;
    }

    // looper_out = pressure * looper_out;
    looper_out = volume * looper_out;

    // Mix the dry/Wet of the looper
    output = drywetmix * looper_out + in[1][i] * (1.0f - drywetmix);

    // Compress the signal
    compressor.Process(output);

    // Process reverb
    reverb.Process(output, output, &out1, &out2);

    // Mix reverb with the dry signal depending on the amount dialed
    output = output * (1.0f - reverbmix) + out1 * reverbmix;

    // Record the output if needed
    if (ui.rec_source == REC_SOURCE_OUT) {
     ui.activeLooper()->Record(output);
    }
    
    out[0][i] = out[1][i] = output;
  }
}

// TODO: Add Voice Stealing/Multiple Playheads?
// TODO: Is USB Midi possible? https://github.com/electro-smith/DaisyExamples/blob/master/seed/USB_MIDI/USB_MIDI.cpp

void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) {
  #ifdef DEBUGMODE
  Serial.print("[MIDI ON] chn<");
  Serial.print((int) inChannel);
  Serial.print("> note<");
  Serial.print((int) inNote);
  Serial.print("> velocity<");
  Serial.print((int) inVelocity);
  Serial.println(">");
  #endif
  // Note Off can come in as Note On w/ 0 Velocity
  if (inVelocity == 0.0f) {
    midi_pitch_offset = 0.0f;
  } 
  else {
    midi_pitch_offset = (int(inNote)-36.0)/12.0f;
  }
}



void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) {
  #ifdef DEBUGMODE
  Serial.print("[MIDI OFF] chn<");
  Serial.print((int) inChannel);
  Serial.print("> note<");
  Serial.print((int) inNote);
  Serial.print("> velocity<");
  Serial.print((int) inVelocity);
  Serial.println(">");
  #endif
  midi_pitch_offset = 0.0f;
}

void handleAftertouch(byte channel, byte channel_pressure) {
  #ifdef DEBUGMODE
  Serial.print("[MIDI AFTER] chn<");
  Serial.print((int) channel);
  Serial.print("> pressure<");
  Serial.print((int) channel_pressure);
  Serial.println(">");
  #endif
  pressure = float(channel_pressure)/127.0f;
}



void setup() {
  float sample_rate;
  // Initialize for Daisy pod at 48kHz
  hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K);
  DAISY.SetAudioBlockSize(64);
  num_channels = hw.num_channels;
  sample_rate = DAISY.get_samplerate();
  blocksize = 64.0f;

  // Create a Tick and a noise source for the Sample and Hold
  tick.Init(10, sample_rate);
  noise.Init();
  
  // Initialize Looper with the buffer
  looper_a.Init(buffer, buffer_length);
  looper_b.Init(buffer_b, buffer_length);
  looper_c.Init(buffer_c, buffer_length);
  looper_d.Init(buffer_d, buffer_length);
  looper_e.Init(buffer_e, buffer_length);

  // Initialize Envelope Follower for the Level LED
  input_envelope_follower.Init(sample_rate);
  input_envelope_follower.SetAttack(100.0);
  input_envelope_follower.SetDecay(1000.0);

  // Initialize Reverb
  reverb.Init(sample_rate);
  reverb.SetFeedback(reverb_decay);
  reverb.SetLpFreq(reverb_tone);

  // Initialize Compressor
  compressor.SetThreshold(-64.0f);
  compressor.SetRatio(2.0f);
  compressor.SetAttack(0.005f);
  compressor.SetRelease(0.1250);

  // Initialize the LFO for modulations
  lfo.Init(sample_rate);
  lfo.SetWaveform(Oscillator::WAVE_TRI);
  lfo.SetAmp(1);
  lfo.SetFreq(lfo_speed);

  // Easer for the random jumps
  easer.setFactor(0.001);

  // Start serial communications
  Serial.begin(250000);

  // Initialize Display
  display.begin(0x3C, true);
  delay(50);
  ui.Render();
  
  // Initialize the LED
  rgb_led.init();

  // Set the analog read and write resolution to 12 bits
  analogReadResolution(12);

  // Setup MIDI handlers
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandleAfterTouchChannel(handleAftertouch);
  
  MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages

  // Set Knob names and display functions
  pot_1.name = "Start";
  pot_2.name = "Length";
  pot_3.setDisplayMode("Pitch", 1000.0f, POT_DISPLAY_MODE_PERCENT);
  pot_4.setDisplayMode("Mix", 100.0f, POT_DISPLAY_MODE_PERCENT);
  pot_5.setDisplayMode("LFO", 100.0f, POT_DISPLAY_MODE_PERCENT);
  pot_6.setDisplayMode("Volume", 400.0f, POT_DISPLAY_MODE_PERCENT);
  pot_7.setDisplayMode("Reverb", 100.0f, POT_DISPLAY_MODE_PERCENT);

  // Set Knob Scaling Modes
  // pot_3.setBipolar();
  pot_3.setPitch();

  // Initialize Buttons (callbacks are assigned in the Ui class)
  button_1.init();
  button_2.init();
  button_3.init();
  button_4.init();
  button_5.init();
  button_6.init();

  // Start the audio Callback
  DAISY.begin(AudioCallback);
}

void loop() {
  // Read the values from the potentiometers
  float p1 = pot_1.read();
  float p2 = pot_2.read();
  float p3 = pot_3.read();
  float p4 = pot_4.read();
  float p5 = pot_5.read();
  float p6 = pot_6.read();
  float p7 = pot_7.read();

  // Update the UI
  ui.update();

  // Read the buttons
  button_1.read();
  button_2.read();
  button_3.read();
  button_4.read();
  button_5.read();
  button_6.read();

  // Set loop-start and loop-length with the potentiometers
  ui.setLoop(p1, p2);
  
  // Tune the pitch to the ten-fold of the potentiometer 3,
  // a bipolar pot, so it returns values from -1.0 to +1.0
  // Pitch should go 10 octaves up/down (10*1.0 = 1000%)
  if (!isnan(p3)) {pitch_val = 10.0f * p3; }

  // Set other parameters (from 0.0 to 1.0)
  if (!isnan(p4)) { drywetmix = p4; }

  switch (ui.fx_mode) {
    case FX_MODE_ALL:
      if (!isnan(p5)) {  lfo_amount = p5; }
      if (!isnan(p6)) {  volume = p6 * 4.0f; }
      if (!isnan(p7)) {  reverbmix = p7; }
      break;
    case FX_MODE_REVERB:
      if (!isnan(p5)) {  reverb_tone = 50.0f + p5 * 20000.0f; }
      if (!isnan(p6)) {  reverb_decay = 0.05f + p6 * 0.94f; }
      if (!isnan(p7)) {  reverbmix = p7; }
      break;
    case FX_MODE_LFO:
      if (!isnan(p5)) {  lfo_kind = p5; }
      if (!isnan(p6)) {  lfo_speed = (p6 * p6 *p6) * 100.0f; }
      if (!isnan(p7)) {  lfo_amount = p7; }
      break;
    case FX_MODE_GRAIN:
      if (!isnan(p5)) {  ui.activeLooper()->grain_count = 1+int(p5); }
      if (!isnan(p6)) {  ui.activeLooper()->grain_spread = p6*10.0f; }
      if (!isnan(p7)) {  ui.activeLooper()->grain_variation = p7; }
      break;
  }

  // Render the UI (frame rate limited by UI_MAX_FPS in ui.h)
  // double start = millis();
  ui.Render();
  // Serial.print("ui Render took ");
  // Serial.print(millis()-start);
  // Serial.println("ms");

  // Set the Color and brightness of the RGB LED in 8 bits
  rgb_led.setAudioLevelIndicator(input_envelope_follower.getValue());

  // MIDI
  MIDI.read();
}




