#include <Arduino.h>
#include <WiFi.h>
#include <Audio.h>
#include <Preferences.h>
#include <AiEsp32RotaryEncoder.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <string>
#define ENCODER_A_PIN 32  // dt
#define ENCODER_B_PIN 33  // clk
#define ENCODER_SWITCH 35 // sw
#define ENCODER_STEPS 4
#define I2S_DOUT 25
#define I2S_BCLK 26
#define I2S_LRC 27
#define ADC_PIN 36

String ssid = "Your WiFi SSID";
String password = "Your Router Password";
String hostname = "ESP32 Solar Radio";
String radio_station;
String temp_radio_station;
String current_track =" ";
String leading_spaces = "      ";
String stream_meta_data;

const int delay_time = 500;
const char *station[] = {// station array stores url of stream 
  "http://ice-the.musicradio.com/LBCUK",
  "http://vis.media-ice.musicradio.com/CapitalMP3",
  "http://media-ice.musicradio.com/ClassicFMMP3", //128K stream
  "http://77.68.84.201/stream/3/", // relaxing jazz
  "https://media-ssl.musicradio.com/Gold",
  "https://media-ssl.musicradio.com/Heart70s",
  "http://media-ice.musicradio.com/Heart80sMP3", //128K stream
  "https://media-ssl.musicradio.com/HeartDance",
  "http://media-ice.musicradio.com/RadioX-M-90s",
  "http://media-ice.musicradio.com/RadioX-M-00s",
  "https://media-ssl.musicradio.com/SmoothChill",
};

int vol_min = 0;
int vol_max = 30;
volatile int current_volume; // volume range 0 - 30
volatile int previous_volume = current_volume;
volatile int current_station; // station stored in NV ram
volatile int previous_station = current_station;
volatile byte state = 0; //starting state
int number_of_stations = (sizeof(station)/sizeof(station[0]))-1;
int adc_reading = 0;
float battery_voltage;
int battery_percentage;

Audio audio;
Preferences preferences;
U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C u8g2(U8G2_R0,-1);// -1 refers to reset pin
u8g2_uint_t offset;			// current offset for the scrolling text
u8g2_uint_t width;			// pixel width of the scrolling text (must be lesser than 128 unless U8G2_16BIT is defined - it is)
u8g2_uint_t station_width;
u8g2_uint_t track_width;

AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ENCODER_A_PIN, ENCODER_B_PIN, ENCODER_SWITCH, -1, ENCODER_STEPS);
TaskHandle_t task1;
TaskHandle_t task2;

void IRAM_ATTR readEncoderISR(){
  rotaryEncoder.readEncoder_ISR();
}

float get_encoder_reading(){
  return (float)rotaryEncoder.readEncoder();
}

void rotary_onButtonClick(){
  Serial.print("current_station: ");Serial.print(current_station);Serial.print(" ");Serial.println(radio_station);
  static unsigned long lastTimePressed = 0;
  if (millis() - lastTimePressed < 200)
    return;
  lastTimePressed = millis();
  state ++;
  if (state == 3){
    state = 0;
  }
  switch (state){
    // 0 = Adjust Station, 1 = Adjust Volume, 2 = Battery Status
    case 0:
      Serial.print("Adjust Station: ");
      rotaryEncoder.setBoundaries(0, number_of_stations, false); //built in circular queue does not seem to work
      rotaryEncoder.setEncoderValue(current_station);
      Serial.print("Station:");Serial.print(current_station);Serial.print(" ");Serial.println(radio_station);
      //u8g2.setFont(u8g2_font_inb21_mr);
      break;

    case 1:
      Serial.print("Adjust Volume: ");
      rotaryEncoder.setBoundaries(vol_min, vol_max, false); //volume range - not circular
      rotaryEncoder.setEncoderValue(current_volume);
      Serial.print("Volume: ");Serial.println(current_volume);
      break;

    case 2:
      Serial.print("Battery Status: ");
      adc_reading = analogRead(ADC_PIN);
      battery_voltage = adc_reading * 7.9 / 3050;
      Serial.print("ADC Reading: ");Serial.println(adc_reading);
      Serial.print("Battery Voltage: ");Serial.println(battery_voltage);
      break;
  }
}

void rotary_encoder_loop(){
	if (rotaryEncoder.encoderChanged()){//dont do anything unless value changed
    switch (state){
      case 0:
        current_station = get_encoder_reading();
        //Serial.print("Station: ");Serial.print(current_station);Serial.print(" ");Serial.println(station[current_station]);
        audio.connecttohost(station[current_station]);
        if (previous_station != current_station){
          preferences.putInt("stored_sta",current_station); // store the current station index in EEPROM
        }
        break;

      case 1:
        current_volume = get_encoder_reading();
        audio.setVolume(current_volume);
        if (current_volume != previous_volume){
          preferences.putInt("stored_vol",current_volume); // store the current volume in EEPROM
        }
        Serial.print("Volume: ");Serial.println(current_volume);
        break;

      case 2:
        // rotary setting does nothing for battery measurement
        break;
    }
 	}
  if (rotaryEncoder.isEncoderButtonClicked()){
    rotary_onButtonClick();
  }
  previous_volume = current_volume;
  previous_station = current_station;
}

