#include <avr/pgmspace.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Streaming.h>
#include <EEPROM.h>
#include "RTClib.h"
#include <Time.h>
#include <TimeLib.h>

const uint8_t days_in_month [12] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const int chipSelect = 10;

const char string_0[] PROGMEM = "1=0-Falling 1-Rising 2-Both";
const char string_1[] PROGMEM = "2=Scan Intervall";
const char string_2[] PROGMEM = "3=0-Date+time,1-Timestamp,2-Timestamp+Millis,3-Systemmillis,4-Systemmillis+TimeDiff";
const char string_3[] PROGMEM = "4=Output 0-Serial 1-SD 2-Both";
const char string_4[] PROGMEM = "5=Minimal Signal Duration in Ms";
const char string_5[] PROGMEM = "6=Debounce in Ms";
const char string_6[] PROGMEM = "7=Activate Channel 1=On  0=Off";
const char string_7[] PROGMEM = "8=change Channel (1-4)";
const char string_8[] PROGMEM = "9=Save!";
const char string_9[] PROGMEM = "10=Set RTC unixtime UTC!";
const char* const string_table[] PROGMEM  = {string_0, string_1, string_2, string_3, string_4, string_5, string_6, string_7, string_8, string_9};
//Beispielconfig
int configs[40] = {1, 5, 2, 0, 50, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

//RTC&Zeit
RTC_DS3231 RTC;
String filename = "";

void setup() {
  loadconfigs();
  Serial.begin(115200);

  if (! RTC.begin())
  {}
  setSyncProvider(syncProvider);
  setSyncInterval(1000);
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  filename += String(now() / 60 / 60);
  filename += ".csv";
}

#define SECONDS_FROM_1970_TO_2000 946684800
int pins[4] = {2, 3, 4, 5}; //Pinconfig

char buffer2[100]; //Serial Buffer
String inputString = ""; //Serial Buffer
boolean stringComplete = false; //Serial Helper

int channel = 1; //Menuhelper
boolean menuactive = false; //Menuhelper
int menuchoice = 0; //Menuhelper
int choicevalue = 0; //Menuhelper

int laststate[4] = {HIGH, HIGH, HIGH, HIGH}; //Statehelper
int state[4] = {HIGH, HIGH, HIGH, HIGH}; //Statehelper

//Testtrash
//int channelorder;
//int chswitch = 1;
//int chswitchbackup;

void loop() {
  if (!menuactive)
    intervallset();
  serialmenu();
}

//Call between Loops by Serial Input
void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    if (!menuactive)
    {
      inputString += inChar;
      if (inChar == '\n') {
        stringComplete = true;
      }
    } else
    {
      if (isDigit(inChar)) {
        inputString += (char)inChar;
      }
      if (inChar == '\n') {
        stringComplete = true;
      }
    }
  }
}


//MenuPrintout
void menu() {
  Serial.print(hour());
  Serial.print(":");
  Serial.print(minute());
  Serial.print(":");
  Serial.print(second());
  Serial.print("-");
  Serial.print(day());
  Serial.print(".");
  Serial.print(month());
  Serial.print(".");
  Serial.println(year());
  Serial.print("Aktiver Kanal: ");
  Serial.println(channel);

  for (int i = 0; i <= 9; i++)
  { int test = ((channel - 1) * 10) + i;
    int tempwert = configs[((((channel - 1) * 10) + i))];
    if (tempwert != -1)
      Serial.print(tempwert);
    spaces(String(tempwert).length());
    strcpy_P(buffer2, (char*)pgm_read_word(&(string_table[i])));
    Serial.println(buffer2);
    delay( 100 );
  }
}

