/*==================================================================================================================================
** fs.c
**==================================================================================================================================
** Tiny File system implementations functions
**==================================================================================================================================
** Date             Author                  Comment
** 08-09-2008       Diogenes                Initial Version
**================================================================================================================================*/

/* Include Area */
#include <string.h>
#include <ctype.h>
#include "fs.h"
#include "card.h"
#include "mcu.h"

/* Constant area */
static const byte *const FAT12 = "FAT12";
static const byte *const FAT16 = "FAT16";

/* FAT Offsets */
#define FORMAT_SIGN                  0x1FE
#define FAT_SYSTEM_TYPE               0x36
#define FIRST_PARTION           ( 0x1BE + 8 )
#define BPB_BYTES_PER_SECTOR          0x0B
#define BPB_SECTOR_PER_CLUSTER        0x0D
#define BPB_NUMBER_OF_FATS            0x10
#define BPB_ROOTS_ENTRIES             0x11
#define BPB_RESERVED_SECTS            0x0E
#define BPB_SECTOR_PER_FAT            0x16
#define BPB_TOTAL_SECTORS             0x13
#define BPB_TOTAL_SECTORS_32          0x20

#define BOOT_SECTOR                      0
#define FORMAT_SIGN_VALUE           0xAA55
#define FIX_BYTES_PER_SECTOR           512U

#define FIRST_CLUSTER                    2
#define CLUSTER_AVAILABLE                0
#define CLUSTER_CHAIN_END           0xFFFF

#define MAX_FNAME_LENGTH                11
#define DEFAULT_FILE_TIME           0xA800  /* Time is 21:00:00 */
#define DEFAULT_FILE_DATE           0x393B  /* Date is 27/09/2008 */

#define ATTR_READ_ONLY   	            0x01
#define ATTR_HIDDEN 	                0x02
#define ATTR_SYSTEM 	                0x04
#define ATTR_VOLUME_ID 	              0x08
#define ATTR_DIRECTORY	              0x10
#define ATTR_ARCHIVE  	              0x20
#define ATTR_LONG_NAME 	 ( ATTR_READ_ONLY|ATTR_HIDDEN|ATTR_SYSTEM|ATTR_VOLUME_ID )

#define ROOT_ENTRY_AVAILABLE          0xE5
#define ROOT_NO_MORE_ENTRIES            -1 
#define ROOT_ENTRY_SIZE                 32
#define ROOT_ENTRIES_PER_SECTOR ( FIX_BYTES_PER_SECTOR / ROOT_ENTRY_SIZE )

#define MAX_FILE_NAME_LENGTH             8
#define MAX_FILE_EXT_LENGTH              3

/* Fat types */
typedef enum
{
  __fat12__,
  __fat16__
} tFatType;

/* Type Area */
typedef struct
{
  byte SectorPerCluster;
  byte FATNumbers;
  word RootDirEntries;
  word SectorPerFat;
  word MaxCluster;
  dword FAT1Start;
  dword RootDirectoryStart;
  dword ClusterStart;
  tFatType FATType;
} tFSInfo;

/* Force the struct aligment to 1 */
#pragma pack(1)

typedef struct
{
  byte FName[11];
  byte Attr;
  word Reserved0;
  word CreationTime;
  word CreationDate;
  word LastAccessDate;
  word Reserved1;
  word LastWriteTime;
  word LastWriteDate;
  word StartCluster;
  dword FileSize;
} tFileInfo;

typedef struct
{
  dword Position;
  dword FileSize;
  word StartCluster;
  word CurrentCluster;
  word RootDirEntry;
  byte Attributes;
  bool Inited;
} tFileDescriptor;

/* Internal functions */
static tFSError TranslateCardError( tCardError CardError );
static bool IsFATTypeValid( byte *BootSector );

/* FAT managment */
static word GetFATEntry( word Cluster );
static void SetFATEntry( word Cluster, word Entry );
static word AddNewCluster( word ClusterValue );
static void ReleaseFATEntries( word FirstCluster );

/* Root dir manage */
static bool ConvertFNameIntoDosFormat( byte *FileName );
static bool SearchFile( byte *FileName );
static word GetAvailableRootEntry( void );
static void UpdateRootEntry( byte *FileName );

/* Cluster management */
static dword Cluster2Sector( word Cluster );

/* Endian independent manage */
static word GetWordFromMedia( byte *Ptr );
static dword GetDWordFromMedia( byte *Ptr );
static void PutWordToMedia( word Value, byte *Ptr );
static void PutDWordToMedia( dword Value, byte *Ptr );

/* Read/Write sector access */
static void ReadSector( dword BlockNumber );
static void WriteSector( dword BlockNumber );
static void WriteFATSector( dword BlockNumber );

/* File scope global vars */
static byte Sector[CARD_SECTOR_LENGTH];
static word FirstAvailableCluster;
static dword LastBlockNumber;
static tFSInfo FSInfo;
static tFileDescriptor FileDescriptor;

