#include <iostream>
#include <string.h>
#include <ctime>
#include <sstream>

#include "stm32h7xx_hal.h"
#include "stm32h7xx_hal_sd.h"
#include "fatfs.h"

#include "FileSys.hpp"
#include "RTClock.hpp"
#include "StateVariables.hpp"

static void MX_SDMMC1_SD_Init(void);
bool writeLineToFile(FIL filName, const std::string_view linetoWrite);

static FIL EngFile;
static FIL MsgFile;
static FIL LogFile;
static FIL DuraFile;
static FIL ISUSFile;

//static char* engFileName = {};
static std::string engFileName;

SD_HandleTypeDef hsd1;

static bool fileSysReady = false;

std::stringstream FileSys::readFile(const char fileName[])
{
	char line[64];
	FIL getFileObject;
	std::stringstream fileContents{""};
	FRESULT fsReturn;
	
	fsReturn = f_open(&getFileObject, fileName, FA_READ);
	if (fsReturn == FR_OK)
	{
		while ((f_gets(line, sizeof(line), &getFileObject)) != nullptr)
		{
			//assert(f_error(&getFileObject) == 0);
			fileContents << line;
		}
		
	}
	else
	{
		std::cout << "Error in readFile f_open, fsReturn = " << fsReturn << std::endl;
	}
	//assert(f_close(&getFileObject) == FR_OK);
	
	return fileContents;
}

void FileSys::writeFile(const char fileName[], const char* contents, uint32_t contentsLength)
{
	FIL writeFileObject;
	FRESULT fatFsReturn;
	UINT numBytesWritten;
	
	fatFsReturn = f_open(&writeFileObject, fileName, FA_WRITE | FA_CREATE_ALWAYS);
	if (fatFsReturn != 0)
		std::cout << "Error: SDLogger::writeFile f_open" << std::endl;
	
	fatFsReturn = f_write(&writeFileObject, contents, contentsLength, &numBytesWritten);
	if (fatFsReturn != 0)
		std::cout << "Error: SDLogger::writeFile f_write" << std::endl;

	fatFsReturn = f_close(&writeFileObject);
	if (fatFsReturn != 0)
		std::cout << "Error: SDLogger::writeFile f_close" << std::endl;
}


bool writeLineToFile(FIL filName, const std::string_view lineToWrite)
{
	FRESULT fatReturn;
	uint32_t bytesWritten;
	std::string line{lineToWrite};
	
	fatReturn = f_open(&filName, engFileName.c_str(), FA_OPEN_APPEND | FA_WRITE);
	if (fatReturn != FR_OK)
		return false;
		
	fatReturn = f_write(&filName, line.c_str(), (UINT)line.size(), (UINT*)&bytesWritten);
	if (fatReturn != FR_OK)
		return false;
	
	fatReturn = f_close(&filName);
	if (fatReturn != FR_OK)
		return false;
}

bool FileSys::writeLine(FileTypes fType, const std::string_view lineToWrite)
{
	FRESULT fatReturn;
	
	switch (fType)
	{
	case engFile:	
		writeLineToFile(EngFile, lineToWrite);
		break;
	case msgFile:
		break;
	case duraFile:
		break;
	case isusFile:
		break;
	case logFile:
		break;
	}

//	openFile(fType);
//		
//	closeFile(fType);
}

bool FileSys::isFileSysReady()
{
	return fileSysReady;
}

void FileSys::initFileSys()
{
	fileSysReady = false;
	
	MX_SDMMC1_SD_Init();
	MX_FATFS_Init();

	auto fatReturn = f_mount(&SDFatFS, (TCHAR const*)SDPath, 0);
	if (fatReturn == 0)
	{
		std::cout << "FileSys f_mount OK" << std::endl;
	}
	else
	{
		std::cout << "***ERROR: in FileSys.initFileSys f_mount, errorNum: " << fatReturn << std::endl;
	}
}

bool FileSys::createDir(char dirName[])
{
	bool returnValue = false;
	
	FRESULT fatReturn = f_mkdir(dirName);
	if (fatReturn == FR_OK)
	{
		std::cout << "FileSys::creatDir created directory: " << dirName  << "  FatFs return value: " << returnValue << std::endl;
		returnValue = true;
	}
	else
	{	
		std::cout << "**ERROR: FileSys::creatDir FAILED to create directory: " << dirName  << "  FatFs return value: " << returnValue << std::endl;
		returnValue = false;
	}
	
	return returnValue;
}

