#include "Audio.h"
#include "SD.h"
#include "FS.h"
#include <LiquidCrystal_I2C.h>
#include "HystFilter.h"

LiquidCrystal_I2C lcd(0x27,16,2); //Specify the address and the size of the display. In this case the serial is 0x27 and it's a 16x2 characters display. 

HystFilter potA( 4096, 34, 15 ); //12 bit ADC = 4096, 34 discrete output values required, margin = 15 units (of 4096)

// Specify the pins used for the SD card reader and the i2S DAC
#define SD_CS          5
#define SPI_MOSI      23
#define SPI_MISO      19
#define SPI_SCK       18
#define I2S_DOUT      25
#define I2S_BCLK      27
#define I2S_LRC       26

Audio audio; //declare audio object (from the ESP32-AudioI2S library)

//AM/FM (pin selection, HIGH/LOW state and unit string)
const int amfmPin = 14;
int switchState = 0;
bool FMSignal;
String signalUnits; //the unit of measure of the frequency, can be any string of text and can change when switching between AM and FM

//Volume (pin selection, initial gain value, increment value and other variables)
const int volUpPin = 17;
const int volDownPin = 16;
int volumeUp = 0;
int volumeDown = 0;
int gainVal = 15;
int volumeIncrement = 1; 

//Potentiometer (pin selection abd variables to store the AnalogRead value)
const int potPin = 34;
long potValue = 0;
String radioFreq;

bool booted=false; //used to trigger the first input reads in the code

int saveState;
const int dbLength = 13;
const char *savedAudio;
const char *radioNoise = "noise.mp3"; //the noise sound is stored in the root of the sd card
//a three-dimensional array of char to store the paths of the audio files for each station. Those have been collected in subfolders of the root directory.
//7 radio stations, 2 signals and 12 maximum audio files + the name of the radio in the first index position of the array
const char *radioDB[7][2][dbLength] = {
  {
    {"Big Kahuna","/101/01_PRIMORDIAL_RAIN.mp3","/101/02_PRIMORDIAL_UNDERWATER_WAILING.mp3","/101/03_PRIMORDIAL_ICE_MELTING.mp3","/101/04_PRIMORDIAL_UNDERWATER_IMPACT.mp3","/101/05_PRIMORDIAL_UNDERWATER.mp3","/101/06_PRIMORDIAL_ICE_BREAK.mp3","/101/07_PRIMORDIAL_SNOW_CRUNCH.mp3","/101/08_PRIMORDIAL_BIRTH.mp3","/101/09_PRIMORDIAL_WAVES.mp3","/101/10_PRIMORDIAL_WHALE.mp3","/101/11_PRIMORDIAL_WATER_EVAPORATING.mp3", 0},
    {"101/101_The_Consciousness_of_Water.mp3"}
  },
  {
    {"Comrade","/102/001_COMRADE_KIDSINBATH.mp3","/102/002_COMRADE_DUCKSINTHELAKE.mp3","/102/003_COMRADE_KIDSONTHEBEACH.mp3","/102/004_COMRADE_ANIMALSINTHELAKE.mp3","/102/005_COMRADE_SINGINGONTHEBEACH.mp3","/102/006_COMRADE_BIRDS.mp3","/102/007_COMRADE_PENGUINS.mp3","/102/008_COMRADE_SWIMMINGPOOL.mp3",0,0,0,0},
    {"/102/102_How_humans_and_animals_can_live_together.mp3"}
  },
  {
    {"Drop out radio","/103/001_ATLANTIS_BOATHORN.mp3","/103/002_ATLANTIS_SUBMARINE.mp3","/103/003_DIVING.mp3","/103/004_ATLANTIDE_SURF.mp3","/103/005_ATLANTIDE_SAILING.mp3","/103/006_ATLANTIS_PADDLING_A_KAYAK.mp3","/103/007_ATLANTIDE_SKI_MOUNTAINEERING.mp3","/103/008_COMRADE_BOAT.mp3",0,0,0,0},
    {"/103/103_Barbarian Days_William_Finnegan.mp3"}
  },
  {
    {"Dripping Rituals","/104/001_RITUALS_FILLING_BATH.mp3","/104/002_RITUALS_CHINESE TEA_CEREMONY.mp3","/104/003_RITUALS_COPTIC_BAPTISM.mp3","/104/004_RITUALS_COOKING.mp3","/104/005_RITUALS_WOMEN_BATH_IN_GANGES_RIVER.mp3","/104/006_RITUALS_COFFEE_MAKING.mp3","/104/007_RITUALS_MALI_FISHING_RITUAL.mp3","/104/008_RITUALS_SAUNA.mp3,","/104/009_RITUALS_THERMAL WATERS.mp3",0,0,0},
    {"/104/104_Mexican_priest_uses_watergun_to_spray_holy_water.mp3"}
  },
  {
    {"Drain Line","/105/001_MB_DRINKING.mp3","/105/002_MB_TANK.mp3","/105/003_MB_BEERCAN.mp3","/105/004_MB_COFFEEMAKER.mp3","/105/005_MB_SIPPING.mp3","/105/006_MB_VIBRATING_STEEL_CABLE.mp3","/105/007_MB_SWALLOW.mp3","/105/008_MB_SALIVA.mp3","/105/009_MB_ENGINE_UNDERWATER.mp3","/105/010_MB_BABYHEART.mp3","/105/011_MB_HEATING_VALVE.mp3","/105/012_MB_PEEING_WC.mp3"},
    {"/105/105_How_Cooling_System_Works.mp3"}
  },
  {
    {"Unblemished","/106/001_HYGENE_SHOWER.mp3","/106/002_HYGENE_DISHWASHER.mp3","/106/003_HYGENE_IRONING.mp3","/106/004_HYGENE_HAND-WASHING_CLOTHES.mp3","/106/005_HYGENE_CAR_WASH.mp3","/106/006_HYGENE_WASHING_FACE_AND_TEETH.mp3","/106/007_HYGENE_FISH_CLEANING_STATION.mp3","/106/008_HYGENE_HIGH_PRESSURE_WATER_PIPE_CLEANING.mp3","/106/009_HYGENE_LAUNDROMAT_AMBIENCE.mp3","/106/010_HYGENE_GARGLING.mp3","/106/011_HYGENE_WASHING_DISHES.mp3",0},
    {"/106/106_Your Cleanliness_1953.mp3"}
  },
  {
    {"Bottled FM","/107/001_BOTTLED_WATER_DISPENSER.mp3","/107/002_BOTTLED_FILL BOTTLE.mp3","/107/003_BOTTLED_PILL_DISSOLVING_WATER.mp3","/107/004_BOTTLED_IRRIGATION.mp3","/107/005_BOTTLED_TOURIST_GUIDE.mp3","/107/006_BOTTLED_MALL_FOUNTAIN.mp3","/107/007_BOTTLED_VERSAILLES_FOUNTAINS.mp3","/107/008_BOTTLED_CRUISE.mp3",0,0,0,0},
    {"/107/107_Is_Water_a_Human_Right.mp3"}
  }
};

