#include "WCharacter.h"
#include "wiring_analog.h"
#include <stdint.h>
#include <limits>
#ifndef Potentiometers_h
#define Potentiometers_h
#include "Arduino.h"
#include "luts.h"
#include "helpers.h"

#include "Adafruit_SH110X.h"
#include "Adafruit_GFX.h"
extern Adafruit_SH1106G display;

// The length of the moving average filter that smooths the
// controls. Higher number is smoother, but less responsive
// and needs more memory.
#define POT_MOVING_AVERAGE_SIZE 2

// Length of the Textbuffer for floats in the UI
#define UI_TEXTBUFFER_LENGTH 6

// Modes
enum PotMode {
  POT_MODE_LIN,
  POT_MODE_BIP,
  POT_MODE_PITCH,
  POT_MODE_SWITCH,
  POT_MODE_LAST
};

// Display Modes
enum PotDisplayMode {
  POT_DISPLAY_MODE_DEFAULT,
  POT_DISPLAY_MODE_PITCH,
  POT_DISPLAY_MODE_PERCENT,
  POT_DISPLAY_MODE_SWITCH,
  POT_DISPLAY_MODE_SWITCH_NUMBERS,
  POT_DISPLAY_MODE_LAST
};

typedef void (*callback_function)(void);

class Potentiometer {
  int pin;
  int readings[POT_MOVING_AVERAGE_SIZE];
  PotMode mode = POT_MODE_LIN;
  PotDisplayMode display_mode = POT_DISPLAY_MODE_DEFAULT;
  float last_reading, last_normalized_reading = 0.0f;
  float display_scale = 1.0f;
  callback_function onChangeFunction;
  Easer easer;

  public:
    Potentiometer(int pin);
    void init();
    void setLinear();
    void setBipolar();
    void setPitch();
    void setSwitch();
    float read();
    void setOnChange(callback_function f);
    void renderUi();
    void setDisplayMode(const char *name, float display_scale, PotDisplayMode display_mode);
    const char *name;
    double last_displayed = 0.0;
    bool should_display = false;
    bool display_value_changes = false;
    bool last_was_nan = false;
    uint8_t switch_positions;
    uint8_t switch_offset = 0;
    const char* const switch_labels[2] = {"LFO", "RANDOM"};
};

Potentiometer::Potentiometer(int pin) {
  this->pin = pin;
}

void Potentiometer::init() {
  analogReadResolution(12);
  easer.setFactor(0.001);
}

void Potentiometer::setLinear() {
  this->mode = POT_MODE_LIN;
}

void Potentiometer::setBipolar() {
  this->mode = POT_MODE_BIP;
}

void Potentiometer::setPitch() {
  this->mode = POT_MODE_PITCH;
}

void Potentiometer::setSwitch() {
  this->mode = POT_MODE_SWITCH;
}

float Potentiometer::read() {
  int reading = analogRead(this->pin);
  // Shift all readings in the buffer over by one position, deleting the oldest
  // and adding the newest
  for (int i=0; i<POT_MOVING_AVERAGE_SIZE; i++) {
    int next = i+1;
    if (next < POT_MOVING_AVERAGE_SIZE) {
      (this->readings)[i] = (this->readings)[next];
    }
  }
  (this->readings)[POT_MOVING_AVERAGE_SIZE-1] = reading;

  // Get the average of the last readings
  reading = 0;
  for (int i=0; i<POT_MOVING_AVERAGE_SIZE; i++) {
    reading += (this->readings)[i];
  }
  reading = reading / POT_MOVING_AVERAGE_SIZE;

  // Convert the last reading to a float and return
  float current_reading = easer.Process(reading / 4096.0f);
  float normalized_reading = current_reading;

  // Depending on the Mode
  switch (this->mode) {
    case POT_MODE_BIP:
      current_reading = get_from_table(bip_lookup, current_reading, bip_lookup_length);
      current_reading = (current_reading - 0.5f) * 2.0f;
      break;
    case POT_MODE_PITCH:
      current_reading = get_from_xy_table(pitch_knob_lookup_x, pitch_knob_lookup_y, current_reading, pitch_knob_lookup_length);
      break;
    case POT_MODE_SWITCH:
      current_reading = int(current_reading * switch_positions);
      break;
  }

  bool changed = abs(normalized_reading - this->last_normalized_reading) > 0.002;

  // If the difference to the last reading is big enough assume the knob has been touched
  if (this->last_normalized_reading && changed) {
    if (display_value_changes) {
      last_displayed = millis();
      should_display = true;
    }
    if (this->onChangeFunction) { this->onChangeFunction(); }
  }

  if (this->last_normalized_reading && !changed) {
    // if (!last_was_nan) {
    //   Serial.print(this->name);
    //   Serial.println(" returned NaN");
    // }
    last_was_nan = true;
    return std::numeric_limits<float>::quiet_NaN();
  }
  last_was_nan = false;
  this->last_reading = current_reading;
  this->last_normalized_reading = normalized_reading;
  return current_reading;
}

