#include <stdint.h>
#include <SPI.h>
#include <Wire.h>
#include <EEPROMex.h>
#include <Encoder.h>
#include <LiquidCrystal.h>
#include <Time.h>
#include "EEPROMAnything.h"

//IR stuff
#include <IRremote.h>
int RECV_PIN = A0;
IRrecv irrecv(RECV_PIN);
decode_results results;

//******************************************
// Change these to suit your IR Remote.
// Codes that are not understood are printed via serial
// use the values printed to replce the ones here.
const unsigned long irOk = 66652 | 1116;
const unsigned long irLeft = 66650 | 1114;
const unsigned long irRight = 66651 | 1115;
const unsigned long irUp = 66648 | 1112;
const unsigned long irDown = 66649 | 1113;
const unsigned long irPower = 66759 | 1223;
//******************************************


// Radio module Library
#include <TEA5767.h>

//Sound Processor chip library
#include <TDA7439.h>
TDA7439 equ;

//Radio module
TEA5767 Radio;
int search_direction;

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(13, 7, 8, 10, 11, 12);

//General variables
boolean powerStateChanged = 1;
boolean IrOKbutton = 0;
boolean tmpPower = 0;
int menuButton = 4;
uint8_t powerButton = 5;
int backlight = 9;
uint8_t blBrightness = 250;
uint8_t timeCursor = 1;
int onPowerLED = A2;
int offPowerLED = A1;
uint8_t powerRelay = 6;
uint8_t fan = 17;
uint8_t loopCount = 0;

//Values that get stored in EEPROM
struct config_t
{
    boolean powerState;
    int activeInput;
    int volumeLevel;
    int bassLevel;
    int midLevel;
    int trebLevel;
    int attLevel;
    int gainLevel;
    double frequency;
} configuration;

//Radio
int search_mode = 0;
unsigned char buf[5];

//For displaying time
char twoDigitNumber[3] = "00";
char tftTime[6] = "00:00";

   //creating the custom fonts:
   byte seg_1[8] = {
     B11111,
     B11111,
     B00000,
     B00000,
     B00000,
     B00000,
     B00000,};
     
   byte seg_2[8] = {
     B00000,
     B00000,
     B00000,
     B00000,
     B00000,
     B11111,
     B11111,};
     
   byte seg_3[8] = {
     B11111,
     B11111,
     B00000,
     B00000,
     B00000,
     B11111,
     B11111,};
     
   byte seg_4[8] = {
     B11111,
     B11111,
     B11111,
     B11111,
     B11111,
     B11111,
     B11111,};
     
   byte seg_5[8] = {
     B00000,
     B00000,
     B00000,
     B01110,
     B01110,
     B01110,
     B00000,};
	 
   byte seg_6[8] = {
     B00000,
     B00000,
     B00000,
     B00000,
     B00000,
     B10000,
     B10000,};
	 
   byte seg_7[8] = {
     B00000,
     B00000,
     B00000,
     B00100,
     B00100,
     B10100,
     B10100,};
	 
   byte seg_8[8] = {
     B00000,
     B00001,
     B00001,
     B00101,
     B00101,
     B10101,
     B10101,};

   
   int customchar[10][6] ={{4,1,4,4,2,4},  //0
                           {1,4,32,2,4,2},  //1
                           {3,3,4,4,2,2},
                           {1,3,4,2,2,4},
                           {4,2,4,32,32,4},
                           {4,3,3,2,2,4},
                           {4,3,3,4,2,4},
                           {1,1,4,32,32,4},
                           {4,3,4,4,2,4},
                           {4,3,4,2,2,4}}; //9

const int menuTimeout = 5000;
unsigned long lastChange = 0;

//Encoder related
volatile byte enc=0;  // flags for the interrupt routine

//Used for reducing the resolution of the encoder
volatile byte encDownCount=0;
volatile byte encUpCount=0;
#define encResDivider 100

