#include "pch.h"

#include "AZBK_IO.h"
#include "MFCStrUtil.h"
#include <io.h>

fs::path g_strConfImgPath;   // путь к содержимому SD-Карты, там хранится всё нужное

AZ_UNIT::AZ_UNIT()
{
}

AZ_UNIT::~AZ_UNIT()
{
	Close();
}

void AZ_UNIT::setFulPathName(const CString &strFullFileName)
{
	m_fsFullOrigPathName = strFullFileName;
}

CString AZ_UNIT::getFulPathName() const
{
	return m_fsFullOrigPathName;
}

bool AZ_UNIT::FillFileParameters()
{
	bool bRet = false;

	if (Open(m_fsFullOrigPathName, AZIO_OPEN_RD))
	{
		bRet = m_pIO && m_pIO->FillFileParameters(m_fsFullOrigPathName, m_Params);
		Close();
	}

	return bRet;
}

// определение корневого устройства и в зависимости от него
// создание нужного объекта
bool AZ_UNIT::ParseRootDev(const CString &strFullFileName)
{
	TCHAR chRoot = 0;

	if (!strFullFileName.IsEmpty())  // если имя не пустое
	{
		CString fsFullPathName = strFullFileName;

		// определим корневое устройство
		if (strFullFileName.GetAt(1) == _T(':'))
		{
			chRoot = strFullFileName.GetAt(0);
		}
		else
		{
			// по умолчанию будем считать, дисковым устройством
			chRoot = _T('0');
			fsFullPathName = _T("0:") + strFullFileName;
		}

		Close(); // если в этом слоте что-то было, надо культурно всё отпустить

		switch (chRoot)
		{
			case _T('0'):
			case _T('1'):
				// сделаем локальный дисковый путь.
				m_pIO = std::make_unique<io_unit_file>(chRoot);
				break;

			case _T('N'):
			case _T('R'):
			case _T('P'):
			case _T('S'):
				m_pIO = std::make_unique<io_unit_net>(chRoot);
				break;
		}

		return m_pIO != nullptr;
	}

	return false;
}


// открыть файл, вход:
// strFullFileName - имя файла с полным путём и именем корневого устройства
bool AZ_UNIT::Open(const CString &strFullFileName, uint32_t mode)
{
	if (ParseRootDev(strFullFileName))  // если имя не пустое
	{
		m_nCurrentBlock = 0;
		m_wrkPtr = 0;
		return m_pIO->open(strFullFileName, m_Params, mode);
	}

	return false;
}

bool AZ_UNIT::Open(uint32_t mode)
{
	return Open(m_fsFullOrigPathName, mode);
}

void AZ_UNIT::Flush()
{
	if (m_bWrite && m_pIO)
	{
		m_bWrite = false;

		if (m_wrkPtr > 0)
		{
			memset(&m_wrkBuf[m_wrkPtr], 0, AZNetwork::LANDISK_DATA_SIZE - m_wrkPtr);
			m_pIO->writeBlock(m_nCurrentBlock, &m_wrkBuf[0]);
		}
	}
}

// закрыть открытый файл
void AZ_UNIT::Close()
{
	if (m_pIO)
	{
		Flush();
		m_pIO->close();
		m_pIO.reset();
	}
}

// получить сообщение об ошибке
// выход: код ошибки,
//		  strError - текст ошибки
int AZ_UNIT::GetError(CString &strError)
{
	if (m_pIO)
	{
		return m_pIO->getError(strError);
	}

	return -1;
}

// прочитать блок размером 512 байт
// вход: blk - номер блока
//       buf - буфер, куда читать
bool AZ_UNIT::ReadBlock(uint32_t blk, void *buf)
{
	if (m_pIO && m_pIO->readBlock(blk, buf))
	{
		m_nCurrentBlock = blk;
		return true;
	}

	return false;
}

