#include "main.h"

void taster_abfrage();
void set_lichtszene();
void set_moveszene();
bool licht_update();
void szene_oh_tannebaum();
void szene_nachts_dorf();
void szene_schnee_dorf();
void szene_nachts();
void szene_tag_dorf();
void move_zugoben();
void move_zugunten();
void move_schiene_oben();
void show_counters();
void updateNVS();


unsigned int licht_all_count = 0;
unsigned int move_all_count = 0;
unsigned long last_updated_NVS = 0;

bool licht_is_fading = false;

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

  taster_aussen_licht.begin();
  taster_aussen_move.begin();
  taster_train_unten.begin();
  taster_train_oben.begin();
  taster_schiene.begin();

  relais_wolke_schiene.begin();
  relais_spiegel.begin();

  zugoben.begin();
  zugunten.begin();
  licht.begin();
  NVS.begin();
  licht_all_count = NVS.getInt("licht");
  move_all_count = NVS.getInt("move");

  moni.init();
  moni.setBrigthness(100);

  licht_state_flag = LSTERNBILD1 | LSTERNBILD2 | LSTERNBILD3 | LSTERNE1 | LSTERNE2 | LBAUHOF | LBURG | LTANNE;
  mp3ply.begin();
  mp3ply.stop();

  led_sterne1.setBrigthness(20);
  led_sterne2.setBrigthness(20);
  led_sternbilder1.setBrigthness(220);
  led_sternbilder2.setBrigthness(220);
  led_sternbilder3.setBrigthness(220);

  led_bauhof.setBrigthness(40);
  led_burg.setBrigthness(40);
  led_teich.setBrigthness(1);
  led_back.setBrigthness(200);
  //blau led_back.setHS(32000,200);
  led_back.setHS(9000,180);
  led_teich.setHS(40000,255);

}

void loop() {
  updateNVS();
  show_counters();  

  taster_abfrage();
  set_lichtszene();
  set_moveszene();
  licht_is_fading = licht_update();
  
}

bool next_licht_szene_possible = true;
bool next_move_szene_possible = true;

uint8_t licht_szene = 0;
uint8_t move_szene = 0;
unsigned long szene_time_ms = 0;
unsigned long szene_overall_time_ms = 0;

void set_lichtszene()
{
  if(next_licht_szene_possible == false)
  {
    szene_overall_time_ms = millis();
    switch(licht_szene)
    {
      case 0:
        szene_nachts();
        break;
      case 1:
        szene_nachts_dorf();
        break;
      case 2:
        szene_schnee_dorf();
        break;
      case 3:
        szene_tag_dorf();
        break;
      case 4:
        szene_oh_tannebaum();        
        break;
      default:
        licht_szene = 0;
    }
  }
  if(millis() - szene_overall_time_ms > SZENE_TIMEOUT_TO_SET_TO_DEFAULT_MS)
  {
    licht_state_flag = LSTERNBILD1 | LSTERNBILD2 | LSTERNBILD3 | LSTERNE1 | LSTERNE2 | LTANNE;
    licht_szene = 0;
    taster_aussen_licht.reset();
    next_licht_szene_possible = true;
  }
}

void set_moveszene()
{
  if(next_move_szene_possible == false)
  {
    switch(move_szene)
    {
      case 0:
        move_zugoben();
        break;
      case 1:
        move_zugunten();
        break;
      case 2:
        move_schiene_oben();
        break;
      default:
        move_szene = 0;
        break;
    }
  }
}

