/*!
 *  @file     RigState.h
 */

#ifndef __RigState_h__
#define __RigState_h__

#include <Arduino.h>

#define UBITX_VFOB_FLAG   0x00000001
#define UBITX_SPLIT_FLAG  0x00000002
#define UBITX_RIT_FLAG    0x00000004
#define UBITX_XIT_FLAG    0x00000008
#define UBITX_CW_FLAG     0x00000010
#define UBITX_USB_FLAG    0x00000020
#define UBITX_TX_FLAG     0x00000040

#ifdef TEENSYDUINO
#define DISABLEINTS(CMD) do { noInterrupts(); CMD; interrupts(); } while (0)
#else
#define DISABLEINTS(CMD) do { CMD; } while (0)
#endif

enum RigStateWord {
  DIRTY_WORD = 0,
  VFOA_WORD,
  VFOB_WORD,
  OFFSETS_WORD,
  FLAGS_WORD,
  NUM_WORDS
};

inline RigStateWord& operator++(RigStateWord& orig) {
  orig = static_cast<RigStateWord>(orig + 1);
  // NOTE: Will overflow...
  return orig;
}

inline RigStateWord operator++(RigStateWord& orig, int) {
  RigStateWord rVal = orig;
  ++orig;
  return rVal;
}

struct UBitxRigState {
  volatile uint32_t data[NUM_WORDS] = {0};

  void begin();

  void send_RIGINF();
  void receive_RIGINF(int numBytes = sizeof(data));

  /*!
   *  @brief  Set the dirty bit for the specified word.
   *  
   *  @param  w
   *          The word to mark as dirty.
   */
  inline void setDirty(RigStateWord w) {
    data[DIRTY_WORD] |= w < NUM_WORDS ? 1 << w : 0;
  }

  /*!
   *  @brief  Set the dirty bits for all words.
   */
  inline void setDirty() { DISABLEINTS( data[DIRTY_WORD] = 0xFFFFFFFF ); }

  /*!
   *  @brief  Clear the dirty bit for the specified word.
   *  
   *  @param  w
   *          The word to mark as clean.
   */
  inline void setClean(RigStateWord w) {
    data[DIRTY_WORD] &= ~(w < NUM_WORDS ? 1 << w : 0);
  }

  /*!
   *  @brief  Clear the dirty bits for all words.
   */
  inline void setClean() { DISABLEINTS( data[DIRTY_WORD] = 0 ); }

  /*!
   *  @brief  Check whether the specified word is clean.
   *  
   *  @param  w
   *          The word to check for clean status.
   *          
   *  @return True if the word is clean.
   */
  inline bool isClean(RigStateWord w) {
    bool clean;
    DISABLEINTS( clean = ((1 << w) & data[DIRTY_WORD]) > 0 ? false : true );
    return clean;
  }

  /*!
   *  @brief  Check whether the data is clean (as a whole).
   *  
   *  @return True if the data is clean (no dirty fields).
   */
  inline bool isClean() {
    bool clean;
    DISABLEINTS( clean = data[DIRTY_WORD] == 0 );
    return clean;
  }
  
  /*!
   *  @brief  Check whether the specified word is dirty.
   *  
   *  @param  w
   *          The word to check for dirty status.
   *          
   *  @return True if the word is dirty.
   */
  inline bool isDirty(RigStateWord w) {
    bool dirty;
    DISABLEINTS( dirty = ((1 << w) & data[DIRTY_WORD]) > 0 ? true : false );
    return dirty;
  }
  
  /*!
   *  @brief  Check whether the data is dirty (as a whole).
   *  
   *  @return True if the data is dirty (at least one dirty field).
   */
  inline bool isDirty() { 
    bool dirty;
    DISABLEINTS( dirty = data[DIRTY_WORD] != 0 );
    return dirty; 
  }

  /*!
   *  @brief  Set the VFO A frequency.
   *  
   *  @param  freq
   *          The new frequency in Hz.
   */
  inline void setFreqA(uint32_t freq, bool mark = true) { 
    DISABLEINTS( data[VFOA_WORD] = freq;
                 if (mark) setDirty(VFOA_WORD) );
  }
  
  inline uint32_t getFreqA() const { 
    uint32_t result;
    DISABLEINTS( result = data[VFOA_WORD] );
    return result;
  }

  /*!
   *  @brief  Set the VFO B frequency.
   *  
   *  @param  freq
   *          The new frequency in Hz.
   */
  inline void setFreqB(uint32_t freq, bool mark = true) { 
    DISABLEINTS( data[VFOB_WORD] = freq );
  }
  
  inline uint32_t getFreqB() const { 
    uint32_t result; 
    DISABLEINTS( result = data[VFOB_WORD] );
    return result; 
  }

  inline void setRIT(int16_t offset, bool mark = true) { 
    DISABLEINTS( data[OFFSETS_WORD] = (int32_t(offset) << 16) | (0x0000FFFF & data[OFFSETS_WORD]);
                 if (mark) setDirty(OFFSETS_WORD) );
  }
  
  inline int16_t getRIT() const { 
    int16_t result;
    DISABLEINTS( result = data[OFFSETS_WORD] >> 16 );
    return result; 
  }

  inline void setXIT(int16_t offset, bool mark = true) { 
    DISABLEINTS( data[OFFSETS_WORD] = (0xFFFF0000 & data[OFFSETS_WORD]) | offset;
                 if (mark) setDirty(OFFSETS_WORD) );
  }
  
  inline int16_t getXIT() const { 
    int16_t result;
    DISABLEINTS( result = 0x0000FFFF & data[OFFSETS_WORD] );
    return result; 
  }