// записать блок размером 512 байт
// вход: blk - номер блока
//       buf - буфер, откуда брать данные
bool AZ_UNIT::WriteBlock(uint32_t blk, void *buf)
{
	if (m_pIO && m_pIO->writeBlock(blk, buf))
	{
		m_nCurrentBlock = blk;
		return true;
	}

	return false;
}

// читать файл.
// Вход: buf - буфер, куда читать,
//		 len - размер буфера в байтах
// Выход: количество прочитанных байтов
uint32_t AZ_UNIT::ReadFile(const uint8_t *buf, uint32_t len)
{
	if (m_pIO)
	{
		int readed = 0; // сколько прочитано байтов
		auto p = const_cast<uint8_t *>(buf);

		while (len > AZNetwork::LANDISK_DATA_SIZE) // пока можно читать целыми блоками
		{
			if (!m_pIO->readBlock(m_nCurrentBlock, p))
			{
				return readed;
			}

			m_nCurrentBlock++; // двигаем блок только при удачной операции
			len -= AZNetwork::LANDISK_DATA_SIZE;
			readed += AZNetwork::LANDISK_DATA_SIZE;
			p += AZNetwork::LANDISK_DATA_SIZE;
		}

		if (len) // если остался остаток.
		{
			if (!m_pIO->readBlock(m_nCurrentBlock, &m_wrkBuf[0], len))
			{
				return readed;
			}

			m_nCurrentBlock++; // двигаем блок только при удачной операции
			memcpy(p, &m_wrkBuf[0], len);
			readed += len;
		}

		return readed;
	}

	return 0;
}

// записать файл.
// Вход: buf - буфер, откуда брать данные,
//		 len - размер буфера в байтах
// Выход: количество записанных байтов
uint32_t AZ_UNIT::WriteFile(const uint8_t *buf, uint32_t len)
{
	if (m_pIO)
	{
		int writed = 0;
		auto p = const_cast<uint8_t *>(buf);

		while (len > AZNetwork::LANDISK_DATA_SIZE) // пока можно писать целыми блоками
		{
			if (!m_pIO->writeBlock(m_nCurrentBlock, p))
			{
				return writed;
			}

			m_nCurrentBlock++; // двигаем блок только при удачной операции
			len -= AZNetwork::LANDISK_DATA_SIZE;
			writed += AZNetwork::LANDISK_DATA_SIZE;
			p += AZNetwork::LANDISK_DATA_SIZE;
		}

		if (len) // если остался остаток.
		{
			memcpy(&m_wrkBuf[0], p, len); // копируем в буфер
			memset(&m_wrkBuf[len], 0, AZNetwork::LANDISK_DATA_SIZE - len); // забиваем остаток нулями

			if (!m_pIO->writeBlock(m_nCurrentBlock, &m_wrkBuf[0], len))
			{
				return writed;
			}

			m_nCurrentBlock++; // двигаем блок только при удачной операции
			writed += len;
		}

		return writed;
	}

	return 0;
}

bool AZ_UNIT::GetF(const uint8_t *buf, uint32_t len)
{
	if (m_pIO)
	{
		auto p = const_cast<uint8_t *>(buf);

		if (!m_pIO->readBlock(m_nCurrentBlock, &m_wrkBuf[0]))
		{
			return false;
		}

		m_nCurrentBlock++; // двигаем блок только при удачной операции
		m_wrkPtr = 0;

		while (len-- > 0) // пока что-то есть, работаем
		{
			*p++ = m_wrkBuf[m_wrkPtr++]; // получаем данные

			if (m_wrkPtr >= AZNetwork::LANDISK_DATA_SIZE) // если буфер полностью прочитан,
			{
				m_wrkPtr = 0;

				if (!m_pIO->readBlock(m_nCurrentBlock, &m_wrkBuf[0]))
				{
					return false;
				}

				m_nCurrentBlock++; // двигаем блок только при удачной операции
			}
		}
	}

	return false;
}

