#include <Arduino.h>
#include "TS590.h"
#include "Debug.h"

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

/*!
 *  @brief  Send a command to the PC via CAT.  Note that the command
 *          should not include the trailing terminator (;).  That will
 *          be automatically added.
 *  @param  format
 *          A printf-style format string.
 *  @param  args
 *          Zero or more arguments to include in the command.
 */
void ts590SendCommand(const char* format, ...) {
  static char outBuf[ts590CommandMaxLength];
  va_list args;
  va_start(args, format);
  vsprintf(outBuf, format, args);
  va_end(args);
  Serial.print(outBuf);
  Serial.print(";");
}

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

/*!
 *  @brief  Create a new CAT command.  It should be initialized with
 *          a 2-character command prefix.
 *  @param  pre
 *          A 2-character command prefix.  If more than 2 characters
 *          are supplied, only the first two will be used.  If less
 *          than two are supplied, then the command will be
 *          initialized with a null prefix.
 */
TS590Command::TS590Command(const char* pre)
: myPrefix(pre), prefixLength(strlen(pre))
{}

TS590Command::~TS590Command() {}

/*!
 *  @brief  Determine whether this is a Read command or not.  by
 *          default, if it's a 2-letter command, it's a Read.
 *  @return True if a Read command; false otherwise.
 */
bool TS590Command::isReadCommand(const char* cmd) const {
  if (strlen(cmd) == prefixLength) {
    return true;
  } else {
    return false;
  }
}

/*!
 *  @brief  Process the provided command.  If the command is a Set
 *          command, it calls handleCommand().  If Auto Information
 *          is eet (by the rig), sendResponse() is called at the end.
 *          If the command is a Read command, it also calls 
 *          sendResponse().  Finally, if necessary, it will return
 *          any error codes to the PC.
 *  @param  cmd
 *          The current command string received from the PC via CAT.
 *          It should be null-terminated, and should no longer have
 *          the terminator (;).
 */
void TS590Command::process(const char* cmd) {
  theError = NoError;

  if (isReadCommand(cmd)) {
    DBGCMD( sendResponse(cmd) );
  } else {
    DBGCMD( handleCommand(cmd) );
    switch(theError) {
      case NoError:
      if (theRig->isAI()) {
        DBGCMD( sendResponse(cmd) );
      }
      break;

      case SyntaxError:
      DBGCMD( ts590SyntaxError() );
      break;

      case CommError:
      DBGCMD( ts590CommError() );
      break;

      case ProcessError:
      DBGCMD( ts590ProcessError() );
      break;
    }
  }  
}

/*!
 *  @brief  Set the syntax error flag.  This is cleared at the 
 *          beginning of each call to process().
 */
void TS590Command::setSyntaxError() {
  theError = SyntaxError;
}

/*!
 *  @brief  Set the comms error flag.  This is cleared at the 
 *          beginning of each call to process().
 */
void TS590Command::setCommError() {
  theError = CommError;
}

/*!
 *  @brief  Set the process error flag.  This is cleared at the 
 *          beginning of each call to process().
 */
void TS590Command::setProcessError() {
  theError = ProcessError;
}

/*!
 *  @brief  Set the rig that will be used to process commands.
 *  @param  r
 *          Pointer to the UBitxRig object.
 */
void TS590Command::setRig(UBitxRig* r) {
  theRig = r;
}

/*!
 *  @brief  Set the DSP that will be used to process commands.
 *  @param  d
 *          Pointer to the UBitxDSP object.
 */
void TS590Command::setDSP(UBitxDSP* d) {
  theDSP = d;
}

UBitxRig* TS590Command::theRig = &Rig;
UBitxDSP* TS590Command::theDSP = &DSP;
TS590Error TS590Command::theError = NoError;

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

void TS590Command_Bool::handleCommand(const char* cmd) {
  setter(cmd[length()] == '0' ? false : true);
}

void TS590Command_Bool::sendResponse(const char* cmd) {
  ts590SendCommand("%s%s", prefix(), getter() ? "1" : "0");
}
/**********************************************************************/

