// BEGIN TEST
#include <Keypad.h>

const uint8_t ROWS = 4;
const uint8_t COLS = 4;
char keys[ROWS][COLS] = {
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
};

uint8_t colPins[COLS] = { 5, 4, 3 }; // Pins connected to C1, C2, C3
uint8_t rowPins[ROWS] = { 9, 8, 7, 6 }; // Pins connected to R1, R2, R3, R4

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);


#include <U8glib.h>
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST); // Fast I2C / TWI
// END TEST


/*
  38400 baud
  Arduino Nano
  ATmega328P (Old Bootloader)
  AVRISP mkII programmer
*/
#include <NmraDcc.h>
NmraDcc  Dcc;

// Interrupt 0 ist Digital Pin 2 (DCC Input)
int P_DCCIn = 2;
int P_LAAuf = A5;          // Aufzug rauf
int P_LAAb = A4;           // Aufzug runter
int P_PosIn = A2;          // Ebenen-Sensor, analog, max 1023

int   RRPosIn = -1;        // rocrail Positionsanfrage (1-16)
short LAPos = -1;          // aktuelle LA Position (1-16)
short LADir = 0;           // 1 aufw, 0 steht, -1 abw
short LASyncT = 2 * 5000;  // Zeit in ms-Sekunden die LA rauf / runter fährt in Sync-Phase und auf ein P_PosIn wartet
short posTab[16][4] = {
  {200, 100, 1013, 0}, // 1
  {200, 100, 998, 0},
  {200, 100, 984, 0},
  {200, 100, 970, 0},
  {200, 100, 955, 0},  // 5
  {200, 100, 931, 0},
  {200, 100, 908, 0},
  {200, 100, 886, 0},
  {200, 100, 851, 0},
  {200, 100, 818, 0},  // 10
  {200, 100, 777, 0},
  {200, 100, 738, 0},
  {200, 100, 696, 0},
  {200, 100, 654, 0},
  {200, 100, 596, 0}, // 15
  {200, 100, 535, 0}, // 16: Verzögerung von unten, oben, ADC-Wert, cycleCounter
};

boolean isError = false;
boolean doLogDebug = true;
unsigned long uptime;
unsigned long cycle = 0;

boolean syncActive = true;
unsigned long startSyncT;

boolean isReady = false;


void setup() {
  // BEGIN TEST
  u8g.setFont(u8g_font_gdr25);
  u8g.setColorIndex(1);
  // END TEST

  Serial.begin(38400);
  log("Setup ...");

  pinMode(P_PosIn, INPUT);
  Dcc.pin(P_DCCIn, 0);
  Dcc.init(MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0);

  log("Setup done");

  startSyncT = millis();
  LADir = 1;
  log("LAauf, Sync sucht aufwärts");
}

void notifyDccAccTurnoutOutput(uint16_t Addr, uint8_t Direction, uint8_t OutputPower) {
  // Addr = (RR_Addr - 1) * 4 + Port
  // => Ebene 1 = (13 - 1) * 4 + 1 = 48
  RRPosIn = Addr - 48;
  log("Gewünschte Ebene: ", RRPosIn);
}