bool AZ_UNIT::PutF(const uint8_t *buf, uint32_t len)
{
	if (m_pIO)
	{
		auto p = const_cast<uint8_t *>(buf);

		while (len-- > 0) // пока что-то есть, работаем
		{
			m_wrkBuf[m_wrkPtr++] = *p++; // заполняем буфер
			m_bWrite = true;

			if (m_wrkPtr >= AZNetwork::LANDISK_DATA_SIZE) // если буфер заполнился,
			{
				m_wrkPtr = 0;

				if (!m_pIO->writeBlock(m_nCurrentBlock, &m_wrkBuf[0]))
				{
					return false;
				}

				m_nCurrentBlock++; // двигаем блок только при удачной операции
				m_bWrite = false;
			}
		}

		return true;
	}

	return false;
}

bool AZ_UNIT::OpenDir(const CString &strFullPath)
{
	if (ParseRootDev(strFullPath))
	{
		return m_pIO->openDir(strFullPath);
	}

	return false;
}

bool AZ_UNIT::ReadDir(az_HFSitem *p)
{
	return m_pIO && m_pIO->readDir(p, m_Params);
}

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

io_unit_file::io_unit_file(TCHAR ch)
	: io_unit_prototype(ch)
	, m_File(nullptr)
	, m_hFile(0)
	, m_file{}
{}

bool io_unit_file::FillFileParameters(const CString &fsFullFilePath, az_UnitParams &params) const
{
	CFileStatus fst;

	if (CFile::GetStatus(fsFullFilePath, fst))
	{
		params.m_nDate = (fst.m_ctime.GetDay() & 037) | ((fst.m_ctime.GetMonth() & 017) << 5) | ((fst.m_ctime.GetYear() - 1980) << 9);
		params.m_nTime = (fst.m_ctime.GetHour() << 11) | (fst.m_ctime.GetMinute() << 5) | ((fst.m_ctime.GetSecond() / 2) & 037);
		params.m_nAttr = fst.m_attribute;
		params.m_bRO = !!(fst.m_attribute & CFile::Attribute::readOnly);
		params.m_nSize = static_cast<uint32_t>(fst.m_size);
		params.m_nBlkSize = static_cast<uint32_t>(fst.m_size + 0777) >> 9; //размер в блоках. блок - 512 байт
		return true;
	}

	return false;
}

// на входе - имя файла, для AZBK в виде [[0:]/][path/]filename.ext
// т.е. тут может быть как полный путь, так и относительный, так и вообще только имя файла без пути
CString io_unit_file::ParseFilePathName(const CString &str) const
{
	if (!str.IsEmpty())
	{
		CString s = str.GetString();

		if (s.GetAt(1) == _T(':'))
		{
			s = s.Mid(2);
		}

		const fs::path p = fs::path(s.GetString()).relative_path().make_preferred();
		return { (g_strConfImgPath / p).c_str() };
	}

	// и возвращаем всё остальное, прицепленное к базовому пути.
	return { g_strConfImgPath.c_str() };
}


bool io_unit_file::open(const CString &fsFullFilePath, az_UnitParams &params, uint32_t mode)
{
	m_fsFileName = ParseFilePathName(fsFullFilePath);
	CString m = _T("r"); // по умолчанию - для чтения

	switch (mode & AZIO_OPEN_RW)
	{
		case AZIO_OPEN_RD:
			// открываем файл для чтения (ничего делать не надо, всё уже сделано)
			break;

		case AZIO_OPEN_WR:
			// открываем файл для записи, создаём новый или открываем существующий
			m = (mode & AZIO_OPEN_CR) ? _T("w") : _T("r+");
			break;

		case AZIO_OPEN_RW:
			// открываем файл для чтения-записи, создаём новый или открываем существующий
			m = (mode & AZIO_OPEN_CR) ? _T("w+") : _T("r+");
			break;
	}

	m += _T('b');

	if (mode & AZIO_OPEN_CR) // если задан флаг создания, то сперва надо открыть файл
	{
		m_File = _tfopen(m_fsFileName, m.GetString());

		if (m_File) // потом проверять атрибуты
		{
			FillFileParameters(m_fsFileName, params);
		}
	}
	else
	{
		// иначе наоборот, сперва получаем параметры
		if (FillFileParameters(m_fsFileName, params))
		{
			m_File = _tfopen(m_fsFileName, m.GetString());
		}
	}

	return m_File != nullptr; // если файл открылся, то ОК
}

