//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//                            FAT16/32 File IO Library
//                                    V2.6
//                              Ultra-Embedded.com
//                            Copyright 2003 - 2012
//
//                         Email: admin@ultra-embedded.com
//
//                                License: GPL
//   If you would like a version with a more permissive license for use in
//   closed source commercial applications please contact me for details.
//-----------------------------------------------------------------------------
//
// This file is part of FAT File IO Library.
//
// FAT File IO Library 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 2 of the License, or
// (at your option) any later version.
//
// FAT File IO Library 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 FAT File IO Library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#include <string.h>
#include "fat_defs.h"
#include "fat_access.h"
#include "fat_table.h"

#ifndef FAT_BUFFERS
    #define FAT_BUFFERS 1
#endif

#ifndef FAT_BUFFER_SECTORS
    #define FAT_BUFFER_SECTORS 1
#endif

#if FAT_BUFFERS < 1 || FAT_BUFFER_SECTORS < 1
    #error "FAT_BUFFERS & FAT_BUFFER_SECTORS must be at least 1"
#endif

//-----------------------------------------------------------------------------
//                            FAT Sector Buffer
//-----------------------------------------------------------------------------
#define FAT32_GET_32BIT_WORD(pbuf, location)        ( GET_32BIT_WORD(pbuf->ptr, location) )
#define FAT32_SET_32BIT_WORD(pbuf, location, value) { SET_32BIT_WORD(pbuf->ptr, location, value); pbuf->dirty = 1; }
#define FAT16_GET_16BIT_WORD(pbuf, location)        ( GET_16BIT_WORD(pbuf->ptr, location) )
#define FAT16_SET_16BIT_WORD(pbuf, location, value) { SET_16BIT_WORD(pbuf->ptr, location, value); pbuf->dirty = 1; }

//-----------------------------------------------------------------------------
// fatfs_fat_init:
//-----------------------------------------------------------------------------
void fatfs_fat_init(struct fatfs *fs)
{
    int i;

    // FAT buffer chain head
    fs->fat_buffer_head = NULL;

    for (i=0;i<FAT_BUFFERS;i++)
    {
        // Initialise buffers to invalid
        fs->fat_buffers[i].address = FAT32_INVALID_CLUSTER;
        fs->fat_buffers[i].dirty = 0;
        memset(fs->fat_buffers[i].sector, 0x00, sizeof(fs->fat_buffers[i].sector));
        fs->fat_buffers[i].ptr = NULL;

        // Add to head of queue
        fs->fat_buffers[i].next = fs->fat_buffer_head;
        fs->fat_buffer_head = &fs->fat_buffers[i];
    }
}
//-----------------------------------------------------------------------------
// fatfs_fat_writeback: Writeback 'dirty' FAT sectors to disk
//-----------------------------------------------------------------------------
static int fatfs_fat_writeback(struct fatfs *fs, struct fat_buffer *pcur)
{
    if (pcur)
    {
        // Writeback sector if changed
        if (pcur->dirty)
        {
            if (fs->disk_io.write_media)
            {
                uint32 sectors = FAT_BUFFER_SECTORS;
                uint32 offset = pcur->address - fs->fat_begin_lba;

                // Limit to sectors used for the FAT
                if ((offset + FAT_BUFFER_SECTORS) <= fs->fat_sectors)
                    sectors = FAT_BUFFER_SECTORS;
                else
                    sectors = fs->fat_sectors - offset;

                if (!fs->disk_io.write_media(pcur->address, pcur->sector, sectors))
                    return 0;
            }

            pcur->dirty = 0;
        }

        return 1;
    }
    else
        return 0;
}
//-----------------------------------------------------------------------------
// fatfs_fat_read_sector: Read a FAT sector
//-----------------------------------------------------------------------------
static struct fat_buffer *fatfs_fat_read_sector(struct fatfs *fs, uint32 sector)
{
    struct fat_buffer *last = NULL;
    struct fat_buffer *pcur = fs->fat_buffer_head;

    // Itterate through sector buffer list
    while (pcur)
    {
        // Sector within this buffer?
        if ((sector >= pcur->address) && (sector < (pcur->address + FAT_BUFFER_SECTORS)))
            break;

        // End of list?
        if (pcur->next == NULL)
        {
            // Remove buffer from list
            if (last)
                last->next = NULL;
            // We the first and last buffer in the chain?
            else
                fs->fat_buffer_head = NULL;
        }

        last = pcur;
        pcur = pcur->next;
    }

    // We found the sector already in FAT buffer chain
    if (pcur)
    {
        pcur->ptr = (uint8 *)(pcur->sector + ((sector - pcur->address) * FAT_SECTOR_SIZE));
        return pcur;
    }

    // Else, we removed the last item from the list
    pcur = last;

    // Add to start of sector buffer list (now newest sector)
    pcur->next = fs->fat_buffer_head;
    fs->fat_buffer_head = pcur;

    // Writeback sector if changed
    if (pcur->dirty)
        if (!fatfs_fat_writeback(fs, pcur))
            return 0;

