/*
 * fat32.c
 *
 * Created: 02.04.2019 20:40:55
 *  Author: Juraj
 */ 

#include "fat32.h"

struct FAT32_app fat32;

static enum retCode FAT32_getNextSector() 
{
	Try((fat32.currentCluster & FAT32_CLUSTER_MASK) >= FAT32_CLUSTER_END, EDenied);
	Try((fat32.currentCluster & FAT32_CLUSTER_MASK) == FAT32_CLUSTER_FREE, EDenied);

	if(fat32.sectorIndex >= fat32.clusterSize) {
		Try(SDCARD_read((fat32.bootAddress_ABS + fat32.fatAddress_LBA + ((fat32.currentCluster & 0xFFFFFF80) >> 7)) * FAT32_SECTOR_SIZE, fat32.buffer), EHardwareProblem);
		fat32.currentCluster = *(uint32_t*)(fat32.buffer + (fat32.currentCluster & 0x0000007F));
		Try((fat32.currentCluster & FAT32_CLUSTER_MASK) >= FAT32_CLUSTER_END, EEof);
		fat32.sectorIndex = 0;
	}

	Try(SDCARD_read((fat32.bootAddress_ABS + fat32.dataAdress_LBA + (fat32.currentCluster - 2) * fat32.clusterSize + fat32.sectorIndex) * FAT32_SECTOR_SIZE, fat32.buffer), 0);
	fat32.bufferIndex = 0;
	fat32.sectorIndex++;

	return ENone;
}

enum retCode FAT32_init()
{
	/* Get MBR sector */
	Try(SDCARD_read(0, fat32.buffer), EDenied);

	/* Check partition and get BOOT sector */
	Try(!(fat32.buffer[FAT32_OFFSET_PART_1 + FAT32_OFFSET_PART_TYPE] == FAT32_CHS || fat32.buffer[FAT32_OFFSET_PART_1 + FAT32_OFFSET_PART_TYPE] == FAT32_LBA), EDenied);
	fat32.bootAddress_ABS = *(uint32_t*)(fat32.buffer+FAT32_OFFSET_PART_1+FAT32_OFFSET_PART_BEGIN);
	Try(SDCARD_read(fat32.bootAddress_ABS * FAT32_SECTOR_SIZE, fat32.buffer), EDenied);

	/* Check valid SECTOR size */
	Try(*(uint16_t*)(fat32.buffer+FAT32_OFFSET_BOOT_SECTOR_BYTES) != FAT32_SECTOR_SIZE, EDenied);

	/* Get CLUSTER size in sectors */
	fat32.clusterSize = fat32.buffer[FAT32_OFFSET_BOOT_CLUS_SECTORS];

	/* Get FAT start address */
	fat32.fatAddress_LBA = *(uint16_t*)(fat32.buffer+FAT32_OFFSET_BOOT_RESERVED);

	/* Get DATA start address */
	fat32.dataAdress_LBA = fat32.buffer[FAT32_OFFSET_BOOT_FATCOUNT] * *(uint32_t*)(fat32.buffer+FAT32_OFFSET_BOOT_FATSECTORS) + fat32.fatAddress_LBA;

	/* Get root cluster number */
	fat32.rootCluster = *(uint32_t*)(fat32.buffer+FAT32_OFFSET_BOOT_ROOT_CLUS);

	return ENone;
}

enum retCode FAT32_goToRoot()
{
	fat32.currentCluster = fat32.rootCluster;
	fat32.sectorIndex = 0;

	return FAT32_getNextSector();
}

enum retCode FAT32_nextFile(const char* extension, struct FAT32_file* file)
{
	Assert(file);
	Try((fat32.currentCluster & FAT32_CLUSTER_MASK) >= FAT32_CLUSTER_END, EEof);

	struct FAT32_fileHeader {
		char name[8];
		char extension[3];
		union attrib {
			uint8_t byte;
			struct bit {
				uint8_t readOnly  : 1;
				uint8_t hidden    : 1;
				uint8_t system    : 1;
				uint8_t volume    : 1;
				uint8_t directory : 1;
				uint8_t archive	  : 1;
			} bit;
		} attrib;
		uint8_t __reserved0[8];
		uint16_t clusterHigh;
		uint8_t __reserved1[4];
		uint16_t clusterLow;
		uint32_t size;
	} *header;

	while(fat32.buffer[fat32.bufferIndex] != 0) {
		if(fat32.bufferIndex >= 0x200) {
			if(FAT32_getNextSector()) {
				break;
			}
		}

		header = (struct FAT32_fileHeader*)(fat32.buffer + fat32.bufferIndex);
		if((header->name[0] != 0xE5) && header->attrib.bit.archive && (!extension || !strncmp(extension, (char*)(header->extension), 3))) {
			for(uint8_t i = 0; i < 8; ++i) {
				file->name[i] = header->name[i];
			}
			file->name[8] = 0;
			file->ID = (uint32_t)header->clusterLow | ((uint32_t)header->clusterHigh << 16);
			file->size = header->size;
			fat32.bufferIndex += 32;
			return ENone;
		}
		fat32.bufferIndex += 32;

		if(fat32.bufferIndex >= 0x200) {
			if(FAT32_getNextSector()) {
				break;
			}
		}
	}

	return EDenied;
}

enum retCode FAT32_file_open(struct FAT32_file* file)
{
	fat32.currentCluster = file->ID;
	fat32.sectorIndex = 0;
	fat32.fileSize = file->size;

	return FAT32_getNextSector();
}

int16_t FAT32_read(uint8_t* str, const uint16_t maxlen)
{
	if(fat32.fileSize == 0) {
		return -1;
	}

	int16_t i;
	for(i = 0; i < maxlen && fat32.fileSize; ++i, ++fat32.bufferIndex, --fat32.fileSize) {
		if(fat32.bufferIndex >= 0x200) {
			FAT32_getNextSector();
		}

		str[i] = fat32.buffer[fat32.bufferIndex];

		if((str[i] == '\n') || (str[i] == '\r')) {
			str[i] = 0;
			++i;
			++fat32.bufferIndex;
			--fat32.fileSize;
			break;
		}
	}

	return i;
}