void io_unit_file::close()
{
	if (m_File)
	{
		fclose(m_File);
		m_File = nullptr;
	}
}

int io_unit_file::getError(CString &strError) const
{
	unsigned long en = 0;
	_get_doserrno(&en);
	_set_doserrno(0);
	auto es = _tcserror(en);
	strError = es ? es : _T("");
	return en;
}

bool io_unit_file::readBlock(uint32_t nBlk, void *buf, int size /*= 0*/)
{
	if (m_File)
	{
		if (fseek(m_File, nBlk * AZNetwork::LANDISK_DATA_SIZE, SEEK_SET) == 0)
		{
			if (size == 0)
			{
				size = AZNetwork::LANDISK_DATA_SIZE;
			}

			if (size == fread(buf, 1, size, m_File))
			{
				return true;
			}
		}
	}

	_set_doserrno(EBADF);
	return false;
}


bool io_unit_file::writeBlock(uint32_t nBlk, void *buf, int size /*= 0*/)
{
	if (m_File)
	{
		if (fseek(m_File, nBlk * AZNetwork::LANDISK_DATA_SIZE, SEEK_SET) == 0)
		{
			if (size == 0)
			{
				size = AZNetwork::LANDISK_DATA_SIZE;
			}

			if (size == fwrite(buf, 1, size, m_File))
			{
				return true;
			}
		}
	}

	_set_doserrno(EBADF);
	return false;
}


bool io_unit_file::openDir(const CString &strFullPath)
{
	m_fsFileName = ParseFilePathName(strFullPath);
	const fs::path r = fs::path(m_fsFileName.GetString());
	CString findPath = (r / _T("*.*")).c_str();

	if (m_hFile)
	{
		_findclose(m_hFile);
		m_hFile = 0;
	}

	m_hFile = _wfindfirst(findPath, &m_file);
	return (m_hFile != -1);
}

bool io_unit_file::readDir(az_HFSitem *p, az_UnitParams &params)
{
	if (m_hFile == 0)
	{
		// вызвали команду без открытия каталога
		return false;
	}

	const fs::path r = fs::path(m_fsFileName.GetString());
	CString file = (r / m_file.name).c_str();

	if (FillFileParameters(file, params))
	{
		p->fsize = params.m_nSize;
		p->fattrib = params.m_nAttr;
		p->fdate = params.m_nDate;
		p->ftime = params.m_nTime;

		if (_tcscmp(m_file.name, _T(".")) == 0 || _tcscmp(m_file.name, _T("..")) == 0)
		{
			CString name1(m_file.name);
			MFCStrUtil::WideStr2Str(name1, p->fname, true);
		}
		else
		{
			TCHAR buffer[MAX_PATH] {};
			GetShortPathName(file, buffer, MAX_PATH);
			CString name1(buffer);
			int n = name1.ReverseFind(_T('\\'));

			if (n >= 0)
			{
				name1 = name1.Mid(++n);
			}

			// теперь разобьём на имя и расширение
			n = name1.ReverseFind(_T('.'));
			CString fname, fext;

			if (n >= 0)
			{
				fname = name1.Left(n);
				fext = name1.Mid(n);
			}
			else
			{
				fname = name1;
				fext = _T("    ");
			}

			constexpr auto FileNameLen = 8;
			MFCStrUtil::WideStr2Str(fname, std::span{ p->fname.begin(), FileNameLen }, true);
			MFCStrUtil::WideStr2Str(fext, std::span{ p->fname.begin() + FileNameLen, 4 }, true);
		}
	}

	if (_wfindnext(m_hFile, &m_file) == 0)
	{
		return true;
	}

	_findclose(m_hFile);
	m_hFile = 0;
	return false;
}

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

