#define BufferSize 512

class tFileBuffer
  { private:
      int           TheFileNumber;
    public:
      int           NChars;
      char          TheBuffer[BufferSize];
      char          TheFileName[13];

      void SetFileNumber(const int N);
      void NextFileNumber()            { SetFileNumber(TheFileNumber + 1); }
  }; // tFileBuffer

void tFileBuffer::SetFileNumber(const int N)
  { int ExtensionStart = 1;
    TheFileNumber = N;
    sprintf(TheFileName, "%d", N);
    if (TheFileName[1] != 0)
      { ExtensionStart = 2; }
    TheFileName[ExtensionStart    ] = '.';
    TheFileName[ExtensionStart + 1] = 't';
    TheFileName[ExtensionStart + 2] = 'x';
    TheFileName[ExtensionStart + 3] = 't';
    TheFileName[ExtensionStart + 4] =  0;
  } // tFileBuffer::SetFileNumber

#define MaxLineSize 200

class tFileSystem
  { private:
        tFileBuffer DumpBuffer;
                int DumpBufferProcessed;
               bool DumpFileEverOpen;
      unsigned long DumpFileProcessed;
      unsigned long DumpFileSize;
               bool DumpOver;
               bool DumpSeekNeeded;
      unsigned long NextCharToDump;
                int RetrievedChars;
               char RetrievedLine[MaxLineSize];
        tFileBuffer SaveBuffer;

      void ClearRetrievedLine();
      void DumpRetrievedLine();
      void ExtendRetrievedLine(const char C);
      void FillDumpBuffer();
      void FlushSaveBuffer();
      void GetDumpFileSize();
      void NextDumpFile();
      void SaveAChar(const char C);
    public:

      bool CleareduSDCard();
      bool DumpedALine();
      void DumpFinished();
      void SaveALine(const char TheLine[]);
      bool setup();
      void ShutDown() { FlushSaveBuffer(); }
      void StartDump();
  }; // tFileSystem

bool tFileSystem::CleareduSDCard()
  { const char RootFileName[]   = "/";
    
    bool Result = false;

    File Root = SD.open(RootFileName);
    if (not Root)
      { Infrastructure.BadNews("no uSD root", btwFileSystemTrouble); }
    else
      { // Root opened
        Result = true;
        while (true)
          { // one entry
            File Entry = Root.openNextFile();
            if (not Entry)
              { break; }
            SD.remove(Entry.name());
          } // one entry
        Root.close();
      } // Root opened

    return Result;
  } // tFileSystem::CleareduSDCard()

void tFileSystem::ClearRetrievedLine()
  { RetrievedLine[0] = 0;
    RetrievedChars = 0;
  } // tFileSystem::ClearRetrievedLine

bool tFileSystem::DumpedALine()
  { bool Result = false;
    bool Done = false;
    char C;

    ClearRetrievedLine();
    // char DebugMessage[100];
    // sprintf(DebugMessage, "Entered DumpedALine dumping from %s, saving to %s, ",
    //                          DumpBuffer.TheFileName, SaveBuffer.TheFileName);
    // Infrastructure.TellWindows(DebugMessage);

    while (not Done)
      { // while not Done
              if (DumpOver)                                 { Done = true;           }
         else if (NextCharToDump >= GrossDumpSize)          { DumpOver = true;       }
         else if (not DumpFileEverOpen)                     { GetDumpFileSize();     }
         else if (DumpFileProcessed >= DumpFileSize)        { NextDumpFile();        }
         else if (DumpSeekNeeded)                           { FillDumpBuffer();      }
         else if (DumpBufferProcessed >= DumpBuffer.NChars) { DumpSeekNeeded = true; }
         else
           { // DumpBuffer usable
             C = DumpBuffer.TheBuffer[DumpBufferProcessed];
             DumpBufferProcessed++;
             DumpFileProcessed++;
             NextCharToDump++;
             ExtendRetrievedLine(C);
             if (RetrievedChars >= MaxLineSize - 1)
               { ExtendRetrievedLine(EXCLAIM);
                 Result = true;
                 Done = true;
               }
             else if ((C == '\n') or (C == EXCLAIM) or (C =='\r'))
               { // EOL
                 if (RetrievedChars < 2)
                   { ClearRetrievedLine(); }
                 else
                   { // nonempty line
                     DumpRetrievedLine();
                     Done = true;
                     Result = true;
                   } // nonempty line
               } // EOL
           } // DumpBuffer usable
      }; // while not done

    return Result;
    // Infrastructure.TellWindows("Leaving DumpedALine");
  } // tFileSystem::DumpedALine()