void setup()
{

    lcd.begin(16, 2);
	
	//Initialise the custom characters
    lcd.createChar(1, seg_1);
    lcd.createChar(2, seg_2);
    lcd.createChar(3, seg_3);
    lcd.createChar(4, seg_4);
    lcd.createChar(5, seg_5);
    lcd.createChar(6, seg_6);
    lcd.createChar(7, seg_7);
    lcd.createChar(8, seg_8);
	
    
   irrecv.enableIRIn(); // Start the receiver
    
    attachInterrupt(0, encoder, FALLING);  // Interrupt for rotary encoder
    
    Serial.begin(9600);
    Wire.begin();
    Radio.init();
    
    pinMode(2, INPUT);       // Encoder pin
    digitalWrite(2, HIGH);   // Enable pull-up resistor

    pinMode(3, INPUT);     // Encoder pin
    digitalWrite(3, HIGH); // Enable pull-up resistor
    
    pinMode(powerButton, INPUT); //Set buttons as inputs
    pinMode(menuButton, INPUT);
    
    digitalWrite(powerButton, HIGH); //Enable internal pullups
    digitalWrite(menuButton, HIGH);
    
    pinMode(fan, OUTPUT);
    digitalWrite(fan, 0);
    
    pinMode(powerRelay, OUTPUT);
    digitalWrite(powerRelay, 0);
    
    pinMode(offPowerLED, OUTPUT);
    digitalWrite(offPowerLED, HIGH);
    
    pinMode(onPowerLED, OUTPUT);
    digitalWrite(onPowerLED, HIGH);

   EEPROM_readAnything(0, configuration);

   if (configuration.activeInput == 255) configuration.activeInput = 0;
   if (configuration.volumeLevel == 255) configuration.volumeLevel = 0;
   if (configuration.bassLevel == 255) configuration.bassLevel = 0;
   if (configuration.midLevel == 255) configuration.midLevel = 0;
   if (configuration.trebLevel == 255) configuration.trebLevel = 0;
   if (configuration.attLevel == 255) configuration.attLevel = 0;
   if (configuration.gainLevel == 255) configuration.gainLevel = 0;
   if (configuration.frequency < 88) configuration.frequency = 88;
   
   equ.setInput(configuration.activeInput+1);
   equ.inputGain(configuration.gainLevel);
   equ.setVolume(configuration.volumeLevel);
   equ.setSnd(configuration.bassLevel,1);
   equ.setSnd(configuration.midLevel,2);
   equ.setSnd(configuration.trebLevel,3);
   equ.spkAtt(configuration.attLevel);
   
   if (configuration.powerState == 0){
     digitalWrite(offPowerLED, LOW);
     digitalWrite(onPowerLED,HIGH);
   }else{
     digitalWrite(offPowerLED, HIGH);
     digitalWrite(onPowerLED, LOW);
   }
}