io_unit_net::io_unit_net(TCHAR ch)
	: io_unit_prototype(ch)
{
}

io_unit_net::~io_unit_net()
{
	AZNetwork::landisk_uninit();
}

bool io_unit_net::FillFileParameters(const CString &fsFullFilePath, az_UnitParams &params) const
{
	auto s = reinterpret_cast<const AZNetwork::landisk_answer_status_t *>(m_tmp.data());
	params.m_nDate = s->fdate;
	params.m_nTime = s->ftime;
	params.m_nAttr = s->fattr;
	params.m_bRO = !!(s->fattr & CFile::Attribute::readOnly);
	params.m_nSize = s->fsize;
	params.m_nBlkSize = static_cast<uint32_t>(params.m_nSize + 0777) >> 9; //размер в блоках. блок - 512 байт
	return true;
}

bool io_unit_net::open(const CString &fsFullFilePath, az_UnitParams &params, uint32_t mode)
{
	fsCurrentFilePath.clear();
	auto chStr = MFCStrUtil::WideStr2Str(fsFullFilePath);

	if ((m_error = AZNetwork::landisk_cmd(chStr.get(), 0, m_tmp.data(), AZNetwork::LANDISK_CMD_OPEN)) == 0)
	{
		fsCurrentFilePath = chStr.get();
		FillFileParameters(fsFullFilePath, params);
		return true;
	}

	TRACE("OpenFile :: Network error: %d\n", m_error);
	return false;
}

void io_unit_net::close()
{
	m_error = AZNetwork::landisk_cmd(fsCurrentFilePath.c_str(), 0, m_tmp.data(), AZNetwork::LANDISK_CMD_CLOSE);

	if (m_error)
	{
		TRACE("CloseFile :: Network error: %d\n", m_error);
	}
}

int io_unit_net::getError(CString &strError) const
{
	return m_error;
}

bool io_unit_net::readBlock(uint32_t nBlk, void *buf, int size /*= 0*/)
{
	if ((m_error = AZNetwork::landisk_cmd(fsCurrentFilePath.c_str(), nBlk, m_tmp.data(), AZNetwork::LANDISK_CMD_READ)) == 0)
	{
		memcpy(buf, m_tmp.data(), AZNetwork::LANDISK_DATA_SIZE);
		return true;
	}

	TRACE("readBlock :: Network error: %d\n", m_error);
	return false;
}

bool io_unit_net::writeBlock(uint32_t nBlk, void *buf, int size /*= 0*/)
{
	if ((m_error = AZNetwork::landisk_cmd(fsCurrentFilePath.c_str(), nBlk, reinterpret_cast<uint8_t *>(buf), AZNetwork::LANDISK_CMD_WRITE)) == 0)
	{
		return true;
	}

	TRACE("writeBlock :: Network error: %d\n", m_error);
	return false;
}


bool io_unit_net::openDir(const CString &strFullPath)
{
	// fsCurrentFilePath.clear();
	// auto chStr = MFCStrUtil::WideStr2Str(CString(strFullPath.c_str()));
	// if ((m_error = AZNetwork::landisk_cmd(chStr.get(), 0, work_buffer, AZNetwork::LANDISK_CMD_OPENDIR)) == 0)
	// {
	//  fsCurrentFilePath = chStr.get();
	//  FillFileParameters(strFullPath, params);
	//  return true;
	// }
	//
	// TRACE("OpenFile :: Network error: %d\n", m_error);
	return false;
}

bool io_unit_net::readDir(az_HFSitem *p, az_UnitParams &params)
{
	return false;
}