void TS590Command_UL::handleCommand(const char* cmd) {
  unsigned val = static_cast<unsigned>(strtoul(&cmd[length()], NULL, 10));
  if (val < myMin) {
    val = myMin;
  } else if (val > myMax) {
    val = myMax;
  }
  val = (val * mySlope) + myIntercept;
  setter(val);
}

void TS590Command_UL::sendResponse(const char* cmd) {
  unsigned val = getter();
  val = (val - myIntercept) / mySlope;
  if (val < myMin) {
    val = myMin; 
  } else if (val > myMax) {
    val = myMax;
  }
  ts590SendCommand("%s%0*u", prefix(), myWidth, getter());
}

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

void TS590_FR::handleCommand(const char* cmd) {
  if (strlen(cmd) == 3) {
    switch (cmd[2]) {
      case '0':
      rig()->setVFOA();
      rig()->setSplitOff();
      break;
      
      case '1':
      rig()->setVFOB();
      rig()->setSplitOff();
      break;
      
      case '2':
      // TODO: Need to add something for channel mode.
      break;
      
      default:
      setSyntaxError();
    }
  } else {
    setSyntaxError();
  }
}

void TS590_FR::sendResponse(const char* cmd) {
  if (rig()->isVFOA()) {
    ts590SendCommand("FR0");
  } else if (rig()->isVFOB()) {
    ts590SendCommand("FR1");      
  } else {
    ts590SendCommand("FR2");
  }
}

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

void TS590_FT::handleCommand(const char* cmd) {
  if (strlen(cmd) == 3) {
    switch (cmd[2]) {
      case '0':
      if (rig()->isVFOA()) {
        rig()->setSplitOff();  
      } else if (rig()->isVFOB()) {
        rig()->setSplitOn();
      } else {
        setSyntaxError();
      }          
      break;
      
      case '1':
      if (rig()->isVFOA()) {
        rig()->setSplitOn();
      } else if (rig()->isVFOB()) {
        rig()->setSplitOff();
      } else {
        setSyntaxError();
      }
      break;
      
      default:
      setSyntaxError();
    }
  } else {
    setSyntaxError();
  }
}

void TS590_FT::sendResponse(const char* cmd) {
  if (rig()->isVFOA()) {
    ts590SendCommand(rig()->isSplit() ? "FT1" : "FT0");
  } else if (rig()->isVFOB()) {
    ts590SendCommand(rig()->isSplit() ? "FT0" : "FT1");
  } else {
    ts590SendCommand("FT2");
  }
}

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

void TS590_MD::handleCommand(const char* cmd) {
  if (strlen(cmd) == 3) {
    switch (cmd[2]) {
      case '0': // None (setting failure)
      case '4': // FM - not supported
      case '5': // AM - not supported
      case '6': // FSK - not supported
      case '8': // None (setting failure)
      case '9': // FSK-R - not supported
      setProcessError();
      break;

      case '1': // LSB
      rig()->setLSB();
      break;

      case '2': // USB
      rig()->setUSB();
      break;

      case '3': // CW
      rig()->setCW();
      break;

      case '7': // CW-R
      rig()->setCWR();
      break;

      default:
      setSyntaxError();
    }
  } else {
    setSyntaxError();
  }
}

void TS590_MD::sendResponse(const char* cmd) {
  if (rig()->isModeCW()) {
    ts590SendCommand("MD3");      
  } else if (rig()->isModeCWR()) {
    ts590SendCommand("MD7");
  } else if (rig()->isModeUSB()) {
    ts590SendCommand("MD2");
  } else if (rig()->isModeLSB()) {
    ts590SendCommand("MD1");
  } else {
    ts590SendCommand("MD0");
  }
}

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

int ssbHiCut[14] = {1000, 1200, 1400, 1600, 1800, 2000, 2200, 2400, 2600, 2800, 3000, 3400, 4000, 5000};
int ssbLoCut[12] = {0, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};

int ssbWidth[14] = {50, 80, 100, 150, 200, 250, 300, 400, 500, 600, 1000, 1500, 2000, 2500};
int ssbCenter[14] = {1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1750, 1800, 1900, 2000, 2100, 2210};

