/*!
 *  @file     RigState.cpp
 *  
 *  @mainpage uBITX V5X Software - RigState
 *  
 *  @section  introsec Introduction
 *  
 *  TBD
 *  
 *  @section  dependencies Dependencies
 *  
 *  TBD
 *  
 *  @section  author Author
 *  
 *  Written by Rob "Scrape" French, KC4UPR
 *  
 *  @section  license License
 *  
 *  TBD
 */

#include "Debug.h"
#include "RigState.h"

/***********************************************************************
 * COMMON FUNCTIONS
 * 
 * The following are all common to RigState objects, whether on the
 * Raduino or on the TeensyDSP.
 **********************************************************************/

static uint32_t zeroes[1] = {0};  // used to transmit zeroes

/*!
 *  @brief  Begin using the RigState object.  In order to force an 
 *          initial update (i.e. sending current state to the remote 
 *          device), all fields are initially marked dirty.
 */
void UBitxRigState::begin() {
  setDirty();
}

/***********************************************************************
 * RADUINO FUNCTIONS
 * 
 * The following are specific to the Raduino implementation.  Note that
 * this depends on the use of the TEENSYDUINO #define, which may result
 * in a fragile implementation for other development environments (e.g.
 * if the normal Arduino IDE is not being used).
 **********************************************************************/

#ifndef TEENSYDUINO

#include <Wire.h>
#include "ubitx.h"
#include "ubitx_eemap.h"

extern unsigned long frequency, ritRxFrequency, ritTxFrequency;
extern unsigned long vfoA;
extern unsigned long vfoB;
extern char cwMode;
extern char isUSB;
extern char vfoActive;
extern char ritOn;
extern char splitOn;
extern char inTx;
void setFrequency(unsigned long);

/*!
 *  @brief  Send the RigState from the Raduino to the TeensyDSP.  The
 *          basic process is:  (1) read in any updated (dirty) data
 *          from the Raduino's state variables; (2) transmit the dirty
 *          data to the TeensyDSP; (2a) for clean data, zeroes are
 *          transmitted; (3) mark all data as clean.
 */
void UBitxRigState::send_RIGINF() {
  readDirty();
  Wire.beginTransmission(I2CMETER_ADDR);
  Wire.write(I2CMETER_RIGINF);
  for (RigStateWord i = DIRTY_WORD; i < NUM_WORDS; i++) {
    if (i == DIRTY_WORD || isDirty(i)) {
      // always send the current dirty bits
      // or, bytes for updated (dirty) fields
      Wire.write((byte*)&data[i], sizeof(uint32_t));
    } else {
      // otherwise, send out zeroes
      Wire.write((byte*)&zeroes, sizeof(uint32_t));
      //----------------------------------------------------------------
      // NOTE:  I am sending these zeroed out fields under a possibly
      // mistaken assumption that in doing so, I will be sending a 
      // constant voltage on the SDA line most of the time, i.e. no
      // bit changes, and so this will help reduce noise generated by
      // I2C traffic (since most of the time there will be no updates.)
      //----------------------------------------------------------------
    }
  }
  Wire.endTransmission();  
  IFDEBUG( serialHexState("Sent") );
  //IFDEBUG( serialPrettyState("Sent") );
  setClean();
}

//  delay(1);   // 1ms - some delay required between ending transmission and requesting?

/*!
 *  @brief  Receive the RigState from the TeensyDSP.  This generally
 *          reflects changes due to CAT transmission to the TeensyDSP.
 *  @param  numBytes
 *          Number of bytes received from the TeensyDSP.
 */
void UBitxRigState::receive_RIGINF(int numBytes) {
  // Retrieve all of the deltas.  Mark any received fields as dirty.  It
  // is assumed that send_RIGINF() was called immedaitely before this,
  // so the fields are already clean.
  byte* ptr = (byte*)&data;
  Wire.requestFrom(I2CMETER_ADDR, sizeof(data));
  for (RigStateWord i = DIRTY_WORD; i < NUM_WORDS && Wire.available(); i++) {
    for (size_t j = 0; j < sizeof(uint32_t) && Wire.available(); j++) {
      byte incomingByte = Wire.read();
      if (i == DIRTY_WORD || isDirty(i)) {
        // always overwrite the dirty bits
        // and, update bytes for fields marked dirty
        *ptr = incomingByte;
      }
      ptr++;
    }
  }
  writeDirty();
  IFDEBUG( serialHexState("Rcvd") );
  //IFDEBUG( serialPrettyState("Rcvd") );
  setClean(); // They get marked dirty as req'd during readDirty().
}