    // Address is now new sector
    pcur->address = sector;

    // Read next sector
    if (!fs->disk_io.read_media(pcur->address, pcur->sector, FAT_BUFFER_SECTORS))
    {
        // Read failed, invalidate buffer address
        pcur->address = FAT32_INVALID_CLUSTER;
        return NULL;
    }

    pcur->ptr = pcur->sector;
    return pcur;
}
//-----------------------------------------------------------------------------
// fatfs_fat_purge: Purge 'dirty' FAT sectors to disk
//-----------------------------------------------------------------------------
int fatfs_fat_purge(struct fatfs *fs)
{
    struct fat_buffer *pcur = fs->fat_buffer_head;

    // Itterate through sector buffer list
    while (pcur)
    {
        // Writeback sector if changed
        if (pcur->dirty)
            if (!fatfs_fat_writeback(fs, pcur))
                return 0;

        pcur = pcur->next;
    }

    return 1;
}

//-----------------------------------------------------------------------------
//                        General FAT Table Operations
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// fatfs_find_next_cluster: Return cluster number of next cluster in chain by
// reading FAT table and traversing it. Return 0xffffffff for end of chain.
//-----------------------------------------------------------------------------
uint32 fatfs_find_next_cluster(struct fatfs *fs, uint32 current_cluster)
{
    uint32 fat_sector_offset, position;
    uint32 nextcluster;
    struct fat_buffer *pbuf;

    // Why is '..' labelled with cluster 0 when it should be 2 ??
    if (current_cluster == 0)
        current_cluster = 2;

    // Find which sector of FAT table to read
    if (fs->fat_type == FAT_TYPE_16)
        fat_sector_offset = current_cluster / 256;
    else
        fat_sector_offset = current_cluster / 128;

    // Read FAT sector into buffer
    pbuf = fatfs_fat_read_sector(fs, fs->fat_begin_lba+fat_sector_offset);
    if (!pbuf)
        return (FAT32_LAST_CLUSTER);

    if (fs->fat_type == FAT_TYPE_16)
    {
        // Find 32 bit entry of current sector relating to cluster number
        position = (current_cluster - (fat_sector_offset * 256)) * 2;

        // Read Next Clusters value from Sector Buffer
        nextcluster = FAT16_GET_16BIT_WORD(pbuf, (uint16)position);

        // If end of chain found
        if (nextcluster >= 0xFFF8 && nextcluster <= 0xFFFF)
            return (FAT32_LAST_CLUSTER);
    }
    else
    {
        // Find 32 bit entry of current sector relating to cluster number
        position = (current_cluster - (fat_sector_offset * 128)) * 4;

        // Read Next Clusters value from Sector Buffer
        nextcluster = FAT32_GET_32BIT_WORD(pbuf, (uint16)position);

        // Mask out MS 4 bits (its 28bit addressing)
        nextcluster = nextcluster & 0x0FFFFFFF;

        // If end of chain found
        if (nextcluster >= 0x0FFFFFF8 && nextcluster <= 0x0FFFFFFF)
            return (FAT32_LAST_CLUSTER);
    }

    // Else return next cluster
    return (nextcluster);
}
//-----------------------------------------------------------------------------
// fatfs_set_fs_info_next_free_cluster: Write the next free cluster to the FSINFO table
//-----------------------------------------------------------------------------
void fatfs_set_fs_info_next_free_cluster(struct fatfs *fs, uint32 newValue)
{
    if (fs->fat_type == FAT_TYPE_16)
        ;
    else
    {
        // Load sector to change it
        struct fat_buffer *pbuf = fatfs_fat_read_sector(fs, fs->lba_begin+fs->fs_info_sector);
        if (!pbuf)
            return ;

        // Change
        FAT32_SET_32BIT_WORD(pbuf, 492, newValue);
        fs->next_free_cluster = newValue;

        // Write back FSINFO sector to disk
        if (fs->disk_io.write_media)
            fs->disk_io.write_media(pbuf->address, pbuf->sector, 1);

        // Invalidate cache entry
        pbuf->address = FAT32_INVALID_CLUSTER;
        pbuf->dirty = 0;
    }
}
//-----------------------------------------------------------------------------
// fatfs_find_blank_cluster: Find a free cluster entry by reading the FAT
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fatfs_find_blank_cluster(struct fatfs *fs, uint32 start_cluster, uint32 *free_cluster)
{
    uint32 fat_sector_offset, position;
    uint32 nextcluster;
    uint32 current_cluster = start_cluster;
    struct fat_buffer *pbuf;

    do
    {
        // Find which sector of FAT table to read
        if (fs->fat_type == FAT_TYPE_16)
            fat_sector_offset = current_cluster / 256;
        else
            fat_sector_offset = current_cluster / 128;

        if ( fat_sector_offset < fs->fat_sectors)
        {
            // Read FAT sector into buffer
            pbuf = fatfs_fat_read_sector(fs, fs->fat_begin_lba+fat_sector_offset);
            if (!pbuf)
                return 0;

            if (fs->fat_type == FAT_TYPE_16)
            {
                // Find 32 bit entry of current sector relating to cluster number
                position = (current_cluster - (fat_sector_offset * 256)) * 2;

                // Read Next Clusters value from Sector Buffer
                nextcluster = FAT16_GET_16BIT_WORD(pbuf, (uint16)position);
            }
            else
            {
                // Find 32 bit entry of current sector relating to cluster number
                position = (current_cluster - (fat_sector_offset * 128)) * 4;

                // Read Next Clusters value from Sector Buffer
                nextcluster = FAT32_GET_32BIT_WORD(pbuf, (uint16)position);

                // Mask out MS 4 bits (its 28bit addressing)
                nextcluster = nextcluster & 0x0FFFFFFF;
            }

            if (nextcluster !=0 )
                current_cluster++;
        }
        else
            // Otherwise, run out of FAT sectors to check...
            return 0;
    }
    while (nextcluster != 0x0);

    // Found blank entry
    *free_cluster = current_cluster;
    return 1;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_fat_set_cluster: Set a cluster link in the chain. NOTE: Immediate
// write (slow).
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fatfs_fat_set_cluster(struct fatfs *fs, uint32 cluster, uint32 next_cluster)
{
    struct fat_buffer *pbuf;
    uint32 fat_sector_offset, position;

    // Find which sector of FAT table to read
    if (fs->fat_type == FAT_TYPE_16)
        fat_sector_offset = cluster / 256;
    else
        fat_sector_offset = cluster / 128;

    // Read FAT sector into buffer
    pbuf = fatfs_fat_read_sector(fs, fs->fat_begin_lba+fat_sector_offset);
    if (!pbuf)
        return 0;

    if (fs->fat_type == FAT_TYPE_16)
    {
        // Find 16 bit entry of current sector relating to cluster number
        position = (cluster - (fat_sector_offset * 256)) * 2;

        // Write Next Clusters value to Sector Buffer
        FAT16_SET_16BIT_WORD(pbuf, (uint16)position, ((uint16)next_cluster));
    }
    else
    {
        // Find 32 bit entry of current sector relating to cluster number
        position = (cluster - (fat_sector_offset * 128)) * 4;

        // Write Next Clusters value to Sector Buffer
        FAT32_SET_32BIT_WORD(pbuf, (uint16)position, next_cluster);
    }

    return 1;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_free_cluster_chain: Follow a chain marking each element as free
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fatfs_free_cluster_chain(struct fatfs *fs, uint32 start_cluster)
{
    uint32 last_cluster;
    uint32 next_cluster = start_cluster;

    // Loop until end of chain
    while ( (next_cluster != FAT32_LAST_CLUSTER) && (next_cluster != 0x00000000) )
    {
        last_cluster = next_cluster;

        // Find next link
        next_cluster = fatfs_find_next_cluster(fs, next_cluster);

        // Clear last link
        fatfs_fat_set_cluster(fs, last_cluster, 0x00000000);
    }

    return 1;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_fat_add_cluster_to_chain: Follow a chain marking and then add a new entry
// to the current tail.
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fatfs_fat_add_cluster_to_chain(struct fatfs *fs, uint32 start_cluster, uint32 newEntry)
{
    uint32 last_cluster = FAT32_LAST_CLUSTER;
    uint32 next_cluster = start_cluster;

    if (start_cluster == FAT32_LAST_CLUSTER)
        return 0;

    // Loop until end of chain
    while ( next_cluster != FAT32_LAST_CLUSTER )
    {
        last_cluster = next_cluster;

        // Find next link
        next_cluster = fatfs_find_next_cluster(fs, next_cluster);
        if (!next_cluster)
            return 0;
    }

    // Add link in for new cluster
    fatfs_fat_set_cluster(fs, last_cluster, newEntry);

    // Mark new cluster as end of chain
    fatfs_fat_set_cluster(fs, newEntry, FAT32_LAST_CLUSTER);

    return 1;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_count_free_clusters:
//-----------------------------------------------------------------------------
uint32 fatfs_count_free_clusters(struct fatfs *fs)
{
    uint32 i,j;
    uint32 count = 0;
    struct fat_buffer *pbuf;

    for (i = 0; i < fs->fat_sectors; i++)
    {
        // Read FAT sector into buffer
        pbuf = fatfs_fat_read_sector(fs, fs->fat_begin_lba + i);
        if (!pbuf)
            break;

        for (j = 0; j < FAT_SECTOR_SIZE; )
        {
            if (fs->fat_type == FAT_TYPE_16)
            {
                if (FAT16_GET_16BIT_WORD(pbuf, (uint16)j) == 0)
                    count++;

                j += 2;
            }
            else
            {
                if (FAT32_GET_32BIT_WORD(pbuf, (uint16)j) == 0)
                    count++;

                j += 4;
            }
        }
    }

    return count;
}