int saveStatesDB[7] = {1,1,1,1,1,1,1}; //savestates of the 7 radio stations. the value of a specific station increases after the end of every audio.

void clearLCDLine(int line) //function to clear a single line of the LCD display. Created by bperrybap: https://forum.arduino.cc/index.php?topic=212460.msg4375326#msg4375326
{
        lcd.setCursor(0,line);
        for(int n = 0; n < 16; n++)
        {
                lcd.print(" ");
        }
}

void Station(int radioIndex){ //called when tuning to a new radio station or when a new audio plays in the same one.
    //RADIO NAME
    String radioName = radioDB[radioIndex][0][0];
    saveState = saveStatesDB[radioIndex]; //read from db

    Serial.println(radioName);
    clearLCDLine(1);
    lcd.setCursor(0, 1);
    lcd.print(radioName);

    //RADIO AUDIO
    if(FMSignal){ //select FM audio if FMSignal is true
      savedAudio = radioDB[radioIndex][0][saveState];
    }
    else{ //select AM audio if FMSignal is false
      savedAudio = radioDB[radioIndex][1][0];
    }
    audio.connecttoFS(SD, savedAudio); //play the selected audio
    Serial.println(savedAudio);
}

void SaveAudio(int radioIndex){ //read, increase and write the savestate into the db
  saveState = saveStatesDB[radioIndex]; //read from db
  saveState++;
  if(saveState==dbLength || saveState==0) {saveState=1;};
  saveStatesDB[radioIndex] = saveState; //write to db
}

void audio_eof_mp3(const char *info){  //triggered at the end of the audio file (from the ESP32-AudioI2S library)
    Serial.print("eof_mp3     ");Serial.println(info);

    if(potValue==4){SaveAudio(0); Station(0);} //radioIndex
    else if(potValue==8){SaveAudio(1); Station(1);} //radioIndex
    else if(potValue==12){SaveAudio(2); Station(2);} //radioIndex
    else if(potValue==16){SaveAudio(3); Station(3);} //radioIndex
    else if(potValue==20){SaveAudio(4); Station(4);} //radioIndex
    else if(potValue==24){SaveAudio(5); Station(5);} //radioIndex
    else if(potValue==28){SaveAudio(6); Station(6);} //radioIndex
    else if(potValue==31){clearLCDLine(1); audio.connecttoFS(SD, radioNoise);}

    Serial.println("SaveStatesDB:");
    for(int a=0; a < 6; a++){
        Serial.print(saveStatesDB[a]);
        Serial.print(" ");
    }
    Serial.println();
}

