/*  This file is part of Whitix.
 *
 *  Whitix 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.
 *
 *  Whitix 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 Whitix; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <console.h>
#include <typedefs.h>
#include <fs/vfs.h>
#include <malloc.h>
#include <error.h>
#include <init.h>
#include "fat.h"

struct SuperBlockOps fatSbOps=
{
	.allocVNode = NULL,
	.readVNode = FatReadVNode,
	.writeVNode = FatWriteVNode,
};

struct VNodeOps fatVNodeOps=
{
	.create = FatCreate,
	.remove = FatRemove,
	.lookup = FatLookup,
	.mkDir = FatMkDir,
	.rmDir = FatRmDir,
	.truncate = FatTruncate,
	.blockMap= FatBlockMap,
};

struct VfsSuperBlock* FatReadSuper(struct StorageDevice* dev,int flags,char* data)
{
	struct FatBootSector* bootSec;
	struct Buffer* buff;
	struct VfsSuperBlock* retVal;
	struct FatSbInfo* fatSbInfo;
	DWORD clusterCount,totalSectors;

	if (!dev)
		return NULL;

	/* The soft block size at the start does not matter, so long as it is above 512,
	 * which is guaranteed. */

	buff=BlockRead(dev,0);
	if (!buff)
	{
		printf("FAT: Failed to read superblock\n");
		return NULL;
	}

	bootSec=(struct FatBootSector*)(buff->data);

	if (bootSec->bytesPerSec != 512 && bootSec->bytesPerSec != 1024 && bootSec->bytesPerSec != 2048 && bootSec->bytesPerSec
		!= 4096)
	{
		BlockFree(buff);
		return NULL;
	}

	retVal=VfsAllocSuper(dev,flags);
	if (!retVal)
		goto end;

	retVal->sbOps=&fatSbOps;

	/* Allocate the FAT-specific superblock structure */
	fatSbInfo=(struct FatSbInfo*)malloc(sizeof(struct FatSbInfo));
	if (!fatSbInfo)
		goto fail;

	retVal->privData=(void*)fatSbInfo;

	/* Fill the structure with info */
	fatSbInfo->fatStart=bootSec->reservedSectorCnt;

	if (!bootSec->secsPerFat && bootSec->secsPerFat32)
	{
		/* Must be FAT32 then */
		fatSbInfo->rootDirStart=bootSec->rootClus;
		fatSbInfo->fatLength=bootSec->secsPerFat32;
	}else{
		fatSbInfo->rootDirStart=bootSec->reservedSectorCnt+(bootSec->numFats*bootSec->secsPerFat);
		fatSbInfo->fatLength=bootSec->secsPerFat;
	}

	totalSectors=(bootSec->totalSecSmall) ? bootSec->totalSecSmall : bootSec->totalSectorsLarge;
	fatSbInfo->rootDirLength=(bootSec->numRootDirEnts*sizeof(struct FatDirEntry))/bootSec->bytesPerSec; /* FAT32 doesn't use this */
 	fatSbInfo->firstDataSector=bootSec->reservedSectorCnt+(bootSec->numFats*fatSbInfo->fatLength)+fatSbInfo->rootDirLength;
	fatSbInfo->clusterLength=bootSec->bytesPerSec*bootSec->sectorsPerClus;
	fatSbInfo->secsPerClus=bootSec->sectorsPerClus;
	fatSbInfo->totalDataSectors=totalSectors-fatSbInfo->firstDataSector;
	fatSbInfo->numFats=bootSec->numFats;

	clusterCount=fatSbInfo->totalDataSectors/bootSec->sectorsPerClus;

	/* Set the FAT type and end of cluster marker, depending on the number of clusters */
	if (clusterCount < 4085)
	{
		fatSbInfo->fatType=12;
		fatSbInfo->invalidCluster=0xFF8;
	}else if (clusterCount < 65525)
	{
		fatSbInfo->fatType=16;
		fatSbInfo->invalidCluster=0xFFF8;
	}else{
		fatSbInfo->fatType=32;
		fatSbInfo->invalidCluster=0xFFFFFF8;
	}

	printf("Drive is a FAT%u volume\n",(DWORD)fatSbInfo->fatType);

	if (BlockSetSize(dev,bootSec->bytesPerSec))
		goto fail;

	retVal->mount=VNodeGet(retVal,FAT_ROOT_ID);

	if (!retVal->mount)
		goto fail;

end:
	if (buff->refs)
		BlockFree(buff);
	return retVal;

fail:
	/* privData is freed in VfsFreeSuper */
	VfsFreeSuper(retVal);
	retVal=NULL;
	goto end;
}

static struct FileSystem fatFileSystem={
	.name="FAT",
	.readSuper=FatReadSuper,
};

int FatInit()
{
	return VfsRegisterFileSystem(&fatFileSystem);
}

void FatExit()
{
	VfsDeregisterFileSystem(&fatFileSystem);
}

FsInit(FatInit);