/*!
 *  @brief  Write dirty fields from the RigState out to the Raduino 
 *          variables.
 */
void UBitxRigState::writeDirty() {
  // VFO A frequency
  if (isDirty(VFOA_WORD)) {
    if (vfoActive == VFO_A) {
      setFrequency(getFreqA());
    } else {
      vfoA = getFreqA();
    }    
  }

  // VFO B frequency
  if (isDirty(VFOB_WORD)) {
    if (vfoActive == VFO_B) {
      setFrequency(getFreqB());
    } else {
      vfoB = getFreqB(); 
    }    
  }

  // RIT and XIT frequencies
  if (isDirty(OFFSETS_WORD)) {
    // RIT
    ritRxFrequency = getRIT() + ritTxFrequency;
    if (ritOn == 1) {
      if (inTx == 0) {
        setFrequency(ritRxFrequency);
      } else {
        setFrequency(ritTxFrequency);
      }
    }
    // XIT - TODO
  }

  // VFO A/B selection
  if (isDirty(FLAGS_WORD)) {
    char prev = vfoActive;
    vfoActive = isVFOA() ? VFO_A : VFO_B;
    if (vfoActive != prev) {
      if (vfoActive == VFO_A) {
        if (vfoA != frequency) {
          setFrequency(vfoA);      
        } 
      } else if (vfoActive == VFO_B) {
        if (vfoB != frequency) {
          setFrequency(vfoB);
        }
      }
    }  

    // Split on/off
    splitOn = isSplit() ? 1 : 0;

    // RIT on/off
    prev = ritOn;
    ritOn = isRIT() ? 1 : 0;
    if (ritOn != prev) {
      if ((ritOn == 1) && (inTx == 0)) {
        setFrequency(ritRxFrequency);
      }
    }

    // XIT on/off
    // TODO

    // Mode
    prev = (cwMode << 1) | isUSB;
    isUSB = isModeUSB() ? 1 : 0;
    if (isModeCW()) {
      cwMode = 2; // 2 = cwu
    } else if (isModeCWR()) {
      cwMode = 1; // 1 = cwl
    } else {
      cwMode = 0; // 0 = no cw
    }
    if ((cwMode << 1) | isUSB != prev) {
      setFrequency(frequency);
    }
  }
}

/*!
 *  @brief  Read current Raduino variables into the RigState
 *          (if they are changed) and set the appropriate dirty flags.
 *  @param  r
 *          RigState reference to put the values into.
 */
void UBitxRigState::readDirty() {
  unsigned long freq;
  short offset;
  
  // VFO A frequency
  freq = (vfoActive == VFO_A) ? frequency : vfoA;
  if (getFreqA() != freq) {
    setFreqA(freq);
  }

  // VFO B frequency
  freq = (vfoActive == VFO_B) ? frequency : vfoB;
  if (getFreqB() != freq) {
    setFreqB(freq);
  }
  
  // RIT frequency
  if (inTx) {
    offset = ritRxFrequency - ritTxFrequency;
  } else {
    offset = frequency - ritTxFrequency;
  }
  if (getRIT() != offset) {
    setRIT(offset);
  }

  // XIT frequency
  offset = 0; // xitRxFrequency - frequency;
  if (getXIT() != offset) {
    setXIT(offset);
  }
 
  // VFO A/B selection
  if (isVFOA() && vfoActive == VFO_B) {
    setVFOB();
  } else if (isVFOB() && vfoActive == VFO_A) {
    setVFOA();
  }

  // Split selection
  if (isSplit() && splitOn == 0) {
    setSplitOff();
  } else if (!isSplit() && splitOn != 0) {
    setSplitOn();
  }

  // RIT selection
  if (isRIT() && ritOn == 0) {
    setRITOff();
  } else if (!isRIT() && ritOn != 0) {
    setRITOn();
  }

  // XIT selection
  //setXITOff();
  // TODO

  // Mode
  char prev = (isModeCW() ? 4 : 0) | (isModeCWR() ? 2 : 0) | (isModeUSB() ? 1 : 0);
  char curr = (cwMode << 1) | isUSB;
  if (curr != prev) {
    if (cwMode == 2) {
      setCW();
    } else if (cwMode == 1) {
      setCWR();
    } else {
      if (isUSB) {
        setUSB();
      } else {
        setLSB();
      }
    }
  }
}

/***********************************************************************
 * TEENSYDSP FUNCTIONS
 * 
 * The following are specific to the TeensyDSP implementation.  Note
 * that this depends on the use of the TEENSYDUINO #define, which may 
 * result in a fragile implementation for other development environments 
 * (e.g. if the normal Arduino IDE is not being used).
 **********************************************************************/

