/*
  FFT, CW Decode for uBITX
  KD8CEC, Ian Lee

  Version : 0.8
  -----------------------------------------------------------------------
  License : See fftfunctions.cpp for FFT and CW Decode.
**********************************************************************/

#include <ADC.h>
#include <i2c_t3.h>   // using i2c_t3 library for multiple I2C busses
#include <EEPROM.h>
#include "TeensyDSP.h"

//const uint8_t responseHeader[11]={'p', 'm', '.', 's', 'p', '.', 't', 'x', 't', '=', '"'}; //for Spectrum from DSP
//const uint8_t responseFooter[4]={'"', 0xFF, 0xFF, 0xFF};
//const char hexCodes[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', };

#ifdef DEBUG
int i2cCmdCounter[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int i2cRespCounter[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#endif

unsigned long SAMPLE_INTERVAL = 0;

int i2cCommand = 0;
//void calculateCoeff(uint8_t freqIndex);
uint8_t cwDecodeHz = 9;
int magnitudelimit_low = 30;

char forwardBuff[MAX_FORWARD_BUFF_LENGTH + 1];
static int nowBuffIndex = 0;
static char etxCount = 0;
static char nowSendingProtocol = 0;

uint8_t SMeterToUartSend       = 0; //0 : Send, 1: Idle
uint8_t SMeterToUartIdleCount  = 0;
#define SMeterToUartInterval  4

char DSPType = 1; //0 : Not Use, 1 : FFT, 2 : Morse Decoder, 3 : RTTY Decoder
char FFTToUartIdleCount = 0;
#define FFTToUartInterval 2

elapsedMillis sinceForward = 0;
uint8_t responseCommand = 0;  //
bool isTX = false;

/**********************************************************************/

UBitxRigState catState;
UBitxRigState rigState;

/**********************************************************************/

void responseConfig()
{
  if (responseCommand == 2)
  {
    unsigned long returnValue = 0;
    if (DSPType == 0)
    {
      returnValue = 94; //None
    }
    else if (DSPType == 1)
    {
      returnValue = 95; //Spectrum (FFT) mode
    }
    else if (DSPType == 2)
    {
      returnValue = 100 + cwDecodeHz;
    }

    returnValue = returnValue << 8;
    returnValue = returnValue |  (SMeterToUartSend & 0xFF);
    returnValue = returnValue << 8;
    uint8_t tmpValue = 0;
    if (magnitudelimit_low > 255)
      tmpValue = 255;
    else if (magnitudelimit_low < 1)
      tmpValue = 0;
    else
      tmpValue = magnitudelimit_low;
    returnValue = returnValue |  (tmpValue & 0xFF);

    sendCommandUL('v', returnValue);     //Return data
    sendCommandUL('g', 0x6A);     //Return data
  }
  responseCommand = 0;
}

//Result : if found .val=, 1 else 0
/*!
    @brief Parse commands...
*/

char commandParser(int lastIndex)
{
  //Analysing Forward data
  //59 58 68 4A   1C 5F 6A E5     FF FF 73
  //Find Loopback protocol
  // 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19    20 21 22
  //70 6D 2E 76 76 2E 76 61 6C 3D 33 38 34 38 39 35 33 36 32 38    FF FF FF
  //pm.vv.val=3848953628\xFF\xFF\xFF
  //1234567890XXX
  //
  int startIndex = 0;

  //Loop back command has 13 ~ 23
  if (lastIndex < 13)
  {
    return 0;
  }

  //Protocol MAX Length : 22
  if (lastIndex >= 22)
  {
    startIndex = lastIndex - 22;
  }
  else
  {
    startIndex = 0;
  }

  for (int i = lastIndex - 3; i >= startIndex + 7; i--)
  {
    //Find =
    if (forwardBuff[i - 3] == 'v' && forwardBuff[i - 2] == 'a' && forwardBuff[i - 1] == 'l' && forwardBuff[i] == '=') //0x3D
    {
      uint8_t command1 = forwardBuff[i - 6]; //v
      uint8_t command2 = forwardBuff[i - 5]; //v
      //     i-4    //.
      forwardBuff[lastIndex - 2] = 0;
      long commandVal = atol(&forwardBuff[i + 1]);
      uint8_t *readBuff = (uint8_t *)&commandVal;

      //Loop Back
      if (command1 == 'v' && command2 == 'v')
      {
        int calcChecksum = readBuff[0] + readBuff[1] + readBuff[2];
        calcChecksum = calcChecksum % 256;

        //Correct Checksum and Receiver is DSP Moudle protocol v1.0
        if (calcChecksum == readBuff[3] && readBuff[2] == 0x6A)
        {
          //Serial1.print("Correct Checksum Command : ");
          //Serial1.println(readBuff[1]);
          uint8_t cmd1 = readBuff[1];
          if (cmd1 == 94)
          {
            DSPType = 0;
            EEPROM.put(EEPROM_DSPTYPE, DSPType);
          }
          else if (cmd1 == 95)
          {
            //Serial1.println("Spectrum Mode");
            DSPType = 1;
            EEPROM.put(EEPROM_DSPTYPE, DSPType);
          }
          else if (cmd1 >= 100 && cmd1 <= 145)
          {
            cwDecodeHz = cmd1 - 100;
            //calculateCoeff(cwDecodeHz);
            DSPType = 2;
            EEPROM.put(EEPROM_DSPTYPE, DSPType);
            EEPROM.put(EEPROM_CW_FREQ, cwDecodeHz);
          }
          else if (cmd1 > 1 && cmd1 <= 5) //2~5 : Request Configuration
          {
            responseCommand = cmd1;
          }
          else if (cmd1 == 50 || cmd1 == 51) //Set Configuration
          {
            SMeterToUartSend = (cmd1 == 51);
            EEPROM.put(EEPROM_SMETER_UART, SMeterToUartSend);
          }
          else if (cmd1 >= 146 && cmd1 <= 156 )
          {
            //Save Mode
            magnitudelimit_low = (cmd1 - 146) * 10;
            EEPROM.put(EEPROM_CW_MAG_LOW, magnitudelimit_low);
          } //end of if
        } //end of check Checksum
      }   //end of check Protocol (vv)
      else if (command1 == 'c' && command2 == 't')  //TX, RX
      {
        if (commandVal == 0)  //RX
        {
          isTX = false;
          SMeterToUartIdleCount = 0;
        }
        else if (commandVal == 1) //TX
        {
          isTX = true;
          SMeterToUartIdleCount = 0;
        }
      }

      return 1;
    }     //end of check Protocol (.val)
  }       //end of for

  //Not found Protocol (.val=
  return 0;
}



//#define PROTOCOL_TIMEOUT = 100

/*!
    @brief Forwards serial data from the RX line to the TX line.
*/

void forwardData(void)
{
  char recvChar;

  if (Serial1.available() > 0)
  {
    Serial1.flush();

    // Check RX buffer for available data.
    while (Serial1.available() > 0)
    {
      recvChar = char(Serial1.read());

      forwardBuff[nowBuffIndex] = recvChar;

      if (recvChar == 0xFF)       // found ETX
      {
        etxCount++;               // Nextion protocol, ETX: 0xFF, 0xFF, 0xFF
        if (etxCount >= 3)
        {
          // Finished Protocol
          if (commandParser(nowBuffIndex) == 1)
          {
            nowSendingProtocol = 0; // finished 1 set command
            etxCount = 0;
            nowBuffIndex = 0;
          }
        }
      }
      else
      {
        nowSendingProtocol = 1; // sending data
        etxCount = 0;
      }

      Serial1.write(recvChar);
      sinceForward = 0;
      nowBuffIndex++;

      if (nowBuffIndex > MAX_FORWARD_BUFF_LENGTH - 2)
      {
        nowBuffIndex = 0;
      }
    }

    Serial1.flush();
  }
  else
  {
    // check timeout
  }
}

/**********************************************************************/

void sendMeterData(uint8_t isSend)
{
  scaledSMeter = Sensors.sMeterScaled();

  /*
    1 : with noise (not use 0 ~ S3)
    2 : -93 ~ -89
    3 : -88 ~ -81
    4 : -80 ~ -78
    5 : -77 ~ -72
    6 : -71 ~ -69
  */

  if (isSend == 1)
  {
    if (L_scaledSMeter != scaledSMeter)
    {
      L_scaledSMeter = scaledSMeter;
      sendCommand1Num(CMD_SMETER, L_scaledSMeter);
    }
  }
}

/**********************************************************************/

//void sendFFTData(void)
//{
//  int readValue = 0;
//    for (int i = 0; i < 11; i++)
//      Serial1.write(responseHeader[i]);
//
//    for(int i = 1; i < 64; i++)
//    {
//      readValue = (int)(FFTReal[i]);
//      if (readValue < 0)
//      {
//        readValue = 0;
//      }
//      else if (readValue>255)
//      {
//        readValue=255;
//      }
//      Serial1.write(hexCodes[readValue >> 4]);
//      Serial1.write(hexCodes[readValue & 0xf]);
//    }
//
//  for (int i = 0; i < 4; i++)
//    Serial1.write(responseFooter[i]);
//}

void setup()
{
#ifdef DEBUG
  Serial.begin(38400);
#endif

  DBGCMD( DSP.begin() );
  DBGCMD( TR.begin()  );
  
  // load configuration
  EEPROM.get(EEPROM_DSPTYPE, DSPType);
  if (DSPType > 5)
  {
    DSPType = 1;
  }

  // signal meter
  EEPROM.get(EEPROM_SMETER_UART, SMeterToUartSend);
  if (SMeterToUartSend > 2)
  {
    SMeterToUartSend = 1;
  }

  // something with CW decoding...
  EEPROM.get(EEPROM_CW_FREQ, cwDecodeHz);
  if (cwDecodeHz > 40 || cwDecodeHz < 1)
  {
    cwDecodeHz = 9;
  }

  // EEPROM_CW_MAG_LOW
  EEPROM.get(EEPROM_CW_MAG_LOW, magnitudelimit_low);
  if (magnitudelimit_low > 1000 || magnitudelimit_low < 1)
  {
    magnitudelimit_low = 50;
  }

  // put your setup code here, to run once:

  // slave Wire1 configuration for communication with the Raduino
  Wire1.begin(I2CMETER_ADDR);
  Wire1.onReceive(i2cReceiveEvent);
  Wire1.onRequest(i2cRequestEvent);

  // Serial1 configuration for communication with Raduino (RX) and Nextion (TX)
  Serial1.begin(9600, SERIAL_8N1);
  Serial1.flush();

  SAMPLE_INTERVAL = round(1000000 * (1.0 / SAMPLE_FREQUENCY));
  //calculateCoeff(cwDecodeHz);  //Set 750Hz //9 * 50 + 300 = 750Hz
  //Serial1.println("Start...");
}

/*!
    @brief  Receive a command via I2C.  The most recent command will be received, which will
            indicate which data the DSP should be preparing to return.
    @param  numBytes
            Number of bytes received--not used in this procedure.
*/
void i2cReceiveEvent(size_t numBytes)
{  
  int readCommand = 0;

  while (Wire1.available() > 0) {
    readCommand = Wire1.read();
    if (readCommand == I2CMETER_RIGINF) {
      size_t len = 0;
      uint8_t* ptr = (uint8_t*)&rigState;
      while (Wire.available() > 0) {
        uint8_t b = Wire.read();
        if (len < sizeof(UBitxRigState)) {
          ptr[len++] = b;
        }
      }      
    }
  }

//  while (Wire1.available() > 0) // for Last command
//  {
//    readCommand = Wire1.read();
//    // KC4UPR:  Note that this looks to be only reading the last command, i.e.
//    // if multiple commands have been queued up, only the last will get executed.
//  }

  if (0x50 <= readCommand && readCommand <= 0x59)
  {
    #ifdef DEBUG
      i2cCmdCounter[readCommand - 0x50]++;
    #endif
    i2cCommand = readCommand;
  }
}

/*!
    @brief  Respond to a request from the I2C Master (Raduino).  Returns the appropriate data
            based on whatever command was previously issued.
*/
void i2cRequestEvent(void)
{
  //int maxValue = 0;
  //int minValue = 30000;
  //int readValue = 0;
  //unsigned long curr = 0;

  switch (i2cCommand) {
    case I2CMETER_CALCS:
      // Returns an already-calculated S-meter value.
      Wire1.write(scaledSMeter);
      #ifdef DEBUG
        i2cRespCounter[i2cCommand - 0x50]++;
      #endif
      break;

    case I2CMETER_UNCALCS:
      // Returns a raw signal strength value.
      Wire1.write(Sensors.sMeterUnscaled() >> 2); // divided by 4... do we want this?
      #ifdef DEBUG
        i2cRespCounter[i2cCommand - 0x50]++;
      #endif
      break;

    case I2CMETER_CALCP:
      // Returns a raw forward power value.
      Wire1.write(int(fwdPower * 100.0));
      #ifdef DEBUG
        i2cRespCounter[i2cCommand - 0x50]++;
      #endif
      break;

    case I2CMETER_CALCR:
      // Returns a raw reverse power value.
      Wire1.write(int(revPower * 100.0));
      #ifdef DEBUG
        i2cRespCounter[i2cCommand - 0x50]++;
      #endif
      break;

    case I2CMETER_RIGINF:
    // Receive current rig state; transmit any CAT updates, if required.
    Wire1.write(catState.header); // temporary - just writing a single, null byte
    break;

    case I2CMETER_REQCAT:
    // Provide latest CAT updates, if any.
    
    break;

    default:
      break;
  }
}

//extern void Decode_Morse(float magnitude);
//extern double coeff;

#define LAST_TIME_INTERVAL 159

// for boot delay, a lot of data to transfer
// Delay 2.5 Sec
byte isBooted = 0;

//======================================================================
// ADC PROCESSES
//======================================================================

elapsedMillis sinceFrameMillis = 0;
elapsedMillis sinceADCMillis = 0;

#define FRAME_RATE        40
#define FRAME_INTERVAL_MS (1000/FRAME_RATE)

const int frameIntervalMillis = FRAME_INTERVAL_MS;

#define ADC_SAMPLE_RATE   120
#define ADC_INTERVAL_MS   (1000/ADC_SAMPLE_RATE)

const int adcIntervalMillis = ADC_INTERVAL_MS;

//======================================================================
// MAIN LOOP
//======================================================================

#ifdef DEBUG
int frameCounter = 0;
#endif

void loop()
{
  //char isProcess = 0; // 0: init, 1: complete ADC sampling, 2: complete FFT
  //isProcess = 0;

  forwardData();

  if (isBooted < 100)
  {
    //Delay 20msec
    for (int i = 0; i < 20; i++)
    {
      forwardData();
      delay(1);
    }
    isBooted++;
    return;
  }

  if (sinceFrameMillis > frameIntervalMillis) {
    // Do stuff that we do once per frame--I/O.
    // TODO: debug output (frame skipping / utilization).
    sinceFrameMillis = 0;

#ifdef DEBUG
    // For debugging, output some debug info every 1.0" (40 frames @ 40 Hz).
    frameCounter++;
    if (frameCounter % 40 == 0) {
      Serial.println("======================================================================");
      Serial.print("DBG: Frame: ");
      Serial.print(frameCounter);
      if (isTX) {
        Serial.print(", Loop State: TX");
      } else {
        Serial.print(", Loop State: RX");
      }
      if (TR.transmitting()) {
        Serial.println(", TR State: TX");
      } else {
        Serial.println(", TR State: RX");
      }
      Serial.print("DBG: S-Meter Raw: ");
      Serial.print(Sensors.sMeterUnscaled());
      Serial.print(", S-Meter Scaled: ");
      Serial.println(scaledSMeter);
      Serial.print("DBG: VSWR Calc: ");
      Serial.print(calcVSWR, 2);      
      Serial.print(", VSWR Scaled: ");
      Serial.print(scaledVSWR);
      Serial.print(", FWD PWR: ");
      Serial.print(fwdPower, 2);
      Serial.print(", REV PWR: ");
      Serial.println(revPower, 2);
      Serial.print("Audio Memory: ");
      Serial.print(AudioMemoryUsage());
      Serial.print(",");
      Serial.println(AudioMemoryUsageMax());      
      Serial.println("----------------------------------------------------------------------");
      Serial.print("Enabled/Active: PTT: ");
      Serial.print(TR.pttEnabled() ? "Y" : "N"); Serial.print("/"); Serial.print(TR.pttPressed() ? "Y" : "N");
      Serial.print(", VOX: ");
      Serial.print(TR.voxEnabled() ? "Y" : "N"); Serial.print("/"); Serial.print(TR.voxActivated() ? "Y" : "N");
      Serial.print(", Key: ");
      Serial.print(TR.keyEnabled() ? "Y" : "N"); Serial.print("/"); Serial.print(TR.keyPressed() ? "Y" : "N");
      Serial.print(", CAT: ");
      Serial.print(TR.catEnabled() ? "Y" : "N"); Serial.print("/"); Serial.print(TR.catActivated() ? "Y" : "N");      
      Serial.println();
      Serial.print("I2C Command/Response: ");
      for (int i = 0x50; i <= 0x59; i++) {
        Serial.print(i, HEX); Serial.print(": "); 
        Serial.print(i2cCmdCounter[i - 0x50]); Serial.print("/");
        Serial.print(i2cRespCounter[i - 0x50]); Serial.print(", ");
      }
      Serial.println();
    }
#endif

    TR.update();

    if (isTX) {
      calcVSWR = Sensors.VSWR();
      scaledVSWR = byte(Sensors.scaledVSWR());
      fwdPower = Sensors.Pfwd();
      revPower = Sensors.Prev();

      // Send SWR meter information.
      if (L_scaledVSWR != scaledVSWR) {
        L_scaledVSWR = scaledVSWR;
        sendCommand1Num(CMD_SMETER, scaledVSWR);
      }

      // Send forward power.
      if (L_fwdPower != fwdPower) {
        L_fwdPower = fwdPower;
        sendCommandL('m', int(fwdPower * 100.0));  // watts x 100?
        sendCommand1Num('m', 2);
      }

      // Send reverse power.
      //if (L_revPower != revPower) {
      //  L_revPower = revPower;
      //  sendCommandL('m', int(revPower * 100.0));  // watts x 100?
      //  sendCommand1Num('m', 2);
      //}

      // Does there need to be some kind of 250-500ms delay after this???
      // Delay 250msec ~ 500msec for Nextion LCD Processing (using m protocol)
      //for (int i = 0; i < 10; i++) {
      //  forwardData();
      //  if (!isTX) { //if TX -> RX break
      //    break;
      //  }
      //  delay(25);
      //} //end of delay time

      // Send SWR.
      if (L_calcVSWR != calcVSWR) {
        L_calcVSWR = calcVSWR;
        sendCommandL('m', int(calcVSWR * 100.0)); // SWR x 100?
        sendCommand1Num('m', 3);
      }

    } else {  // RX

      // Send Signal Meter to UART
      if (SMeterToUartSend == 1 && nowSendingProtocol == 0)        //SMeter To Uart Send
      {
        //nowSendingProtocol -> not finished data forward, (not found 0xff, 0xff, 0xff yet)
        sendMeterData(1);
      } else {
        sendMeterData(0); //only calculate Signal Level
      }

    }

    // Forward any data that came in while we were updating stuff.
    forwardData();
  }

  if (sinceADCMillis > adcIntervalMillis) {
    // Do stuff that we do once per ADC interval--ADC colllection.
    // TODO: debug output (frame skipping / utilization).
    sinceADCMillis = 0;

    if (isTX) {
      Sensors.updatePower();
    } else {  // RX
      Sensors.updateSMeter();
      Sensors.updateSupply();
    }

    // Forward any data that came in while we were reading sensors.
    //forwardData();
  }

  // Check Response Command
  if (responseCommand > 0 && sinceForward > LAST_TIME_INTERVAL)
  {
    responseConfig();
  }

  //  //===========================================
  //  //TRANSCEIVER STATUS : RX
  //  //===========================================
  //  //===================================================================================
  //  // DSP Routine
  //  //===================================================================================
  //  if (DSPType == 1 && sinceForward > LAST_TIME_INTERVAL) // spectrum: FFT => send To UART
  //  {
  //    FFTToUartIdleCount = 0;
  //
  //    if (isProcess == 1)
  //    {
  //      FFT(FFTReal, FFTImag, SAMPLESIZE, 7);
  //      isProcess = 2;
  //    }
  //
  //    forwardData();
  //
  //    if (isProcess == 2)
  //    {
  //      for (uint16_t k = 0; k < SAMPLESIZE; k++)
  //      {
  //        FFTReal[k] = sqrt(FFTReal[k] * FFTReal[k] + FFTImag[k] * FFTImag[k]);
  //      }
  //
  //      isProcess = 3;
  //    }
  //
  //    forwardData();
  //
  //    if (isProcess == 3)
  //    {
  //      if (nowSendingProtocol == 0)  //Idle Status
  //      {
  //        sendFFTData();
  //      }
  //    }
  //  }
  //  else if (DSPType == 2)          //Decode Morse
  //  {
  //    //Implement Goertzel_algorithm
  //    //https://en.wikipedia.org/wiki/Goertzel_algorithm
  //
  //    /*
  //    ω = 2 * π * Kterm / Nterms;
  //    cr = cos(ω);
  //    ci = sin(ω);
  //    coeff = 2 * cr;
  //
  //    sprev = 0;
  //    sprev2 = 0;
  //    for each index n in range 0 to Nterms-1
  //      s = x[n] + coeff * sprev - sprev2;
  //      sprev2 = sprev;
  //      sprev = s;
  //    end
  //
  //    power = sprev2 * sprev2 + sprev * sprev - coeff * sprev * sprev2;
  //     */
  //    double Q1 = 0;
  //    double Q2 = 0;
  //
  //    for (unsigned index = 0; index < DECODE_MORSE_SAMPLESIZE; index++)
  //    {
  //      float Q0;
  //      Q0 = coeff * Q1 - Q2 + FFTReal[index];
  //      Q2 = Q1;
  //      Q1 = Q0;
  //    }
  //    double magnitudeSquared = (Q1*Q1)+(Q2*Q2)-Q1*Q2*coeff;  // we do only need the real part //
  //    double magnitude = sqrt(magnitudeSquared);
  //
  //    Decode_Morse(magnitude);
  //  } //end of if

}

//======================================================================
// EOF
//======================================================================
