#include <SPI.h>
#include <SD.h>
#include <RTClib.h>
#include <DS18B20.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>

// constants
#define TREND_FALL 1
#define TREND_RISE 2
#define TREND_CONST 3

#include "./hardware_conf.h"
#include "./charset.h"
#include "./software_conf.h"
#include "./menus.h"

// devices

RTC_DS1307 rtc;
LiquidCrystal lcd(LCD_RSPin, LCD_EnPin, LCD_D4Pin, LCD_D5Pin, LCD_D6Pin, LCD_D7Pin);
DS18B20 ds(Sensors_PIN);

// Variables

DateTime now, oldTime;
unsigned long ms, tempMs, logMs, cardMs, buttonMs, menuMs;
uint8_t sID;
uint8_t buttonState, oldButtonState;

File logFile;         // log file
bool isSD = true;     // indicate SD Card in slot
unsigned int samples; // counting log samples
uint8_t sensors;      // detected sensors

bool isFirstMeasurement;

float sensorCurrent[MAX_SENSORS];
float sensorOld[MAX_SENSORS];
float sensorMax[MAX_SENSORS];
float sensorMin[MAX_SENSORS];
float sensorAverage[MAX_SENSORS];
uint8_t sensorTrend[MAX_SENSORS];

float trendThreshold = 0.1;
unsigned long LOG_TIME_MS = 15 * 1000;
unsigned long SAMPLE_TIME_MS = 1000;

uint8_t menuMode = 0;    // current menu: 0 - view mode; 1 - main menu
uint8_t viewMode = 0;    // current view (see menu view)
uint8_t menuOption = 0;  // current main menu option
bool redrawMenu = false;
bool sounds = true;

// SD Services

void testSDCard() {
  isSD = SD.begin(SD_CSPin);
}

void createLogFile() {
  sID = 0;
  logFile = SD.open(FILE_NAME, FILE_WRITE);
  if (logFile) {
    // sensor description
    while (ds.selectNext()) {
      sID++;
      logFile.print(F("Sensor #"));
      logFile.print(sID);
      logFile.print(F(" at address: "));

      uint8_t address[8];
      ds.getAddress(address);
      for (uint8_t i = 0; i < 8; i++) {
        logFile.print(address[i], HEX);
      }

      logFile.print(F(" Model: "));
      switch (ds.getFamilyCode()) {
        case MODEL_DS18S20:
          logFile.print(F("DS18S20/DS1820"));
          break;
        case MODEL_DS1822:
          logFile.print(F("DS1822"));
          break;
        case MODEL_DS18B20:
          logFile.print(F("DS18B20"));
          break;
        default:
          logFile.print(F("Unrecognized Device"));
          break;
      }
      logFile.print(F(" Resolution: "));
      logFile.println(ds.getResolution());
    }

    // file header
    logFile.print(F("Date,Time,"));
    for (sID = 1; sID <= sensors; sID++) {
      logFile.print(F("Sensor #")); logFile.print(sID);
      if (sID < sensors) {
        logFile.print(",");
      } else {
        logFile.println();
      }
    }
    samples = 0;
    clearSensors();
    startUpTone();
  } else {
    isSD = false;
    SD.end();
  }
  logFile.close();
}

void writeLogFile() {
  logFile = SD.open(FILE_NAME, FILE_WRITE);
  if (logFile) {

    // sprintf(str,"%2d.%2d.%2d",now.year(),now.month(),now.day());
    // logFile.print(str);
    logFile.print(now.year());
    logFile.print('.');
    logFile.print(now.month());
    logFile.print('.');
    logFile.print(now.day());

    logFile.print(FIELD_DELIMITER);

    // sprintf(str,"%2d:%2d:%2d",now.hour(),now.minute(),now.second());
    // logFile.print(str);

    logFile.print(now.hour());
    logFile.print(':');
    if (now.minute() < 10) {
      logFile.print('0');
    }
    logFile.print(now.minute());
    logFile.print(':');
    if (now.second() < 10) {
      logFile.print('0');
    }
    logFile.print(now.second());

    // sensor(s) data
    for (sID = 0; sID <= sensors; sID++) {
      logFile.print(FIELD_DELIMITER);
      logFile.print(sensorCurrent[sID]);
    }
    logFile.println();
    samples++;
  } else {
    isSD = false;
    SD.end();
  }
  logFile.close();
}

// Sensor services

