/*
 * FreeRTOS+FAT FS V1.0.0 (C) 2013 HCC Embedded
 *
 * The FreeRTOS+FAT SL license terms are different to the FreeRTOS license 
 * terms.
 * 
 * FreeRTOS+FAT SL uses a dual license model that allows the software to be used 
 * under a standard GPL open source license, or a commercial license.  The 
 * standard GPL license (unlike the modified GPL license under which FreeRTOS 
 * itself is distributed) requires that all software statically linked with 
 * FreeRTOS+FAT SL is also distributed under the same GPL V2 license terms.  
 * Details of both license options follow:
 * 
 * - Open source licensing -
 * FreeRTOS+FAT SL is a free download and may be used, modified, evaluated and
 * distributed without charge provided the user adheres to version two of the 
 * GNU General Public License (GPL) and does not remove the copyright notice or 
 * this text.  The GPL V2 text is available on the gnu.org web site, and on the
 * following URL: http://www.FreeRTOS.org/gpl-2.0.txt.
 * 
 * - Commercial licensing -
 * Businesses and individuals who for commercial or other reasons cannot comply
 * with the terms of the GPL V2 license must obtain a commercial license before 
 * incorporating FreeRTOS+FAT SL into proprietary software for distribution in 
 * any form.  Commercial licenses can be purchased from 
 * http://shop.freertos.org/fat_sl and do not require any source files to be 
 * changed.
 *
 * FreeRTOS+FAT SL is distributed in the hope that it will be useful.  You
 * cannot use FreeRTOS+FAT SL unless you agree that you use the software 'as
 * is'.  FreeRTOS+FAT SL is provided WITHOUT ANY WARRANTY; without even the
 * implied warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. Real Time Engineers Ltd. and HCC Embedded disclaims all
 * conditions and terms, be they implied, expressed, or statutory.
 *
 * http://www.FreeRTOS.org
 * http://www.FreeRTOS.org/FreeRTOS-Plus
 *
 */
#include "../../api/fat_sl.h"

#include "fat.h"
#include "util.h"
#include "volume.h"
#include "drv.h"

#include "../../version/ver_fat_sl.h"
#if VER_FAT_SL_MAJOR != 3 || VER_FAT_SL_MINOR != 2
 #error Incompatible FAT_SL version number!
#endif

/****************************************************************************
 *
 * _f_writefatsector
 *
 * writing fat sector into volume, this function check if fat was modified
 * and writes data
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
unsigned char _f_writefatsector ( void )
{
  unsigned char  a;

  if ( gl_volume.modified )
  {
    unsigned long  fatsector = gl_volume.firstfat.sector + gl_volume.fatsector;

    if ( gl_volume.fatsector >= gl_volume.firstfat.num )
    {
      return F_ERR_INVALIDSECTOR;
    }

    for ( a = 0 ; a < gl_volume.bootrecord.number_of_FATs ; a++ )
    {
      unsigned char  ret;
      ret = _f_writeglsector( fatsector );
      if ( ret )
      {
        return ret;
      }

      fatsector += gl_volume.firstfat.num;
    }

    gl_volume.modified = 0;
  }

  return F_NO_ERROR;
} /* _f_writefatsector */



/****************************************************************************
 *
 * _f_getfatsector
 *
 * read a fat sector from media
 *
 * INPUTS
 *
 * sector - which fat sector is needed, this sector number is zero based
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
unsigned char _f_getfatsector ( unsigned long sector )
{
  unsigned char  a;

  if ( gl_volume.fatsector != sector )
  {
    unsigned long  fatsector;

    gl_volume.fatsector = sector;

    if ( gl_volume.fatsector >= gl_volume.firstfat.num )
    {
      return F_ERR_INVALIDSECTOR;
    }

    fatsector = gl_volume.firstfat.sector + gl_volume.fatsector;

    for ( a = 0 ; a < gl_volume.bootrecord.number_of_FATs ; a++ )
    {
      if ( !_f_readglsector( fatsector ) )
      {
        return F_NO_ERROR;
      }

      fatsector += gl_volume.firstfat.num;
    }

    return F_ERR_READ;
  }

  return F_NO_ERROR;
} /* _f_getfatsector */