void tFileSystem::DumpFinished()
  { if (CleareduSDCard())
      { GrossDumpSize = 0;
        DumpBuffer.SetFileNumber(1);
        SaveBuffer.SetFileNumber(1);
        Infrastructure.SetGlobalStatus(gsPassThrough);
        digitalWrite(pinuSD, LOW);
        Infrastructure.TellWindows(btwDumpEnded);
      }
  } // tFileSystem::DumpFinished()

void tFileSystem::DumpRetrievedLine()
  { // debulk DumpedALine
    // char Message[100];
    // Infrastructure.TellWindows("  Entered DumpRetrievedLine");
    // sprintf(Message, "Infrastructure.TellWindows('%s', btwDumpData)\n",
    //                     RetrievedLine);
    // Infrastructure.SayInIDE(Message);
//    if (not Infrastructure.IsGrounded(pinGNDShutDown))
//      {
      delay(15);    // was 1, and got gaps. fewer with 10
      Infrastructure.TellWindows(RetrievedLine, btwDumpData);
//      }
    ClearRetrievedLine();
    // Infrastructure.TellWindows("  Leaving DumpRetrievedLine");
  } // tFileSystem::DumpRetrievedLine()

void tFileSystem::ExtendRetrievedLine(const char C)
  { RetrievedLine[RetrievedChars] = C;
    RetrievedChars++;
    RetrievedLine[RetrievedChars] = 0;
  } // tFileSystem::ExtendRetrievedLine

void tFileSystem::FillDumpBuffer()
  { // seek and read
    char Message[100];   //   1                           2   3  4    5    6
    sprintf(Message, "FS.FillDumpBuffer(): dumping block\t%lu\t/\t%lu\tfrom\t%s",
                             DumpFileProcessed / BufferSize,
                             (DumpFileSize + BufferSize - 1) / BufferSize,
                             DumpBuffer.TheFileName);
    Infrastructure.TellWindows(Message, btwDumpingBlock);
    elmsSinceTickled = 0;  // 2022-11-29 avoid timeout during dump

    File DumpFile = SD.open(DumpBuffer.TheFileName);
    if (DumpFile)
      { // opened
        if (DumpFile.seek(DumpFileProcessed))
          { DumpBuffer.NChars = DumpFile.read(DumpBuffer.TheBuffer, BufferSize);
            DumpBufferProcessed = 0;
            DumpSeekNeeded = false;
          }
        else
          { DumpFileProcessed = DumpFileSize; }
        DumpFile.close();
      } // opened

    else
      { DumpFileProcessed = DumpFileSize; }
    // Infrastructure.TellWindows("  Leaving FillDumpBuffer");
  } // tFileSystem::FillDumpBuffer()