void getSensors() {
  sID = 0;
  while (ds.selectNext()) {
    sID++;
    float newVal = ds.getTempC();

    // trend
    float dVal = newVal - sensorOld[sID];
    if (dVal > trendThreshold) {
      sensorTrend[sID] = TREND_FALL;
    } else if (dVal < -trendThreshold) {
      sensorTrend[sID] = TREND_RISE;
    } else {
      sensorTrend[sID] = TREND_CONST;
    }
    if (sensorTrend[sID] != TREND_CONST) {
      sensorOld[sID] = newVal;
    }

    // current
    sensorCurrent[sID] = newVal;

    // min & max
    if (newVal < sensorMin[sID]) {
      sensorMin[sID] = newVal;
    }
    if (newVal > sensorMax[sID]) {
      sensorMax[sID] = newVal;
    }

    // average
    if (isFirstMeasurement) {
      dVal = newVal;
    } else {
      dVal = (sensorAverage[sID] + newVal) / 2;
    }
    sensorAverage[sID] = dVal;
  }
  isFirstMeasurement = false;
}

void clearSensors() {
  for (sID = 1; sID <= sensors; sID++) {
    sensorMin[sID] = 127;
    sensorMax[sID] = -127;
    sensorAverage[sID] = 0;
    sensorTrend[sID] = TREND_CONST;
  }
  isFirstMeasurement = true;
}

//
// interface
//

void errorHalt(String s) {
  lcd.clear();
  lcd.println(s);
  // Serial.println(s);
  while (1) {
    errorTone(); delay(2000);
  }
}

void showTime() {
  lcd.setCursor(14, 0); lcd.write(' ');
  if (now.hour() < 10) {
    lcd.write(' ');
  }
  lcd.print(now.hour());
  if (now.second() & 1) {
    lcd.write(':');
  } else {
    lcd.write(' ');
  }
  if (now.minute() < 10) {
    lcd.write('0');
  }
  lcd.print(now.minute());
}

void showSensors() {
float dVal;
char fstr[6];
char str[16];

  for (sID = 1; sID <= sensors; sID++) {
    switch (viewMode) {
      case 0: // current
        dVal = sensorCurrent[sID];
        break;
      case 1: // minimum
        dVal = sensorMin[sID];
        break;
      case 2: // average
        dVal = sensorAverage[sID];
        break;
      case 3: // maximum
        dVal = sensorMax[sID];
        break;
    }
    dtostrf(dVal,6,1,fstr);
    sprintf(str,"T%1d%c%s%c ",sID,char(sensorTrend[sID]),fstr,char(0xdf));

    lcd.setCursor((sID - 1) * 10, 1);
    lcd.print(str);
    // lcd.write('T'); lcd.print(sID); lcd.write(byte(sensorTrend[sID]));
    // lcd.print(fstr);
    // lcd.write(byte(0xDF));
  }
}

void showCardAvailable() {
  byte ch;
  if (isSD) {
    ch = 242;
  } else {
    errorTone();
    ch = byte('!');
  }
  if (menuMode == 0) {
    lcd.setCursor(13, 0);
    lcd.write(ch);
  }
}

byte selectMenu(byte sel, byte max) {
  if (buttonState == 1) { // previous entry
    buttonUpTone();
    if (sel > 0) {
      sel--;
    } else {
      sel = max - 1;
    }
  } else if (buttonState == 2) { // next entry
    buttonDownTone();
    if (sel < max - 1) {
      sel++;
    } else {
      sel = 0;
    }
  }
  return sel;
}

// view mode menu

void updateViewMode() {
  lcd.setCursor(0, 0); lcd.print(menuView[viewMode]);
  showSensors();
}

void buttons4ViewMode() {
  viewMode = selectMenu(viewMode, viewMenuEntries);
  if (buttonState < 4) {
    updateViewMode();
  } else {
    menuMode = 1;
    menuOption = DEFAULT_MAINMENU;
    redrawMenu = true;
    menuMs = ms;
    acceptTone();
  }
}

// Main menu

void updateSelectMenu(char *items[], byte current, byte maxEntries) {
  lcd.clear();
  lcd.setCursor(0, 1);
  lcd.print("> ");
  lcd.print(items[current]);
  lcd.setCursor(2, 0);
  if (current > 0) {
    lcd.print(items[current - 1]);
  } else {
    lcd.print(items[maxEntries - 1]);
  }
  redrawMenu = false;
}

void buttons4MainMenu() {
  menuMs = ms;
  menuOption = selectMenu(menuOption, mainMenuEntries);
  if ((buttonState < 4) || redrawMenu) {
    updateSelectMenu(menuMain, menuOption, mainMenuEntries);
  } else {
    switch (menuOption) {
      case 1: // Eject SD Card
        ejectSDCard();
        break;
      case 2: // Measurements...
        menuMode = 2;
        menuOption = DEFAULT_MAINMENU;
        redrawMenu = true;
        acceptTone();
        return;
      case 3: // Toggle sounds
        sounds = !sounds;
        acceptTone();
        return;
      case 4: // Stats
        showStats();
        break;
    }
    back2ViewMode();
  }
}

