// ************* input from XBee ****************************************
// routine IDs 6xxx

class tXBee
  { private:
      char         Buffer[200];
      char         CommandPrefix[3];
      int          DestinationLow;
      char         HexedID[5];      // used for debugging
      bool         LineReady;
      unsigned int msCommandOKWait;
      int          NChars;

      void Clear();
      bool CommandMode();
      bool GotOKCR();
      bool LeaveCommandMode();
      bool SendCommand(const char Command[], const char Parameter[], const bool ExpectOK);
      bool SendCommand(const char Command[], const char Parameter[])
              { return SendCommand(Command, Parameter, true); }
      bool SendCommand(const char Command[])
              { return SendCommand(Command, ""); }
      bool SetDestination(const int NewDestinationLow);
      bool SetString(const char TheCommand[], char TheString[], int MaxLength);
    public:
      bool Begin();
      void GetChars();
      void ProcessLine();  // public for use by Windows.DoTreatAsXBeeInput
      bool Transmit(const int DestinationID, char Message[]);
  }; // tXBee

bool tXBee::Begin()                               // base station
  { // routine ID 6010
    const int XBeeBaudRate    = 38400;

    bool Result;

    DestinationLow  = 0;
    msCommandOKWait = 2 * msOneSecond;
    Clear();
    sprintf(CommandPrefix, "AT");
    sprintf(HexedID, "0000");

    serialXBee.begin(XBeeBaudRate);
    Result = true;
    return Result;
  } // tXBee::Begin()

void tXBee::Clear()
  { NChars = 0;
    Buffer[0] = 0;
    LineReady = false;
  } // tXBee::Clear()

 boolean tXBee::CommandMode()
  { // routine ID 6020
    // enter command mode

    const char         CmdModeRQ[]    = "+++";
    const int          msBetweenTries =  5 * msOneSecond;
    const unsigned int msMinGuardTime = msOneSecond;
    const int          msTooLong      = 30 * msOneSecond;
    
    const unsigned int msGuardTime    = msMinGuardTime + 500;

    elapsedMillis msTrying;
    bool Result = false;

    msTrying = 0;
    while (msTrying < msTooLong)
      { // keep trying
        delay(msGuardTime);
        serialXBee.print(CmdModeRQ);
        delay(msGuardTime);
        if (GotOKCR())
          { // success
            Infrastructure.TellWindows(btwXBeeInCommandMode);
            Result = true;
            break;
          } // success
        Infrastructure.TellWindows(btwXBeeDifficultCommandMode);
        delay(msBetweenTries);
      } // keep trying

    return Result;
  } // tXBee::CommandMode

void tXBee::GetChars()                               // base station
  { // called from main loop, when at least one char is available
    char C;
    // Infrastructure.TellWindows("XBee.GetChars()", btwDebugging);
    while (serialXBee.available())
      { // get at least one character
        C = serialXBee.read();
        if (NChars < int(sizeof(Buffer))-1)
          { // nondegenerate
            Buffer[NChars] = C;
            NChars++;
            Buffer[NChars] = 0;
          } // nondegenerate

        LineReady = ((C == CR) or (C == EXCLAIM));
        if ((NChars >= 3)      and
            (Buffer[0] == 'O') and
            (Buffer[1] == 'K') )
          { Clear(); } // 2023-02-10 errant XBee talk

        // Serial.print(C);
        // TV.SmallPrint(C);
        if (LineReady)
          { ProcessLine(); }
      } // get at least one character
    // FileSystem.Log("leaving XBee.GetChars");
  } // tXBee::GetChars()