void loop()
{
  processIR();
  
  tmpPower = digitalRead(powerButton);
  
  //Power button pressed?
  if (tmpPower == 0){
    delay(300); //Avoid rapid power cycles
    configuration.powerState = ~configuration.powerState;
    powerStateChanged = 1;
  }
  
  if (configuration.powerState == 0){
      //Off
      if (powerStateChanged == 1){
        //But only just turned off
        powerStateChanged = 0;
        equ.setVolume(0);
        analogWrite(backlight, 100);
        digitalWrite(offPowerLED, LOW);
        digitalWrite(onPowerLED,HIGH);
        lastChange = millis();
        lcd.clear();
        EEPROM_writeAnything(0, configuration);
        digitalWrite(powerRelay, 0); //Turn off relays etc
        digitalWrite(fan, 0);
        delay(500);
      }
      if (millis() - lastChange > 500){

        lastChange = millis();
      }
  }else{
    // We are on
    if (powerStateChanged == 1){
      //Only just turned on
      EEPROM_writeAnything(0, configuration);
      
      powerStateChanged = 0;
      lcd.clear();
      digitalWrite(backlight, HIGH);
      digitalWrite(offPowerLED, HIGH);
      digitalWrite(onPowerLED, LOW);
      lcd.setCursor(0,0);
      lcd.print("Arduino Powered");
      lcd.setCursor(0,1);
      lcd.print("   Gainclone   ");
      delay(2000);
      lcd.clear();
      digitalWrite(powerRelay, 1);
      digitalWrite(fan, 1);
      equ.setInput(configuration.activeInput+1);
      equ.inputGain(configuration.gainLevel);
      equ.setVolume(configuration.volumeLevel);
      equ.setSnd(configuration.bassLevel,1);
      equ.setSnd(configuration.midLevel,2);
      equ.setSnd(configuration.trebLevel,3);
      equ.spkAtt(configuration.attLevel); 
      if (configuration.activeInput == 0) {
        radio_on();
        radio_status();
      }
    }
    
    if (millis() - lastChange > 500){
      if (loopCount > 200){  //Stop loopcount from overflowing
        loopCount = 5;
      }else{
        loopCount++;
      }
      if (configuration.activeInput == 0){
        if (loopCount > 3){
          loopCount = 0;
          radio_status();
        }
      }else{
        if (loopCount == 4) lcd.clear();
        if (loopCount > 3){

        }
      }
      lastChange = millis();    
    }
        
    if (search_mode == 1) {
      if (Radio.process_search (buf, search_direction) == 1) {
          search_mode = 0;
      }
    }
      
    //Volume control
    if (enc != 0){
      digitalWrite(backlight, HIGH);
      lcd.clear();
      lcd.print("Volume");
      switch(enc){
        case 1:
          configuration.volumeLevel --;
          enc = 0;
          break;
        case 2:
          configuration.volumeLevel ++;
          enc = 0;
          break;
        }
        lastChange = millis();
        configuration.volumeLevel = constrain(configuration.volumeLevel, 0, 48);
        equ.setVolume(configuration.volumeLevel);
        // EEPROM.write(volumeLevel,2);
        force2Digits(configuration.volumeLevel);
        printBigDigit(twoDigitNumber[0],9);
        printBigDigit(twoDigitNumber[1],13);
        loopCount = 0;
      }
        
      //Menu system
      if (digitalRead(menuButton) == 0 | IrOKbutton == 1) {
        digitalWrite(backlight, HIGH);
        IrOKbutton = 0;
        displayMenu();       
      }
    }
}

void radio_on(){
  Radio.set_frequency(configuration.frequency);
}

void radio_tune_up(){
   search_mode = 1;
   search_direction = TEA5767_SEARCH_DIR_UP;
   Radio.search_up(buf);
   radio_status();
}

void radio_tune_down(){
   search_mode = 1;
   search_direction = TEA5767_SEARCH_DIR_DOWN;
   Radio.search_down(buf);
  radio_status();
}

void radio_status(){
  int stereo;
  int signal_level;
  double current_freq;
     
  if (Radio.read_status(buf) == 1) {
    current_freq =  floor (Radio.frequency_available (buf) / 100000 + .5) / 10;
    stereo = Radio.stereo(buf);
    signal_level = Radio.signal_level(buf);
    //print current_freq to lcd
    lcd.setCursor(0,0);
    lcd.print("Radio          ");
    lcd.setCursor(0,1);
    lcd.print(current_freq);
    configuration.frequency = current_freq;
    lcd.setCursor(7,1);
    lcd.print("Mhz     ");
    lcd.setCursor(15,0);
    //signal_level /= 100; //signal level is in percent.
    switch (signal_level){
      case 1: 
      case 2: 
      case 3:
      case 4: 
      case 5: 
        lcd.write(byte(6));
        break;
      case 6:
      case 7: 
      case 8: 
      case 9: 
      case 10:
        lcd.write(byte(7));
        break;
      case 11:case 12:case 13:case 14:
      lcd.write(byte(8));
    }
    lcd.setCursor(15,1);
    if (stereo){
      lcd.print("S"); 
    }else{
      lcd.print("M");
    }
  }
}

void encoder()
{
  if (digitalRead(2) != digitalRead(3))
  {
    encDownCount = 0;
    encUpCount++;
    if (encUpCount >= 100){
      enc = 2;  //if on interrupt the encoder channels are the same, direction is clockwise
      encUpCount = 0;
    }
  }
  else
  {
    encUpCount = 0;
    encDownCount++;
    if (encDownCount >= 100){
      enc = 1;  //if they are not the same, direction is ccw
      encDownCount = 0;
    }
  }
}