// Mesurements menu
void updateValue(unsigned long value, char unit, float divider) {
char fstr[8];
char str[12];
float dVal=value / divider;

  dtostrf(dVal,6,1,fstr);
  sprintf(str,"< %s %c >",fstr,unit);
  lcd.setCursor(4, 1);
  lcd.print(str);
  menuMs = ms;
  buttonMs = ms;
}

long choiceValue(long value, long min, long max, long step, char unit, float divider) {
  bool done = false;
  long butDelay = 0;
  bool firstPress = true;

  acceptTone();
  updateValue(value, unit, divider); oldButtonState = 0;
  while (digitalRead(But3_PIN));
  while (!done) {
    ms = millis();
    if ((ms - buttonMs) > 20) {
      if (butDelay > 0) {
        butDelay--;
      }
      buttonMs = ms;
    }
    buttonState = digitalRead(But1_PIN) << 0;
    buttonState |= digitalRead(But2_PIN) << 1;
    buttonState |= digitalRead(But3_PIN) << 2;
    if (buttonState != 0) {
      if ( butDelay == 0 ) {
        switch (buttonState) {
          case 1: // inc value
            if (value < max) {
              value += step;
            } else {
              value = min;
            }
            break;
          case 2: // dec value
            if (value > min) {
              value -= step;
            } else {
              value = max;
            }
            break;
          case 4:
            done = true;
        }
        if (firstPress) {
          butDelay = 8;
        } else {
          butDelay = 4;
        }
        firstPress = false;
        updateValue(value, unit, divider);
      }
    } else {
      firstPress = true;
    }

    if ((ms - menuMs) > TIMEOUT_MAINMENU) {
      value = -1; done = true; exitTone();
    }
  }
  if (value != -1) {
    enterTone();
  }
  return value;
}

void buttons4MeasurementMenu() {
  unsigned long newVal;

  menuMs = ms;
  menuOption = selectMenu(menuOption, measurementMenuEntries);
  if ((buttonState < 4) || redrawMenu) {
    updateSelectMenu(menuMeasurement, menuOption, measurementMenuEntries);
  } else {
    if (menuOption > 0) {
      lcd.clear();
    }
    switch (menuOption) {
      case 0: // back
        back2ViewMode();
        return;
      case 1: // Reset Minimal/Average/Maximum
        clearSensors();
        enterTone();
        redrawMenu = true;
        return;
      case 2: // Logging period
        lcd.print(F("Logging period time"));
        newVal = choiceValue(long(LOG_TIME_MS / 1000.0), 1, 60 * 60, 1, 's', 1);
        if (newVal > 0) {
          LOG_TIME_MS = newVal * 1000;
        }
        break;
      case 3: // Trend threshold
        lcd.print(F("Trend threshold"));
        newVal = choiceValue(long(trendThreshold * 1000.0), 0, 1000, 100, byte(0xDF), 1000);
        if (newVal >= 0) {
          trendThreshold = float(newVal / 1000.0);
        }
        break;
      case 4: // Sampling period
        lcd.print(F("Sampling period time"));
        newVal = choiceValue(SAMPLE_TIME_MS, 1000, 30 * 1000, 500, 's', 1000);
        if (newVal > 0) {
          SAMPLE_TIME_MS = newVal;
        }
        break;
      case 5: // store current settings
        lcd.print(F("Writeing settings..."));
        writeSettings();
        acceptTone();
        break;
    }
    redrawMenu = true;
  }
}

//
// menus functions
//

void ejectSDCard() {
  lcd.clear();
  lcd.setCursor(2, 0); lcd.println(F("Logging on hold"));
  delay(1000);
  isSD = false; SD.end();
  lcd.clear();
  lcd.println(F("You can remove card."));
  enterTone();
  lcd.setCursor(1, 1);
  lcd.println(F("Press MENU to back"));
  while (!digitalRead(But3_PIN));
  while (digitalRead(But3_PIN));
}

void back2ViewMode() {
  menuMode = 0;
  lcd.clear();
  updateViewMode();
  showTime();
  showCardAvailable();
  showSensors();
  exitTone();
}

void showStats() {

}

//
//
//

void startUpTone () {
  if (sounds) {
    for (byte i = 5; i < 9; i++) {
      tone(BUZZER_PIN, i * 150, 75); delay(75);
    }
  }
}

