#ifndef __TS590_h__
#define __TS590_h__

#include <Arduino.h>
#include <Embedded_Template_Library.h>
#include <etl/delegate.h>
#include "DSP.h"
#include "Rig.h"

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

#define TS590_COMMAND_MAX_LENGTH  50 // including terminator (which will get converted to null)

const int ts590CommandMaxLength = TS590_COMMAND_MAX_LENGTH;

void ts590SendCommand(const char*, ...);

/*!
 *  @brief  Send a syntax error response to the PC via CAT.
 */
inline void ts590SyntaxError() { ts590SendCommand("?"); }

/*!
 *  @brief  Send a communications error response to the PC via CAT.
 */
inline void ts590CommError() { ts590SendCommand("E"); }  

/*!
 *  @brief  Send a processing error response to the PC via CAT.
 */
inline void ts590ProcessError() { ts590SendCommand("O"); }

enum TS590Error {
  NoError,
  SyntaxError,
  CommError,
  ProcessError
};

/**********************************************************************
 **********************************************************************/
/*
class TS590BaseCommand {
  public:
  TS590BaseCommand(const char* prefix)
  : myPrefix(prefix), prefixLength(strlen(prefix))
  {}
  virtual ~TS590BaseCommand() = 0;

  inline const char* prefix() { return &myPrefix[0]; }
  inline size_t length() { return prefixLength; }

  virtual void handleCommand(const char* cmd) = 0;
  virtual void sendResponse(const char* cmd) = 0;
  virtual bool isReadCommand(const char* cmd) const;
  
  inline void 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;
      }
    }  
  }

  private:
  const char* myPrefix;
  size_t prefixLength;
};
*/
typedef etl::delegate<bool(const char*)> ValidateFunc;
typedef etl::delegate<bool(const char*)> IsReadFunc;
typedef etl::delegate<void(bool)> ToggleFunc;

/*
class TS590ToggleCommand : public TS590BaseCommand {
  TS590ToggleCommand(const char* prefix, ToggleFunc& setter, ToggleFunc& getter)
  : TS590BaseCommand(prefix) 
  {}

  private:
  ToggleFunc& mySetter;
  ToggleFunc& myGetter;
};
*/
/**********************************************************************/

/*!
 *  @brief  A TS590S/SG "CAT" command.  This is the base class for all
 *          CAT commands.
 */
class TS590Command {
  public:
  TS590Command(const char* pre);
  virtual ~TS590Command();

  /*!
   *  @brief  Return the 2-character prefix for the command.
   *  @return The 2-character prefix for the command.
   */
  inline const char* prefix() const { return &myPrefix[0]; }

  inline size_t length() const { return prefixLength; }

  /*!
   *  @brief  Return the rig that this command will be used to control.
   */
  inline UBitxRig* rig() const { return theRig; }

  /*!
   *  @brief  Return the DSP that this command will be used to control.
   */
  inline UBitxDSP* dsp() const { return theDSP; }

  /*!
   *  @brief  Handle the provided Set command.  If the Set command
   *          results in an error, then set the appropriate flag with
   *          setSyntaxError(), setCommError(), or setProcessError().
   *  @param  cmd
   *          The current command string received from the PC via CAT.
   *          It should be null-terminated, and should no longer have
   *          the terminator (;).
   */
  virtual void handleCommand(const char* cmd) = 0;

  /*!
   *  @brief  Send a response back to the PC.  This assumes a
   *          successful command (no errors).
   */
  virtual void sendResponse(const char* cmd) = 0;

  virtual bool isReadCommand(const char* cmd) const;
  
  void process(const char* cmd);

  static void setSyntaxError();
  static void setCommError();
  static void setProcessError();
  static void setRig(UBitxRig* r);
  static void setDSP(UBitxDSP* d);

  private:
  const char* myPrefix;
  size_t prefixLength;
  static TS590Error theError;
  static UBitxRig* theRig;
  static UBitxDSP* theDSP;
};

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

typedef etl::delegate<void(bool)> SetBool;
typedef etl::delegate<bool(void)> GetBool;

class TS590Command_Bool : public TS590Command {
  public:
  TS590Command_Bool(const char* prefix, SetBool set, GetBool get)
  : TS590Command(prefix), setter(set), getter(get) {}

  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);

  private:
  SetBool setter;
  GetBool getter;
};

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

