#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) { 
  if (strlen(pre) >= 2) {
    myPrefix[0] = pre[0];
    myPrefix[1] = pre[1];
  }
}

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) == 2) {
    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->getAI()) {
        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;
}

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

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

void TS590_FR::handleCommand(const char* cmd) {
  if (strlen(cmd) == 3) {
    switch (cmd[2]) {
      case '0':
      rig()->selectVFOA(true);
      rig()->splitOff(true);
      break;
      
      case '1':
      rig()->selectVFOB(true);
      rig()->splitOff(true);
      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()->splitOff(true);  
      } else if (rig()->isVFOB()) {
        rig()->splitOn(true);
      } else {
        setSyntaxError();
      }          
      break;
      
      case '1':
      if (rig()->isVFOA()) {
        rig()->splitOn(true);
      } else if (rig()->isVFOB()) {
        rig()->splitOff(true);
      } 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()->selectLSB(true);
      rig()->cwOff(true);
      break;

      case '2': // USB
      rig()->selectUSB(true);
      rig()->cwOff(true);
      break;

      case '3': // CW
      rig()->selectUSB(true);
      rig()->cwOn(true);
      break;

      case '7': // CW-R
      rig()->selectLSB(true);
      rig()->cwOn(true);
      break;

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

void TS590_MD::sendResponse(const char* cmd) {
  if (rig()->isCW()) {
    if (rig()->isUSB()) {
      ts590SendCommand("MD3");      
    } else {
      ts590SendCommand("MD7");
    }
  } else {
    if (rig()->isUSB()) {
      ts590SendCommand("MD2");
    } else {
      ts590SendCommand("MD1");
    }    
  }
}

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

TS590_FA cmdFA;
TS590_FB cmdFB;
TS590_FR cmdFR;
TS590_FT cmdFT;
TS590_MD cmdMD;

TS590Command* catCommands[] = {
  &cmdFA,
  &cmdFB,
  &cmdFR,
  &cmdFT,
  &cmdMD
};
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(), 2);
  #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);

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