void displayMenu() {
  delay(300); // Saves selecting the first option from the original press
  lcd.clear();
  lcd.print("Menu");
  int selected = 0;
  int moved = 1;

  // display options
  lastChange = millis();
  while (digitalRead(menuButton) != 0 && IrOKbutton == 0) {
    processIR();
    if (millis() - lastChange < menuTimeout){
      switch (enc){
        case 1:
          if (selected > 0){
            selected--;
            moved = 1;
          }
          enc = 0;
          lastChange = millis();
          break;
        case 2: 
          if (selected < 4){
            selected++;
            moved = 1;
          }
          enc = 0;
          lastChange = millis();
          break;
      }
  
      if (moved) {
        switch(selected) {
          case 0:
            //lcd.print("Change Units    ");
            lcd.setCursor(0,1);
            lcd.print("Input Select");
            moved = 0;
            break;
          case 1:
            lcd.setCursor(0,1);
            lcd.print("Bass        ");
            moved = 0;
            break;
          case 2:
            lcd.setCursor(0,1);
            lcd.print("Mid        ");
            moved = 0;
            break;
          case 3:
            lcd.setCursor(0,1);
            lcd.print("Treb       ");
            moved = 0;
            break;
          case 4:
            lcd.setCursor(0,1);
            lcd.print("Input Gain  ");
            moved = 0;
            break;
        }
     }
    }else{
     // Timed out
     lcd.clear();
     return;
    } 
  }
  IrOKbutton = 0;
   switch(selected) {
    case 0:
      changeInput();
      break;
    case 1:
      changeSnd(1);
      break;
    case 2:
      changeSnd(2);
      break;
    case 3:
      changeSnd(3);
      break;
   case 4:
      changeGain();
      break;
  }
  delay(300); //Saves going back in to menu
}

void changeInput(){
  delay(300);
  int selected = configuration.activeInput;
  int moved = 1;

  // display options
  lcd.clear();
  lcd.print("Input selection");
  lastChange = millis();
  while (digitalRead(menuButton) != 0 && IrOKbutton == 0) {
    processIR();
    if (millis() - lastChange < menuTimeout){
      switch (enc){
        case 1:
          if (selected > 0){
            selected--;
            moved = 1;
          }
          enc = 0;
          lastChange = millis();
          break;
        case 2: 
          if (selected < 3){
            selected++;
            moved = 1;
          }
          enc = 0;
          lastChange = millis();
          break;
      }
  
      if (moved) {
        switch(selected) {
          case 0:
            lcd.setCursor(0,2);
            lcd.print("Tuner ");
            break;
          case 1:
            lcd.setCursor(0,2);
            lcd.print("DAC   ");
            break;
          case 2:
            lcd.setCursor(0,2);
            lcd.print("CD    ");
            break;
          case 3:
            lcd.setCursor(0,2);
            lcd.print("AUX   ");
            break;
        }
      moved = 0;
     }
    }else{
     lcd.clear();
     return;
    } 
  }
  IrOKbutton = 0;
  // when button is pressed...
  equ.setInput(selected+1);
  configuration.activeInput = selected;
  EEPROM_writeAnything(0, configuration);
  if (selected == 0) {
    lcd.clear();
    radio_on();
    radio_status();
  }else{
    delay(500);
    lcd.clear();
  }
}

