/*! \file fat.c \brief FAT16/32 file system driver. */
//*****************************************************************************
//
// File Name	: 'fat.c'
// Title		: FAT16/32 file system driver
// Author		: Pascal Stang
// Date			: 11/07/2000
// Revised		: 12/12/2000
// Version		: 0.3
// Target MCU	: ATmega103 (should work for Atmel AVR Series)
// Editor Tabs	: 4
//
// This code is based in part on work done by Jesper Hansen for his
//		YAMPP MP3 player project.
//
// NOTE: This code is currently below version 1.0, and therefore is considered
// to be lacking in some functionality or documentation, or may not be fully
// tested.  Nonetheless, you can expect most functions to work.
//
// ----------------------------------------------------------------------------
// 17.8.2008
// Bob!k & Raster, C.P.U.
// Original code was modified especially for the SDrive device. 
// Some parts of code have been added, removed, rewrited or optimized due to
// lack of MCU AVR Atmega8 memory.
// ----------------------------------------------------------------------------
//
// This code is distributed under the GNU Public License
//		which can be found at http://www.gnu.org/licenses/gpl.txt
//
//*****************************************************************************

#include <stdio.h>
#include <string.h>

#include "fat.h"
#include "mmc.h"
#include "spi.h"

void USART_SendString(char *buff);
void mmcReadCached(u32 sector);


#define FileNameBuffer atari_sector_buffer
extern unsigned char atari_sector_buffer[256];
extern unsigned char mmc_sector_buffer[512];	// one sector
extern struct GlobalSystemValues GS;
extern struct FileInfoStruct FileInfo;			//< file information for last file accessed


unsigned long fatClustToSect(unsigned short clust)
{
	u32 ret;
	if(clust==MSDOSFSROOT)
	{
		debug("fatClustToSect:");
		u32 res = (u32)((u32)(FirstDataSector) - (u32)(RootDirSectors));
		plotnextnumber(res);
		debug(" ");
		plotnextnumber(FirstDataSector);
		debug(" ");
		plotnextnumber(RootDirSectors);
		debug("\n");
		return res;
	}
	
	ret = (u32)(clust-2);
	ret *= (u32)SectorsPerCluster;
	ret += (u32)FirstDataSector;
	return ((unsigned long)ret);
}


//POZOR - DEBUG !!!
//prvne definovana promenna
u32 debug_endofvariables;		//promenna co je v pameti jako posledni (za ni je uz jen stack)
//POZOR - DEBUG !!!

unsigned short last_dir_start_cluster;
unsigned char last_dir_valid;
unsigned short last_dir_entry;
unsigned long last_dir_sector;
unsigned char last_dir_sector_count;
unsigned short last_dir_cluster;
unsigned char last_dir_index;