const byte TANNENBAUM_VOL = 20;
const unsigned long TANNENBAUM_PLAY_MUSIC_MS = 60000;
typedef enum {TA_INIT = 0, TA_LIGHT_READY, TA_START_PLAYING ,TA_MIN_PLAYING,TA_PLAYING, TA_STOP_PLAYING, TA_FINSIHED} szene_tanne_t;
szene_tanne_t tanne_state = TA_INIT;
void szene_oh_tannebaum()
{
  unsigned long msecs = millis();
  switch(tanne_state)
  {
    case TA_INIT:
      Serial.printf("Tannenbaum spielt\n");
      licht_state_flag = LSTERNBILD1 | LSTERNBILD2 | LSTERNBILD3 | LSTERNE1 | LSTERNE2 | LTANNE; 
      tanne_state = TA_LIGHT_READY;
      break;
    case TA_LIGHT_READY:
      if(licht_is_fading == false)
      {
        tanne_state = TA_START_PLAYING;
        Serial.printf("All lights are ready for Oh Tannebaum\n");
        mp3ply.play_vol(2, TANNENBAUM_VOL);
      }
      break;
    case TA_START_PLAYING:
      szene_time_ms = msecs;
      if(mp3ply.is_playing() > 0)
      {
        tanne_state = TA_MIN_PLAYING;
        Serial.printf("Oh Tannebaum is playing\n");
      }
      break;
    case TA_MIN_PLAYING:
      taster_aussen_licht.reset();
      if(msecs - szene_time_ms >  1000)
        tanne_state = TA_PLAYING;
        break;
    case TA_PLAYING:
      if(msecs - szene_time_ms >  TANNENBAUM_PLAY_MUSIC_MS || taster_aussen_licht.pressed() == true || mp3ply.is_playing() == 0)
        tanne_state = TA_STOP_PLAYING;
      break;
    case TA_STOP_PLAYING:
      if(mp3ply.fade_out() == false)
      {
        //if(mp3ply.is_playing() == 0)
        //{
          Serial.printf("Oh Tannebaum faded out and stopped\n");
          tanne_state = TA_FINSIHED;
        //}
      }
      break;
    case TA_FINSIHED:
      tanne_state = TA_INIT;
      next_licht_szene_possible = true;
      //taster_aussen_licht.reset();
      Serial.printf("Tannenbaum finished\n");
      break;
  }
}

typedef enum {NA_INIT = 0, NA_LIGHT_READY, NA_FINISHED} szene_nacht_t;
szene_nacht_t nacht_state = NA_INIT;
void szene_nachts_dorf()
{
  switch(nacht_state)
  {
    case NA_INIT:
      Serial.printf("Nachts im Dorf\n");
      licht_state_flag = LSTERNBILD1 | LSTERNBILD2 | LSTERNBILD3 | LSTERNE1 | LSTERNE2 | LTANNE | LBURG | LBAUHOF | LTEICH | LHAEUSER;
      nacht_state = NA_LIGHT_READY;
      break;
    case NA_LIGHT_READY:
      if(licht_is_fading == false)
      {
        nacht_state = NA_FINISHED;
        Serial.printf("All lights are ready for Nachts im Dorf\n");
      }
      break; 
    case NA_FINISHED:
      nacht_state = NA_INIT;
      next_licht_szene_possible = true;
      taster_aussen_licht.reset();
      Serial.printf("Nachts im Dorf finished\n");
      break;
  }       
}

typedef enum {SD_INIT = 0, SD_LIGHT_READY, SD_FINISHED} szene_schnee_t;
szene_schnee_t schnee_state = SD_INIT;
void szene_schnee_dorf()
{
  switch(schnee_state)
  {
    case SD_INIT:
      Serial.printf("Schnee im Dorf\n");
      licht_state_flag = LSTERNE1 | LSTERNE2 | LTANNE | LBURG | LBAUHOF | LTEICH | LSPIEGEL | LHAEUSER;
      schnee_state = SD_LIGHT_READY;
      break;
    case SD_LIGHT_READY:
      if(licht_is_fading == false)
      {
        schnee_state = SD_FINISHED;
        Serial.printf("All lights are ready for Schnee im Dorf\n");
      }
      break; 
    case SD_FINISHED:
      schnee_state = SD_INIT;
      next_licht_szene_possible = true;
      taster_aussen_licht.reset();
      Serial.printf("Schnee im Dorf finished\n");
      break;
  }         
}

typedef enum {DU_INIT = 0, DU_LIGHT_READY, DU_FINISHED} szene_dunkel_t;
szene_dunkel_t dunkel_state = DU_INIT;
void szene_nachts()
{
  switch(dunkel_state)
  {
    case DU_INIT:
      Serial.printf("Dunkel im Dorf\n");
      licht_state_flag = LSTERNBILD1 | LSTERNBILD2 | LSTERNBILD3 | LSTERNE1 | LSTERNE2;
      dunkel_state = DU_LIGHT_READY;
      break;
    case DU_LIGHT_READY:
      if(licht_is_fading == false)
      {
        dunkel_state = DU_FINISHED;
        Serial.printf("All lights are ready for Dunkel im Dorf\n");
      }
      break; 
    case DU_FINISHED:
      dunkel_state = DU_INIT;
      next_licht_szene_possible = true;
      taster_aussen_licht.reset();
      Serial.printf("Dunkel im Dorf finished\n");
      break;
  }         
}