void exitTone() {
  if (sounds) {
    for (byte i = 8; i > 2; i--) {
      tone(BUZZER_PIN, i * 150, 50); delay(50);
    }
  }
}

void buttonUpTone() {
  if (sounds) {
    tone(BUZZER_PIN, 1500, 50);
  }
}

void buttonDownTone() {
  if (sounds) {
    tone(BUZZER_PIN, 1700, 50);
  }
}

void acceptTone() {
  if (sounds) {
    tone(BUZZER_PIN, 500, 100); delay(100);
    tone(BUZZER_PIN, 700, 100); delay(100);
  }
}

void enterTone() {
  if (sounds) {
    tone(BUZZER_PIN, 700, 100); delay(100);
    tone(BUZZER_PIN, 500, 100); delay(100);
  }
}

void errorTone() {
  if (sounds) {
    tone(BUZZER_PIN, 100, 250);
  }
}
//
//
//

void readSettings() {
  byte isStored;
  int adr = 0;

  EEPROM.get(adr, isStored);       adr += sizeof(bool);
  if (isStored==0xFF) {
    return;
  }

  EEPROM.get(adr, LOG_TIME_MS);    adr += sizeof(long);
  EEPROM.get(adr, SAMPLE_TIME_MS); adr += sizeof(long);
  EEPROM.get(adr, trendThreshold); adr += sizeof(float);
}

void writeSettings() {
  byte isStored = 1;
  int adr = 0;

  EEPROM.put(adr, isStored);       adr += sizeof(bool);
  EEPROM.put(adr, LOG_TIME_MS);    adr += sizeof(long);
  EEPROM.put(adr, SAMPLE_TIME_MS); adr += sizeof(long);
  EEPROM.put(adr, trendThreshold); adr += sizeof(float);
}

//
//
//

void setup() {
  pinMode(But1_PIN, INPUT);
  pinMode(But2_PIN, INPUT);
  pinMode(But3_PIN, INPUT);
  
  lcd.begin(20, 4);
  lcd.println(F("Initialization..."));

  lcd.createChar(TREND_FALL, customChar1);
  lcd.createChar(TREND_RISE, customChar2);

  delay(500);

  if (!rtc.begin()) {
    errorHalt(F("RTC not found :("));
  }
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  sensors=2;
  // sensors = ds.getNumberOfDevices();
  // if (sensors==0) { errorHalt(F("Sensor(s) not found :(")); }
  testSDCard();

  if (!digitalRead(But3_PIN)) { // if Menu button is not pressed...
    readSettings();   // read settings from EEPROM
    oldButtonState = 4; // prevents entry into the Main Menu
  } else {
    oldButtonState = 255; // ...otherwise, default settings
  }

  lcd.clear();
  lcd.setCursor(0,1); lcd.print(F("* multiTEMP LOGGER *"));
  showTime();
  showCardAvailable();
  createLogFile();
  delay(1000);
  updateViewMode();
}

void loop() {
  now = rtc.now(); ms = millis();

  buttonState = digitalRead(But1_PIN) << 0;
  buttonState |= digitalRead(But2_PIN) << 1;
  buttonState |= digitalRead(But3_PIN) << 2;
  if ((buttonState != oldButtonState) && ((ms - buttonMs) > BUTTON_THRESHOLD) || redrawMenu) {
    if ((buttonState > 0) || redrawMenu) {
      switch (menuMode) {
        case 0:
          buttons4ViewMode();
          break;
        case 1:
          buttons4MainMenu();
          break;
        case 2:
          buttons4MeasurementMenu();
          break;
      }
    }
    oldButtonState = buttonState;
    menuMs = ms;
    buttonMs = ms;
  }

  if (!isSD) {
    if ((ms - cardMs) > CARD_TIME_MS) {
      testSDCard();
      showCardAvailable();
      if (isSD) {
        createLogFile();
      }
      cardMs = ms;
    }
  } else {
    if ((ms - logMs) > LOG_TIME_MS) {
      getSensors();
      writeLogFile();
      logMs = ms;
    }
  }

  if (menuMode == 0) {
    // show current time & measurements ONLY in view mode...
    if (now.second() != oldTime.second()) {
      showTime();
      oldTime = now;
    }

    if ((ms - tempMs) > SAMPLE_TIME_MS) {
      getSensors();
      showSensors();
      tempMs = ms;
    }
  } else {
    // ...otherwise if time is out, back to view mode :)
    if ((ms - menuMs) > TIMEOUT_MAINMENU) {
      back2ViewMode();
    }
  }

}
GND5VSDASCLSQWRTCDS1307+