/*
 Rocrail - Model Railroad Software
 Copyright (c) 2002-2022 Robert Jan Versluis, Rocrail.net
 All rights reserved.
*/
#include "io.h"
#include "trace.h"
#include "cv.h"
#include <Arduino.h>

//---------- IO constructor.
IO::IO() {
#if defined(__AVR_ATmega4809__)
  // D13 is connected to the LED_BUILTIN but is also SPI clock...
#else
  pinMode(LED_BUILTIN,OUTPUT);
#endif
  setPinLayout(true);
}


void IO::setPinLayout(bool init) {
  if( init ) {
    delay(100);
    initPorts(false);
  }

  #ifdef ARDUINO_ARCH_RP2040
  /*
  // SPI
    #define PIN_SPI0_MISO  (16u)
    #define PIN_SPI0_MOSI  (19u)
    #define PIN_SPI0_SCK   (18u)
    #define PIN_SPI0_SS    (17u)
  // I2C
    #define PIN_WIRE0_SDA  (4u)
    #define PIN_WIRE0_SCL  (5u)

  */
  // Servo: 6,7,8,9
    m_Pins[ 0] = 0;
    m_Pins[ 1] = 1;
    m_Pins[ 2] = 2;
    m_Pins[ 3] = 3;
    m_Pins[ 4] = 10;
    m_Pins[ 5] = 11;
    m_Pins[ 6] = 12;
    m_Pins[ 7] = 13;
    m_Pins[ 8] = 14;
    m_Pins[ 9] = 15;
    m_Pins[10] = 20;
    m_Pins[11] = 21;
    m_Pins[12] = 22;
    m_Pins[13] = 26;
    m_Pins[14] = 27;
    m_Pins[15] = 28;
  #else

    // CANGC2a {2, 3, 4, 5, 6, 7,  8,  9, A7, A6, A5, A4, A3, A2, A1, A0}
    m_Pins[ 0] = 2;
    m_Pins[ 1] = 3; // PWM
    m_Pins[ 2] = 4;
    m_Pins[ 3] = 5; // PWM
    m_Pins[ 4] = 6; // PWM
    m_Pins[ 5] = 7;
    m_Pins[ 6] = 8;
    m_Pins[ 7] = 9; // PWM
  #if defined(__AVR_ATmega4809__)
    m_Pins[ 8] = 21;
    m_Pins[ 9] = 20;
    m_Pins[10] = 19;
    m_Pins[11] = 18;
    m_Pins[12] = 17;
    m_Pins[13] = 16;
    m_Pins[14] = 15;
    m_Pins[15] = 14;
  #else  
    m_Pins[ 8] = A7;
    m_Pins[ 9] = A6;
    m_Pins[10] = A5;
    m_Pins[11] = A4;
    m_Pins[12] = A3;
    m_Pins[13] = A2;
    m_Pins[14] = A1;
    m_Pins[15] = A0;
  #endif
#endif

  if( init ) {
    delay(100);
    initIO(MAX_PORTS);
  }
}



//---------- Init one or all pins.
void IO::initIO(byte portnr) {
  if( portnr < MAX_PORTS ) {
    confIO(portnr, CV::get(EE_PORTCFG+portnr) );
  }
  else {
    for( byte i = 0; i < MAX_PORTS; i++ ) {
      confIO(i, CV::get(EE_PORTCFG+i) );
    }
  }
}


//---------- Init all port as output.
void IO::initPorts(bool in) {
  for( byte i = 0; i < MAX_PORTS; i++ ) {
    pinMode(m_Pins[i], in?INPUT_PULLUP:OUTPUT);
    m_Port[i].state    = 0;
    m_Port[i].duration = 0;
    m_Port[i].timer    = 0;
    m_Port[i].timerval = 0;
    m_Port[i].blink    = 0;
    m_Port[i].ack      = 0;
    m_Port[i].ivalue   = 0;
  }
}


//---------- Set the optional LED.
void IO::setLED(bool on) {
  // The Nano does not have a LED.
#if defined(__AVR_ATmega4809__)
  // D13 is connected to the LED_BUILTIN but is also SPI clock...
#else
  digitalWrite(LED_BUILTIN,on?HIGH:LOW);
#endif
if( on ) m_LED = 10;
}


//---------- Start of Day.
void IO::SoD() {
  for(byte i = 0; i < MAX_PORTS; i++ )
    m_Port[i].state = SOD;
}


//---------- Set the port duration time for block inputs or pulse outputs.
void IO::setPortDuration(byte port, byte duration) {
  if( port < MAX_PORTS ) {
    m_Port[port].duration = duration;
    m_Port[port].timer = 0;
  }
}