bool tXBee::GotOKCR()
  { // routine ID 14030
    // XBee is in command mode, and it has reason to send us nothing
    // but "OK<cr>"
    // Timeout should be implementable with elapsedMillis, but that gives crazy
    // results.
    // char Message[100];
    bool NeedOh = true;
    bool Result = false;
    char C;

    // Infrastructure.SayInIDE("    entering tXBee::GotOKCR()");

    unsigned long Deadline = millis() + msCommandOKWait;

    while (true)
      { // sprintf(Message, "  GotOKCR: millis = %d/%d, available = %d",
        //                       millis(), Deadline, serialXBee.available());
        // Infrastructure.SayInIDE(Message);
        if (millis() > Deadline)
          { break; }

        else if (serialXBee.available() > 0)
          { // worth a try
            C = serialXBee.read();
            if (NeedOh)
              { if (C == 'O')
                  { Infrastructure.SayInIDE("    got Oh");
                    NeedOh = false;
                  }
                else
                  { break; }
              }

            else
              { // sprintf(Message, "    got '%c' (should be 'K')", C);
                // Infrastructure.SayInIDE(Message);
                Result = (C == 'K');
                break;
              }
          } // worth a try
      } // while

    while (serialXBee.available())
      { serialXBee.read(); }

    // Infrastructure.SayInIDE("    leaving tXBee::GotOKCR()"); }
    return Result;
  } // tXBee::GotOKCR()

bool tXBee::LeaveCommandMode()                     // base station
  { // routine ID 14050
    const char CmdNormalMode[] = "CN";
    bool Result = false;

    if (SendCommand(CmdNormalMode, nullstring, false))
      { Result = true; }
    else
      { Infrastructure.TellWindows(btwXBeeStuckInCommandMode); }

    return Result;
  } // tXBee::LeaveCommandMode

void tXBee::ProcessLine()                            // base station
  { // routine ID 6070
    char TimestampedLine[210];
    // line as received should be
    // <board tag> <slotUID> <triple>* <checksum>

    const int MinLineLength = 2;
    if (NChars > MinLineLength)
      { // nondegenerate
        // char        DebugMessage[200];
        // sprintf    (DebugMessage, "XBee.ProcessLine: Nchars = %d", NChars);
        // Infrastructure.TellWindows(DebugMessage, btwDebugging);
        // sprintf    (DebugMessage, "XBee.ProcessLine: Line = %s", Buffer);
        // Infrastructure.TellWindows(DebugMessage, btwDebugging);
        TV.SmallPrint(Buffer[0]);
        DS1307.ReadDS1307();
        sprintf(TimestampedLine, "%s\t%s!", DS1307.LatestTimestamp, Buffer);
        // Serial.printf("  TimeStampedLine = '%s'\n", TimestampedLine);
        if (Infrastructure.GlobalStatus == gsPassThrough)
          { Infrastructure.TellWindows(TimestampedLine, btwLiveData); }
        else
          { FileSystem.SaveALine(TimestampedLine); }
        // Serial.println("Leaving ProcessLine");
      } // nondegenerate

    Clear();
  } // tXBee::ProcessLine

bool tXBee::SendCommand(const char Command[], const char Parameter[], const bool ExpectOK)
  { // routine ID 6080
    // XBee is in command mode.  Send a command to which the only response is OK<cr>
    bool Result;
    // char Message[50];

    // if (Command[0] == 'D')
    //    { sprintf(Message, "command: '%s%s'", Command, Parameter);
    //      Infrastructure.TellWindows(Message, btwXBeeCommandIssued);
    //    }
    // Serial.printf("SendCommand: '%s%s'\n", Command, Parameter);
    serialXBee.print(CommandPrefix);
    serialXBee.print(Command);
    serialXBee.println(Parameter);
    if (ExpectOK)
      { Result = GotOKCR(); }
    else
      { Result = true; }

    return Result;
  } // tXBee::SendCommand(const char Command[], const char Parameter[])