void audio_showstation(const char *info){
  Serial.print("Station: ");Serial.print(current_station);Serial.print(" ");
  radio_station = info;
  Serial.println(info);
  station_width = u8g2.getUTF8Width(info);
}

void audio_showstreamtitle(const char *info){
  Serial.print("streaminfo:  ");
  Serial.println(info);
  current_track = info;
  track_width = u8g2.getUTF8Width(info);		// calculate the pixel width of the text
}

void update_display(void *pvParameters){
  Serial.print("Scrolling message running on core ");
  Serial.println(xPortGetCoreID());
  
  for(;;){
    switch (state){
      case 0:
        stream_meta_data = leading_spaces + radio_station + " " + current_track; // add some leading spaces
        // pad out the message with some spaces so it scrolls with a gap in between
        // width of 6 leading spaces + 1 is 133 pixels - width of display is 128
        width = 171 + station_width + track_width;
        u8g2_uint_t x;
        u8g2.firstPage();
        do {
          // draw the scrolling text at current offset
          x = offset;
          do {								// repeated drawing of the scrolling text...
            u8g2.setCursor(x,26);
            u8g2.print(stream_meta_data);
            x += width;						// add the pixel width of the scrolling text
          } while( x < u8g2.getDisplayWidth() );		// draw again until the complete display is filled
        } while ( u8g2.nextPage() );
        
        offset-=1;							// scroll by one pixel
        if ( (u8g2_uint_t)offset < (u8g2_uint_t)-width )	
          offset = 0;							// start over again
        delay(1);							// wait some small delay
        break;

        case 1:
        u8g2_ClearBuffer;
        u8g2_ClearDisplay;
        u8g2.firstPage();
        do{
          if (current_volume==0){
            u8g2.setCursor(0,24);
            u8g2.print("Muted");
          }else{
            u8g2.setCursor(0,24);
            u8g2.print("Vol: ");
            u8g2.setCursor(80,24);
            u8g2.print(current_volume); // volume range will be 0 to 30
          }
        }while (u8g2.nextPage());
        break;

        case 2:
          u8g2_ClearBuffer;
          u8g2_ClearDisplay;
          u8g2.firstPage();
          do{
              u8g2.setCursor(0,24);
              u8g2.print("Bat");
              u8g2.setCursor(70,24);
              battery_percentage = (battery_voltage/3.0) * 99; // fully charged is 3.3v
              // display up to 99% so it fits in the OLED
              if (battery_percentage > 99){
                battery_percentage = 99;
              }
              u8g2.print(battery_percentage);
              u8g2.setCursor(109,24);
              u8g2.print("%");
          }while (u8g2.nextPage());
          break;
    }
  }
}

void play_the_radio(void *pvParameters){
  Serial.print("Radio stream running on core ");
  Serial.println(xPortGetCoreID());
  for(;;){
    audio.loop();
    rotary_encoder_loop();
  }
}

void setup(){
  Serial.begin(115200);
  u8g2.begin();
  u8g2.setFont(u8g2_font_inb21_mr);	
  u8g2.setFontMode(0);		// enable transparent mode, which is faster
  preferences.begin("settings", false); // false means read/write access
  current_volume = preferences.getInt("stored_vol",5); // volume default 5 if none found
  current_volume = constrain(current_volume, 0, vol_max); 
  current_station = preferences.getInt("stored_sta",0); // first station is default if nothing found
  current_station = constrain(current_station, 0, number_of_stations);
  rotaryEncoder.begin();
  rotaryEncoder.setup(readEncoderISR);
  rotaryEncoder.setEncoderValue(current_station); //set default
  rotaryEncoder.setBoundaries(0, number_of_stations, false); //minValue, maxValue, circleValues true|false
  Serial.print("Loaded from Preferences: Volume ");Serial.print(current_volume);Serial.print(", Station ");Serial.println(current_station);
  Serial.println("Internet Radio starting...");
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.setHostname(hostname.c_str());
  WiFi.begin(ssid.c_str(), password.c_str());
  while (WiFi.status() != WL_CONNECTED){
    delay(1500);
    Serial.print(".");
  }
  Serial.print("Connected to Wifi");
  Serial.print(WiFi.SSID());
  Serial.print("   IP Address: ");
  Serial.println(WiFi.localIP());

  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
  audio.setVolume(current_volume);
  audio.connecttohost(station[current_station]);//set radio playing
  Serial.print("Number of Stations: ");
  Serial.println(number_of_stations);

  //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
    update_display,   // Task function.
    "Task1",          // name of task.
    20000,            // Stack size of task
    NULL,             // parameter of the task
    1,                // priority of the task
    &task1,           // Task handle to keep track of created task
    1);               // pin task to core 1 - the default core

  xTaskCreatePinnedToCore(
    play_the_radio,   // Task function.
    "Task2",          // name of task.
    20000,            // Stack size of task
    NULL,             // parameter of the task
    1,                // priority of the task
    &task2,           // Task handle to keep track of created task
    0);               // pin task to core 0
}

void loop(){// all tasks pinned to a core - don't do anything here
}