bool FileSys::createFile(FileSys::FileTypes fType, std::string fileName)
{
	FRESULT fatfsReturn;
	bool returnValue = false;
	int bytesWritten = 0;
	
	switch(fType)
	{
	case engFile:	
		//memcpy(engFileName, fileName, sizeof(&fileName));
		engFileName = fileName;
		fatfsReturn = f_open(&EngFile, engFileName.c_str(), FA_CREATE_ALWAYS | FA_WRITE);
		fatfsReturn = f_sync(&EngFile);
		fatfsReturn = f_close(&EngFile);
		break;
	case msgFile:
		fatfsReturn = f_open(&MsgFile, fileName.c_str(), FA_CREATE_ALWAYS | FA_WRITE);
		break;
	case duraFile:
		fatfsReturn = f_open(&DuraFile, fileName.c_str(), FA_CREATE_ALWAYS | FA_WRITE);
		break;
	case isusFile:
		fatfsReturn = f_open(&ISUSFile, fileName.c_str(), FA_CREATE_ALWAYS | FA_WRITE);
		break;
	case logFile:
		fatfsReturn = f_open(&LogFile, fileName.c_str(), FA_CREATE_ALWAYS | FA_WRITE);
		break;
	}
	
	if (fatfsReturn == FR_OK)
	{
		std::cout << "FileSys::createFile opened file: " << fileName << std::endl;
		returnValue = true;
	}
	else
	{	
		std::cout << "**ERROR: FileSys::createFile FAILED to open file: "  << "  FatFs return value: " << returnValue << fileName << std::endl;
		returnValue = false;
	}
	
	return returnValue;
}

bool FileSys::closeFile(FileTypes fType, char fileName[])
{
	bool returnValue = false;
	
	switch (fType)
	{
	case engFile:	
		returnValue = f_close(&EngFile);
		break;
	case msgFile:
		returnValue = f_close(&MsgFile);
		break;
	case duraFile:
		returnValue = f_close(&DuraFile);
		break;
	case isusFile:
		returnValue = f_close(&ISUSFile);
		break;
	case logFile:
		returnValue = f_close(&LogFile);
		break;
	}
	
	if (returnValue == FR_OK)
	{
		std::cout << "FileSys::openEngFile opened file: " << fileName << std::endl;
		returnValue = true;
	}
	else
	{	
		std::cout << "**ERROR: FileSys::openEngFile FAILED to open file: " << fileName << std::endl;
		returnValue = false;
	}
	
	return returnValue;
}

void FileSys::createAllFiles()
{
	char fileName[] = "CPF003.test";
	tm dtStruct = RTClock::getDateTimeStruct();
	char dirName[48];
	FRESULT fatReturn;

	snprintf(dirName,
		sizeof(dirName),
		"/Mission%d%s%02dT%02d%02d%06d",
		dtStruct.tm_year + 1900,
		RTClock::getMonthText(dtStruct.tm_mon).data(),
		dtStruct.tm_mday,
		dtStruct.tm_hour,
		dtStruct.tm_min,
		dtStruct.tm_sec); 
	
	uint16_t currentProfileNum = StateVariables::getProfileNum();
	
	if (currentProfileNum == 0)
	{
		fatReturn = f_mkdir(dirName);
		if (fatReturn != 0)
		{
			std::cout << "OpenAllFiles f_mkdir failed" << std::endl;
		}
	}
	
	char filePath[65];
	snprintf(filePath,
		sizeof(filePath),
		"%s/CPF001.%03d.eng",
		dirName,
		currentProfileNum);
	
	fatReturn = f_open(&EngFile, fileName, FA_CREATE_ALWAYS | FA_WRITE);
	if (fatReturn == 0)
	{
		std::cout << "FileSys openAllFiles  f_open OK" << std::endl;
	}
	else
	{
		std::cout << "***ERROR: in FileSys.initFileSys f_open, errorNum: " << fatReturn << std::endl;
	}
	f_close(&SDFile);
	fileSysReady = true;
}

unsigned FileSys::listDirectories(char* path)
{
	// FROM FatFS documentation example

	unsigned res;
	DIR dir;
	UINT i;
	static FILINFO fno;

	res = f_opendir(&dir, path);
	if (res == FR_OK)
	{
		for (;;)
		{
			res = f_readdir(&dir, &fno); /* Read a directory item */
			if (res != FR_OK || fno.fname[0] == 0)
			{
				break; /* Break on error or end of dir */
			}
			if (fno.fattrib & AM_DIR)
			{
				/* It is a directory */
				i = strlen(path);
				sprintf(&path[i], "/%s", fno.fname);
				res = listDirectories(path); /* Enter the directory */
				if (res != FR_OK)
				{
					break;
				}
				path[i] = 0;
			}
			else
			{
				/* It is a file. */
				// printf("%s/%s\n", path, fno.fname);
				std::cout << path << " " << fno.fname << std::endl; // TODO Need to fix '//' for files in folders
			}
		}
		f_closedir(&dir);
	}
	return (res);
}

static void MX_SDMMC1_SD_Init(void)
{
	hsd1.Instance = SDMMC1;
	hsd1.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING;
	hsd1.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE;
	hsd1.Init.BusWide = SDMMC_BUS_WIDE_4B;
	hsd1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE;
	hsd1.Init.ClockDiv = 0;
}