typedef enum {TAG_INIT = 0, TAG_LIGHT_READY, TAG_FINISHED} szene_tag_t;
szene_tag_t tag_state = TAG_INIT;
void szene_tag_dorf()
{
  switch(tag_state)
  {
    case TAG_INIT:
      Serial.printf("Tag im Dorf\n");
      licht_state_flag = LTANNE | LBACK;
      tag_state = TAG_LIGHT_READY;
      break;
    case TAG_LIGHT_READY:
      if(licht_is_fading == false)
      {
        tag_state = TAG_FINISHED;
        Serial.printf("All lights are ready for Tag im Dorf\n");
      }
      break; 
    case TAG_FINISHED:
      tag_state = TAG_INIT;
      next_licht_szene_possible = true;
      taster_aussen_licht.reset();
      Serial.printf("Tag im Dorf finished\n");
      break;
  }         
}


unsigned int zugunten_error = 0;
const unsigned int ZUGUNTEN_MAX_ERROR_COUNT = 5;
const unsigned long ZUGUNTEN_DRIVES_TO_STATION_MS = 5000;
const unsigned long ZUGUNTEN_STOPPED_AT_STATION_MS = 5000;
const unsigned long ZUGUNTEN_MAX_TIME_TO_HOME_MS = 15000; ///timeout time
typedef enum {ZU_INIT = 0, ZU_AT_HOME , ZU_DRIVES_TO_STATION, ZU_STOPPES_AT_STATION, ZU_STOPPED_AT_STATION, ZU_DRIVES_HOME, ZU_STOPPES_AT_HOME} szene_zugunten_t;
szene_zugunten_t zugunten_state = ZU_INIT;
void move_zugunten()
{
  unsigned long msecs = millis();
  switch(zugunten_state)
  {
    case ZU_INIT:
      Serial.printf("Zugunten fährt\n");
      taster_train_unten.reset();
      if (zugunten_error > ZUGUNTEN_MAX_ERROR_COUNT)
        zugunten_state = ZU_STOPPES_AT_HOME;
      else
        zugunten_state = ZU_AT_HOME;
      break;
    case ZU_AT_HOME:
      szene_time_ms = msecs;
      if(zugunten.fade_on() == false)
        zugunten_state = ZU_DRIVES_TO_STATION;
      break;
    case ZU_DRIVES_TO_STATION:
      if(msecs - szene_time_ms > ZUGUNTEN_DRIVES_TO_STATION_MS)
      {
        zugunten_state = ZU_STOPPES_AT_STATION;
        zugunten.switch_zero_relais(false);
      }
      break;
    case ZU_STOPPES_AT_STATION:
      szene_time_ms = msecs;
      if(zugunten.fade_off() == false)
        zugunten_state = ZU_STOPPED_AT_STATION;
      break;
    case ZU_STOPPED_AT_STATION:
      if(msecs - szene_time_ms > ZUGUNTEN_STOPPED_AT_STATION_MS)
      {
        zugunten_state = ZU_DRIVES_HOME;
        szene_time_ms = msecs;
      }
      break;
    case ZU_DRIVES_HOME:
      if(zugunten.fade_on() == false)
        if(taster_train_unten.pressed() == true || (msecs-szene_time_ms) > ZUGUNTEN_MAX_TIME_TO_HOME_MS)
        {
          if( (msecs-szene_time_ms) > ZUGUNTEN_MAX_TIME_TO_HOME_MS)
          {
            Serial.printf("Train unten runs ins timeout while driving back at Home\n");
            zugunten_error++;
          }
          else{
            Serial.printf("Train unten back at Home pressed\n");
            zugunten_error = 0;
          }
          zugunten_state = ZU_STOPPES_AT_HOME;
          zugunten.switch_zero_relais(true);
        }
      break;
    case ZU_STOPPES_AT_HOME:
      if(zugunten.fade_off() == false)
      {
        zugunten_state = ZU_INIT;
        next_move_szene_possible = true;
        taster_aussen_move.reset();
        Serial.printf("Zugunten stopped\n");
      }
      break;
  }
}

