/**
 * Copyright (c) 2011-2024 Bill Greiman
 * This file is part of the SdFat library for SD memory cards.
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#pragma once
/**
 * \file
 * \brief ExFatPartition include file.
 */
#include "../common/FsBlockDevice.h"
#include "../common/FsCache.h"
#include "../common/FsStructs.h"
#include "../common/SysCall.h"
/** Set EXFAT_READ_ONLY non-zero for read only */
#ifndef EXFAT_READ_ONLY
#define EXFAT_READ_ONLY 0
#endif  // EXFAT_READ_ONLY
/** Type for exFAT partition */
const uint8_t FAT_TYPE_EXFAT = 64;

class ExFatFile;
//------------------------------------------------------------------------------
/**
 * \struct DirPos_t
 * \brief Internal type for position in directory file.
 */
struct DirPos_t {
  /** current cluster */
  uint32_t cluster;
  /** offset */
  uint32_t position;
  /** directory is contiguous */
  bool isContiguous;
};
//==============================================================================
/**
 * \class ExFatPartition
 * \brief Access exFat partitions on raw file devices.
 */
class ExFatPartition {
 public:
  ExFatPartition() = default;  // cppcheck-suppress uninitMemberVar
  /** \return the number of bytes in a cluster. */
  uint32_t bytesPerCluster() const { return m_bytesPerCluster; }
  /** \return the power of two for bytesPerCluster. */
  uint8_t bytesPerClusterShift() const {
    return m_bytesPerSectorShift + m_sectorsPerClusterShift;
  }
  /** \return the number of bytes in a sector. */
  uint16_t bytesPerSector() const { return m_bytesPerSector; }
  /** \return the power of two for bytesPerSector. */
  uint8_t bytesPerSectorShift() const { return m_bytesPerSectorShift; }