//---------- Reset the port duration and timer.
void IO::resetPortTimer(byte port) {
  if( port < MAX_PORTS ) {
    m_Port[port].duration = 0;
    m_Port[port].timer = 0;
  }
}


//---------- Get the port its current timer value.
byte IO::getPortTimer(byte port) {
  if( port < MAX_PORTS ) {
    return m_Port[port].timer;
  }
  return 0;
}


//---------- Set the port state for saving at power off.
byte IO::setPortState(byte port, byte state, byte blink, byte resetAck) {
  byte statechanged = false;
  if( port < MAX_PORTS ) {
    if( m_Port[port].state != state )
      statechanged = true;
    m_Port[port].state = state;
    m_Port[port].blink = blink;
    if( resetAck ) {
      m_Port[port].ack = ACK_PENDING;
    }
  }
  return statechanged;
}


//---------- Get the current port state.
byte IO::getPortState(byte port) {
  if( port < MAX_PORTS ) {
    return m_Port[port].state;
  }
  return LOW;
}

int IO::getPortValue(byte port) {
  if( port < MAX_PORTS ) {
    return m_Port[port].ivalue;
  }
  return 0;
}


//---------- Is the port timer elapsed?
byte IO::isPortTimer(byte port) {
  if( port < MAX_PORTS ) {
    if( m_Port[port].duration > 0 && m_Port[port].timer >= m_Port[port].duration )
      return true;
  }
  return false;
}


byte IO::isPortBlink(byte port) {
  if( port < MAX_PORTS ) {
    return m_Port[port].blink;
  }
  return false;
}


//---------- Increase the port timers by one.
void IO::increasePortTimers() {
  for(byte i = 0; i < MAX_PORTS; i++ ) {
    if( m_Port[i].duration > 0 )
      m_Port[i].timer++;
      if( m_Port[i].ack & ACK_PENDING ) {
        m_Port[i].ack++;
      }
  }
}

void IO::tick10ms() {
  if( m_LED > 0 ) {
    m_LED--;
    if( m_LED == 0 )
      setLED(false);
  }
}


//---------- Init a port as output or input.
void IO::confIO(byte port, byte conf) {
  if( (conf & PORTCFG_IO) == PORTCFG_OUT ) {
    //trace("port %02d(%02X) set OUTPUT", port, m_Pins[port]);
    pinMode(m_Pins[port], OUTPUT);
    setOutput(port, LOW, false);
  }
  else if( (conf & PORTCFG_IO) == PORTCFG_IN ) {
    //trace("port %02d(%02X) set INPUT_PULLUP", port, m_Pins[port]);
    pinMode(m_Pins[port], INPUT_PULLUP);
  }
}


//---------- Set the output port to HIGH or LOW.
void IO::setOutput(byte port, byte on, bool inv) {
  if( port < MAX_PORTS ) {
    if( inv ) {
#if defined(__AVR_ATmega328P__)      
      if( m_Pins[port] == A6 || m_Pins[port] == A7 )
        analogWrite(m_Pins[port], on?0:1000);
      else
#endif
        digitalWrite(m_Pins[port], on?LOW:HIGH);
    }
    else {
#if defined(__AVR_ATmega328P__)      
      if( m_Pins[port] == A6 || m_Pins[port] == A7 )
        analogWrite(m_Pins[port], on?1000:0);
      else
#endif
        digitalWrite(m_Pins[port], on?HIGH:LOW);
    }
  }
}


bool IO::ackInput(byte port, byte state) {
  if( m_Port[port].state == state ) {
    //trace("ackOK %d,%d", port, state);
    m_Port[port].ack = ACK_OK;
    return true;
  }
  return false;
}


bool IO::isAckInput(byte port) {
  if( (m_Port[port].ack & ACK_PENDING) && (m_Port[port].ack & 0x7F) > 5 ) {
    return false;
  }
  return true;
}


//---------- Get the input port its digital value.
bool IO::getInput(byte port) {
  if( port < MAX_PORTS ) {
    byte val = LOW;
#ifdef ARDUINO_ARCH_RP2040
    val = digitalRead(m_Pins[port]);
    return ((val==LOW) ? true:false);
#else
    if( m_Pins[port] == A6 || m_Pins[port] == A7 ) {
      m_Port[port].ivalue = analogRead(m_Pins[port]);
      if( m_Port[port].ivalue > 512 ) 
        val = HIGH;
    }
    else {
      val = digitalRead(m_Pins[port]);
      if( m_Pins[port] >= A0 && m_Pins[port] <= A5 )
        m_Port[port].ivalue = analogRead(m_Pins[port]);
    }
    return ((val==LOW) ? true:false);
  #endif
  }
  return 0;
}