unsigned int zugoben_error = 0;
const unsigned int ZUGOBEN_MAX_ERROR_COUNT = 5;
const unsigned long ZUGOBEN_DRIVES_TO_BRIDGE_MS = 2000;
const unsigned long ZUGOBEN_DRIVES_ON_BRIDGE_MS = 2000;
const unsigned long ZUGOBEN_MAX_TIME_TO_HOME_MS = 10000; ///timeout time
const uint8_t ZUGOBEN_SPEED_ON_BRIDGE = 80;
typedef enum {ZO_INIT = 0, ZO_AT_HOME, ZO_DRIVES_TO_BRIDGE, ZO_DRIVES_SLOWDOWN_ON_BRIDGE, ZO_DRIVES_ON_BRIDGE, ZO_DRIVES_HOME, ZO_STOPPES_AT_HOME} szene_zugoben_t;
szene_zugoben_t zugoben_state = ZO_INIT;
void move_zugoben()
{
  unsigned long msecs = millis();
  switch(zugoben_state)
  {
    case ZO_INIT:
      Serial.printf("Zugoben fährt\n");
      taster_train_oben.reset();
      if (zugoben_error > ZUGOBEN_MAX_ERROR_COUNT)
        zugoben_state = ZO_STOPPES_AT_HOME;
      else
        zugoben_state = ZO_AT_HOME;
      break;
    case ZO_AT_HOME:
      szene_time_ms = msecs;
      if(zugoben.fade_on() == false)
        zugoben_state = ZO_DRIVES_TO_BRIDGE;
      break;
    case ZO_DRIVES_TO_BRIDGE:
      if(msecs - szene_time_ms > ZUGOBEN_DRIVES_TO_BRIDGE_MS)
        zugoben_state = ZO_DRIVES_SLOWDOWN_ON_BRIDGE;
      break;
    case ZO_DRIVES_SLOWDOWN_ON_BRIDGE:
      szene_time_ms = msecs;
      if(zugoben.fade_down_to(ZUGOBEN_SPEED_ON_BRIDGE) == false)
        zugoben_state = ZO_DRIVES_ON_BRIDGE;
      break;
    case ZO_DRIVES_ON_BRIDGE:
      if(msecs - szene_time_ms > ZUGOBEN_DRIVES_ON_BRIDGE_MS)
      {
        zugoben_state = ZO_DRIVES_HOME;
        szene_time_ms = msecs;
      }
      break;
    case ZO_DRIVES_HOME:
      if(zugoben.fade_on() == false)
        if(taster_train_oben.pressed() == true || (msecs-szene_time_ms) > ZUGOBEN_MAX_TIME_TO_HOME_MS)
        {
          if((msecs-szene_time_ms) > ZUGOBEN_MAX_TIME_TO_HOME_MS)
          {
            Serial.printf("Train oben runs in time out while driving back at home\n");
            zugoben_error++;
          }
          else
          {
            Serial.printf("Train oben back at Home pressed\n");
            zugoben_error = 0;
          }
          zugoben_state = ZO_STOPPES_AT_HOME;
        }
      break;
    case ZO_STOPPES_AT_HOME:
      if(zugoben.fade_off() == false)
      {
        zugoben_state = ZO_INIT;
        next_move_szene_possible = true;
        taster_aussen_move.reset();
        Serial.printf("Zugoben stopped at home\n");
      }
      break;
  }
}

