//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//                            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 <stdlib.h>
#include <string.h>
#include "fat_misc.h"

//-----------------------------------------------------------------------------
// fatfs_lfn_cache_init: Clear long file name cache
//-----------------------------------------------------------------------------
void fatfs_lfn_cache_init(struct lfn_cache *lfn, int wipeTable)
{
    int i = 0;

    lfn->no_of_strings = 0;

#if FATFS_INC_LFN_SUPPORT

    // Zero out buffer also
    if (wipeTable)
        for (i=0;i<MAX_LONGFILENAME_ENTRIES;i++)
            memset(lfn->String[i], 0x00, MAX_LFN_ENTRY_LENGTH);
#endif
}
//-----------------------------------------------------------------------------
// fatfs_lfn_cache_entry - Function extracts long file name text from sector
// at a specific offset
//-----------------------------------------------------------------------------
#if FATFS_INC_LFN_SUPPORT
void fatfs_lfn_cache_entry(struct lfn_cache *lfn, uint8 *entryBuffer)
{
    uint8 LFNIndex, i;
    LFNIndex = entryBuffer[0] & 0x1F;

    // Limit file name to cache size!
    if (LFNIndex > MAX_LONGFILENAME_ENTRIES)
        return ;

    // This is an error condition
    if (LFNIndex == 0)
        return ;

    if (lfn->no_of_strings == 0)
        lfn->no_of_strings = LFNIndex;

    lfn->String[LFNIndex-1][0] = entryBuffer[1];
    lfn->String[LFNIndex-1][1] = entryBuffer[3];
    lfn->String[LFNIndex-1][2] = entryBuffer[5];
    lfn->String[LFNIndex-1][3] = entryBuffer[7];
    lfn->String[LFNIndex-1][4] = entryBuffer[9];
    lfn->String[LFNIndex-1][5] = entryBuffer[0x0E];
    lfn->String[LFNIndex-1][6] = entryBuffer[0x10];
    lfn->String[LFNIndex-1][7] = entryBuffer[0x12];
    lfn->String[LFNIndex-1][8] = entryBuffer[0x14];
    lfn->String[LFNIndex-1][9] = entryBuffer[0x16];
    lfn->String[LFNIndex-1][10] = entryBuffer[0x18];
    lfn->String[LFNIndex-1][11] = entryBuffer[0x1C];
    lfn->String[LFNIndex-1][12] = entryBuffer[0x1E];

    for (i=0; i<MAX_LFN_ENTRY_LENGTH; i++)
        if (lfn->String[LFNIndex-1][i]==0xFF)
            lfn->String[LFNIndex-1][i] = 0x20; // Replace with spaces
}
#endif
//-----------------------------------------------------------------------------
// fatfs_lfn_cache_get: Get a reference to the long filename
//-----------------------------------------------------------------------------
#if FATFS_INC_LFN_SUPPORT
char* fatfs_lfn_cache_get(struct lfn_cache *lfn)
{
    // Null terminate long filename
    if (lfn->no_of_strings == MAX_LONGFILENAME_ENTRIES)
        lfn->Null = '\0';
    else if (lfn->no_of_strings)
        lfn->String[lfn->no_of_strings][0] = '\0';
    else
        lfn->String[0][0] = '\0';

    return (char*)&lfn->String[0][0];
}
#endif
//-----------------------------------------------------------------------------
// fatfs_entry_lfn_text: If LFN text entry found
//-----------------------------------------------------------------------------
#if FATFS_INC_LFN_SUPPORT
int fatfs_entry_lfn_text(struct fat_dir_entry *entry)
{
    if ((entry->Attr & FILE_ATTR_LFN_TEXT) == FILE_ATTR_LFN_TEXT)
        return 1;
    else
        return 0;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_entry_lfn_invalid: If SFN found not relating to LFN
//-----------------------------------------------------------------------------
#if FATFS_INC_LFN_SUPPORT
int fatfs_entry_lfn_invalid(struct fat_dir_entry *entry)
{
    if ( (entry->Name[0]==FILE_HEADER_BLANK)  ||
         (entry->Name[0]==FILE_HEADER_DELETED)||
         (entry->Attr==FILE_ATTR_VOLUME_ID) ||
         (entry->Attr & FILE_ATTR_SYSHID) )
        return 1;
    else
        return 0;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_entry_lfn_exists: If LFN exists and correlation SFN found
//-----------------------------------------------------------------------------
#if FATFS_INC_LFN_SUPPORT
int fatfs_entry_lfn_exists(struct lfn_cache *lfn, struct fat_dir_entry *entry)
{
    if ( (entry->Attr!=FILE_ATTR_LFN_TEXT) &&
         (entry->Name[0]!=FILE_HEADER_BLANK) &&
         (entry->Name[0]!=FILE_HEADER_DELETED) &&
         (entry->Attr!=FILE_ATTR_VOLUME_ID) &&
         (!(entry->Attr&FILE_ATTR_SYSHID)) &&
         (lfn->no_of_strings) )
        return 1;
    else
        return 0;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_entry_sfn_only: If SFN only exists
//-----------------------------------------------------------------------------
int fatfs_entry_sfn_only(struct fat_dir_entry *entry)
{
    if ( (entry->Attr!=FILE_ATTR_LFN_TEXT) &&
         (entry->Name[0]!=FILE_HEADER_BLANK) &&
         (entry->Name[0]!=FILE_HEADER_DELETED) &&
         (entry->Attr!=FILE_ATTR_VOLUME_ID) &&
         (!(entry->Attr&FILE_ATTR_SYSHID)) )
        return 1;
    else
        return 0;
}
// TODO: FILE_ATTR_SYSHID ?!?!??!
//-----------------------------------------------------------------------------
// fatfs_entry_is_dir: Returns 1 if a directory
//-----------------------------------------------------------------------------
int fatfs_entry_is_dir(struct fat_dir_entry *entry)
{
    if (entry->Attr & FILE_TYPE_DIR)
        return 1;
    else
        return 0;
}
//-----------------------------------------------------------------------------
// fatfs_entry_is_file: Returns 1 is a file entry
//-----------------------------------------------------------------------------
int fatfs_entry_is_file(struct fat_dir_entry *entry)
{
    if (entry->Attr & FILE_TYPE_FILE)
        return 1;
    else
        return 0;
}
//-----------------------------------------------------------------------------
// fatfs_lfn_entries_required: Calculate number of 13 characters entries
//-----------------------------------------------------------------------------
#if FATFS_INC_LFN_SUPPORT
int fatfs_lfn_entries_required(char *filename)
{
    int length = (int)strlen(filename);

    if (length)
        return (length + MAX_LFN_ENTRY_LENGTH - 1) / MAX_LFN_ENTRY_LENGTH;
    else
        return 0;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_filename_to_lfn:
//-----------------------------------------------------------------------------
#if FATFS_INC_LFN_SUPPORT
void fatfs_filename_to_lfn(char *filename, uint8 *buffer, int entry, uint8 sfnChk)
{
    int i;
    int nameIndexes[MAX_LFN_ENTRY_LENGTH] = {1,3,5,7,9,0x0E,0x10,0x12,0x14,0x16,0x18,0x1C,0x1E};

    // 13 characters entries
    int length = (int)strlen(filename);
    int entriesRequired = fatfs_lfn_entries_required(filename);

    // Filename offset
    int start = entry * MAX_LFN_ENTRY_LENGTH;

    // Initialise to zeros
    memset(buffer, 0x00, FAT_DIR_ENTRY_SIZE);

    // LFN entry number
    buffer[0] = (uint8)(((entriesRequired-1)==entry)?(0x40|(entry+1)):(entry+1));

    // LFN flag
    buffer[11] = 0x0F;

    // Checksum of short filename
    buffer[13] = sfnChk;

    // Copy to buffer
    for (i=0;i<MAX_LFN_ENTRY_LENGTH;i++)
    {
        if ( (start+i) < length )
            buffer[nameIndexes[i]] = filename[start+i];
        else if ( (start+i) == length )
            buffer[nameIndexes[i]] = 0x00;
        else
        {
            buffer[nameIndexes[i]] = 0xFF;
            buffer[nameIndexes[i]+1] = 0xFF;
        }
    }
}
#endif
//-----------------------------------------------------------------------------
// fatfs_sfn_create_entry: Create the short filename directory entry
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
void fatfs_sfn_create_entry(char *shortfilename, uint32 size, uint32 startCluster, struct fat_dir_entry *entry, int dir)
{
    int i;

    // Copy short filename
    for (i=0;i<FAT_SFN_SIZE_FULL;i++)
        entry->Name[i] = shortfilename[i];

    // Unless we have a RTC we might as well set these to 1980
    entry->CrtTimeTenth = 0x00;
    entry->CrtTime[1] = entry->CrtTime[0] = 0x00;
    entry->CrtDate[1] = 0x00;
    entry->CrtDate[0] = 0x20;
    entry->LstAccDate[1] = 0x00;
    entry->LstAccDate[0] = 0x20;
    entry->WrtTime[1] = entry->WrtTime[0] = 0x00;
    entry->WrtDate[1] = 0x00;
    entry->WrtDate[0] = 0x20;

    if (!dir)
        entry->Attr = FILE_TYPE_FILE;
    else
        entry->Attr = FILE_TYPE_DIR;

    entry->NTRes = 0x00;

    entry->FstClusHI = FAT_HTONS((uint16)((startCluster>>16) & 0xFFFF));
    entry->FstClusLO = FAT_HTONS((uint16)((startCluster>>0) & 0xFFFF));
    entry->FileSize = FAT_HTONL(size);
}
#endif
//-----------------------------------------------------------------------------
// fatfs_lfn_create_sfn: Create a padded SFN
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fatfs_lfn_create_sfn(char *sfn_output, char *filename)
{
    int i;
    int dotPos = -1;
    char ext[3];
    int pos;
    int len = (int)strlen(filename);

    // Invalid to start with .
    if (filename[0]=='.')
        return 0;

    memset(sfn_output, ' ', FAT_SFN_SIZE_FULL);
    memset(ext, ' ', 3);

    // Find dot seperator
    for (i = 0; i< len; i++)
    {
        if (filename[i]=='.')
            dotPos = i;
    }

    // Extract extensions
    if (dotPos!=-1)
    {
        // Copy first three chars of extension
        for (i = (dotPos+1); i < (dotPos+1+3); i++)
            if (i<len)
                ext[i-(dotPos+1)] = filename[i];

        // Shorten the length to the dot position
        len = dotPos;
    }

    // Add filename part
    pos = 0;
    for (i=0;i<len;i++)
    {
        if ( (filename[i]!=' ') && (filename[i]!='.') )
        {
            if (filename[i] >= 'a' && filename[i] <= 'z')
                sfn_output[pos++] = filename[i] - 'a' + 'A';
            else
                sfn_output[pos++] = filename[i];
        }

        // Fill upto 8 characters
        if (pos==FAT_SFN_SIZE_PARTIAL)
            break;
    }

    // Add extension part
    for (i=FAT_SFN_SIZE_PARTIAL;i<FAT_SFN_SIZE_FULL;i++)
    {
        if (ext[i-FAT_SFN_SIZE_PARTIAL] >= 'a' && ext[i-FAT_SFN_SIZE_PARTIAL] <= 'z')
            sfn_output[i] = ext[i-FAT_SFN_SIZE_PARTIAL] - 'a' + 'A';
        else
            sfn_output[i] = ext[i-FAT_SFN_SIZE_PARTIAL];
    }

    return 1;
}
//-----------------------------------------------------------------------------
// fatfs_itoa:
//-----------------------------------------------------------------------------
static void fatfs_itoa(uint32 num, char *s)
{
    char* cp;
    char outbuf[12];
    const char digits[] = "0123456789ABCDEF";

    // Build string backwards
    cp = outbuf;
    do
    {
        *cp++ = digits[(int)(num % 10)];
    }
    while ((num /= 10) > 0);

    *cp-- = 0;

    // Copy in forwards
    while (cp >= outbuf)
        *s++ = *cp--;

    *s = 0;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_lfn_generate_tail:
// sfn_input = Input short filename, spaced format & in upper case
// sfn_output = Output short filename with tail
//-----------------------------------------------------------------------------
#if FATFS_INC_LFN_SUPPORT
#if FATFS_INC_WRITE_SUPPORT
int fatfs_lfn_generate_tail(char *sfn_output, char *sfn_input, uint32 tailNum)
{
    int tail_chars;
    char tail_str[12];

    if (tailNum > 99999)
        return 0;

    // Convert to number
    memset(tail_str, 0x00, sizeof(tail_str));
    tail_str[0] = '~';
    fatfs_itoa(tailNum, tail_str+1);

    // Copy in base filename
    memcpy(sfn_output, sfn_input, FAT_SFN_SIZE_FULL);

    // Overwrite with tail
    tail_chars = (int)strlen(tail_str);
    memcpy(sfn_output+(FAT_SFN_SIZE_PARTIAL-tail_chars), tail_str, tail_chars);

    return 1;
}
#endif
#endif
//-----------------------------------------------------------------------------
// fatfs_convert_from_fat_time: Convert FAT time to h/m/s
//-----------------------------------------------------------------------------
#if FATFS_INC_TIME_DATE_SUPPORT
void fatfs_convert_from_fat_time(uint16 fat_time, int *hours, int *minutes, int *seconds)
{
    *hours = (fat_time >> FAT_TIME_HOURS_SHIFT) & FAT_TIME_HOURS_MASK;
    *minutes = (fat_time >> FAT_TIME_MINUTES_SHIFT) & FAT_TIME_MINUTES_MASK;
    *seconds = (fat_time >> FAT_TIME_SECONDS_SHIFT) & FAT_TIME_SECONDS_MASK;
    *seconds = *seconds * FAT_TIME_SECONDS_SCALE;
}
//-----------------------------------------------------------------------------
// fatfs_convert_from_fat_date: Convert FAT date to d/m/y
//-----------------------------------------------------------------------------
void fatfs_convert_from_fat_date(uint16 fat_date, int *day, int *month, int *year)
{
    *day = (fat_date >> FAT_DATE_DAY_SHIFT) & FAT_DATE_DAY_MASK;
    *month = (fat_date >> FAT_DATE_MONTH_SHIFT) & FAT_DATE_MONTH_MASK;
    *year = (fat_date >> FAT_DATE_YEAR_SHIFT) & FAT_DATE_YEAR_MASK;
    *year = *year + FAT_DATE_YEAR_OFFSET;
}
//-----------------------------------------------------------------------------
// fatfs_convert_to_fat_time: Convert h/m/s to FAT time
//-----------------------------------------------------------------------------
uint16 fatfs_convert_to_fat_time(int hours, int minutes, int seconds)
{
    uint16 fat_time = 0;

    // Most FAT times are to a resolution of 2 seconds
    seconds /= FAT_TIME_SECONDS_SCALE;

    fat_time = (hours & FAT_TIME_HOURS_MASK) << FAT_TIME_HOURS_SHIFT;
    fat_time|= (minutes & FAT_TIME_MINUTES_MASK) << FAT_TIME_MINUTES_SHIFT;
    fat_time|= (seconds & FAT_TIME_SECONDS_MASK) << FAT_TIME_SECONDS_SHIFT;

    return fat_time;
}
//-----------------------------------------------------------------------------
// fatfs_convert_to_fat_date: Convert d/m/y to FAT date
//-----------------------------------------------------------------------------
uint16 fatfs_convert_to_fat_date(int day, int month, int year)
{
    uint16 fat_date = 0;

    // FAT dates are relative to 1980
    if (year >= FAT_DATE_YEAR_OFFSET)
        year -= FAT_DATE_YEAR_OFFSET;

    fat_date = (day & FAT_DATE_DAY_MASK) << FAT_DATE_DAY_SHIFT;
    fat_date|= (month & FAT_DATE_MONTH_MASK) << FAT_DATE_MONTH_SHIFT;
    fat_date|= (year & FAT_DATE_YEAR_MASK) << FAT_DATE_YEAR_SHIFT;

    return fat_date;
}
#endif
//-----------------------------------------------------------------------------
// fatfs_print_sector:
//-----------------------------------------------------------------------------
#ifdef FATFS_DEBUG
void fatfs_print_sector(uint32 sector, uint8 *data)
{
    int i;
    int j;

    FAT_PRINTF(("Sector %d:\n", sector));

    for (i=0;i<FAT_SECTOR_SIZE;i++)
    {
        if (!((i) % 16))
        {
            FAT_PRINTF(("  %04d: ", i));
        }

        FAT_PRINTF(("%02x", data[i]));
        if (!((i+1) % 4))
        {
            FAT_PRINTF((" "));
        }

        if (!((i+1) % 16))
        {
            FAT_PRINTF(("   "));
            for (j=0;j<16;j++)
            {
                char ch = data[i-15+j];

                // Is printable?
                if (ch > 31 && ch < 127)
                {
                    FAT_PRINTF(("%c", ch));
                }
                else
                {
                    FAT_PRINTF(("."));
                }
            }

            FAT_PRINTF(("\n"));
        }
    }
}
#endif