bool tXBee::SetDestination(const int NewDestinationLow)    // base station
  { // routine ID 6090
    // set destination iff not already set

    const char CmdDestinationHigh[] = "DH";
    const char CmdDestinationLow[]  = "DL";
    const char CmdWriteToFlash[]    = "WR";

    bool Result = false;
    char Message[50];

    // sprintf(Message, "SetDestination(%d)", NewDestinationLow);
    // Infrastructure.TellWindows(Message, btwXBeeSettingDestination);

    if (NewDestinationLow == DestinationLow)
      { Result = true; }

    else if (not CommandMode())
      { Infrastructure.BadNews("Couldn't enter command mode.",  btwXBeeNoCommandMode); }

    else
      { // go into command mode
        sprintf(HexedID, "%x", NewDestinationLow);
        sprintf(Message, "hex(%d) = <%s>", NewDestinationLow, HexedID);
        Infrastructure.TellWindows(Message, btwDebugging);

        if (not SendCommand(CmdDestinationLow, HexedID))
          { // fail
            sprintf(Message, "SetDestination(%s)", HexedID);
            Infrastructure.BadNews(Message, btwXBeeNoSetDestination);
          } // fail

        else if (not SendCommand(CmdDestinationHigh, "0"))
          { Infrastructure.BadNews("DestinationHigh", btwXBeeNoSetDestination); } // fail

        else if (not SendCommand(CmdWriteToFlash))
          { Infrastructure.BadNews("writing to flash", btwXBeeNoSetDestination); } // fail

        else if (not LeaveCommandMode())
          { Infrastructure.BadNews("Couldn't leave command mode.",  btwXBeeStuckInCommandMode); }

        else
          { // success
            DestinationLow = NewDestinationLow;
            Result = true;
          } // success
      } // got into command mode

    return Result;
  } // tXBee::SetDestination

bool tXBee::SetString(const char TheCommand[], char TheString[], int MaxLength)
  { char          C;
    // char Message[100];
    int           CharsReceived = 0;
    elapsedMillis elmsWaiting = 0;
    bool          Result = false;
    bool          StillReading = true;

    // sprintf(Message, "Entering SetString('%s', %d)\n", TheCommand, MaxLength);
    // Infrastructure.SayInIDE(Message);

    if (not CommandMode())
      { // Infrastructure.SayInIDE("Couldn't enter command mode");
      }
    else
      { // got into command mode
        serialXBee.printf("%s%s\r", CommandPrefix, TheCommand);

        while (StillReading)
          { // leave loop only when done reading
            // await chars of <TheString><cr>"
            if (serialXBee.available())
              { // one character
                C = serialXBee.read();
                // sprintf(Message, "received '%c' (%d)", C, C);
                // Infrastructure.SayInIDE(Message);

                 if (C == CR)
                   { // got CR
                     // sprintf("CR: about to return with '%s'", TheString);
                     // Infrastructure.SayInIDE(Message);
                     StillReading = false;
                   } // got CR

                 else if (elmsWaiting > msCommandOKWait)
                   { Infrastructure.SayInIDE("  timed out");
                     StillReading = false;
                   }

                 else
                   { // ordinary character
                     TheString[CharsReceived] = C;
                     CharsReceived++;
                     TheString[CharsReceived] = 0;
                     if (CharsReceived >= MaxLength)
                       { // got full bufferload
                         // sprintf(Message, "full: about to return with '%s'",
                         //                   TheString);
                         // Infrastructure.SayInIDE(Message);
                         StillReading = false;
                       } // got full bufferload
                   } // ordinary character
              } // one character
          } // leave loop only when done reading
        Result = LeaveCommandMode() and (CharsReceived > 0);
      } // got into command mode

    return Result;
  }  // tXBee::SetString

bool tXBee::Transmit(const int DestinationID, char Message[])   // base station
  { bool Result = false;

    if (SetDestination(DestinationID))
      { // destination set
        // char DebugMessage[200];
        // sprintf(DebugMessage, "XBee.Transmit: sending %s to XBee #%d", Message, DestinationID);
        // Infrastructure.TellWindows(DebugMessage, btwDebugging);
        serialXBee.print(Message);
        serialXBee.print(EXCLAIM);
        Result = true;
      } // destination set

    return Result;
  } // tXBee::Transmit

// ************* end input from XBee ****************************************