unsigned int soben_error = 0;
const unsigned int SOBEN_MAX_ERROR_COUNT = 5;
const unsigned long SOBEN_MAX_DRIVES_TIME_MS = 30000; ///timeout time
typedef enum {SO_INIT = 0, SO_AT_HOME, SO_DRIVES_HOME, SO_STOPPES_AT_HOME} szene_soben_t;
szene_soben_t soben_state = SO_INIT;
void move_schiene_oben()
{
  unsigned long msecs = millis();
  switch(soben_state)
  {
    case SO_INIT:
      Serial.printf("Schiene oben fährt\n");
      taster_schiene.reset();
      if(soben_error > SOBEN_MAX_ERROR_COUNT)
        soben_state = SO_STOPPES_AT_HOME;
      else
        soben_state = SO_AT_HOME;
      break;
    case SO_AT_HOME:
      szene_time_ms = millis();
      relais_wolke_schiene.on();
      soben_state = SO_DRIVES_HOME;
      break;
    case SO_DRIVES_HOME:
        if(taster_schiene.pressed() == true || (msecs-szene_time_ms) > SOBEN_MAX_DRIVES_TIME_MS)
        {
          if((msecs-szene_time_ms) > SOBEN_MAX_DRIVES_TIME_MS)
          {
            soben_error++;
            Serial.printf("Schiene oben timeout while driving home.\n");
          }
          else
          {
            soben_error = 0;
            Serial.printf("Schiene oben back at Home pressed\n");
          }
          soben_state = SO_STOPPES_AT_HOME;
        }
      break;
    case SO_STOPPES_AT_HOME:
      relais_wolke_schiene.off();
      soben_state = SO_INIT;
      next_move_szene_possible = true;
      taster_aussen_move.reset();
      Serial.printf("Schiene oben stopped at home\n");
      break;
  }
}



bool licht_update()
{
  bool fade_updated = false;
  bool rainbow_updated = false;
  fade_updated = led_sterne1.update() || fade_updated;
  fade_updated = led_sterne2.update() || fade_updated;
  fade_updated = led_sternbilder1.update() || fade_updated;
  fade_updated = led_sternbilder2.update() || fade_updated;
  fade_updated = led_sternbilder3.update()|| fade_updated;
  rainbow_updated = led_tanne.update(leds::UP_RAINBOW) || rainbow_updated;
  fade_updated = led_bauhof.update() || fade_updated;
  fade_updated = led_burg.update() || fade_updated;
  fade_updated = led_teich.update() || fade_updated;
  fade_updated = led_back.update() || fade_updated;

  if((licht_state_flag & LHAEUSER) == 0)
  {
    //Serial.printf("Fade off lights at houses");
    fade_updated = licht.fade_off() || fade_updated;
  }
  else
  {
    //Serial.printf("Fade on lights at houses");
    fade_updated = licht.fade_on() || fade_updated;
  }

  if((licht_state_flag & LSPIEGEL) == 0)
    relais_spiegel.off();
  else
    relais_spiegel.on();
  

  if(true == rainbow_updated || true == fade_updated)
  {
    rgb_leds.show();
  }
  return(fade_updated);
}

void taster_abfrage()
{
  if(next_licht_szene_possible == true)
  {
    if(taster_aussen_licht.pressed() == true)
    {
      Serial.printf("Aussenlicht pressed\n");
      licht_szene++;
      licht_all_count++;
      next_licht_szene_possible = false;
    }
  }

  if(next_move_szene_possible == true)
  {
    if(taster_aussen_move.pressed() == true)
    {
      Serial.printf("Move pressed\n");
      move_szene++;
      move_all_count++;
      next_move_szene_possible = false;
    }
  }
}

void updateNVS()
{
  static uint8_t value = 0;
  if(millis() - last_updated_NVS > 1000)
  {
    last_updated_NVS = millis();
    switch(value)
    {
      case 1:
        NVS.setInt("licht", licht_all_count);
        break;
      case 2:
        NVS.setInt("move", move_all_count);
        break;
      default:
        value = 0;
    }
    value++;
  }
}

unsigned long display_time_ms = 0;
const unsigned long MAX_DISPLAY_TIME_BEFORE_FLIP_MS = 600000;
void show_counters()
{
  if(millis() - display_time_ms > MAX_DISPLAY_TIME_BEFORE_FLIP_MS)
  {
    display_time_ms = millis();
    moni.flipScreenVert();
  }
  moni.clear();
  moni.header("Zaehler");
  moni.data(30,12,(uint64_t)licht_all_count, "Licht");
  moni.data(100,12, (uint64_t)move_all_count, "Moves");
  moni.data(15,38,(uint64_t)zugunten_error, "ZUErr");
  moni.data(60,38,(uint64_t)zugoben_error, "ZOErr");
  moni.data(100,38,(uint64_t)soben_error, "SOErr");
  moni.show();
}