  inline void setVFOA(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] &= ~UBITX_VFOB_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  }

  inline void setVFOB(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] |= UBITX_VFOB_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  }
  
  inline bool isVFOA() const { 
    bool result;
    DISABLEINTS( result = data[FLAGS_WORD] & UBITX_VFOB_FLAG ? false : true );
    return result; 
  }
  
  inline bool isVFOB() const { 
    bool result;
    DISABLEINTS( result = data[FLAGS_WORD] & UBITX_VFOB_FLAG ? true : false );
    return result; 
  }

  inline void setSplitOn(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] |= UBITX_SPLIT_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  }

  inline void setSplitOff(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] &= ~UBITX_SPLIT_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  }
  
  inline bool isSplit() const { 
    bool result;
    DISABLEINTS( result = data[FLAGS_WORD] & UBITX_SPLIT_FLAG ? true : false );
    return result; 
  }

  inline void setRITOn(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] |= UBITX_RIT_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  } 
  
  inline void setRITOff(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] &= ~UBITX_RIT_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  } 
  
  inline bool isRIT() const { 
    bool result;
    DISABLEINTS( result = data[FLAGS_WORD] & UBITX_RIT_FLAG ? true : false );
    return result;
  }
  
  inline void setXITOn(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] |= UBITX_XIT_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  } 
  
  inline void setXITOff(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] &= ~UBITX_XIT_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  } 
  
  inline bool isXIT() const { 
    bool result;
    DISABLEINTS( result = data[FLAGS_WORD] & UBITX_XIT_FLAG ? true : false );
    return result;
  }
  
  inline void setUSB(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] |= UBITX_USB_FLAG; 
                 data[FLAGS_WORD] &= ~UBITX_CW_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  }
  
  inline void setLSB(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] &= ~UBITX_USB_FLAG; 
                 data[FLAGS_WORD] &= ~UBITX_CW_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  }
  
  inline void setCW(bool mark = true)  { 
    DISABLEINTS( data[FLAGS_WORD] |= UBITX_USB_FLAG; 
                 data[FLAGS_WORD] |= UBITX_CW_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  }
  
  inline void setCWR(bool mark = true) { 
    DISABLEINTS( data[FLAGS_WORD] &= ~UBITX_USB_FLAG; 
                 data[FLAGS_WORD] |= UBITX_CW_FLAG; 
                 if (mark) setDirty(FLAGS_WORD) );
  }
  
  inline bool isModeUSB() const { 
    bool result;
    DISABLEINTS( result = ((data[FLAGS_WORD] & UBITX_USB_FLAG) > 0) && ((data[FLAGS_WORD] & UBITX_CW_FLAG) == 0) );
    return result;
  }
  
  inline bool isModeLSB() const { 
    bool result;
    DISABLEINTS( result = ((data[FLAGS_WORD] & UBITX_USB_FLAG) == 0) && ((data[FLAGS_WORD] & UBITX_CW_FLAG) == 0) );
    return result; 
  }

  inline bool isModeCWAny() const {
    bool result;
    DISABLEINTS( result = (data[FLAGS_WORD] & UBITX_CW_FLAG) > 0 );
    return result;
  }
  
  inline bool isModeCW() const { 
    bool result;
    DISABLEINTS( result = ((data[FLAGS_WORD] & UBITX_USB_FLAG) > 0) && ((data[FLAGS_WORD] & UBITX_CW_FLAG) > 0) );
    return result; 
  }
  
  inline bool isModeCWR() const { 
    bool result;
    DISABLEINTS( result = ((data[FLAGS_WORD] & UBITX_USB_FLAG) == 0) && ((data[FLAGS_WORD] & UBITX_CW_FLAG) > 0) );
    return result; 
  }

#ifdef DEBUG
  void serialHexState(const char* label);
  void serialPrettyState(const char* label);
#endif

#ifndef TEENSYDUINO
  // These methods are only defined in the Raduino (Arduino) case of the
  // RigState, not in the TeensyDSP (Teensy) case.
  void writeDirty();  // write fields FROM RigState TO Raduino
  void readDirty();   // read variables FROM Raduino TO RigState
#endif
};

#ifndef TEENSYDUINO

extern UBitxRigState& rigState;

#endif

/*
NOTE:  This is all currently OBE, leaving it here for reference/future cleanup.

Protocol discussion:
- I2C master: Raduino
- I2C slave:  TeensyDSP

Raduino state:
  - Baseline uBITX variables
  - I2C buffer
    - On I2C transmit:  make updates based on current variables
    - On I2C receive:
      - Update based on received I2C responses
      - Update associated variables
    
TeensyDSP state:
  - CAT buffer
    - Used to receive command from CAT (when commands arrive via Serial)
    - Used to transmit state to Raduino (when requested via Wire1)
  - Raduino buffer
    - Used to receive state from Raduino (when received via Wire1)
    - Used to transmit responses to CAT (over Serial)
  - Questions
    - How can these be synchronized?
      - At the tail end of an I2C request handler.  Before sending the response to the Raduino via I2C:
        - Copy updated CAT buffer items to the Raduino buffer.
        - Copy updated Raduino buffer items to the CAT buffer.
        - In the case of conflicts, CAT wins.
        - Transmit the CAT buffer state to the Raduino.
- TeensyDSP updates 'outgoing' state based on CAT inputs.
  - Make change to data.
  - Mark data as dirty, if different than incoming state.
- When requested, Teensy DSP sends 'outgoing' state to Raduino.
  - Send dirty data over I2C.
  - Mark data as clean.  
 */

#endif

/***********************************************************************
 * EOF
 **********************************************************************/