/****************************************************************************
 *
 * _f_setclustervalue
 *
 * set a cluster value in the FAT
 *
 * INPUTS
 *
 * cluster - which cluster's value need to be modified
 * data - new value of the cluster
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
unsigned char _f_setclustervalue ( unsigned long cluster, unsigned long _tdata )
{
  unsigned char  ret;

  switch ( gl_volume.mediatype )
  {
    case F_FAT16_MEDIA:
    {
      unsigned long   sector = cluster;
      unsigned short  s_data = (unsigned short)( _tdata & 0xffff ); /*keep 16 bit only*/

      sector /= ( F_SECTOR_SIZE / 2 );
      cluster -= sector * ( F_SECTOR_SIZE / 2 );

      ret = _f_getfatsector( sector );
      if ( ret )
      {
        return ret;
      }

      if ( _f_getword( &gl_sector[cluster << 1] ) != s_data )
      {
        _f_setword( &gl_sector[cluster << 1], s_data );
        gl_volume.modified = 1;
      }
    }
    break;

    case F_FAT12_MEDIA:
    {
      unsigned char   f12new[2];
      unsigned long   sector = cluster;
      unsigned short  pos;
      unsigned short  s_data = (unsigned short)( _tdata & 0x0fff ); /*keep 12 bit only*/

      if ( cluster & 1 )
      {
        s_data <<= 4;
      }

      _f_setword( f12new, s_data ); /*create new data*/

      sector += sector / 2;       /*1.5 bytes*/
      pos = (unsigned short)( sector % F_SECTOR_SIZE );
      sector /= F_SECTOR_SIZE;

      ret = _f_getfatsector( sector );
      if ( ret )
      {
        return ret;
      }

      if ( cluster & 1 )
      {
        f12new[0] |= gl_sector[pos] & 0x0f;
      }

      if ( gl_sector[pos] != f12new[0] )
      {
        gl_sector[pos] = f12new[0];
        gl_volume.modified = 1;
      }

      pos++;
      if ( pos >= 512 )
      {
        ret = _f_getfatsector( sector + 1 );
        if ( ret )
        {
          return ret;
        }

        pos = 0;
      }

      if ( !( cluster & 1 ) )
      {
        f12new[1] |= gl_sector[pos] & 0xf0;
      }

      if ( gl_sector[pos] != f12new[1] )
      {
        gl_sector[pos] = f12new[1];
        gl_volume.modified = 1;
      }
    }
    break;

    case F_FAT32_MEDIA:
    {
      unsigned long  sector = cluster;
      unsigned long  oldv;

      sector /= ( F_SECTOR_SIZE / 4 );
      cluster -= sector * ( F_SECTOR_SIZE / 4 );

      ret = _f_getfatsector( sector );
      if ( ret )
      {
        return ret;
      }

      oldv = _f_getlong( &gl_sector[cluster << 2] );

      _tdata &= 0x0fffffff;
      _tdata |= oldv & 0xf0000000; /*keep 4 top bits*/

      if ( _tdata != oldv )
      {
        _f_setlong( &gl_sector[cluster << 2], _tdata );
        gl_volume.modified = 1;
      }
    }
    break;

    default:
      return F_ERR_INVALIDMEDIA;
  } /* switch */

  return F_NO_ERROR;
} /* _f_setclustervalue */


/****************************************************************************
 *
 * _f_getclustervalue
 *
 * get a cluster value from FAT
 *
 * INPUTS
 *
 * cluster - which cluster value is requested
 * pvalue - where to store data
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
unsigned char _f_getclustervalue ( unsigned long cluster, unsigned long * pvalue )
{
  unsigned long  val;
  unsigned char  ret;

  switch ( gl_volume.mediatype )
  {
    case F_FAT16_MEDIA:
    {
      unsigned long  sector = cluster;
      sector /= ( F_SECTOR_SIZE / 2 );
      cluster -= sector * ( F_SECTOR_SIZE / 2 );

      ret = _f_getfatsector( sector );
      if ( ret )
      {
        return ret;
      }

      val = _f_getword( &gl_sector[cluster << 1] );
      if ( val >= ( F_CLUSTER_RESERVED & 0xffff ) )
      {
        val |= 0x0ffff000;                                       /*extends it*/
      }

      if ( pvalue )
      {
        *pvalue = val;
      }
    }
    break;

    case F_FAT12_MEDIA:
    {
      unsigned char   dataf12[2];
      unsigned long   sector = cluster;
      unsigned short  pos;

      sector += sector / 2;       /*1.5 bytes*/
      pos = (unsigned short)( sector % F_SECTOR_SIZE );
      sector /= F_SECTOR_SIZE;

      ret = _f_getfatsector( sector );
      if ( ret )
      {
        return ret;
      }

      dataf12[0] = gl_sector[pos++];

      if ( pos >= 512 )
      {
        ret = _f_getfatsector( sector + 1 );
        if ( ret )
        {
          return ret;
        }

        pos = 0;
      }

      dataf12[1] = gl_sector[pos];

      val = _f_getword( dataf12 );

      if ( cluster & 1 )
      {
        val = val >> 4;
      }
      else
      {
        val = val & 0xfff;
      }

      if ( val >= ( F_CLUSTER_RESERVED & 0xfff ) )
      {
        val |= 0x0ffff000;                                      /*extends it*/
      }

      if ( pvalue )
      {
        *pvalue = val;
      }
    }
    break;

    case F_FAT32_MEDIA:
    {
      unsigned long  sector = cluster;
      sector /= ( F_SECTOR_SIZE / 4 );
      cluster -= sector * ( F_SECTOR_SIZE / 4 );

      ret = _f_getfatsector( sector );
      if ( ret )
      {
        return ret;
      }

      if ( pvalue )
      {
        *pvalue = _f_getlong( &gl_sector[cluster << 2] ) & 0x0fffffff;       /*28bit*/
      }
    }
    break;

    default:
      return F_ERR_INVALIDMEDIA;
  } /* switch */

  return F_NO_ERROR;
} /* _f_getclustervalue */