//Menucontrol
void serialmenu() {
  // Serial.println(menuchoice);
  if (stringComplete)

    if (!menuactive && inputString == "menu\n")
    {
      menuactive = true;
      stringComplete = false;
      inputString = "";
      //   menuchoice=0;
      menu();

    } else if (menuactive && menuchoice == 0)
    {
      menuchoice = inputString.toInt();
      strcpy_P(buffer2, (PGM_P)pgm_read_word(&(string_table[menuchoice - 1])));
      Serial.println(buffer2);
      Serial.println("Neuen Wert eingeben!");
      stringComplete = false;
      inputString = "";

    } else if (menuactive && menuchoice == 8)
    {
      choicevalue = inputString.toInt();
      if (choicevalue < 4) {
        channel = choicevalue;
      } else
      {
        Serial.println("Falsche Eingabe");
      }
      stringComplete = false;
      inputString = "";
      menuchoice = 0;

      menu();
    } else if (menuactive && menuchoice == 9)
    {
      saveconfigs((channel - 1));
      menuchoice = 0;
      menuactive = false;
      stringComplete = false;
      inputString = "";
      menu();
    } else if (menuactive && menuchoice == 10)
    {
      static time_t tLast;
      time_t t = inputString.toInt() + 3600;
      RTC.adjust(t);
      stringComplete = false;
      inputString = "";
      menuchoice = 0;
      menuactive = false;
      menu();
      //dump any extraneous input


    }

    else if (menuactive && menuchoice != 0)
    {
      choicevalue = inputString.toInt();
      configs[((channel - 1) * 10) + (menuchoice - 1)] = choicevalue;
      stringComplete = false;
      inputString = "";
      menuchoice = 0;

      menu();
    }
}

//Menuhelper für Zeilen
void spaces(int sizechars) {
  for (int z = 8; z != sizechars; z--)
  {
    Serial.print(" ");
  }

}

boolean debouncing[4] = {false, false, false, false}; //Entprellt?
boolean mininterval[4] = {true, true, true, true}; //Minimale Impulslänge für Aktion
unsigned long lastintervallmillis[4] = {millis(), millis(), millis(), millis()};
unsigned long changestate[4] = {millis(), millis(), millis(), millis()};
unsigned long lastdebounce[4] = {millis(), millis(), millis(), millis()};
unsigned long lastminpulse[4] = {millis(), millis(), millis(), millis()};

//ControlUnit
void intervallset() {
  unsigned long tempmillis = millis();
  for (int i = 0; i <= 3; i++) {
    if (tempmillis >= lastintervallmillis[i] + configs[(i * 10) + 1] && configs[(i * 10) + 6] == 1)
    {
      //  Serial.println("check state");
      lastintervallmillis[i] = tempmillis;
      int tempstate = digitalRead(pins[i]);

      if (tempstate != laststate[i])
      {
        debouncing[i] = true;
        lastminpulse[i] = true;
        laststate[i] = tempstate;
        changestate[i] = tempmillis;
        mininterval[i] = true;
        //Serial.println("Statechange");
      }

      if (debouncing[i]) {
        // Serial.println("debouncing");
        if (tempmillis >= changestate[i] + configs[((i * 10) + 5)] && debouncing[i])
        { debouncing[i] = false;
          mininterval[i] = false;
        }
      }
      if (tempmillis >= changestate[i] + configs[((i * 10) + 5)] + configs[((i * 10) + 4)] && !debouncing[i] && !mininterval[i])
      {
        //Serial.println("check mininterval");
        state[i] = laststate[i];
        debouncing[i] = false;
        lastminpulse[i] = false;
        mininterval[i] = true;
        int Flanke = configs[(i * 10)];
        if (Flanke == 2)
        {
          Flanke = tempstate;
        }
        if (state[i] == Flanke) {
          if (configs[((i * 10) + 3)] == 0 || configs[((i * 10) + 3)] == 2)
          {
            Serial.println( createlogtext(state[i], i, now(), millis()));
          }
          if (configs[((i * 10) + 3)] == 1 || configs[((i * 10) + 3)] == 2)  {
            File dataFile = SD.open(filename, FILE_WRITE);
            if (dataFile) {
              dataFile.println( createlogtext(state[i], i, now(), millis() % 1000));
              dataFile.close();
            }

          }
        }
      }
    }
  }
}