  /** Clear the cache and returns a pointer to the cache.  Not for normal apps.
   * \return A pointer to the cache buffer or zero if an error occurs.
   */
  uint8_t* cacheClear() { return m_dataCache.clear(); }
  /** \return the cluster count for the partition. */
  uint32_t clusterCount() const { return m_clusterCount; }
  /** \return the cluster heap start sector. */
  uint32_t clusterHeapStartSector() const { return m_clusterHeapStartSector; }
  /** End access to volume
   * \return pointer to sector size buffer for format.
   */
  uint8_t* end() {
    m_fatType = 0;
    return cacheClear();
  }
  /** \return the FAT length in sectors */
  uint32_t fatLength() const { return m_fatLength; }
  /** \return the FAT start sector number. */
  uint32_t fatStartSector() const { return m_fatStartSector; }
  /** \return Type FAT_TYPE_EXFAT for exFAT partition or zero for error. */
  uint8_t fatType() const { return m_fatType; }
  /** \return free cluster count or -1 if an error occurs. */
  int32_t freeClusterCount();
  /** Initialize a exFAT partition.
   * \param[in] dev The blockDevice for the partition.
   * \param[in] part The partition to be used.  Legal values for \a part are
   * 1-4 to use the corresponding partition on a device formatted with
   * a MBR, Master Boot Record, or zero if the device is formatted as
   * a super floppy with the FAT boot sector in sector volStart.
   * \param[in] volStart location of volume if part is zero.
   *
   * \return true for success or false for failure.
   */
  bool init(FsBlockDevice* dev, uint8_t part, uint32_t volStart = 0);
  /**
   * Check for device busy.
   *
   * \return true if busy else false.
   */
  bool isBusy() { return m_blockDev->isBusy(); }
  /** \return the root directory start cluster number. */
  uint32_t rootDirectoryCluster() const { return m_rootDirectoryCluster; }
  /** \return the root directory length. */
  uint32_t rootLength();
  /** \return the number of sectors in a cluster. */
  uint32_t sectorsPerCluster() const { return 1UL << m_sectorsPerClusterShift; }
#ifndef DOXYGEN_SHOULD_SKIP_THIS
  uint32_t __attribute__((error("use sectorsPerCluster()"))) blocksPerCluster();
#endif  // DOXYGEN_SHOULD_SKIP_THIS
  /** \return the power of two for sectors per cluster. */
  uint8_t sectorsPerClusterShift() const { return m_sectorsPerClusterShift; }
  //----------------------------------------------------------------------------
#ifndef DOXYGEN_SHOULD_SKIP_THIS
  void checkUpcase(print_t* pr);
  bool printDir(print_t* pr, ExFatFile* file);
  void dmpBitmap(print_t* pr);
  void dmpCluster(print_t* pr, uint32_t cluster, uint32_t offset,
                  uint32_t count);
  void dmpFat(print_t* pr, uint32_t start, uint32_t count);
  void dmpSector(print_t* pr, uint32_t sector);
  bool printVolInfo(print_t* pr);
  void printFat(print_t* pr);
  void printUpcase(print_t* pr);
#endif  // DOXYGEN_SHOULD_SKIP_THIS
  //----------------------------------------------------------------------------
 private:
  /** ExFatFile allowed access to private members. */
  friend class ExFatFile;
  uint32_t bitmapFind(uint32_t cluster, uint32_t count);
  bool bitmapModify(uint32_t cluster, uint32_t count, bool value);
  //----------------------------------------------------------------------------
  // Cache functions.
  uint8_t* bitmapCachePrepare(uint32_t sector, uint8_t option) {
#if USE_EXFAT_BITMAP_CACHE
    return m_bitmapCache.prepare(sector, option);
#else   // USE_EXFAT_BITMAP_CACHE
    return m_dataCache.prepare(sector, option);
#endif  // USE_EXFAT_BITMAP_CACHE
  }
  void cacheInit(FsBlockDevice* dev) {
#if USE_EXFAT_BITMAP_CACHE
    m_bitmapCache.init(dev);
#endif  // USE_EXFAT_BITMAP_CACHE
    m_dataCache.init(dev);
  }
  bool cacheSync() {
#if USE_EXFAT_BITMAP_CACHE
    return m_bitmapCache.sync() && m_dataCache.sync() && syncDevice();
#else   // USE_EXFAT_BITMAP_CACHE
    return m_dataCache.sync() && syncDevice();
#endif  // USE_EXFAT_BITMAP_CACHE
  }
  void dataCacheDirty() { m_dataCache.dirty(); }
  void dataCacheInvalidate() { m_dataCache.invalidate(); }
  uint8_t* dataCachePrepare(uint32_t sector, uint8_t option) {
    return m_dataCache.prepare(sector, option);
  }
  uint32_t dataCacheSector() { return m_dataCache.sector(); }
  bool dataCacheSync() { return m_dataCache.sync(); }
  //----------------------------------------------------------------------------
  uint32_t clusterMask() const { return m_clusterMask; }
  uint32_t clusterStartSector(uint32_t cluster) {
    return m_clusterHeapStartSector +
           ((cluster - 2) << m_sectorsPerClusterShift);
  }
  uint8_t* dirCache(const DirPos_t* pos, uint8_t options);
  int8_t dirSeek(DirPos_t* pos, uint32_t offset);
  int8_t fatGet(uint32_t cluster, uint32_t* value);
  bool fatPut(uint32_t cluster, uint32_t value);
  uint32_t chainSize(uint32_t cluster);
  bool freeChain(uint32_t cluster);
  uint16_t sectorMask() const { return m_sectorMask; }
  bool syncDevice() { return m_blockDev->syncDevice(); }
  bool cacheSafeRead(uint32_t sector, uint8_t* dst) {
    return m_dataCache.cacheSafeRead(sector, dst);
  }
  bool cacheSafeWrite(uint32_t sector, const uint8_t* src) {
    return m_dataCache.cacheSafeWrite(sector, src);
  }
  bool cacheSafeRead(uint32_t sector, uint8_t* dst, size_t count) {
    return m_dataCache.cacheSafeRead(sector, dst, count);
  }
  bool cacheSafeWrite(uint32_t sector, const uint8_t* src, size_t count) {
    return m_dataCache.cacheSafeWrite(sector, src, count);
  }
  bool readSector(uint32_t sector, uint8_t* dst) {
    return m_blockDev->readSector(sector, dst);
  }
  bool writeSector(uint32_t sector, const uint8_t* src) {
    return m_blockDev->writeSector(sector, src);
  }
  //----------------------------------------------------------------------------
  static const uint8_t m_bytesPerSectorShift = 9;
  static const uint16_t m_bytesPerSector = 1 << m_bytesPerSectorShift;
  static const uint16_t m_sectorMask = m_bytesPerSector - 1;
  //----------------------------------------------------------------------------
#if USE_EXFAT_BITMAP_CACHE
  FsCache m_bitmapCache;
#endif  // USE_EXFAT_BITMAP_CACHE
  FsCache m_dataCache;
  uint32_t m_bitmapStart;
  uint32_t m_fatStartSector;
  uint32_t m_fatLength;
  uint32_t m_clusterHeapStartSector;
  uint32_t m_clusterCount;
  uint32_t m_rootDirectoryCluster;
  uint32_t m_clusterMask;
  uint32_t m_bytesPerCluster;
  FsBlockDevice* m_blockDev;
  uint8_t m_fatType = 0;
  uint8_t m_sectorsPerClusterShift;
};
