/***************************************************
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/
// font   chars    char     line
// size  per line  width   height  lines
//  1      39       ? 6      ?8
//  2      19       ?12     ?16
//  3      12       ?20     ?24
//  4      10       ?25     ?30     4
//  5       8       ?30     ?40
#include "Adafruit_ThinkInk.h"

#define EPD_DC      10       // can be any pin, but required!
#define EPD_CS       9       // can be any pin, but required!
#define EPD_BUSY    18       // can set to -1 to not use a pin (will wait a fixed delay)
#define SRAM_CS     16       // can set to -1 to not use a pin (uses a lot of RAM!)
#define EPD_RESET   19       // can set to -1 and share with chip Reset (can't deep sleep)
const int pinXBee       = 20;   // grounded if XBee in use
const int pinUSB        = 21;   // grounded if USB in use
const int pinHeartSetup = 22;   // grounded to start with heart
const int pinHeartLoop  = 23;   // grounded to show heart q loopD

// 2.13" Tricolor EPD with SSD1680 chipset
ThinkInk_213_Tricolor_RW display(EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);

         const char EXCLAIM = '!';
               bool HeartShown;
const unsigned long msOneSecond = 1000;
const unsigned long msOneMinute = 60 * msOneSecond;
const unsigned long msMinUpdateInterval = 5 * msOneMinute;
      elapsedMillis msSinceLastUpdate;
               bool USBInUse;

#define MaxLines 16
#define MaxLength 40
struct tLine
  { int TheSize;
    int TheColor;
    char TheText[MaxLength];
  }; // tLine


class tLineHolder
  { private:
      tLine Line[MaxLines];
      int   LinesExpected;
      int   NLines;
      bool  TheseLinesNew;

      void ShowLine(const int Index);
    public:
      void Clear(const int Expected)
        { NLines = 0;
          LinesExpected = Expected;
          Line[0].TheText[0] = 0;
          TheseLinesNew = true; 
        }
      void SaveRawLine(const char RawLine[]);
      void ShowTheLines();
  }; // tLineHolder

void tLineHolder::SaveRawLine(const char RawLine[])
  { // line is
    //    SC <text>
    // where SC is
    //   size (in '1' .. '5') and color ('0' or '1')  OR
    //   Z<n> where <n> is number of lines to expect

    if (USBInUse)
      { Serial.printf("saving '%s' as line #%d\n", RawLine, NLines); }
    if (RawLine[0] == 'Z')
      { Clear(RawLine[1]-'0'); }
    else if ((NLines < MaxLines) and
             (RawLine[0] != 0)   and
             (RawLine[1] != 0))
      { // nondegenerate
        Line[NLines].TheSize  = RawLine[0] - '0';
        Line[NLines].TheColor = RawLine[1] - '0';
        for (int I = 0; I < MaxLength; I++)
          { Line[NLines].TheText[I] = RawLine[I+2];
            if (RawLine[I+2] == 0)
              { break; }
          }
        NLines++;
      } // nondegenerate
  } // tLineHolder::SaveRawLine

void tLineHolder::ShowLine(const int Index)
  { display.setTextSize(Line[Index].TheSize);
    if (Line[Index].TheColor == 1)
      { display.setTextColor(EPD_RED, EPD_WHITE); }
    else
      { display.setTextColor(EPD_BLACK, EPD_WHITE); }
    display.println(Line[Index].TheText);
  } // tLineHolder::ShowLine

void tLineHolder::ShowTheLines()
  { if (TheseLinesNew and (NLines >= LinesExpected))
      { // ready to go
        display.clearBuffer();
        display.setCursor(0, 0);
        for (int I = 0; I < NLines; I++)
          { ShowLine(I); }
        display.display();
        TheseLinesNew = false;
        HeartShown = false;
      } // ready to go
  } // tLineHolder::ShowTheLines()


  tLineHolder TheLines;
         char USBLine[MaxLength];
          int USBNChars;
         bool XBeeInUse;
         char XBeeLine[MaxLength];
          int XBeeNChars;

void Heart()
  { const int Radius    =  25;
    const int Y0        =  30;
    const int X0        = 125;
    const int XLeftCtr  = X0        - Radius;
    const int XRightCtr = X0        + Radius;
    const int R2        = Radius * Radius;
    const int R2_16     = 16 * R2;
    int X;
    int Y;
    int X_2R;
    const int CharHalfWidth =  6;
    const int LineHeight    = 16;
    const char AMPERSAND    = '&';

    display.clearBuffer();

    for (X = 0; X <= Radius; X++) // atria
      { // one X, 4 lines
        Y = sqrt(R2 - X * X);
        display.writeFastVLine(XLeftCtr  - X, Y0 - Y, Y, EPD_RED);
        display.writeFastVLine(XLeftCtr  + X, Y0 - Y, Y, EPD_RED);
        display.writeFastVLine(XRightCtr - X, Y0 - Y, Y, EPD_RED);
        display.writeFastVLine(XRightCtr + X, Y0 - Y, Y, EPD_RED);
      } // one X, 4 lines

    for (X = 0; X <= 2 * Radius; X++) // ventricles
      { // one X, two lines
        X_2R = X + 2 * Radius;
        Y = sqrt(R2_16 - X_2R * X_2R);
        display.writeFastVLine(X0 - X, Y0, Y, EPD_RED);
        display.writeFastVLine(X0 + X, Y0, Y, EPD_RED);
      } // one X, two lines

    if (random(10) == 0)
      { // text in heart
        display.setTextColor(EPD_WHITE, EPD_RED);
        display.setTextSize(2);

        display.setCursor(X0 - 3*CharHalfWidth, Y0);                  display.print( "Bob"   );
        display.setCursor(X0 -   CharHalfWidth, Y0 +     LineHeight); display.print(AMPERSAND);
        display.setCursor(X0 - 4*CharHalfWidth, Y0 + 2 * LineHeight); display.print( "Judy"  );
      } // text in heart

    display.display();
    HeartShown = true;
  } // Heart()

void setup()
  { pinMode(pinUSB, INPUT_PULLUP);                 // USB connection
    USBInUse = (digitalRead(pinUSB) == LOW);       // ..
    if (USBInUse)                                  // ..
      { Serial.begin(115200);                      // ..
        delay(3000);                               // ..
        Serial.println("Serial port OK");          // ..
        USBNChars = 0;                             // ..
      } // USB                                     // ..

    pinMode(pinXBee, INPUT_PULLUP);                // XBee connection
    XBeeInUse = (digitalRead(pinXBee) == LOW);     // ..
    if (USBInUse)                                  // ..
      { Serial.printf("XBeeInUse = %d\n",          // ..
                        XBeeInUse);                // .. 
      }                                            // ..
    if (XBeeInUse)                                 // ..
      { Serial1.begin(38400);                      // ..
        while (!Serial1)                           // ..
          { delay(10); }                           // ..
        XBeeNChars = 0;                            // ..
      }                                            // ..

    if (USBInUse)                                  // eInk
      { Serial.println("EPD update test"); }       // ..
    display.begin(THINKINK_TRICOLOR);              // ..
    display.setTextColor(EPD_BLACK);               // ..
    display.setTextWrap(true);                     // ..

    HeartShown = false;
    pinMode(pinHeartLoop, INPUT_PULLUP);
    pinMode(pinHeartSetup, INPUT_PULLUP);
    if (digitalRead(pinHeartSetup) == LOW)
      { Heart(); } // start with heart
    else
      { // start with text sampler
        TheLines.SaveRawLine("Z5!"                                       ); // screen image
        //                      0204060810121416182022242628303234363840
        TheLines.SaveRawLine("10black line, size 1                   39" ); // ..
        TheLines.SaveRawLine("21red line, size 2  20"                    ); // ..
        TheLines.SaveRawLine("31red size 3 13"                           ); // ..
        TheLines.SaveRawLine("40black 4 10"                              ); // ..
        TheLines.SaveRawLine("51red 5  8"                                ); // ..
        TheLines.ShowTheLines();                                            // ..
      } // start with text sampler

  } // setup

void loop()
  { char C;
    if (USBInUse)
      { // collect and process USB chars
         while (Serial.available())
          { // one USB char
            C = Serial.read();
            Serial.print(C);
            switch (C)
              { case       0:
                case EXCLAIM:
                case    '\n': TheLines.SaveRawLine(USBLine);
                              USBNChars = 0;
                              break;
                     default: USBLine[USBNChars] = C;
                              USBNChars++;
                              USBLine[USBNChars] = 0;
                              break;
              } // switch
          } // one USB char
      } // collect and process USB chars

    if (XBeeInUse)
      { // collect & process XBee chars
        while (Serial1.available())
          { // one XBee char
            C = Serial1.read();
            if (USBInUse)
              { Serial.print(C); }

            switch (C)
              { case       0:
                case EXCLAIM:
                case    '\n': TheLines.SaveRawLine(XBeeLine);
                              XBeeNChars = 0;
                              break;
                     default: XBeeLine[XBeeNChars] = C;
                              XBeeNChars++;
                              XBeeLine[XBeeNChars] = 0;
                              break;
              } // switch
          } // one XBee char
      } // collect & process XBee chars

    if (msSinceLastUpdate > msMinUpdateInterval)
      { // time to update
        if (HeartShown)
          { TheLines.ShowTheLines(); } 
        else if (digitalRead(pinHeartLoop) != LOW) 
          { TheLines.ShowTheLines(); } 
        else
          { Heart(); } 
        msSinceLastUpdate = 0;
      } // time to update
  } // loop