//Generate Unixtime for RTC Setup
uint32_t get_unixtime(tmElements_t t)
{
  uint8_t i;
  uint16_t d;
  int16_t y;
  uint32_t rv;
  int tmpYear = t.Year + 1970;
  if (tmpYear >= 2000) {
    y = tmpYear - 2000;
  } else {
    return 0;
  }

  d = t.Day - 1;
  for (i = 1; i < (int)t.Month; i++) {
    d += pgm_read_byte(days_in_month + i - 1);
  }
  if ((int)t.Month > 2 && y % 4 == 0) {
    d++;
  }
  // count leap days
  d += (365 * y + (y + 3) / 4);
  rv = ((d * 24UL + (int)t.Hour) * 60 + (int)t.Minute) * 60 + (int)t.Second + SECONDS_FROM_1970_TO_2000;
  return rv;
}

//print date and time to Serial
void printDateTime(time_t t)
{
  printDate(t);
  Serial << ' ';
  printTime(t);
}

//print time to Serial
void printTime(time_t t)
{
  printI00(hour(t), ':');
  printI00(minute(t), ':');
  printI00(second(t), ' ');
}

//print date to Serial
void printDate(time_t t)
{
  printI00(day(t), 0);
  Serial << monthShortStr(month(t)) << _DEC(year(t));
}

//Print an integer in "00" format (with leading zero),
//followed by a delimiter character to Serial.
//Input value assumed to be between 0 and 99.
void printI00(int val, char delim)
{
  if (val < 10) Serial << '0';
  Serial << _DEC(val);
  if (delim > 0) Serial << delim;
  return;
}

void saveconfigs(int channel) {
  for (int i = channel * 10; i <= channel * 10 + 10; i++)
    eepromWriteInt(i * 2, configs[i]);
}


void loadconfigs() {
  for (int i = 0; i <= 39; i++)
    configs[i] = eepromReadInt(i * 2);
}
void eepromWriteInt(int adr, int wert) {
  byte low, high;
  low = wert & 0xFF;
  high = (wert >> 8) & 0xFF;
  EEPROM.write(adr, low); // dauert 3,3ms
  EEPROM.write(adr + 1, high);
  return;
} //eepromWriteInt
int eepromReadInt(int adr) {
  byte low, high;
  low = EEPROM.read(adr);
  high = EEPROM.read(adr + 1);
  return low + ((high << 8) & 0xFF00);
} //eepromReadInt
unsigned long laststatemillis1[4];
unsigned long laststatemillis0[4];
String createlogtext(int state, int channel, unsigned long newunixtime, unsigned long newmillis) {
  //"3=0-Datum+Uhrzeit,1-Timestamp,2-TimestampMillis,3-Systemtime";
  String tempstring = "'";
  int checkconfig = configs[(channel * 10) + 2];

  switch (checkconfig) {
    case 0:

      tempstring += day();
      tempstring += ".";
      tempstring += month();
      tempstring += ".";
      tempstring += year();
      tempstring += "-";
      tempstring += hour();
      tempstring += ":";
      tempstring += minute();
      tempstring += ":";
      tempstring += second();
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += channel;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += state;
      tempstring += "'";

      return tempstring;
      break;
    //do something when var equals 1
    case 1:
      tempstring += newunixtime;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += channel;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += state;
      tempstring += "'";
      return tempstring;
    case 2:

      tempstring += newunixtime;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += newmillis;
      tempstring += "'";

      tempstring += ",";
      tempstring += "'";
      tempstring += channel;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += state;
      tempstring += "'";
      return tempstring;
      break;
    case 3:

      tempstring += newmillis;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += channel;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += state;
      tempstring += "'";
      return tempstring;
      break;
    case 4:

      tempstring += newmillis;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += channel;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      tempstring += state;
      tempstring += "'";
      tempstring += ",";
      tempstring += "'";
      if (state == 0) {
        tempstring += newmillis - laststatemillis0[channel];
        laststatemillis0[channel] = newmillis;
      } else {
        tempstring += newmillis - laststatemillis1[channel];
        laststatemillis1[channel] = newmillis;
      }
      tempstring += "'";

      return tempstring;
      break;
  }
}
time_t syncProvider()
{
  return RTC.now().unixtime();
}