#else

#include <i2c_t3.h>

/*!
 *  @brief  Receive RIGINF data from the Raduino.  This method should
 *          be called on the TeensyDSP 'radState' (Raduino state) 
 *          instance, when a RIGINF signal is received via I2C.  It 
 *          receives the incoming data from the Raduino and updates the
 *          state.
 */
void UBitxRigState::receive_RIGINF(int numBytes) {
  byte* ptr = (byte*)&data;
  setClean();  // we'll get new dirty bits via the I2C message
  for (RigStateWord i = DIRTY_WORD; i < NUM_WORDS && Wire1.available(); i++) {
    for (size_t j = 0; j < sizeof(uint32_t) && Wire1.available(); j++) {
      byte incomingByte = Wire1.read();
      if (i == DIRTY_WORD || isDirty(i)) {
        // always overwrite the dirty bits
        // and, update bytes for fields marked dirty
        *ptr = incomingByte;
      }
      ptr++;
    }
  }

  IFDEBUG( serialHexState("Rcvd") );
  IFDEBUG( serialPrettyState("Rcvd") );
}

/**********************************************************************/
/*!
 *  @brief  Handle a RIGINF signal from the Raduino.  This method should
 *          be called on the TeensyDSP 'catState' (CAT state) 
 *          instance, when a RIGINF signal is received via I2C.  It 
 *          sends a response to the Raduino via I2C, using the Wire1
 *          interface.
 */
void UBitxRigState::send_RIGINF() {
  for (RigStateWord i = DIRTY_WORD; i < NUM_WORDS; i++) {
    if (i == DIRTY_WORD || isDirty(i)) {
      // always send the current dirty bits
      // or, bytes for updated (dirty) fields
      Wire1.write((byte*)&data[i], sizeof(uint32_t));
    } else {
      // otherwise, send out zeroes
      Wire1.write((byte*)&zeroes, sizeof(uint32_t));
      //----------------------------------------------------------------
      // NOTE:  I am sending these zeroed out fields under a possibly
      // mistaken assumption that in doing so, I will be sending a 
      // constant voltage on the SDA line most of the time, i.e. no
      // bit changes, and so this will help reduce noise generated by
      // I2C traffic (since most of the time there will be no updates.)
      //----------------------------------------------------------------
    }
  }
  IFDEBUG( serialHexState("Sent") );
  IFDEBUG( serialPrettyState("Sent") );
  setClean();  // now that we've sent them, they're clean
  //--------------------------------------------------------------------
  // TODO:  Need to look at possibly merging the two states together at
  // this point.  The purpose would be to minimize the turnaround time
  // for getting the most recent data to a CAT response.
  //--------------------------------------------------------------------  
}

#endif

#ifdef DEBUG

char debugString[81] = {'\0'};

void UBitxRigState::serialHexState(const char* label = "RigState") {
  Serial.print(label);
  sprintf(debugString, ": %#010lx, %#010lx, %#010lx, %#010lx, %#010lx", 
    data[DIRTY_WORD], data[VFOA_WORD], data[VFOB_WORD], data[OFFSETS_WORD], data[FLAGS_WORD]);
  Serial.println(debugString);
}

void UBitxRigState::serialPrettyState(const char* label = "RigState") {
  Serial.println(label);
  sprintf(debugString, "VFO A : %011ld %1c   /   VFO B : %011ld %1c", 
    getFreqA(), isDirty(VFOA_WORD) ? 'D' : ' ', getFreqB(), isDirty(VFOB_WORD) ? 'D' : ' ');
  Serial.println(debugString);
  sprintf(debugString, "RIT   : %011ld %1c   /   XIT   : %011ld %1c", 
    getRIT(), isDirty(OFFSETS_WORD) ? 'D' : ' ', getXIT(), isDirty(OFFSETS_WORD) ? 'D' : ' ');
  Serial.println(debugString);
  sprintf(debugString, "Split? %1c / VFO? %1c / RIT? %1c / XIT? %1c / Mode? %3s",
    isSplit() ? 'Y' : 'N', isVFOA() ? 'A' : 'B', isRIT() ? 'Y' : 'N', isXIT() ? 'Y' : 'N',
    isModeUSB() ? "USB" : (isModeLSB() ? "LSB" : (isModeCW() ? "CW " : (isModeCWR() ? "CWR" : "   "))));
  Serial.println(debugString);
}

#endif


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

#ifndef TEENSYDUINO

UBitxRigState _rigState;
UBitxRigState& rigState = _rigState;

#endif

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