/**
 * Marlin 3D Printer Firmware
 * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
 *
 * Based on Sprinter and grbl.
 * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */
#pragma once

#include "../../../../libs/W25Qxx.h"

#define HAS_SPI_FLASH_COMPRESSION 1

/**
 * This class manages and optimizes SPI Flash data storage,
 * keeping an internal buffer to write and save full SPI flash
 * pages as needed.
 *
 * Since the data is always in the buffer, the class is also
 * able to support fast on-the-fly RLE compression/decompression.
 *
 * In testing with the current LVGL_UI it compacts 2.9MB of icons
 * (which have lots of runs) down to 370kB!!! As a result the UI
 * refresh rate becomes faster and now all LVGL UI can fit into a
 * tiny 2MB SPI Flash, such as the Chitu Board.
 *
 * == Usage ==
 *
 * Writing:
 *
 * The class keeps an internal buffer that caches data until it
 * fits into a full SPI Flash page. Each time the buffer fills up
 * the page is saved to SPI Flash. Sequential writes are optimal.
 *
 *    SPIFlashStorage.beginWrite(myStartAddress);
 *    while (there is data to write)
 *      SPIFlashStorage.addData(myBuffer, bufferSize);
 *    SPIFlashStorage.endWrite(); // Flush remaining buffer data
 *
 * Reading:
 *
 * When reading, it loads a full page from SPI Flash at once and
 * keeps it in a private SRAM buffer. Data is loaded as needed to
 * fullfill requests. Sequential reads are optimal.
 *
 *    SPIFlashStorage.beginRead(myStartAddress);
 *    while (there is data to read)
 *      SPIFlashStorage.readData(myBuffer, bufferSize);
 *
 * Compression:
 *
 * The biggest advantage of this class is the RLE compression.
 * With compression activated a second buffer holds the compressed
 * data, so when writing data, as this buffer becomes full it is
 * flushed to SPI Flash.
 *
 * The same goes for reading: A compressed page is read from SPI
 * flash, and the data is uncompressed as needed to provide the
 * requested amount of data.
 */
class SPIFlashStorage {
public:
  // Write operation
  static void beginWrite(uint32_t startAddress);
  static void endWrite();
  static void writeData(uint8_t* data, uint16_t size);

  // Read operation
  static void beginRead(uint32_t startAddress);
  static void readData(uint8_t* data, uint16_t size);

  static uint32_t getCurrentPage() { return m_currentPage; }

private:
  static void flushPage();
  static void savePage(uint8_t* buffer);
  static void loadPage(uint8_t* buffer);
  static void readPage();
  static uint16_t inData(uint8_t* data, uint16_t size);
  static uint16_t outData(uint8_t* data, uint16_t size);

  static uint8_t m_pageData[SPI_FLASH_PageSize];
  static uint32_t m_currentPage;
  static uint16_t m_pageDataUsed;
  static inline uint16_t pageDataFree() { return SPI_FLASH_PageSize - m_pageDataUsed; }
  static uint32_t m_startAddress;
  #if HAS_SPI_FLASH_COMPRESSION
    static uint8_t m_compressedData[SPI_FLASH_PageSize];
    static uint16_t m_compressedDataUsed;
    static inline uint16_t compressedDataFree() { return SPI_FLASH_PageSize - m_compressedDataUsed; }
  #endif
};

extern SPIFlashStorage SPIFlash;