unsigned char fatInit()
{
	struct partrecord PartInfo;
	struct bpb710 *bpb;

	// Moved here since init vars are in ROM!
	last_dir_entry=0x0;
	last_dir_sector=0;
	last_dir_sector_count=0;
	last_dir_cluster=0;
	last_dir_index=0;

	// read partition table
	// TODO.... error checking
	debug("FAT INIT:");
	mmcReadCached(0);
	// map first partition record	
	// save partition information to global PartInfo
	PartInfo = *((struct partrecord *) ((struct partsector *) (char*)mmc_sector_buffer)->psPart);

	if(mmc_sector_buffer[0x36]=='F' && mmc_sector_buffer[0x37]=='A' && mmc_sector_buffer[0x38]=='T' && mmc_sector_buffer[0x39]=='1' && mmc_sector_buffer[0x3a]=='6')
	{
		PartInfo.prPartType = PART_TYPE_FAT16LBA; //0x04
		PartInfo.prStartLBA = 0x00;
	}
	
	// Read the Partition BootSector
	// **first sector of partition in PartInfo.prStartLBA
	mmcReadCached(PartInfo.prStartLBA);
	bpb = (struct bpb710 *) ((struct bootsector710 *) (char*)mmc_sector_buffer)->bsBPB;

	// setup global disk constants
	PartInfo.prStartLBA = 0x1e8; // FIXME
	FirstDataSector	= PartInfo.prStartLBA;

	RootDirSectors = bpb->bpbRootDirEnts>>4; // /16 ; 512/16 = 32  (sector size / max entries in one sector = number of sectors)

	debug("PartInto ");
	plotnextnumber(PartInfo.prStartLBA);
	debug(" ");
	plotnextnumber(bpb->bpbRootDirEnts>>4);
	debug("\n");

	// bpbFATsecs is non-zero and is therefore valid
	FirstDataSector	+= bpb->bpbResSectors + bpb->bpbFATs * bpb->bpbFATsecs + RootDirSectors;

	SectorsPerCluster	= bpb->bpbSecPerClust;
	BytesPerSector		= bpb->bpbBytesPerSec;
	FirstFATSector		= bpb->bpbResSectors + PartInfo.prStartLBA;

	// set current directory to root (\)
	FileInfo.vDisk.dir_cluster = MSDOSFSROOT; //RootDirStartCluster;

    last_dir_start_cluster=0xffff;
	//last_dir_valid=0;		//<-- neni potreba, protoze na zacatku fatGetDirEntry se pri zjisteni
							//ze je (FileInfo.vDisk.dir_cluster!=last_dir_start_cluster)
							//vynuluje last_dir_valid=0;

	if (   PartInfo.prPartType==PART_TYPE_DOSFAT16
		|| PartInfo.prPartType==PART_TYPE_FAT16
		|| PartInfo.prPartType==PART_TYPE_FAT16LBA
	   )
	{
		return 1; //je to ok
		debug("OK\n");
	}
	else
	{
		return 0; //neni to zadny filesystem co umime
		debug("FAIL\n");
	}
}

//////////////////////////////////////////////////////////////