typedef etl::delegate<void(unsigned)> SetUL;
typedef etl::delegate<unsigned(void)> GetUL;

class TS590Command_UL : public TS590Command {
  public:
  TS590Command_UL(const char* prefix, size_t width, unsigned min, unsigned max, SetUL set, GetUL get)
  : TS590Command(prefix), myWidth(width), myMin(min), myMax(max), mySlope(1), myIntercept(0), setter(set), getter(get) {}

  TS590Command_UL(const char* prefix, size_t width, unsigned min, unsigned max, unsigned slope, unsigned intercept, SetUL set, GetUL get)
  : TS590Command(prefix), myWidth(width), myMin(min), myMax(max), mySlope(slope), myIntercept(intercept), setter(set), getter(get) {}

  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);

  private:
  size_t myWidth;
  unsigned myMin;
  unsigned myMax;
  unsigned mySlope;
  unsigned myIntercept;
  SetUL setter;
  GetUL getter;
};

/**********************************************************************/
/*
typedef etl::delegate<void(bool)> SetULArray;
typedef etl::delegate<bool(void)> GetULArray;

class TS590Command_ULArray : public TS590Command {
  public:
  TS590Command_ULArray(const char* prefix, SetUL set, GetUL get)
  : TS590Command(prefix), setter(set), getter(get) {}

  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);

  private:
  SetUL setter;
  GetUL getter;
};
*/
/**********************************************************************/

/*!
 *  @brief  CAT command for setting or reading the VFO A/B frequency.
 */
template<bool VFOA>
class TS590_FAB : public TS590Command {
  public:
  TS590_FAB(): TS590Command(VFOA ? "FA" : "FB") {}

  virtual void handleCommand(const char* cmd) {
    if (strlen(cmd) == 13) {
      unsigned long freq = strtoul(&cmd[2], NULL, 10);
      if (VFOA) {
        rig()->setFreqA(freq);
      } else {
        rig()->setFreqB(freq);
      }
    } else {
      setSyntaxError();
    }
  }

  virtual void sendResponse(const char* cmd) {
    ts590SendCommand(VFOA ? "FA%011u" : "FB%011u", VFOA ? rig()->getFreqA() : rig()->getFreqB());
  }
};

typedef TS590_FAB<true> TS590_FA;
typedef TS590_FAB<false> TS590_FB;

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

/*!
 *  @brief  CAT command for setting the receiver VFO.  This will always
 *          disable split mode, if it was previously enabled.
 */
class TS590_FR : public TS590Command {
  public:
  TS590_FR(): TS590Command("FR") {}
  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);
};

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

/*!
 *  @brief  CAT command for setting the transmitter VFO.  If it is
 *          different than the receiver VFO, then split mode will be
 *          automatically enabled.
 */
class TS590_FT : public TS590Command {
  public:
  TS590_FT(): TS590Command("FT") {}
  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);
};

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

/*!
 *  @brief  CAT command for setting the mode.
 */
class TS590_MD : public TS590Command {
  public:
  TS590_MD(): TS590Command("MD") {}
  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);
};

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

/*!
 *  @brief  CAT command for setting the receiver high-cut frequency.
 */
class TS590_SH : public TS590Command {
  public:
  TS590_SH(): TS590Command("SH") {}
  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);
  private:
  unsigned index;
};

/*!
 *  @brief  CAT command for setting the receiver low-cut frequency.
 */
class TS590_SL : public TS590Command {
  public:
  TS590_SL(): TS590Command("SL") {}
  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);
  private:
  unsigned index;
};

/*!
 *  CAT command for enabling or disabling the mic VOX.
 */
class TS590_VX : public TS590Command {
  public:
  TS590_VX(): TS590Command("VX") {}
  virtual void handleCommand(const char* cmd);
  virtual void sendResponse(const char* cmd);
  private:
  unsigned index;
};

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

class UBitxTS590 {
  public:
  UBitxTS590(TS590Command** cmds, int len): commands(cmds), numCommands(len) {}
  
  void begin();
  void update();

  private:
  void processCommand();
  
  char buf[ts590CommandMaxLength] = {0};
  int bufLen = 0;
  TS590Command** commands;
  int numCommands;
};

extern UBitxTS590 TS590;

#endif

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