/****************************************************************************
 *
 * _f_clustertopos
 *
 * convert a cluster position into physical sector position
 *
 * INPUTS
 *
 * cluster - original cluster position
 * pos - position structure to fills the position
 *
 ***************************************************************************/
void _f_clustertopos ( unsigned long cluster, F_POS * pos )
{
  pos->cluster = cluster;

  if ( !cluster )
  {
    pos->sector = gl_volume.root.sector;
    pos->sectorend = pos->sector + gl_volume.root.num;
  }
  else
  {
    unsigned long  sectorcou = gl_volume.bootrecord.sector_per_cluster;
    pos->sector = ( pos->cluster - 2 ) * sectorcou + gl_volume._tdata.sector;
    pos->sectorend = pos->sector + sectorcou;
  }

  if ( cluster >= F_CLUSTER_RESERVED )
  {
    pos->sectorend = 0;
  }

  pos->pos = 0;
} /* _f_clustertopos */




/****************************************************************************
 *
 * _f_getcurrsector
 *
 * read current sector according in file structure
 *
 * INPUTS
 * f - internal file pointer
 *
 * RETURNS
 * error code or zero if successful
 *
 ***************************************************************************/
unsigned char _f_getcurrsector ( void )
{
  unsigned char  ret;
  unsigned long  cluster;

  if ( gl_file.pos.sector == gl_file.pos.sectorend )
  {
    gl_volume.fatsector = (unsigned long)-1;
    ret = _f_getclustervalue( gl_file.pos.cluster, &cluster );
    if ( ret )
    {
      return ret;
    }

    if ( cluster >= F_CLUSTER_RESERVED )
    {
      return F_ERR_EOF;
    }

    gl_file.prevcluster = gl_file.pos.cluster;
    _f_clustertopos( cluster, &gl_file.pos );
  }

  return _f_readglsector( gl_file.pos.sector );
} /* _f_getcurrsector */



/****************************************************************************
 *
 * _f_alloccluster
 *
 * allocate cluster from FAT
 *
 * INPUTS
 * pcluster - where to store the allocated cluster number
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/

unsigned char _f_alloccluster ( unsigned long * pcluster )
{
  unsigned long  maxcluster = gl_volume.maxcluster;
  unsigned long  cou;
  unsigned long  cluster = gl_volume.lastalloccluster;
  unsigned long  value;
  unsigned char  ret;

  for ( cou = 0 ; cou < maxcluster ; cou++ )
  {
    if ( cluster >= maxcluster )
    {
      cluster = 0;
    }

    ret = _f_getclustervalue( cluster, &value );
    if ( ret )
    {
      return ret;
    }

    if ( !value )
    {
      gl_volume.lastalloccluster = cluster + 1;   /*set next one*/
      *pcluster = cluster;

      return F_NO_ERROR;
    }

    cluster++;
  }

  return F_ERR_NOMOREENTRY;
} /* _f_alloccluster */




/****************************************************************************
 *
 * _f_removechain
 *
 * remove cluster chain from fat
 *
 * INPUTS
 * cluster - first cluster in the cluster chain
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/

unsigned char _f_removechain ( unsigned long cluster )
{
  gl_volume.fatsector = (unsigned long)-1;

  if ( cluster < gl_volume.lastalloccluster ) /*this could be the begining of alloc*/
  {
    gl_volume.lastalloccluster = cluster;
  }

  while ( cluster < F_CLUSTER_RESERVED && cluster >= 2 )
  {
    unsigned long  nextcluster;

    unsigned char  ret = _f_getclustervalue( cluster, &nextcluster );
    if ( ret )
    {
      return ret;
    }

    ret = _f_setclustervalue( cluster, F_CLUSTER_FREE );
    if ( ret )
    {
      return ret;
    }

    cluster = nextcluster;
  }

  return _f_writefatsector();
} /* _f_removechain */