void TS590_SH::handleCommand(const char* cmd) {
  if (strlen(cmd) == 4) {
    index = strtoul(&cmd[2], NULL, 10);
    if (index < sizeof(ssbHiCut) / sizeof(ssbHiCut[0])) {
      dsp()->setRxFilterHi(ssbHiCut[index]);
    } else {          
      setSyntaxError();
    }
  } else {
    setSyntaxError();
  }
}

void TS590_SH::sendResponse(const char* cmd) {
  ts590SendCommand("SH%02u", index);
}

void TS590_SL::handleCommand(const char* cmd) {
  if (strlen(cmd) == 4) {
    index = strtoul(&cmd[2], NULL, 10);
    if (index < sizeof(ssbLoCut) / sizeof(ssbLoCut[0])) {
      dsp()->setRxFilterLo(ssbLoCut[index]);
    } else {          
      setSyntaxError();
    }
  } else {
    setSyntaxError();
  }
}

void TS590_SL::sendResponse(const char* cmd) {
  ts590SendCommand("SL%02u", index);
}

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

void TS590_VX::handleCommand(const char* cmd) {
  
}

void TS590_VX::sendResponse(const char* cmd) {
  
}

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

void nullSetFunc(unsigned x) { return; }
unsigned getIDFunc() {
#ifndef USE_TS590SG_CAT
  return 021;
#else
  return 023;
#endif
}

SetUL setAG = SetUL::create<UBitxDSP, &UBitxDSP::setLineOut255>(DSP);
GetUL getAG = GetUL::create<UBitxDSP, &UBitxDSP::getLineOut255>(DSP);
SetUL setAI = [](unsigned v) -> void { v == 0 ? Rig.aiOff() : Rig.aiOn(); };
GetUL getAI = []() -> unsigned { return Rig.isAI() ? 4 : 0; };
//SetUL setSidetone = SetUL::create<UBitxDSP, ...>(...);
//GetUL getSidetone = GetUL::create<UBitxDSP, ...>(...);
SetUL setUSBin = SetUL::create<UBitxDSP, &UBitxDSP::setUSBIn9>(DSP);
GetUL getUSBin = GetUL::create<UBitxDSP, &UBitxDSP::getUSBIn9>(DSP);
SetUL setUSBout = SetUL::create<UBitxDSP, &UBitxDSP::setUSBOut9>(DSP);
GetUL getUSBout = GetUL::create<UBitxDSP, &UBitxDSP::getUSBOut9>(DSP);
SetUL setACC2in = SetUL::create<UBitxDSP, &UBitxDSP::setLineIn9>(DSP);
GetUL getACC2in = GetUL::create<UBitxDSP, &UBitxDSP::getLineIn9>(DSP);
SetUL setACC2out = SetUL::create<UBitxDSP, &UBitxDSP::setLineOut9>(DSP);
GetUL getACC2out = GetUL::create<UBitxDSP, &UBitxDSP::getLineOut9>(DSP);
SetUL setVoxDelay = SetUL::create<UBitxDSP, &UBitxDSP::setDataVoxDelay>(DSP);
GetUL getVoxDelay = GetUL::create<UBitxDSP, &UBitxDSP::getDataVoxDelay>(DSP);
SetUL setUSBvox = SetUL::create<UBitxDSP, &UBitxDSP::setUSBVOXThresh9>(DSP);
GetUL getUSBvox = GetUL::create<UBitxDSP, &UBitxDSP::getUSBVOXThresh9>(DSP);
SetUL setACC2vox = SetUL::create<UBitxDSP, &UBitxDSP::setLineVOXThresh9>(DSP);
GetUL getACC2vox = GetUL::create<UBitxDSP, &UBitxDSP::getLineVOXThresh9>(DSP);
SetUL setID = SetUL::create<nullSetFunc>();
GetUL getID = GetUL::create<getIDFunc>();