unsigned char fatGetDirEntry(unsigned short entry, unsigned char use_long_names)
{
	unsigned long sector;
	struct direntry *de = 0;	// avoid compiler warning by initializing
	struct winentry *we;
	unsigned char haveLongNameEntry;
	unsigned char gotEntry;
	unsigned short b;
	u08 index;
	unsigned short entrycount = 0;
	unsigned short actual_cluster = FileInfo.vDisk.dir_cluster;
	unsigned char seccount=0;

	haveLongNameEntry = 0;
	gotEntry = 0;

	if (FileInfo.vDisk.dir_cluster!=last_dir_start_cluster)
	{
		//zmenil se adresar, takze musi pracovat s nim
		//a zneplatnit last_dir polozky
		last_dir_start_cluster=FileInfo.vDisk.dir_cluster;
		last_dir_valid=0;

		debug("LastDirStartCluster:");
		plotnextnumber(last_dir_start_cluster);
		plotnextnumber(last_dir_valid);
		debug("\n");
	}

	if ( !last_dir_valid
		 || (entry<=last_dir_entry && (entry!=last_dir_entry || last_dir_valid!=1 || use_long_names!=0))
	   )
	{
		//musi zacit od zacatku
		sector = fatClustToSect(FileInfo.vDisk.dir_cluster);
		//index = 0;
		debug("ReadFromBegin:");
		plotnextnumber(sector);
		debug("\n");
		goto fat_read_from_begin;
	}
	else
	{
		//muze zacit od posledne pouzite
		entrycount=last_dir_entry;
		index = last_dir_index;
		sector=last_dir_sector;
		actual_cluster=last_dir_cluster;
		seccount = last_dir_sector_count;
		debug("ReadFromLastEntry:");
		plotnextnumber(sector);
		debug("\n");
		goto fat_read_from_last_entry;
	}

	while(1)
	{
		debug("Index:");
		plotnextnumber(index);
		debug("\n");
		if(index==16)	// time for next sector ?
		{
			if ( actual_cluster==MSDOSFSROOT )
			{
				//v MSDOSFSROOT se nerozlisuji clustery
				//protoze MSDOSFSROOT ma sektory stale dal za sebou bez clusterovani
				if (seccount>=RootDirSectors) return 0; //prekrocil maximalni pocet polozek v MSDOSFSROOT
			}
			else //MUSI BYT!!! (aby neporovnaval seccount je-li actual_cluster==MSDOSFSROOT)
			if( seccount>=SectorsPerCluster )
			{
				//next cluster
				//pri prechodu pres pocet sektoru v clusteru
				actual_cluster = nextCluster(actual_cluster);
				sector=fatClustToSect(actual_cluster);
				seccount=0;
			}
fat_read_from_begin:
			index = 0;
			seccount++;		//ted bude nacitat dalsi sektor (musi ho zapocitat)
fat_read_from_last_entry:
			mmcReadCached( sector++ ); 	//prave ho nacetl
			de = (struct direntry *) (char*)mmc_sector_buffer;
			de+=index;
		}
		
		// check the status of this directory entry slot
		if(de->deName[0] == 0x00) //SLOT_EMPTY
		{
			// slot is empty and this is the end of directory
			gotEntry = 0;
			break;
		}
		else if(de->deName[0] == 0xE5)	//SLOT_DELETED
		{
			// this is an empty slot
			// do nothing and move to the next one
		}
		else
		{
			// this is a valid and occupied entry
			// is it a part of a long file/dir name?
			if( de->deAttributes==ATTR_LONG_FILENAME )
			{
				// we have a long name entry
				// cast this directory entry as a "windows" (LFN: LongFileName) entry
				u08 i;
				unsigned char *fnbPtr;

				//pokud nechceme longname, NESMI je vubec kompletovat
				//a preskoci na dalsi polozku
				//( takze ani haveLongNameEntry nikdy nebude nastaveno na 1)
				//Jinak by totiz preplacaval dalsi kusy atari_sector_bufferu 12-255 ! BACHA!!!
				if (!use_long_names) goto fat_next_dir_entry;

				we = (struct winentry *) de;
				
				b = WIN_ENTRY_CHARS*( (unsigned short)((we->weCnt-1) & 0x0f));		// index into string
				fnbPtr = &FileNameBuffer[b];

				for (i=0;i<5;i++)	*fnbPtr++ = we->wePart1[i*2];	// copy first part
				for (i=0;i<6;i++)	*fnbPtr++ = we->wePart2[i*2];	// second part
				for (i=0;i<2;i++)	*fnbPtr++ = we->wePart3[i*2];	// and third part

				/*
				{
				//unsigned char * sptr;
				//sptr=we->wePart1;
				//do { *fnbPtr++ = *sptr++; sptr++; } while (sptr<(we->wePart1+10)); // copy first part
				//^-- pouziti jen tohoto prvniho a druhe dva pres normalni for(..) uspori 10 bajtu,
				//pri pouziti i dalsich dvou timto stylem uz se to ale zase prodlouzi - nechaaaaaapu!
				//sptr=we->wePart2;
				//do { *fnbPtr++ = *sptr++; sptr++; } while (sptr<(we->wePart2+12)); // second part
				//sptr=we->wePart3;
				//do { *fnbPtr++ = *sptr++; sptr++; } while (sptr<(we->wePart3+4));  // and third part
				}
				*/

				if (we->weCnt & WIN_LAST) *fnbPtr = 0;				// in case dirnamelength is multiple of 13, add termination
				if ((we->weCnt & 0x0f) == 1) haveLongNameEntry = 1;	// flag that we have a complete long name entry set
			}
			else
			{
				// we have a short name entry
				
				// check if this is the short name entry corresponding
				// to the end of a multi-part long name entry
				if(haveLongNameEntry)
				{
					// a long entry name has been collected
					if(entrycount == entry)		
					{
						// desired entry has been found, break out
						gotEntry = 2;
						break;
					}
					// otherwise
					haveLongNameEntry = 0;	// clear long name flag
					entrycount++;			// increment entry counter		
				}
				else
				{
					// entry is a short name (8.3 format) without a
					// corresponding multi-part long name entry

					//Zcela vynecha adresar "." a disk label
					if( (de->deName[0]=='.' && de->deName[1]==' ' && (de->deAttributes & ATTR_DIRECTORY) ) //je to "." adresar
						|| (de->deAttributes & ATTR_VOLUME) ) goto fat_next_dir_entry;

					if(entrycount == entry)
					{
						/*
						fnbPtr = &FileNameBuffer[string_offset];
						for (i=0;i<8;i++)	*fnbPtr++ = de->deName[i];		// copy name
						for (i=0;i<3;i++)	*fnbPtr++ = de->deExtension[i];	// copy extension
						*fnbPtr = 0;										// null-terminate
						*/
						u08 i;
						unsigned char *dptr;

						dptr = &FileNameBuffer;
						for (i=0;i<11;i++)	*dptr++ = de->deName[i];		// copy name+ext
						*dptr=0;	//ukonceni za nazvem

						// desired entry has been found, break out
						//pokud chtel longname, tak to neni a proto upravi 8+3 jmeno na nazev.ext
						//aby to melo formatovani jako longname
						if (use_long_names)
						{
							//upravi 'NAME    EXT' na 'NAME.EXT'
							//(vyhazi mezery a prida tecku)
							//krome nazvu '.          ', a '..         ', tam jen vyhaze mezery
							unsigned char *sptr;
							//EXT => .EXT   (posune vcetne 0x00 za koncem extenderu)
							dptr=(FileNameBuffer+12);
							i=4; do { sptr=dptr-1; *dptr=*sptr; dptr=sptr; i--; } while(i>0);
							if (FileNameBuffer[0]!='.') *dptr='.'; //jen pro jine nez '.' a '..'
							//NAME    .EXT => NAME.EXT
							sptr=dptr=&FileNameBuffer;
							do
							{
							 if ((*sptr)!=' ') *dptr++=*sptr;
							 sptr++;
							} while (*sptr);
							*dptr=0; //ukonceni
						}
						//
						gotEntry = 1;
						break;
					}
					// otherwise
					entrycount++;			// increment entry counter		
				}
			}
		}
fat_next_dir_entry:
		// next directory entry
		de++;
		// next index
		index++;
	}
	
	// we have a file/dir to return
	// store file/dir starting cluster (start of data)
	FileInfo.vDisk.start_cluster = (unsigned short) (de->deStartCluster);
	// fileindex
	FileInfo.vDisk.file_index = entry;	//fileindex teto polozky
	// store file/dir size (note: size field for subdirectory entries is always zero)
	FileInfo.vDisk.size = de->deFileSize;
	// store file/dir attributes
	FileInfo.Attr = de->deAttributes;
	// store file/dir last update time
	FileInfo.Time = (unsigned short) (de->deMTime[0] | de->deMTime[1]<<8);
	// store file/dir last update date
	FileInfo.Date = (unsigned short) (de->deMDate[0] | de->deMDate[1]<<8);

	if(gotEntry)
	{
		debug(" gotEntry ");
		last_dir_entry = entrycount;
		last_dir_index = index;
		last_dir_sector = sector-1; //protoze ihned po nacteni se posune
		last_dir_cluster = actual_cluster;
		last_dir_sector_count = seccount; //skace se az za inkrementaci seccount, takze tady se 1 neodecita!
		last_dir_valid=gotEntry;
	}
	else
	{
		debug(" not gotEntry ");
	}

	return gotEntry;
}


unsigned short nextCluster(unsigned short cluster)
{
	  unsigned short nextCluster;
	  unsigned long fatOffset;
	  unsigned long sector;
	  unsigned long offset;

	  // two FAT bytes (16 bits) for every cluster
	  fatOffset = ((u32)cluster) << 1;

	  // calculate the FAT sector that we're interested in
	  sector = FirstFATSector + (fatOffset / ((u32)BytesPerSector));
	  // calculate offset of the our entry within that FAT sector
	  offset = fatOffset % ((u32)BytesPerSector);

	  // if we don't already have this FAT chunk loaded, go get it
	  // read sector of FAT table
  	  mmcReadCached( sector );

	  // read the nextCluster value
	  nextCluster = (*((unsigned short*) &((char*)mmc_sector_buffer)[offset])) & FAT16_MASK;

	  // check to see if we're at the end of the chain
	  if (nextCluster == (CLUST_EOFE & FAT16_MASK))
		nextCluster = 0;

	return (nextCluster&0xFFFF);
}