void setup(){
    //Init SD card reader
    pinMode(SD_CS, OUTPUT);
    digitalWrite(SD_CS, HIGH);
    SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
    SD.begin(SD_CS);
    //init serial monitor (optional)
    Serial.begin(115200);
    //Init the LCD display
    lcd.init();
    lcd.clear();
    lcd.backlight();
    //Init the I2S DAC
    audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
    audio.setVolume(gainVal); // 0...21
    delay(300);
    //Init AM/FM switch and volume pins
    pinMode(amfmPin, INPUT_PULLUP);
    pinMode(volUpPin, INPUT_PULLUP);
    pinMode(volDownPin, INPUT_PULLUP);
    //start initial device name animation
    lcd.setCursor(0, 0);
    lcd.print("WATER");
    Serial.println("WATER");
    delay(500);
    lcd.setCursor(0, 1);
    lcd.print("FREQUENCIES");
    Serial.println("FREQUENCIES");
    delay(1000);
    //read initial AM/FM trigger state
      if(switchState==HIGH){FMSignal = true;} //FM RADIO
      else{FMSignal = false;} //AM RADIO
    booted=true;
    lcd.clear(); //clear the screen
}
//END OF SETUP FUNCTION

long previousMillis = 0;
long interval = 500;

static int potValueOld = 0;
static int oldSwitchState = 0;
int alpha = 2;
int beta = 3;

void loop(){

  switchState = digitalRead(amfmPin); //read switch state
  volumeUp = digitalRead(volUpPin); //read vol+ state
  volumeDown = digitalRead(volDownPin); //read vol- state

  //potValue = potA.getOutputLevel(analogRead(potPin)); //read potentiometer value with hysterisis filter
  potValue = (potA.getOutputLevel(analogRead(potPin)) * alpha + potValue * beta) / (alpha + beta); //read potentiometer value with hysterisis filter and a further noise attenuation operation

  unsigned long currentMillis = millis();

  if(((potValueOld != potValue || switchState != oldSwitchState) && currentMillis - previousMillis > interval) || booted){ //trigger only when the ESP32 is booted and when the potentiometer or the switch are moved (after a short interval) 

    previousMillis = currentMillis;
    
    if (switchState != oldSwitchState){ //triggers at any state change of the switch
      clearLCDLine(0);
      if(switchState==HIGH){
        FMSignal = true; //FM RADIO
      }
      else{
        FMSignal = false; //AM RADIO
      }
    }

      if(FMSignal){
        signalUnits = "Hz"; //signal unit string for FM
        radioFreq = String(432 + potValue) +" "+signalUnits;
        clearLCDLine(0);
        lcd.setCursor(0, 0);
        lcd.print(radioFreq);
      }
      else{
        signalUnits = "Hz"; //signal unit string for AM
        radioFreq = String(125 + potValue*2) +" "+signalUnits;
        clearLCDLine(0);
        lcd.setCursor(0, 0);
        lcd.print(radioFreq);
      }

    if(potValueOld != potValue || booted){ //triggers and any value change of the potentiometer or if the device as been booted

      //RADIO FREQUENCY
      Serial.println(radioFreq);
      clearLCDLine(0);
      lcd.setCursor(0, 0);
      lcd.print(radioFreq);

      if(booted){
      booted=false;
      }
    }

      if(potValue==4){Station(0);} //radioIndex
      else if(potValue==8){Station(1);} //radioIndex
      else if(potValue==12){Station(2);} //radioIndex
      else if(potValue==16){Station(3);} //radioIndex
      else if(potValue==20){Station(4);} //radioIndex
      else if(potValue==24){Station(5);} //radioIndex
      else if(potValue==28){Station(6);} //radioIndex
      else if(potValue==31){audio.connecttoFS(SD, radioNoise); clearLCDLine(1);}
      else{audio.stopSong(); clearLCDLine(1);};

      potValueOld = potValue;
      oldSwitchState = switchState;
  }

  if(volumeUp==LOW && gainVal < 22 && currentMillis - previousMillis > 300){ //triggers volume up. If the user keep pressed it will trigger every 300ms 
    previousMillis = currentMillis;
    gainVal += volumeIncrement;
    audio.setVolume(gainVal);
    Serial.println("Vol: "+String(gainVal));
    clearLCDLine(0);
    lcd.setCursor(0, 0);
    lcd.print("Vol: "+String(gainVal));
  }

  if(volumeDown==LOW && gainVal > -1 && currentMillis - previousMillis > 300){ //triggers volume down. If the user keep pressed it will trigger every 300ms 
    previousMillis = currentMillis;
    gainVal -= volumeIncrement;
    audio.setVolume(gainVal);
    Serial.println("Vol: "+String(gainVal));
    clearLCDLine(0);
    lcd.setCursor(0, 0);
    lcd.print("Vol: "+String(gainVal));
  }

  audio.loop();

}

// optional from the ESP32-AudioI2S library (not used)
void audio_info(const char *info){
    Serial.print("info        "); Serial.println(info);
}
void audio_id3data(const char *info){  //id3 metadata
    Serial.print("id3data     ");Serial.println(info);
}
void audio_bitrate(const char *info){
    Serial.print("bitrate     ");Serial.println(info);
}