TS590Command_UL TS590_AG("AG0", 3, 0, 255, setAG, getAG);
TS590Command_UL TS590_AI("AI", 1, 0, 4, setAI, getAI);
// TS590_AS
// TS590_BD
// TS590_BU
// TS590_CA
// TS590_CD0
// TS590_CD1
// TS590_CD2
// TS590_CH
#ifndef USE_TS590SG_CAT
//TS590Command_UL TS590_EX034("EX0340000", 2, 0, 14, 50, 300, setSideTone, getSideTone);
TS590Command_UL TS590_EX064("EX0640000", 1, 0, 9, setUSBin, getUSBin);
TS590Command_UL TS590_EX065("EX0650000", 1, 0, 9, setUSBout, getUSBout);
TS590Command_UL TS590_EX066("EX0660000", 1, 0, 9, setACC2in, getACC2in);
TS590Command_UL TS590_EX067("EX0670000", 1, 0, 9, setACC2out, getACC2out);
TS590Command_UL TS590_EX070("EX0700000", 2, 0, 20, 5, 0, setVoxDelay, getVoxDelay);
TS590Command_UL TS590_EX071("EX0710000", 1, 0, 9, setUSBvox, getUSBvox);
TS590Command_UL TS590_EX072("EX0720000", 1, 0, 9, setACC2vox, getACC2vox);
#else
//TS590Command_UL TS590_EX040("EX0400000", 2, 0, 14, 50, 300, setSideTone, getSideTone);
TS590Command_UL TS590_EX071("EX0710000", 1, 0, 9, setUSBin, getUSBin);
TS590Command_UL TS590_EX072("EX0720000", 1, 0, 9, setUSBout, getUSBout);
TS590Command_UL TS590_EX073("EX0730000", 1, 0, 9, setACC2in, getACC2in);
TS590Command_UL TS590_EX074("EX0740000", 1, 0, 9, setACC2out, getACC2out);
TS590Command_UL TS590_EX077("EX0770000", 2, 0, 20, 5, 0, setVoxDelay, getVoxDelay);
TS590Command_UL TS590_EX078("EX0780000", 1, 0, 9, setUSBvox, getUSBvox);
TS590Command_UL TS590_EX079("EX0790000", 1, 0, 9, setACC2vox, getACC2vox);
#endif
TS590Command_UL TS590_ID("ID", 3, 21, 23, setID, getID);

TS590_FA cmdFA;
TS590_FB cmdFB;
TS590_FR cmdFR;
TS590_FT cmdFT;
TS590_MD cmdMD;
TS590_SH cmdSH;
TS590_SL cmdSL;

TS590Command* catCommands[] = {
  &cmdFA,
  &cmdFB,
  &cmdFR,
  &cmdFT,
  &cmdMD,
  &cmdSH,
  &cmdSL
};
int numCatCommands = sizeof(catCommands) / sizeof(catCommands[0]);

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

void UBitxTS590::begin() {
  Serial.begin(9600); // USB is always 12 Mbit/sec
  #ifdef DEBUG
    delay(500);
    Serial.print("DBG: Number of CAT commands: ");
    Serial.println(numCommands);
    for (int i = 0; i < numCommands; i++) {
      Serial.print("     ");
      Serial.println(commands[i]->prefix());
    }
  #endif
}

void UBitxTS590::update() {
  char incomingChar;
  
  while (Serial.available()) {
    if (bufLen < ts590CommandMaxLength) {
      incomingChar = Serial.read();      
      if (incomingChar == ';') {
        buf[bufLen++] = '\0';
        strupr(buf);
        processCommand();
      } else if (incomingChar == '\n' && bufLen == 0) {
        ;
      } else {
        buf[bufLen++] = incomingChar;
      }
    } else {
      // too long... we're going to bail on this.
      ts590SyntaxError();
      bufLen = 0;
    }
  }
}

typedef class TS590Command* PCmd;

int compareCATCommands(const void* a, const void* b) {
  TS590Command const *B = *(TS590Command const **)b;
  int cmp = strncmp((char*)a, (char*)B->prefix(), B->length());
  #ifdef DEBUG
    Serial.print("Comparison: ");
    Serial.print((char*)a);
    Serial.print(" ? ");
    Serial.print((char*)B->prefix());
    Serial.print(" --> ");
    Serial.println(cmp);
  #endif
  return cmp;
}

void UBitxTS590::processCommand() {
  TS590Command** cmd = (TS590Command**)bsearch(buf, commands, numCommands, sizeof(TS590Command*), compareCATCommands);
  if (cmd == NULL) {
    ts590SyntaxError();
  } else {
    (*cmd)->process(buf);
  }
  bufLen = 0;
}

UBitxTS590 TS590(catCommands, numCatCommands);

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