void loop() {
  uptime = millis();
  //Dcc.process();

// BEGIN TEST
  char key = keypad.getKey();
  if (key != NO_KEY) {
    String t = "";
    t += key;
    RRPosIn = t.toInt();
    log("Gewünschte Ebene: ", RRPosIn);
  }

  u8g.firstPage();
  String x = String(analogRead(P_PosIn));
  do {
    u8g.drawStr(25, 25, x.c_str());
  } while (u8g.nextPage());
// END TEST  

  if (isError) {
  } else {
    if (syncActive) {
      unsigned long passed = uptime - startSyncT;
      //logd("passed: ", passed);
      boolean timeIsOver = passed > LASyncT;
/*
      // TODO remove statement (implement it in main logic instead)
      LAPos = leadingPosIn();

      // TODO remove if
      if(passed > 100){
        int px = analogRead(P_PosIn);

        for(byte e=1; e<=16; e++){
          if(posTab[e-1][2] > px-3 && posTab[e-1][2] < px+3){
            LAPos = e;
          }
        }
        Serial.print("PosIn: ");
        Serial.print(px);
        if(LAPos != -1){
          Serial.print(", LAPos: ");
          Serial.println(LAPos);
        }else{
          Serial.println();
        }

        LAPos = -1;
        startSyncT = uptime;
      }
      return;
*/

      if (timeIsOver) {
        if (LADir == 1) {
          LADir = -1;
          timeIsOver = false;
          startSyncT = millis();
          LAab();
          log("LAab, Sync sucht abwärts");
        } else {
          syncActive = false;
          isError = true;
          LAstop(LADir, LAPos);
          log("ERROR: LA Sync hat keine Position gefunden.");
        }
      } else {
        if (LADir == 1) {
          LAauf();
        }

        LAPos = leadingPosIn();
        if(LAPos > 0){
          syncActive = false;
          LAstop(LADir, LAPos);
          log("LA Sync hat Position gefunden: ", LAPos);
          isReady = true;          
        }
/*
        int px = analogRead(P_PosIn);
        logd("PosIn: ", px);
        if (px >= 100) { // skip floating analog in
          LAPos = findPos(px);
          // TODO test LAPos != -1
          syncActive = false;
          LAstop(LADir, LAPos);
          log("PosIn: ", px);
          log("LA Sync hat Position gefunden: ", LAPos);
          isReady = true;
        }
*/
      }
    } else if (isReady) {
      if (RRPosIn != -1) {
        /*
        int px = analogRead(P_PosIn);
        log("PosIn: ", px);
        LAPos = findPos(px);
        */

        LAPos = leadingPosIn();
        if(LAPos > 0){
          // TODO matchen RRPos und LAPos? LAPos=16 == RRPos=16 oder RRPos=1?
          if (RRPosIn < LAPos) {
            log("Position erkannt: ", LAPos);
            log("LAauf, Richtung: ", RRPosIn);
            LAauf();
          } else if (RRPosIn > LAPos) {
            log("Position erkannt: ", LAPos);
            log("LAab, Richtung: ", RRPosIn);
            LAab();
          } else {
            LAstop(LADir, LAPos);
            log("LA hat Position gefunden: ", LAPos);
            RRPosIn = -1;
          }
        }      
      }
    }
  }
}

void LAauf() {
  analogWrite(P_LAAb, 0);
  analogWrite(P_LAAuf, 255);
}

void LAab() {
  analogWrite(P_LAAb, 255);
  analogWrite(P_LAAuf, 0);
}

void LAstop(short dir, short pos) {
  short posDelay = 0;
  if (dir == 1 && pos > 0) {
    posDelay = posTab[pos - 1][1];
  } else if (dir == -1 && pos > 0) {
    posDelay = posTab[pos - 1][0];
  }
  log("Stop auf Position: ", pos);
  log("Verzögerung: ", posDelay);
  delay(posDelay);
  analogWrite(P_LAAuf, 0);
  analogWrite(P_LAAb, 0);
}

short findPos(int px){
  if(px > 100){ // skip floating analog in
    for(byte e=1; e<=16; e++){
      if(posTab[e-1][2] > px-3 && posTab[e-1][2] < px+3){
        return e;
      }
    }
  }
  return -1;
}

// returns 0=collecting, -1=no leading position found, >=1=found position nr
short leadingPosIn(){
  if(++cycle < 100){
    // collect values
    int px = analogRead(P_PosIn) + random(1, 10);
    //logd("PosIn: ", px);
    int cycleLAPos = findPos(px);

    bool posFound = cycleLAPos > -1;
    if(posFound){
      //logd("pos: ", cycleLAPos);
      posTab[cycleLAPos-1][3] += 1;
    }
    return 0;     
  }else{
    // find position with highest counter value
    short mostFoundPos = -1;
    byte maxCount = 0;
    for(byte e=1; e<=16; e++){
      if(posTab[e-1][3] > maxCount){
        maxCount = posTab[e-1][3];
        mostFoundPos = e;
        //logd("leading pos: ", mostFoundPos);
      }
    }

    // reset counters
    for(byte e=1; e<=16; e++){
      //logd("pos: ", e);
      //logd("count: ", posTab[e-1][3]);
      posTab[e-1][3] = 0;
    }
    cycle = 0;

    return mostFoundPos;
  }
}


void logd(char a[]) {
  logd(a, -333);
}

void logd(char a[], int b) {
  if (!doLogDebug) return;
  if (sizeof(b) > 0 && b != -333) {
    Serial.print(a);
    Serial.println(b);
  } else {
    Serial.println(a);
  }
}

void log(char a[]) {
  log(a, -333);
}

void log(char a[], int b) {
  if (sizeof(b) > 0 && b != -333) {
    Serial.print(a);
    Serial.println(b);
  } else {
    Serial.println(a);
  }
}