void Potentiometer::setOnChange(callback_function f) {
  this->onChangeFunction = f;
}

void Potentiometer::setDisplayMode(const char *name, float display_scale, PotDisplayMode display_mode) {
  this->display_value_changes = true;
  this->name = name;
  this->display_scale = display_scale;
  this->display_mode = display_mode;
}

void Potentiometer::renderUi() {
  double now = millis();
    if (this->should_display) {
      int x_margin = 28;
      int y_margin = 13;
      int x_center = display.width()/2;
      int y_center = display.height()/2;
      // Render a rectangle Backdrop for the text
      display.fillRect(3+x_margin, 3+y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE);
      display.fillRect(x_margin, y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE);
      display.fillRect(x_margin+1, y_margin+1, display.width()-(x_margin+1)*2, display.height()-(y_margin+1)*2, SH110X_BLACK);
      

      // Render the name of the parameter (e.g. "Pitch")
      centeredText(this->name, x_center, y_center-4, SH110X_WHITE);
      
      // Choose how many digits to display depending on the mode
      int digits = 2;
      if (this->display_mode == POT_DISPLAY_MODE_PERCENT) { digits = 0; }
      // Allocate a buffer for the float and convert it into characters
      char value_buffer[UI_TEXTBUFFER_LENGTH]; // Buffer big enough for 7-character float
      dtostrf(this->last_reading*display_scale, 6, digits, value_buffer); // Leave room for too large numbers!
      
      // If we are on a bipolar pot display an indicator if we are in the center
      if (this->mode == POT_MODE_BIP && this->last_reading > -0.0001 && this->last_reading < 0.0001) {
        display.fillTriangle(x_center, y_center+10, x_center+3, y_center+15, x_center-3, y_center+15, SH110X_WHITE);
      }

      // If we are on a pitch pot display an indicator if we are in the the right steps
      if (this->mode == POT_MODE_PITCH) {
        float reading_mod = fmod(abs(this->last_reading), 0.05f);
        if (reading_mod > 0.999f || reading_mod < 0.001f) {
          display.fillTriangle(x_center, y_center+10, x_center+3, y_center+15, x_center-3, y_center+15, SH110X_WHITE);
        }
      }

      // The float value may contain some empty whitespace characters, remove them by
      // first figuring out which the first actual character is
      int nonwhite = 0;
      for (int i=0; i<UI_TEXTBUFFER_LENGTH; i++) {
        if (value_buffer[i] == ' ') {
          nonwhite++;
        }
      }

      // Create a new buffer that can hold everything
      char text_buffer[UI_TEXTBUFFER_LENGTH+6];

      // Copy all non-white characters over
      for (int i = 0; i<UI_TEXTBUFFER_LENGTH; i++) {
        text_buffer[i] = value_buffer[i+int(nonwhite)];
      }

      // Figure out where the last character (\0) in our new buffer is
      int last = UI_TEXTBUFFER_LENGTH+6;
      for (int i=16; i>0; i--) {
        if (text_buffer[i] == '\0') {
          last = i;
        }
      }

      // Add units depending on the display mode : )
      if (this->display_mode == POT_DISPLAY_MODE_PERCENT) {
        text_buffer[last] = ' ';
        text_buffer[last+1] = '%';
        text_buffer[last+2] = '\0';
      } else if (this->display_mode == POT_DISPLAY_MODE_PITCH) {
        text_buffer[last] = ' ';
        text_buffer[last+1] = 'S';
        text_buffer[last+2] = 'e';
        text_buffer[last+3] = 'm';
        text_buffer[last+4] = 'i';
        text_buffer[last+5] = '\0';
      }

      // Render that new text
      if (this->display_mode == POT_DISPLAY_MODE_SWITCH) {
        centeredText(switch_labels[int(last_reading+switch_offset)], x_center, y_center+4, SH110X_WHITE);
      } else if (this->display_mode == POT_DISPLAY_MODE_SWITCH_NUMBERS) {
        sprintf(text_buffer, "%d", int(last_reading));  
        centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE);
      } else {
        centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE);
      }
    }

    // Show this for 700 ms after it has been last touched
    if ((now - this->last_displayed) > 700.0) {
      this->should_display = false;
    }
}


#endif