/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : FS_Mount
** Description   : Mount file system
** Input         : Nothing
** Output        : FS Error 
** Notes         : Only FAT12 and FAT16 supported
----------------------------------------------------------------------------------------------------------------------------------*/
tFSError FS_Mount( void )
{
  tCardError CardError;
  word ReservedSectors, RootDirSectors;
  dword TotalSectors;
  dword BootSector = BOOT_SECTOR;

  /* Init internal vars */
  LastBlockNumber = -1;
  FirstAvailableCluster  = FIRST_CLUSTER;

  /* Try init low level driver [ SD/MMC cards only supported ] */
  CardError = Card_Init();

  if ( CardError != CARD_Success )
  {
    return( TranslateCardError( CardError ));
  }

  /* Read the boot sector */
  ReadSector( BootSector );

  /* Check for BOOT signature */
  if ( GetWordFromMedia( &Sector[FORMAT_SIGN] ) != FORMAT_SIGN_VALUE )
  {
    return( FS_MediaInvalidFormat );
  }

  /* Determine FAT type */
  if ( IsFATTypeValid( Sector ) == FALSE )
  {
    /* Check if is MBR */
    BootSector = GetWordFromMedia( &Sector[FIRST_PARTION] );

    ReadSector( BootSector );

    if ( IsFATTypeValid( Sector ) == FALSE )
    {
      return( FS_MediaInvalidFormat );
    }
  }

  /* Check byte per Sector */
  if ( GetWordFromMedia( &Sector[BPB_BYTES_PER_SECTOR] ) != FIX_BYTES_PER_SECTOR )
  {
    return( FS_MediaInvalidFormat );
  }

  /* Load sector per clusters */
  FSInfo.SectorPerCluster = Sector[BPB_SECTOR_PER_CLUSTER];

  /* Load number of FATs */
  FSInfo.FATNumbers = Sector[BPB_NUMBER_OF_FATS];

  /* Load root entries */
  FSInfo.RootDirEntries = GetWordFromMedia( &Sector[BPB_ROOTS_ENTRIES] );

  /* Load reserved sectors */
  ReservedSectors = GetWordFromMedia( &Sector[BPB_RESERVED_SECTS] );

  /* Load sectors x FAT */
  FSInfo.SectorPerFat = GetWordFromMedia( &Sector[BPB_SECTOR_PER_FAT] );

  /* Compute LBA of first FAT */
  FSInfo.FAT1Start = BootSector + ReservedSectors;

  /* Compute LBA of root directory */
  FSInfo.RootDirectoryStart = FSInfo.FAT1Start + ( FSInfo.FATNumbers * FSInfo.SectorPerFat );

  RootDirSectors = ( FSInfo.RootDirEntries * ROOT_ENTRY_SIZE ) / FIX_BYTES_PER_SECTOR ;

  /* Compute LBA of Cluster start */
  FSInfo.ClusterStart = FSInfo.RootDirectoryStart + RootDirSectors;

  /* Load Total sectors */
  TotalSectors = ( dword ) GetWordFromMedia( &Sector[BPB_TOTAL_SECTORS] );

  if ( TotalSectors == 0 )
  {
    TotalSectors = GetDWordFromMedia( &Sector[BPB_TOTAL_SECTORS_32] );
  }

  /* Compute Max clusters */
  FSInfo.MaxCluster = ( word )(( TotalSectors - ReservedSectors - ( FSInfo.FATNumbers * FSInfo.SectorPerFat ) - RootDirSectors ) / 
                                 FSInfo.SectorPerCluster );
  FSInfo.MaxCluster += 2;

  /* Init file descriptor */
  memset( &FileDescriptor, 0, sizeof( FileDescriptor ));

  /* Signal success */
  return( FS_Success );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : FS_OpenFile
** Description   : Open file
** Input         : Open mode and file name
** Output        : FS Error 
** Notes         : Only 8.3 file name mode is supported
----------------------------------------------------------------------------------------------------------------------------------*/
tFSError FS_OpenFile( byte OpenMode, const byte *FileName )
{
  byte FNameDOSFormat[MAX_FNAME_LENGTH+2]; 
  word StrLength;

  /* Check valid open mode */
  if (!( OpenMode & ( FS_NORMAL | FS_CREATE | FS_TRUNC )))
  {
    /* Invalid mode */
    return( FS_InvalidMode );
  }

  StrLength = strlen( FileName );

  /* Check max length */
  if (( StrLength > ( MAX_FNAME_LENGTH + 1 )) || ( StrLength == 0 ))
  {
    /* Invalid length */
    return( FS_InvalidFileName );
  }

  /* Make room for complete file name */
  strcpy( FNameDOSFormat, FileName );

  /* Check and convert file name into DOS internal FAT format */
  if ( ConvertFNameIntoDosFormat( FNameDOSFormat ) == FALSE )
  {
    return( FS_InvalidFileName );
  }

  /* Check if file already exits */
  if ( SearchFile( FNameDOSFormat ) == TRUE )
  {
    /* File exits then check if truncate has been requested */
    if ( OpenMode & FS_TRUNC )
    {
      /* Truncate existing file to 0 bytes */
      ReleaseFATEntries( FileDescriptor.StartCluster );
      SetFATEntry( FileDescriptor.StartCluster, CLUSTER_CHAIN_END );
      FileDescriptor.FileSize = 0;

      /* Update corresponding root dir entry */
      UpdateRootEntry( NULL );
    }
  }
  else
  {
    /* File not found */
    if ( OpenMode & FS_CREATE )
    {
      /* Create a new file */

      /* Search a free cluster */
      FileDescriptor.StartCluster = AddNewCluster( CLUSTER_CHAIN_END );

      if ( FileDescriptor.StartCluster == CLUSTER_AVAILABLE )
      {
        /* Space exausted */
        return( FS_MediaSpaceFull );
      }

      /* Search a new root dir entry */
      FileDescriptor.RootDirEntry = GetAvailableRootEntry();

      if ( FileDescriptor.RootDirEntry == ROOT_NO_MORE_ENTRIES )
      {
        /* No more spaces in root dir */
        return( FS_NoMoreRootDirEntries );
      }

      /* New file is zero length */
      FileDescriptor.FileSize = 0;
      FileDescriptor.Attributes = ATTR_ARCHIVE;

      /* Update corresponding root dir entry */
      UpdateRootEntry( FNameDOSFormat );
    }
    else
    {
      return( FS_FileNotFound );
    }
  }

  /* Update internal file descriptor info */
  FileDescriptor.Position = 0;
  FileDescriptor.CurrentCluster = FileDescriptor.StartCluster;

  /* Indicated operation was succesfully */
  FileDescriptor.Inited = TRUE;

  /* Signal success */
  return( FS_Success );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : FS_ReadFile
** Description   : Read from file
** Input         : User buffer, bytes to read and bytes alreday readedl 
** Output        : FS Error 
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
tFSError FS_ReadFile( void *Buffer, word BytesToRead, word *BytesRead )
{
  word NBytes, RestOfSector;
  dword SectorN;
  byte *PtrBuffer = ( byte * ) Buffer;
  word UnReadBytes = BytesToRead;

  /* FS_Open not done */
  if ( !FileDescriptor.Inited )
  {
    return( FS_InvalidMode );
  }

  /* Check max write */
  if ( BytesToRead > MAX_LENGTH_READ_WRITE )
  {
    /* Invalid length */
    return( FS_InvalidParameter );
  }

  if ( BytesToRead == 0 )
  {
    /* Do nothing */
    *BytesRead = 0;

    return( FS_Success );
  }

  /* Loop until no more data to read */
  while ( UnReadBytes > 0 )
  {
    NBytes = UnReadBytes;
    RestOfSector = ( word ) ( FIX_BYTES_PER_SECTOR - ( FileDescriptor.Position % FIX_BYTES_PER_SECTOR ));

    /* Check if position can overflow */
    if ( FileDescriptor.FileSize < ( FileDescriptor.Position + NBytes ))
    {
      NBytes = ( word ) ( FileDescriptor.FileSize - FileDescriptor.Position );
    }

    if ( NBytes == 0 )
    {
      /* Do nohting */
      break;
    }

    NBytes = MIN( NBytes, RestOfSector );

    /* Compute the sector of current position */
    SectorN = Cluster2Sector( FileDescriptor.CurrentCluster ) + 
              (( FileDescriptor.Position / FIX_BYTES_PER_SECTOR ) % FSInfo.SectorPerCluster );

    /* Read apropiate sector correspnd to the current cluster */
    ReadSector( SectorN );

    if ((( FileDescriptor.Position % FIX_BYTES_PER_SECTOR ) == 0 ) && ( NBytes == FIX_BYTES_PER_SECTOR ))
    {
      /* Copy complete sector to user buffer */
      memcpy( PtrBuffer, Sector, FIX_BYTES_PER_SECTOR );
    }
    else
    {
      /* Copy partial sector to user buffer */
      memcpy( PtrBuffer, Sector + ( FileDescriptor.Position % FIX_BYTES_PER_SECTOR ),  NBytes );
    }

    /* Updates vars */
    PtrBuffer += NBytes;
    FileDescriptor.Position += NBytes;
    UnReadBytes -= NBytes;

    /* Check if is necessary change the current cluster */
    if ((( FileDescriptor.Position % ( FSInfo.SectorPerCluster * FIX_BYTES_PER_SECTOR )) == 0 ) && 
         ( FileDescriptor.Position < FileDescriptor.FileSize ))
    {
      /* Point to next cluster in the chain */
      FileDescriptor.CurrentCluster = GetFATEntry( FileDescriptor.CurrentCluster );
    }
  }

  /* Update result */
  *BytesRead = BytesToRead - UnReadBytes;

  /* Signal success */
  return( FS_Success );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : FS_WriteFile
** Description   : Write to file
** Input         : User buffer, bytes to read and bytes alreday readedl                                                    
** Output        : FS Error 
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
tFSError FS_WriteFile( void *Buffer, word BytesToWrite, word *BytesWritten )
{
  word NextCluster, NBytes, RestOfSector;
  dword SectorN, RemainderOfCluster, BytesPerCluster;   
  word UnWrittenBytes = BytesToWrite;
  byte *PtrBuffer = ( byte * ) Buffer;

  /* FS_Open not done */
  if ( !FileDescriptor.Inited )
  {
    return( FS_InvalidMode );
  }

  /* Check max write */
  if ( BytesToWrite > MAX_LENGTH_READ_WRITE )
  {
    /* Invalid length */
    return( FS_InvalidParameter );
  }

  if ( BytesToWrite == 0 )
  {
    /* Do nothing */
    *BytesWritten = 0;

    return( FS_Success );
  }

  /* Check correct file attribute permission */
  if ( FileDescriptor.Attributes & ATTR_READ_ONLY )
  {
    return( FS_PermissionDenied );
  }

  /* Check if S.O. fix the start cluster in 0 */
  if ( FileDescriptor.StartCluster == 0 )
  {
    FileDescriptor.StartCluster = AddNewCluster( CLUSTER_CHAIN_END );

    if ( FileDescriptor.StartCluster == CLUSTER_AVAILABLE )
    {
      /* Space exausted */
      return( FS_MediaSpaceFull );
    }

    FileDescriptor.CurrentCluster = FileDescriptor.StartCluster;
  }

  /* Seek to end of file just per security razon */
  if ( FileDescriptor.CurrentCluster < FSInfo.MaxCluster )
  {
    while ( TRUE )
    {
      NextCluster = GetFATEntry( FileDescriptor.CurrentCluster );

      if ( NextCluster > FSInfo.MaxCluster )
      {
        break;
      }

      /* Point to next cluster */
      FileDescriptor.CurrentCluster = NextCluster;
    }
  }

  /* Write function always work in append mode */
  FileDescriptor.Position = FileDescriptor.FileSize;

  /* Init internal vars */
  BytesPerCluster = FSInfo.SectorPerCluster * FIX_BYTES_PER_SECTOR;
  RemainderOfCluster = BytesPerCluster - ( FileDescriptor.Position % BytesPerCluster );

  /* Check if a new cluster must be added */
  if (( BytesToWrite > RemainderOfCluster ) || (( RemainderOfCluster == BytesPerCluster ) && ( FileDescriptor.Position != 0 )))
  {
    NextCluster = AddNewCluster( FileDescriptor.CurrentCluster );

    /* Check is space is exausted */
    if ( NextCluster == CLUSTER_AVAILABLE )
    {
      return( FS_MediaSpaceFull );
    }
  }

  /* Check if is neccesary point to next cluster */
  if ((( FileDescriptor.Position % BytesPerCluster ) == 0 ) && ( FileDescriptor.Position != 0 ))
  {
    FileDescriptor.CurrentCluster = NextCluster;
  }

  /* Compute remainder space in current sector */
  RestOfSector = ( word ) ( FIX_BYTES_PER_SECTOR - ( FileDescriptor.Position % FIX_BYTES_PER_SECTOR ));
  NBytes = MIN( UnWrittenBytes, RestOfSector );

  /* Compute the sector of current position */
  SectorN = Cluster2Sector( FileDescriptor.CurrentCluster ) + 
            (( FileDescriptor.Position / FIX_BYTES_PER_SECTOR ) % FSInfo.SectorPerCluster );

  /* Check if partial o complete sector is required */
  if ((( FileDescriptor.Position % FIX_BYTES_PER_SECTOR ) == 0 ) && ( NBytes == FIX_BYTES_PER_SECTOR ))
  {
    /* A complete sector must be writted */
    memcpy( Sector, PtrBuffer, FIX_BYTES_PER_SECTOR );
    WriteSector( SectorN );
  }
  else
  {
    /* A partial sector must be writted */
    ReadSector( SectorN );
    memcpy(( byte * ) ( Sector + ( FileDescriptor.Position % FIX_BYTES_PER_SECTOR )), PtrBuffer, NBytes );
    WriteSector( SectorN );
  }

  /* Updates vars */
  PtrBuffer += NBytes;
  UnWrittenBytes -= NBytes;
  FileDescriptor.Position += NBytes;
  FileDescriptor.FileSize = FileDescriptor.Position;

  /* Check if exists remaining bytes */
  if ( UnWrittenBytes > 0 )
  {
    if (( FileDescriptor.Position % BytesPerCluster ) == 0 )
    {
      /* In another cluster */
      FileDescriptor.CurrentCluster = NextCluster;

      /* Compute the sector of the current position */
      SectorN = Cluster2Sector( FileDescriptor.CurrentCluster ) + 
                (( FileDescriptor.Position / FIX_BYTES_PER_SECTOR ) % FSInfo.SectorPerCluster );
    }
    else
    {
      /* Inside the same cluster */
      SectorN ++;
    }

    /* A partial sector must be writted */
    ReadSector( SectorN );
    memcpy( Sector, PtrBuffer, UnWrittenBytes );
    WriteSector( SectorN );

    /* Updates vars */
    FileDescriptor.Position += UnWrittenBytes;
    FileDescriptor.FileSize = FileDescriptor.Position;
    UnWrittenBytes = 0;
  }

  /* Update Root dir entry */
  UpdateRootEntry( NULL );

  /* Update result */
  *BytesWritten = BytesToWrite - UnWrittenBytes;

  /* Signal success */
  return( FS_Success );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : ConvertFNameIntoDosFormat
** Description   : Convert a file name into DOS internal format
** Input         : File name
** Output        : True if convertion was ok otherwise false 
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
bool ConvertFNameIntoDosFormat( byte *FName )
{
  byte FNameConvert[MAX_FNAME_LENGTH+1];
  byte *PtrExt;
  word StrLength = strlen( FName );
  byte *PtrChar = FName;

  /* Check for valid chars in name and convert it into upper case */
  while ( *PtrChar )
  {
    if ( !isalnum( *PtrChar ))
    {
      /* Check for special chars */
      switch ( *PtrChar )
      {
        /* Valid chars */
        case '$': case '%': case '-': case '_':
        case '@': case '!': case '#': case '&': 
        case '.':
          break;
        default:
          /* Invalid char */
          return( FALSE );
      }
    }
    else
    {
      /* Translate into upper case */
      *PtrChar = ( byte ) toupper( *PtrChar );
    }

    PtrChar ++;
  }

  /* Init blank buffer */
  memset( FNameConvert, ' ', sizeof( FNameConvert ));
  FNameConvert[MAX_FNAME_LENGTH] = 0;

  /* Check if extension exist */
  PtrExt = strchr( FName, '.' );

  /* Char '.' cannot start file name */
  if ( PtrExt == FName )
  {
    return( FALSE );
  }

  if ( PtrExt != NULL ) 
  {
    if ( *( ++PtrExt ) != '\0' )
    {
      /* File extension exist and have at least one char */
      word ExtLength = strlen( PtrExt );
      StrLength -= ( ExtLength + 1 );

      /* Limit extension to 3 chars */
      ExtLength = MIN( ExtLength, MAX_FILE_EXT_LENGTH );

      /* Right justify extension */
      memcpy( &FNameConvert[8], PtrExt, ExtLength );
    }
    else
    {
      /* Strip false extension */
      StrLength --;
    }
  }

  /* Limit file name in 8 chars */
  StrLength = MIN( StrLength, MAX_FILE_NAME_LENGTH );

  /* Copy file name */
  memcpy( FNameConvert, FName, StrLength );

  /* Dump the result */
  strcpy( FName, FNameConvert );

  /* Signal success */
  return( TRUE );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : SearchFile
** Description   : Search file in Root directory area 
** Input         : File name
** Output        : True if file is founded otherwise false 
** Notes         : If file is founded global current file descriptor is updated
----------------------------------------------------------------------------------------------------------------------------------*/
bool SearchFile( byte *FileName )
{
  word IndexEntries;
  byte IndexSector, RootDirSectorsN;
  byte FNameTemp[MAX_FNAME_LENGTH+1];
  tFileInfo *FileInfo;

  /* Compute quantity sectors in Root dir */
  RootDirSectorsN = ( byte ) ( FSInfo.RootDirEntries / ROOT_ENTRIES_PER_SECTOR );

  /* Scan root dir */
  for ( IndexSector = 0; IndexSector < RootDirSectorsN; IndexSector ++ )
  {
    /* Read phisycall sector */
    ReadSector( FSInfo.RootDirectoryStart + IndexSector );

    for ( IndexEntries = 0; IndexEntries < FIX_BYTES_PER_SECTOR; IndexEntries += ROOT_ENTRY_SIZE )
    {
      /* Get one entry */
      FileInfo = ( tFileInfo * ) &Sector[IndexEntries];

      /* Get file name */
      strncpy( FNameTemp, FileInfo->FName, MAX_FNAME_LENGTH );

      /* Make string */
      FNameTemp[MAX_FNAME_LENGTH] = 0;

      if (( strcmp( FNameTemp, FileName ) == 0 ) && ( FileInfo->Attr & ATTR_ARCHIVE ))
      {
        /* Convert to apropiate endian */
        FileDescriptor.FileSize = GetDWordFromMedia(( byte * ) &FileInfo->FileSize );
        FileDescriptor.StartCluster = GetWordFromMedia(( byte * ) &FileInfo->StartCluster );
        FileDescriptor.RootDirEntry = ( word ) (( IndexSector * ROOT_ENTRIES_PER_SECTOR ) + ( IndexEntries / ROOT_ENTRY_SIZE ));
        FileDescriptor.Attributes = FileInfo->Attr;

        /* File name match */
        return( TRUE );
      }
    }
  }

  /* File not founded */
  return( FALSE );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : GetAvailableRootEntry
** Description   : Get a available root dir entry
** Input         : Nothing
** Output        : Number of available root entry or -1 if no more space available
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
word GetAvailableRootEntry( void )
{
  word IndexEntries;
  byte IndexSector, RootDirSectorsN, Value;
  tFileInfo *FileInfo;

  /* Compute quantity sectors in Root dir */
  RootDirSectorsN = ( byte ) ( FSInfo.RootDirEntries / ROOT_ENTRIES_PER_SECTOR );

  /* Scan root dir */
  for ( IndexSector = 0; IndexSector < RootDirSectorsN; IndexSector ++ )
  {
    /* Read phisycall sector */
    ReadSector( FSInfo.RootDirectoryStart + IndexSector );

    for ( IndexEntries = 0; IndexEntries < FIX_BYTES_PER_SECTOR; IndexEntries += ROOT_ENTRY_SIZE )
    {
      /* Get one entry */
      FileInfo = ( tFileInfo * ) &Sector[IndexEntries];

      Value = FileInfo->FName[0];

      /* Check if entry is available */
      if (( Value == 0 ) || ( Value == ROOT_ENTRY_AVAILABLE ))
      {
        return(( word ) (( IndexSector * ROOT_ENTRIES_PER_SECTOR ) + ( IndexEntries / ROOT_ENTRY_SIZE )));
      }
    }
  }

  /* No more available entries */
  return( ROOT_NO_MORE_ENTRIES );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : UpdateRootEntry
** Description   : Update a root dir entry
** Input         : File name 
** Output        : Nothing
** Notes         : If file name is null only update entry
----------------------------------------------------------------------------------------------------------------------------------*/
void UpdateRootEntry( byte *FileName )
{
  dword SectorN;
  word Offset;
  tFileInfo *FileInfo;

  /* Get sector */
  SectorN = FSInfo.RootDirectoryStart + ( FileDescriptor.RootDirEntry / ROOT_ENTRIES_PER_SECTOR );
  
  /* Get Offset */
  Offset = ( FileDescriptor.RootDirEntry % ROOT_ENTRIES_PER_SECTOR ) * ROOT_ENTRY_SIZE;

  /* Read sector */
  ReadSector( SectorN );

  /* Read the current entry */
  FileInfo = ( tFileInfo * ) &Sector[Offset];

  /* If FileName is not NULL then a new root dir entry will be added */
  if ( FileName != NULL )
  {
    /* Fields not managed in zero */
    memset( FileInfo, 0, ROOT_ENTRY_SIZE );

    /* Copy DOS format file name */
    strcpy( FileInfo->FName, FileName );

    /* Set default attributes */
    FileInfo->Attr = FileDescriptor.Attributes;

    /* Defualt values for date and time files properties */
    PutWordToMedia( DEFAULT_FILE_DATE, ( byte * ) &FileInfo->CreationDate );
    PutWordToMedia( DEFAULT_FILE_TIME, ( byte * ) &FileInfo->CreationTime );
    PutWordToMedia( DEFAULT_FILE_DATE, ( byte * ) &FileInfo->LastAccessDate );
  }

  /* Update new values */
  PutWordToMedia( FileDescriptor.StartCluster, ( byte * ) &FileInfo->StartCluster );
  PutDWordToMedia( FileDescriptor.FileSize, ( byte * ) &FileInfo->FileSize );
  PutWordToMedia( DEFAULT_FILE_TIME, ( byte * ) &FileInfo->LastWriteTime );
  PutWordToMedia( DEFAULT_FILE_DATE, ( byte * ) &FileInfo->LastWriteDate );

  /* Write sector */
  WriteSector( SectorN );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : Cluster2Sector
** Description   : Converted a cluster into Sector number 
** Input         : Cluster number 
** Output        : Sector number 
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
dword Cluster2Sector( word Cluster )
{
  if (( Cluster >= 2 ) && ( Cluster < FSInfo.MaxCluster ))
  {
    Cluster -= 2;

    return((( dword ) ( Cluster * FSInfo.SectorPerCluster )) + FSInfo.ClusterStart );
  }
  else
  {
    /* Fire critical error */
    FS_CriticalError( FS_InternalError );
  }

  return( 0 );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : GetFATEntry
** Description   : Geta FAT entry value
** Input         : Cluster number 
** Output        : Current FAT entry or 0 if error happende
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
word GetFATEntry( word Cluster )
{
  dword FATSector;
  word Offset, Entry;

  /* Check valid clusters */
  if (( Cluster >= 2 ) && ( Cluster < FSInfo.MaxCluster ))
  {
    if ( FSInfo.FATType == __fat16__ )
    {
      /* FAT 16 */
      /* Sector = FAT base address + [ Cluster * 2 ] / 512 */
      FATSector = FSInfo.FAT1Start + ( Cluster >> 8 );
  
      /* Offset in cluster is [ Cluster * 2 ] Mod 512 */
      Offset = (( Cluster << 1 ) % FIX_BYTES_PER_SECTOR );

      /* Read FAT sector */
      ReadSector( FATSector );

      /* Return cluster chain */
      return( GetWordFromMedia( &Sector[Offset] ));
    }
    else
    {
      /* FAT 12 */
  
      /* Offset = Cluster * 1.5 */
      Offset = Cluster + ( Cluster >> 1 );
  
      /* Sector = FAT base address + [ Offset ] / 512 */
      FATSector = FSInfo.FAT1Start + ( Offset >> 9 );

      /* Read FAT sector */
      ReadSector( FATSector );

      /* Get Hi byte */
      Entry = Sector[Offset % FIX_BYTES_PER_SECTOR];

      /* Point to next byte */
      Offset ++;

      /* Sector = FAT base address + [ Offset ] / 512 */
      FATSector = FSInfo.FAT1Start + ( Offset >> 9 );

      /* Read FAT sector */
      ReadSector( FATSector );

      /* Get Lo byte */
      Entry |= (( word ) ( Sector[Offset % FIX_BYTES_PER_SECTOR ] ) << 8 );

      /* Check if cluster is odd or even */
      if ( Cluster & 0x0001 )
      {
        /* Odd cluster */
        return( Entry >> 4 );
      }
      else
      {
        /* Even cluster */
        return( Entry & 0x0FFF );
      }
    }
  }

  /* Fire critical error */
  FS_CriticalError( FS_InternalError );

  /* This never is executed */
  return( 0 );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : SetFATEntry
** Description   : Set a FAT entry 
** Input         : Cluster number and entry
** Output        : Nothing
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
void SetFATEntry( word Cluster, word NewEntry )
{
  tWBNumber Value;
  dword FATSector;
  word Offset, Entry;
  byte FAT12OneEntry[2];
  bool IsSectorCross = FALSE;

  /* Check valid clusters */
  if ( FSInfo.FATType == __fat16__ )
  {
    /* FAT 16 */
    /* Sector = FAT base address + [ Cluster * 2 ] / 512 */
    FATSector = FSInfo.FAT1Start + ( Cluster >> 8 );
  
    /* Offset in cluster is [ Cluster * 2 ] Mod 512 */
    Offset = (( Cluster << 1 ) % FIX_BYTES_PER_SECTOR );

    /* Read FAT sector */
    ReadSector( FATSector );

    /* Put new entry value */
    PutWordToMedia( NewEntry, &Sector[Offset] );

    /* Save FAT */
    WriteFATSector( FATSector );
  }
  else
  {
    /* FAT 12 */
  
    /* Offset = Cluster * 1.5 */
    Offset = Cluster + ( Cluster >> 1 );
  
    /* Sector = FAT base address + [ Offset / 512 ] */
    FATSector = FSInfo.FAT1Start + ( Offset >> 9 );

    /* Read FAT sector */
    ReadSector( FATSector );
    
    /* Compute the corresponding cluster entry */
    Entry = Offset % FIX_BYTES_PER_SECTOR;

    /* Get Hi byte */
    FAT12OneEntry[0] = Sector[Entry];

    /* Check if cluster cross sector boundary */
    if ( Entry == ( FIX_BYTES_PER_SECTOR - 1 ))
    {
      IsSectorCross = TRUE;

      /* Read the next sector */
      ReadSector( FATSector + 1 );

      /* Get the next entry */
      FAT12OneEntry[1] = Sector[0];
    }
    else
    {
      FAT12OneEntry[1] = Sector[Entry + 1];
    }

    /* Convert current cluster value in word */
    Value.w = GetWordFromMedia( FAT12OneEntry );

    if ( Cluster & 0x0001 )
    {
      /* Odd Cluster */
      NewEntry <<= 4;
      Value.w &= 0x000F;
    }
    else
    {
      /* Even Cluster */
      NewEntry &= 0x0FFF;
      Value.w &= 0xF000;
    }

    /* Update new value */
    Value.w |= NewEntry;

    /* Save FAT */
    if ( IsSectorCross == TRUE )
    {
      /* Save in backguard form */
      Sector[0] = Value.b.bh;

      /* Save FAT */
      WriteFATSector( FATSector + 1 );

      /* Read the previous sector */
      ReadSector( FATSector );

      Sector[FIX_BYTES_PER_SECTOR - 1] = Value.b.bl;

      /* Save FAT */
      WriteFATSector( FATSector );
    }
    else
    {
      /* Put into FAT buffer */
      PutWordToMedia( Value.w, &Sector[Entry] );

      /* Save FAT */
      WriteFATSector( FATSector );
    }
  }
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : ReleaseFATEntries
** Description   : Release all cluster chain
** Input         : Start Cluster number 
** Output        : Nothing
** Notes         : The process is done in backguard form 
----------------------------------------------------------------------------------------------------------------------------------*/
void ReleaseFATEntries( word FirstCluster )
{
  word Current, Next;

  /* Init sequence */
  Current = FirstCluster;

  /* Mark all the cluster in backward form */
  while ( TRUE )
  {
    /* Get the next cluster chain */
    Next = GetFATEntry( Current );

    /* Check if limit is reached */
    if ( Next >= FSInfo.MaxCluster )
    {
      break;
    }

    /* Mark current cluster like available */
    SetFATEntry( Current, CLUSTER_AVAILABLE );

    Current = Next;
  }

  /* Mark last cluster like available */
  SetFATEntry( Current, CLUSTER_AVAILABLE );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : AddNewCluster
** Description   : Search and add a new cluster chain
** Input         : New Cluster value
** Output        : First available cluster or CLUSTER_AVAILABLE on error 
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
word AddNewCluster( word ClusterValue )
{
  word Index;
  bool Found = FALSE;

  /* Traverse all FAT entries */
  for ( Index = FirstAvailableCluster; Index < FSInfo.MaxCluster; Index ++ )
  {
    if ( GetFATEntry( Index ) == CLUSTER_AVAILABLE )
    {
      break;
    }
  }

  if ( Index == FSInfo.MaxCluster )
  {
    /* Try found cluster from beginig off FAT */
    for ( Index = FIRST_CLUSTER; Index < FirstAvailableCluster; Index ++ )
    {
      if ( GetFATEntry( Index ) == CLUSTER_AVAILABLE )
      {
        FirstAvailableCluster = Index + 1;
        Found = TRUE;
        break;
      }
    }

    if ( Found == FALSE )
    {
      /* No more clusters available */
      return( CLUSTER_AVAILABLE );
    }
  }

  /* Mark clusters */
  if ( ClusterValue == CLUSTER_CHAIN_END )
  {
    /* Called from open and create file */
    SetFATEntry( Index, CLUSTER_CHAIN_END );

    /* And return founded cluster */
    return( Index );
  }

  /* Called from write file */
  SetFATEntry( FileDescriptor.CurrentCluster, Index );
  SetFATEntry( Index, CLUSTER_CHAIN_END );

  /* Return founded cluster */
  return( Index );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : IsFATTypeValid
** Description   : Check is a valid FAT type
** Input         : Pointer to sector 
** Output        : True if FAT type is supported otherwise false
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
bool IsFATTypeValid( byte *BootSector )
{
  /* Check FAT12 */
  if ( strncmp( &BootSector[FAT_SYSTEM_TYPE], FAT12, strlen( FAT12 )) == 0 )
  {
    FSInfo.FATType = __fat12__;
    return( TRUE );
  }

  /* Check FAT16 */
  if ( strncmp( &BootSector[FAT_SYSTEM_TYPE], FAT16, strlen( FAT16 )) == 0 )
  {
    FSInfo.FATType = __fat16__;
    return( TRUE );
  }

  /* No supported file system */
  return( FALSE );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : TransalteCardError
** Description   : Translate a Card error to File system error
** Input         : Card error 
** Output        : FS Error 
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
tFSError TranslateCardError( tCardError CardError )
{
  switch ( CardError )
  {
    case CARD_NoCardPresent:
      return( FS_MediaNoPresent );
    case CARD_InvalidCard:
      return( FS_MediaInvalid );
    case CARD_WriteError:
      return( FS_MediaWriteError );
    case CARD_ReadError:
      return( FS_MediaReadError );
    default:
      return( FS_InternalError );
  }
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : GetWordFromMedia
** Description   : Get a word from media.
** Input         : Byte pointer to value to converter
** Output        : Value converted
** Notes         : This function is endian independent
----------------------------------------------------------------------------------------------------------------------------------*/
word GetWordFromMedia( byte *Ptr )
{
  tWBNumber Number;

  Number.b.bl = *Ptr ++;
  Number.b.bh = *Ptr;

  return( Number.w );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : GetDWordFromMedia                         
** Description   : Get a dword from media.                   
** Input         : Byte pointer to value to converter       
** Output        : Value converted                          
** Notes         : This function is endian independent 
----------------------------------------------------------------------------------------------------------------------------------*/
dword GetDWordFromMedia( byte *Ptr )
{
  tDWBNumber Number;

  Number.b.bll = *Ptr ++;
  Number.b.blh = *Ptr ++;
  Number.b.bhl = *Ptr ++;
  Number.b.bhh = *Ptr;

  return( Number.dw );
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : PutWordToMedia
** Description   : Put a word into Media
** Input         : Word value and Byte pointer to value to converter
** Output        : Nothing
** Notes         : This function is endian independent
----------------------------------------------------------------------------------------------------------------------------------*/
void PutWordToMedia( word Value, byte *Ptr )
{
  tWBNumber Number;

  Number.w = Value;

  *Ptr ++ = Number.b.bl;
  *Ptr    = Number.b.bh;
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : PutDWordToMedia
** Description   : Put a DWord into 
** Input         : Dword value and Byte pointer to value to converter       
** Output        : Nothing
** Notes         : This function is endian independent 
----------------------------------------------------------------------------------------------------------------------------------*/
void PutDWordToMedia( dword Value, byte *Ptr )
{
  tDWBNumber Number;

  Number.dw = Value;

  *Ptr ++ = Number.b.bll;
  *Ptr ++ = Number.b.blh;
  *Ptr ++ = Number.b.bhl;
  *Ptr    = Number.b.bhh;
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : WriteFATSector
** Description   : Write FAt sector 
** Input         : Block number 
** Output        : Nothing
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
void WriteFATSector( dword BlockNumber )
{
  byte Index;

  for ( Index = 0; Index < FSInfo.FATNumbers; Index ++ )
  {
    /* Write first FAT */
    WriteSector( BlockNumber );

    /* Point to Next FAT start */
    BlockNumber += FSInfo.SectorPerFat;
  }
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : ReadSector
** Description   : Wrapper to read card function
** Input         : Block number 
** Output        : Card Error
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
void ReadSector( dword BlockNumber )
{
  tCardError CardError = CARD_Success;

  if ( BlockNumber != LastBlockNumber )
  {
    SetLed( led_on );

    /* Read from card */
    CardError = Card_ReadBlock( BlockNumber, Sector ); 

    /* Remember this block */
    LastBlockNumber = BlockNumber;

    SetLed( led_off );
  }

  if ( CardError != CARD_Success )
  {
    /* Manage critical error */
    FS_CriticalError( TranslateCardError( CardError ));
  }
}
/*----------------------------------------------------------------------------------------------------------------------------------
** Function Name : WriteSector                   
** Description   : Wrapper to write card function
** Input         : Block number and user buffer 
** Output        : Card Error                   
** Notes         : 
----------------------------------------------------------------------------------------------------------------------------------*/
void WriteSector( dword BlockNumber )
{
  tCardError CardError;

  SetLed( led_on );

  CardError = Card_WriteBlock( BlockNumber, Sector ); 

  SetLed( led_off );

  if ( CardError != CARD_Success )
  {
    /* Manage critical error */
    FS_CriticalError( TranslateCardError( CardError ));
  }
}


