/**
 * Copyright (c) 2011-2018 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.
 */
#ifndef FatVolume_h
#define FatVolume_h
/**
 * \file
 * \brief FatVolume class
 */
#include "../BlockDriver.h"
#include "FatLibConfig.h"
#include "FatStructs.h"
#include <stddef.h>
//------------------------------------------------------------------------------
#ifndef DOXYGEN_SHOULD_SKIP_THIS
/** Macro for debug. */
#define DEBUG_MODE 0
#if DEBUG_MODE
#define DBG_FAIL_MACRO                                                                                                 \
    Serial.print(F(__FILE__));                                                                                         \
    Serial.println(__LINE__);
#define DBG_PRINT_IF(b)                                                                                                \
    if (b)                                                                                                             \
    {                                                                                                                  \
        Serial.println(F(#b));                                                                                         \
        DBG_FAIL_MACRO;                                                                                                \
    }
#define DBG_HALT_IF(b)                                                                                                 \
    if (b)                                                                                                             \
    {                                                                                                                  \
        Serial.println(F(#b));                                                                                         \
        DBG_FAIL_MACRO;                                                                                                \
        while (1)                                                                                                      \
            ;                                                                                                          \
    }
#else // DEBUG_MODE
#define DBG_FAIL_MACRO
#define DBG_PRINT_IF(b)
#define DBG_HALT_IF(b)
#endif // DEBUG_MODE
#endif // DOXYGEN_SHOULD_SKIP_THIS
//------------------------------------------------------------------------------
#if ENABLE_ARDUINO_FEATURES
/** Use Print for Arduino */
typedef Print print_t;
#else  // ENABLE_ARDUINO_FEATURES
/**
 * \class CharWriter
 * \brief Character output - often serial port.
 */
class CharWriter
{
  public:
    virtual size_t write(char c) = 0;
    virtual size_t write(const char *s) = 0;
};
typedef CharWriter print_t;
#endif // ENABLE_ARDUINO_FEATURES
//------------------------------------------------------------------------------
// Forward declaration of FatVolume.
class FatVolume;
//------------------------------------------------------------------------------
/**
 * \brief Cache for an raw data block.
 */
union cache_t {
    /** Used to access cached file data blocks. */
    uint8_t data[512];
    /** Used to access cached FAT16 entries. */
    uint16_t fat16[256];
    /** Used to access cached FAT32 entries. */
    uint32_t fat32[128];
    /** Used to access cached directory entries. */
    dir_t dir[16];
    /** Used to access a cached Master Boot Record. */
    mbr_t mbr;
    /** Used to access to a cached FAT boot sector. */
    fat_boot_t fbs;
    /** Used to access to a cached FAT32 boot sector. */
    fat32_boot_t fbs32;
    /** Used to access to a cached FAT32 FSINFO sector. */
    fat32_fsinfo_t fsinfo;
};
//==============================================================================
/**
 * \class FatCache
 * \brief Block cache.
 */
class FatCache
{
  public:
    /** Cached block is dirty */
    static const uint8_t CACHE_STATUS_DIRTY = 1;
    /** Cashed block is FAT entry and must be mirrored in second FAT. */
    static const uint8_t CACHE_STATUS_MIRROR_FAT = 2;
    /** Cache block status bits */
    static const uint8_t CACHE_STATUS_MASK = CACHE_STATUS_DIRTY | CACHE_STATUS_MIRROR_FAT;
    /** Sync existing block but do not read new block. */
    static const uint8_t CACHE_OPTION_NO_READ = 4;
    /** Cache block for read. */
    static const uint8_t CACHE_FOR_READ = 0;
    /** Cache block for write. */
    static const uint8_t CACHE_FOR_WRITE = CACHE_STATUS_DIRTY;
    /** Reserve cache block for write - do not read from block device. */
    static const uint8_t CACHE_RESERVE_FOR_WRITE = CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ;
    /** \return Cache block address. */
    cache_t *block()
    {
        return &m_block;
    }
    /** Set current block dirty. */
    void dirty()
    {
        m_status |= CACHE_STATUS_DIRTY;
    }
    /** Initialize the cache.
     * \param[in] vol FatVolume that owns this FatCache.
     */
    void init(FatVolume *vol)
    {
        m_vol = vol;
        invalidate();
    }
    /** Invalidate current cache block. */
    void invalidate()
    {
        m_status = 0;
        m_lbn = 0XFFFFFFFF;
    }
    /** \return dirty status */
    bool isDirty()
    {
        return m_status & CACHE_STATUS_DIRTY;
    }
    /** \return Logical block number for cached block. */
    uint32_t lbn()
    {
        return m_lbn;
    }
    /** Read a block into the cache.
     * \param[in] lbn Block to read.
     * \param[in] option mode for cached block.
     * \return Address of cached block. */
    cache_t *read(uint32_t lbn, uint8_t option);
    /** Write current block if dirty.
     * \return true for success else false.
     */
    bool sync();

  private:
    uint8_t m_status;
    FatVolume *m_vol;
    uint32_t m_lbn;
    cache_t m_block;
};
//==============================================================================
/**
 * \class FatVolume
 * \brief Access FAT16 and FAT32 volumes on raw file devices.
 */
class FatVolume
{
  public:
    /** Create an instance of FatVolume
     */
    FatVolume() : m_fatType(0)
    {
    }

    /** \return The volume's cluster size in blocks. */
    uint8_t blocksPerCluster() const
    {
        return m_blocksPerCluster;
    }
    /** \return The number of blocks in one FAT. */
    uint32_t blocksPerFat() const
    {
        return m_blocksPerFat;
    }
    /** 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.
     */
    cache_t *cacheClear()
    {
        if (!cacheSync())
        {
            return 0;
        }
        m_cache.invalidate();
        return m_cache.block();
    }
    /** \return The total number of clusters in the volume. */
    uint32_t clusterCount() const
    {
        return m_lastCluster - 1;
    }
    /** \return The shift count required to multiply by blocksPerCluster. */
    uint8_t clusterSizeShift() const
    {
        return m_clusterSizeShift;
    }
    /** \return The logical block number for the start of file data. */
    uint32_t dataStartBlock() const
    {
        return m_dataStartBlock;
    }
    /** \return The sector number for the start of file data. */
    uint32_t dataStartSector() const
    {
        return m_dataStartBlock;
    }
    /** \return The number of File Allocation Tables. */
    uint8_t fatCount()
    {
        return 2;
    }
    /** \return The logical block number for the start of the first FAT. */
    uint32_t fatStartBlock() const
    {
        return m_fatStartBlock;
    }
    /** \return The sector number for the start of the first FAT. */
    uint32_t fatStartSector() const
    {
        return m_fatStartBlock;
    }
    /** \return The FAT type of the volume. Values are 12, 16 or 32. */
    uint8_t fatType() const
    {
        return m_fatType;
    }
    /** Volume free space in clusters.
     *
     * \return Count of free clusters for success or -1 if an error occurs.
     */
    int32_t freeClusterCount();
    /** Initialize a FAT volume.  Try partition one first then try super
     * floppy format.
     *
     * \return The value true is returned for success and
     * the value false is returned for failure.
     */
    bool init()
    {
        return init(1) || init(0);
    }
    /** Initialize a FAT volume.

     * \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 block zero.
     *
     * \return The value true is returned for success and
     * the value false is returned for failure.
     */
    bool init(uint8_t part);
    /** \return The cluster number of last cluster in the volume. */
    uint32_t lastCluster() const
    {
        return m_lastCluster;
    }
    /** \return The number of entries in the root directory for FAT16 volumes. */
    uint16_t rootDirEntryCount() const
    {
        return m_rootDirEntryCount;
    }
    /** \return The logical block number for the start of the root directory
         on FAT16 volumes or the first cluster number on FAT32 volumes. */
    uint32_t rootDirStart() const
    {
        return m_rootDirStart;
    }
    /** \return The volume's cluster size in sectors. */
    uint8_t sectorsPerCluster() const
    {
        return m_blocksPerCluster;
    }
    /** \return The number of blocks in the volume */
    uint32_t volumeBlockCount() const
    {
        return blocksPerCluster() * clusterCount();
    }
    /** \return The number of sectors in the volume */
    uint32_t volumeSectorCount() const
    {
        return sectorsPerCluster() * clusterCount();
    }
    /** Wipe all data from the volume.
     * \param[in] pr print stream for status dots.
     * \return true for success else false.
     */
    bool wipe(print_t *pr = 0);
    /** Debug access to FAT table
     *
     * \param[in] n cluster number.
     * \param[out] v value of entry
     * \return -1 error, 0 EOC, else 1.
     */
    int8_t dbgFat(uint32_t n, uint32_t *v)
    {
        return fatGet(n, v);
    }
    //------------------------------------------------------------------------------
  private:
    // Allow FatFile and FatCache access to FatVolume private functions.
    friend class FatCache;        ///< Allow access to FatVolume.
    friend class FatFile;         ///< Allow access to FatVolume.
    friend class FatFileSystem;   ///< Allow access to FatVolume.
                                  //------------------------------------------------------------------------------
    BlockDriver *m_blockDev;      // block device
    uint8_t m_blocksPerCluster;   // Cluster size in blocks.
    uint8_t m_clusterBlockMask;   // Mask to extract block of cluster.
    uint8_t m_clusterSizeShift;   // Cluster count to block count shift.
    uint8_t m_fatType;            // Volume type (12, 16, OR 32).
    uint16_t m_rootDirEntryCount; // Number of entries in FAT16 root dir.
    uint32_t m_allocSearchStart;  // Start cluster for alloc search.
    uint32_t m_blocksPerFat;      // FAT size in blocks
    uint32_t m_dataStartBlock;    // First data block number.
    uint32_t m_fatStartBlock;     // Start block for first FAT.
    uint32_t m_lastCluster;       // Last cluster number in FAT.
    uint32_t m_rootDirStart;      // Start block for FAT16, cluster for FAT32.
                                  //------------------------------------------------------------------------------
    // block I/O functions.
    bool readBlock(uint32_t block, uint8_t *dst)
    {
        return m_blockDev->readBlock(block, dst);
    }
    bool syncBlocks()
    {
        return m_blockDev->syncBlocks();
    }
    bool writeBlock(uint32_t block, const uint8_t *src)
    {
        return m_blockDev->writeBlock(block, src);
    }
#if USE_MULTI_BLOCK_IO
    bool readBlocks(uint32_t block, uint8_t *dst, size_t nb)
    {
        return m_blockDev->readBlocks(block, dst, nb);
    }
    bool writeBlocks(uint32_t block, const uint8_t *src, size_t nb)
    {
        return m_blockDev->writeBlocks(block, src, nb);
    }
#endif // USE_MULTI_BLOCK_IO
#if MAINTAIN_FREE_CLUSTER_COUNT
    int32_t m_freeClusterCount; // Count of free clusters in volume.
    void setFreeClusterCount(int32_t value)
    {
        m_freeClusterCount = value;
    }
    void updateFreeClusterCount(int32_t change)
    {
        if (m_freeClusterCount >= 0)
        {
            m_freeClusterCount += change;
        }
    }
#else  // MAINTAIN_FREE_CLUSTER_COUNT
    void setFreeClusterCount(int32_t value)
    {
        (void)value;
    }
    void updateFreeClusterCount(int32_t change)
    {
        (void)change;
    }
#endif // MAINTAIN_FREE_CLUSTER_COUNT

    // block caches
    FatCache m_cache;
#if USE_SEPARATE_FAT_CACHE
    FatCache m_fatCache;
    cache_t *cacheFetchFat(uint32_t blockNumber, uint8_t options)
    {
        return m_fatCache.read(blockNumber, options | FatCache::CACHE_STATUS_MIRROR_FAT);
    }
    bool cacheSync()
    {
        return m_cache.sync() && m_fatCache.sync() && syncBlocks();
    }
#else  //
    cache_t *cacheFetchFat(uint32_t blockNumber, uint8_t options)
    {
        return cacheFetchData(blockNumber, options | FatCache::CACHE_STATUS_MIRROR_FAT);
    }
    bool cacheSync()
    {
        return m_cache.sync() && syncBlocks();
    }
#endif // USE_SEPARATE_FAT_CACHE
    cache_t *cacheFetchData(uint32_t blockNumber, uint8_t options)
    {
        return m_cache.read(blockNumber, options);
    }
    void cacheInvalidate()
    {
        m_cache.invalidate();
    }
    bool cacheSyncData()
    {
        return m_cache.sync();
    }
    cache_t *cacheAddress()
    {
        return m_cache.block();
    }
    uint32_t cacheBlockNumber()
    {
        return m_cache.lbn();
    }
    void cacheDirty()
    {
        m_cache.dirty();
    }
    //------------------------------------------------------------------------------
    bool allocateCluster(uint32_t current, uint32_t *next);
    bool allocContiguous(uint32_t count, uint32_t *firstCluster, uint32_t startCluster = 0);
    uint8_t blockOfCluster(uint32_t position) const
    {
        return (position >> 9) & m_clusterBlockMask;
    }
    uint32_t clusterFirstBlock(uint32_t cluster) const;
    int8_t fatGet(uint32_t cluster, uint32_t *value);
    bool fatPut(uint32_t cluster, uint32_t value);
    bool fatPutEOC(uint32_t cluster)
    {
        return fatPut(cluster, 0x0FFFFFFF);
    }
    bool freeChain(uint32_t cluster);
    bool isEOC(uint32_t cluster) const
    {
        return cluster > m_lastCluster;
    }
};
#endif // FatVolume
