#include "coinacceptor.h"
#include "lnurlutil.h"
#include <FunctionalInterrupt.h>

CoinAcceptor::CoinAcceptor(uint8_t pulsePin, uint8_t enablePin, const unsigned int* pulseValues, const size_t maxPulses, const uint8_t pulseWidthMs, const uint16_t maxAllowedCentToInsert) : 
  pulsePin(pulsePin), enablePin(enablePin), pulseValues(pulseValues), maxPulses(maxPulses), pulseWidthMs(pulseWidthMs), maxAllowedCentToInsert(maxAllowedCentToInsert)  {
    /* malloc the array to store the single given coins in*/
    coinNumbers = new unsigned int[maxPulses];
    resetSumValue();
}

CoinAcceptor::~CoinAcceptor() {
    delete[] coinNumbers;
}

// Initialisierung des Pins & Interrupts
void CoinAcceptor::begin() {
    pinMode(enablePin, OUTPUT);
    pinMode(pulsePin, INPUT_PULLUP);
    disable();
    if(digitalRead(pulsePin) == HIGH)
    {
      Serial.println("Attaching interrupt to falling edge of coin acceptor pulse pin.");
      attachInterrupt(digitalPinToInterrupt(pulsePin), std::bind(&CoinAcceptor::pulseIsrHandler,this), FALLING);
    }
    else
    {
      Serial.println("Attaching interrupt to rising edge of coin acceptor pulse pin.");
      attachInterrupt(digitalPinToInterrupt(pulsePin), std::bind(&CoinAcceptor::pulseIsrHandler,this), RISING);  
    }
}

void IRAM_ATTR CoinAcceptor::pulseIsrHandler() {
  currentMs = millis();
  if((lastPulseAtMs == 0) || ((currentMs - lastPulseAtMs) > 1.5*pulseWidthMs))
  {
    coinDetected = true;
    lastPulseAtMs = currentMs;
    coinPulses++;
  }
  allPulses++;
}

void CoinAcceptor::disable() {
  accState = ACC_DISABLED;
  digitalWrite(enablePin, accState);
}

void CoinAcceptor::enable() {
  if(sumValue < maxAllowedCentToInsert)
  {
    accState = ACC_ENABLED;
    coinDetected = false;
    detectionError = 0;
    coinPulses = 0;
    allPulses = 0;
    coinValue = 0;
    lastPulseAtMs = 0;   
    digitalWrite(enablePin, accState);
  }
}

void CoinAcceptor::resetSumValue()
{
  sumValue = 0;
  for (size_t i = 0; i < maxPulses; i++) {
    coinNumbers[i] = 0;
  }
}

uint16_t CoinAcceptor::getSumValue()
{
  return sumValue;
}

std::string CoinAcceptor::getSumValueStr()
{
  float value = sumValue/100.0;
  return(lnurlutil::floatToStringWithPrecision(value,2));
}

std::string CoinAcceptor::getCoinsNumStr() const {
  std::string result = "";

  for (size_t i = 0; i < maxPulses; i++) {
    if(pulseValues[i] > 0)
    {
      char temp[16];
      snprintf(temp, sizeof(temp), " %3d ", coinNumbers[i]);
      result += temp;
    }
  }
  return result;
}

std::string CoinAcceptor::getCoinsValStr() const {
  std::string result = "";

  for (size_t i = 0; i < maxPulses; i++) {
    if(pulseValues[i] > 0)
    {
      char temp[16];
      if(pulseValues[i] < 100)
      {
        snprintf(temp, sizeof(temp), " %2dct", pulseValues[i]);
      }
      else
      {
        snprintf(temp, sizeof(temp), " %1dEur", int(pulseValues[i]/100));
      }
      result += temp;
    }
  }
  return result;
}

uint8_t CoinAcceptor::state() {
  return accState;
}

bool CoinAcceptor::detectCoin() {
  if(true == coinDetected)
  {
    disable();
    long lastPulseBeforeMs = 0;
    long maxMsSinceLastPulse = 8*pulseWidthMs;
    do
    {
      lastPulseBeforeMs = millis() - lastPulseAtMs;
    } while ((lastPulseAtMs == 0) || (lastPulseBeforeMs < maxMsSinceLastPulse ));
    Serial.printf("DONE --- All pulses: %d Coin pulses: %d, Last pulse before: %d ms (max allowed: %d). \n", allPulses, coinPulses, lastPulseBeforeMs, maxMsSinceLastPulse);
    // there is one additial edge in the signal ... remove it
    coinPulses--;
    if((coinPulses > 1) && (coinPulses <= maxPulses))
    {
      coinValue = pulseValues[coinPulses-1]; // coin array index is running from 0 to maxPulses-1 ...
      coinNumbers[coinPulses-1]++;
      sumValue = sumValue + coinValue;
      Serial.printf(" --- Detected coin value: %d cents. Sum is now: %d cents\n", coinValue, sumValue);
    }
    else
    {
      Serial.printf(" ERROR --- Number of pulses doesn't match any teached coin value\n");
      detectionError++;
    }
    coinDetected = false;
    return true;
  }
  return false;
}

uint16_t CoinAcceptor::getCoinValue() {
  return coinValue;
}