void changeSnd(int band){
  int printValue;
  delay(300);
  lcd.clear();
  int selected;
  switch (band){
  case 1:
    lcd.print("Bass");
    selected = configuration.bassLevel;
    break;
  case 2:
    lcd.print("Mid ");
    selected = configuration.midLevel;
    break;
  case 3:
    lcd.print("Treb");
    selected = configuration.trebLevel;
    break;
  }
  if (selected < 0){
    lcd.setCursor(10,0);
    lcd.write(byte(2));
    lcd.setCursor(11,0);
    lcd.write(byte(2));
  }else{
    lcd.setCursor(10,0);
    lcd.print("  ");
  }
  if (selected < 0){
    printValue = selected * -1;
  }else{
    printValue = selected;
  }
  printBigDigit(printValue + 48,13);
  //lcd.print(selected);
  int moved = 0;

  // display options
  lastChange = millis();
  while (digitalRead(menuButton) != 0 &&   IrOKbutton == 0) {
    processIR();
    if (millis() - lastChange < menuTimeout){
      switch (enc){
        case 1:
          if (selected > -7){
            selected--;
            moved = 1;
          }
          enc = 0;
          lastChange = millis();
          break;
        case 2: 
          if (selected < 7){
            selected++;
            moved = 1;
          }
          enc = 0;
          lastChange = millis();
          break;
      }
  
      if (moved) {
      if (selected < 0){
        lcd.setCursor(10,0);
        lcd.write(byte(2));
        lcd.setCursor(11,0);
        lcd.write(byte(2));
      }else{
        lcd.setCursor(10,0);
        lcd.print("  ");
      }
      if (selected < 0){
        printValue = selected * -1;
      }else{
        printValue = selected;
      }
      printBigDigit(printValue + 48,13);
      moved = 0;
      equ.setSnd(selected, band);
     }
    }else{
     lcd.clear();
     return;
    } 
  }
  // when button is pressed...
  IrOKbutton = 0;
    switch (band){
    case 1:
      configuration.bassLevel = selected;
      break;
    case 2:
      configuration.midLevel = selected;
      break;
    case 3:
      configuration.trebLevel = selected;
  }
  EEPROM_writeAnything(0, configuration);
  lcd.clear();
  lcd.print("Saved");
  delay(500);
}

void changeGain(){
  lcd.clear();
  lcd.print("Gain");
  force2Digits(configuration.gainLevel);
  printBigDigit(twoDigitNumber[0],9);
  printBigDigit(twoDigitNumber[1],13);
  delay(300);
  int selected = configuration.gainLevel;
   int moved = 0;

  // display options
  lastChange = millis();
  while (digitalRead(menuButton) != 0 &&   IrOKbutton == 0) {
    processIR();
    if (millis() - lastChange < menuTimeout){
      switch (enc){
        case 1:
          if (selected > 0){
            selected--;
            moved = 1;
          }
          enc = 0;
          lastChange = millis();
          break;
        case 2: 
          if (selected < 30){
            selected++;
            moved = 1;
          }
          enc = 0;
          lastChange = millis();
          break;
      }
  
      if (moved) {
        equ.inputGain(selected);
        force2Digits(selected);
        printBigDigit(twoDigitNumber[0],9);
        printBigDigit(twoDigitNumber[1],13);
        moved = 0;
     }
    }else{
     lcd.clear();
     return;
    } 
  }
  // when button is pressed...
  IrOKbutton = 0;
  lcd.clear();
  lcd.print("Saved");
  configuration.gainLevel = selected;
  EEPROM_writeAnything(0, configuration);
  delay(500);
  lcd.clear();
}

void processIR(){
    if (irrecv.decode(&results)) {
     switch (results.value){
       case irOk:
         Serial.println("Ok");
         //ok button
         IrOKbutton = 1;
         lastChange = millis();
         break;
       case irRight:
         // Rotary right
         enc = 2;  
         lastChange = millis();
         break;
       case irLeft:
         //Rotary left
         enc = 1;
         lastChange = millis();
         break;
       case irPower:
         configuration.powerState = ~configuration.powerState;
         powerStateChanged = 1;
         break;
       case irUp:
         //tune up
         if (configuration.activeInput == 0) radio_tune_up();
         break;
       case irDown:
         if (configuration.activeInput == 0) radio_tune_down();
         break;
       default:
         Serial.println(results.value);
     }

    for (int z=0; z<2; z++)
      irrecv.resume(); // Receive the next value  
  }
}



void force2Digits(int number){
  itoa(number, twoDigitNumber,10);
  if (number >= 0 && number < 10) {
    twoDigitNumber[1] = twoDigitNumber[0];
    twoDigitNumber[0] = '0';
  }
}

void printBigDigit(int number, int xpos){
       lcd.setCursor(xpos,0);
       for (int x=0;x<3;x++){
         lcd.write(byte(customchar[number - 48][x]));
       }
       lcd.setCursor(xpos,1);
       for (int x=3;x<6;x++){
          lcd.write(byte(customchar[number -48][x]));
       } 
}