void tFileSystem::FlushSaveBuffer()
  { char Message[100];
    if (SaveBuffer.NChars > 0)
      { // flush it
        sprintf(Message, "  flushing %d chars to %s",
                              SaveBuffer.NChars, SaveBuffer.TheFileName);
        Infrastructure.TellWindows(Message);
        File SaveFile = SD.open(SaveBuffer.TheFileName, FILE_WRITE);
        if (SaveFile)
          { // nondegenerate
            SaveFile.seek(SaveFile.size());
            SaveFile.write(SaveBuffer.TheBuffer, SaveBuffer.NChars);
            sprintf(Message, "saved\t%d\tchars in\t%s\t, file size now\t%lld\n",
                             SaveBuffer.NChars, SaveBuffer.TheFileName, SaveFile.size());
            Infrastructure.TellWindows(Message, btwSavingBuffer);
            sprintf(Message, " saved(%s)-->%lld  ", SaveBuffer.TheFileName, SaveFile.size());
            TV.SmallPrintString(Message);
            SaveFile.close();
          } // nondegenerate
        else
          { sprintf(Message, "couldn't open %s\n", SaveBuffer.TheFileName);
            Infrastructure.SayInIDE(Message);
          }

        SaveBuffer.NChars = 0;
        digitalWrite(pinuSD, HIGH);
      } // flush it
  } // tFileSystem::FlushSaveBuffer()

void tFileSystem::GetDumpFileSize()
  { // char DebugMessage[100];
    File DumpFile;
    // Infrastructure.TellWindows("  Entered GetDumpFileSize");
    DumpFileSize = 0;
    DumpFileProcessed = 0;
    DumpFile = SD.open(DumpBuffer.TheFileName);
    if (DumpFile)
      { // got it open
        DumpFileEverOpen = true;
        DumpSeekNeeded = true;
        DumpFileSize = DumpFile.size();
        DumpFile.close();
      } // got it open
    else
      { DumpOver = true; }
    // sprintf(DebugMessage, "  Leaving GetDumpFileSize with size(%s) = %lu",
    //                          DumpBuffer.TheFileName, DumpFileSize);
    // Infrastructure.TellWindows(DebugMessage);
  } // tFileSystem::GetDumpFileSize

void tFileSystem::NextDumpFile()
  {
    // Infrastructure.TellWindows("  Entered NextDumpFile");
    FlushSaveBuffer();
    SaveBuffer.NextFileNumber();
    DumpBuffer.NextFileNumber();

    File DumpFile = SD.open(DumpBuffer.TheFileName);
    if (DumpFile)
      { DumpFileEverOpen = false; }
    else
      { DumpOver = true; }
    // Infrastructure.TellWindows("  Leaving NextDumpFile");
  } // tFileSystem::void NextDumpFile()

void tFileSystem::SaveAChar(const char C)
  { if (SaveBuffer.NChars >= BufferSize)
      { FlushSaveBuffer(); }
    SaveBuffer.TheBuffer[SaveBuffer.NChars] = C;
    SaveBuffer.NChars++;
    GrossDumpSize++;
  } // tFileSystem::SaveAChar

void tFileSystem::SaveALine(const char TheLine[])
  { // cliche
    for (int I = 0; TheLine[I] != 0; I++)
      SaveAChar(TheLine[I]);
    SaveAChar('\n');
  } // tFileSystem::SaveALine

bool tFileSystem::setup()
  { bool Result = true;

    DumpBuffer.SetFileNumber(1);
    SaveBuffer.SetFileNumber(1);
    GrossDumpSize = 0;
    NextCharToDump = 0;

    if (not CleareduSDCard())
      { Infrastructure.TellWindows("Couldn't clear uSD", btwFileSystemTrouble);
        Result = false;
      }
    else
      { pinMode(pinuSD, OUTPUT); } // card cleared

    return Result;
  } // tFileSystem::setup()

void tFileSystem::StartDump()
  { FlushSaveBuffer();
    if (GrossDumpSize > 0)
      { // nondegenerate
        DumpOver = false;
        DumpBuffer.SetFileNumber(1);
        DumpFileEverOpen = false;
        NextCharToDump = 0;
        SaveBuffer.SetFileNumber(2);
        Infrastructure.SetGlobalStatus(gsDumpInProgress);
      } // nondegenerate
  }  // tFileSystem::StartDump()



