#include "pch.h"

#include "SafeReleaseDefines.h"
#include "Platform.h"
#include "Ini.h"
#include "AZBK.h"
#include "AZBK\AZBK_Network.h"

#pragma warning(disable:4996)

static const fs::path g_strAZBKSDCardDir = _T("AZBK\\");
static const fs::path g_strAZIniFileName = _T("az.ini");
static const fs::path g_strAZEEPROMFileName = _T("eeprom.dat");
static const CString g_strAZIniRomSection = _T("ROM");
static const CString g_strAZIniDiskSection = _T("DISKS");
static const CString g_strAZIniBootSection = _T("BOOT");
static const CString g_strAZIniLogoSection = _T("LOGO");

CAZBKController::CAZBKController()
	: azcmd(0)
	, azunit(0)
	, azblkn(0)
	, dr_rd_data(nullptr)
	, dr_wr_data(nullptr)
	, curr_rd_buf(nullptr)
	, dr_wcnt(0)
	, az_init_wcnt(AZ_BUF_SIZE)
	, m_nMaxUnit(0)
	, m_pUnits(nullptr)
	, m_nBootUnit(-1)
	, m_timeOffset(0)
	, m_nIoFileSize(0)
	, m_nIoStatus(0)
	, m_fileIo(nullptr)
{
	m_strConfImgPath = g_Config.GetConfCurrPath() / g_strAZBKSDCardDir; // предустановленный путь.
	m_pAZIni = std::make_unique<CIni>();

	if (!m_pAZIni)
	{
		//TODO
		ASSERT(false);
	}
}

CAZBKController::~CAZBKController()
{
	m_pAZIni->Clear();
}

void CAZBKController::Reset()
{
	azcs = 0;
	azdr = 0;
	azbr1 = 0;
	azbr2 = 0;
}

uint16_t CAZBKController::GetFPGAVersion()
{
	// возвращаем версию. Как должно быть на самом деле - не ясно
	return FPGA_version;
}

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

		if (s.at(1) == L':')
		{
			s = s.substr(2);
		}

		const fs::path p = fs::path(s).relative_path().make_preferred();
		return m_strConfImgPath / p;
	}

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


void CAZBKController::getMACAddressFromIni(uint8_t *buf)
{
	memset(buf, 0, 24);
	const CString strVal = m_pAZIni->GetValueString(_T("NETWORK"), _T("MAC"), _T(""));

	if (!strVal.IsEmpty())
	{
		ULONGLONG m = std::stoull(strVal.GetString(), nullptr, 16);
		auto b = reinterpret_cast<uint8_t *>(&m);
		buf[0] = b[5];
		buf[1] = b[4];
		buf[2] = b[3];
		buf[3] = b[2];
		buf[4] = b[1];
		buf[5] = b[0];
	}
}

void CAZBKController::setMACAddressToIni()
{
	if (g_Config.m_strAZNetworkAdapter.IsEmpty())
	{
		g_Config.m_strAZNetworkAdapter = AZNetwork::getAdaptersList()[0].guid;
	}

	auto buf = reinterpret_cast<uint8_t *>(ipaddrblock);

	if (AZNetwork::getMACAddress(buf, g_Config.m_strAZNetworkAdapter))
	{
		ULONGLONG m = 0;
		auto b = reinterpret_cast<uint8_t *>(&m);
		b[5] = buf[0];
		b[4] = buf[1];
		b[3] = buf[2];
		b[2] = buf[3];
		b[1] = buf[4];
		b[0] = buf[5];
		CString str;
		str.Format(_T("%012llx"), m);
		m_pAZIni->SetValueString(_T("NETWORK"), _T("MAC"), str);
	}
}

void CAZBKController::StrIPtoNumIP(uint8_t *buf, int nOffset, CString &src)
{
	TCHAR *s = src.GetBuffer();
	TCHAR *e = nullptr;
	buf[nOffset++] = static_cast<uint8_t>(_tcstol(s, &e, 10));
	s = e + 1;
	buf[nOffset++] = static_cast<uint8_t>(_tcstol(s, &e, 10));
	s = e + 1;
	buf[nOffset++] = static_cast<uint8_t>(_tcstol(s, &e, 10));
	s = e + 1;
	buf[nOffset++] = static_cast<uint8_t>(_tcstol(s, &e, 10));
}

CString CAZBKController::NumIPtoStrIP(uint8_t *buf, int nOffset)
{
	CString str;
	uint8_t *p = buf + nOffset;
	str.Format(_T("%d.%d.%d.%d"), p[0], p[1], p[2], p[3]);
	return str;
}

void CAZBKController::getNetParametersFromIni(uint8_t *buf)
{
	const CString strSection = _T("NETWORK");
	memset(buf, 0, 24);
	CString strVal = m_pAZIni->GetValueString(strSection, _T("IP"), _T(""));

	if (!strVal.IsEmpty())
	{
		StrIPtoNumIP(buf, 0, strVal);
	}

	strVal = m_pAZIni->GetValueString(strSection, _T("MASK"), _T(""));

	if (!strVal.IsEmpty())
	{
		StrIPtoNumIP(buf, 4, strVal);
	}

	strVal = m_pAZIni->GetValueString(strSection, _T("GW"), _T(""));

	if (!strVal.IsEmpty())
	{
		StrIPtoNumIP(buf, 8, strVal);
	}

	strVal = m_pAZIni->GetValueString(strSection, _T("NTP"), _T(""));

	if (!strVal.IsEmpty())
	{
		StrIPtoNumIP(buf, 12, strVal);
	}

	strVal = m_pAZIni->GetValueString(strSection, _T("DNS1"), _T(""));

	if (!strVal.IsEmpty())
	{
		StrIPtoNumIP(buf, 16, strVal);
	}

	strVal = m_pAZIni->GetValueString(strSection, _T("DNS2"), _T(""));

	if (!strVal.IsEmpty())
	{
		StrIPtoNumIP(buf, 20, strVal);
	}
}

void CAZBKController::setNetParametersToIni()
{
	if (g_Config.m_strAZNetworkAdapter.IsEmpty())
	{
		g_Config.m_strAZNetworkAdapter = AZNetwork::getAdaptersList()[0].guid;
	}

	auto buf = reinterpret_cast<uint8_t *>(ipaddrblock);

	if (AZNetwork::getNetParameters(buf, g_Config.m_strAZNetworkAdapter))
	{
		const CString strSection = _T("NETWORK");
		m_pAZIni->SetValueString(strSection, _T("IP"), NumIPtoStrIP(buf, 0));
		m_pAZIni->SetValueString(strSection, _T("MASK"), NumIPtoStrIP(buf, 4));
		m_pAZIni->SetValueString(strSection, _T("GW"), NumIPtoStrIP(buf, 8));
		// getNetParameters не получает параметры NTP, там мы возвращаем статус DHCP
		m_pAZIni->SetValueString(strSection, _T("DHCP"), buf[12] ? _T("ON") : _T("OFF"));
		m_pAZIni->SetValueString(strSection, _T("DNS1"), NumIPtoStrIP(buf, 16));
		m_pAZIni->SetValueString(strSection, _T("DNS2"), NumIPtoStrIP(buf, 20));
	}
}

bool CAZBKController::Init(uint8_t *pMemory)
{
	if (!fs::exists(m_strConfImgPath))
	{
		g_BKMsgBox.Show(_T("Не найден образ microSD карты."), MB_OK);
		return false;
	}

	fs::path strAZini = m_strConfImgPath / g_strAZIniFileName;

	if (!fs::exists(strAZini))
	{
		g_BKMsgBox.Show(_T("Не найден файл az.ini."), MB_OK);
		return false;
	}

	// читаем ини файл
	m_pAZIni->SetIniFileName(strAZini);
	CString strKey, strValue;

	// сперва загрузим логотип
	if (m_pAZIni->FindKeyStart(g_strAZIniLogoSection, strKey, strValue))
	{
		if (!strKey.IsEmpty() && strKey.GetAt(0) == _T('L'))
		{
			// есть логотип, теперь надо выделить и сформировать путь.
			const fs::path strLogoPath = ParseFilePathName({ strValue.GetString() });
			CFile file;
			// если читается, то читаем как есть, иначе - ничего не делаем
			if (file.Open(strLogoPath.c_str(), CFile::modeRead))
			{
				auto len = static_cast<UINT>(file.GetLength());
				const UINT readed = file.Read(pMemory + AZ_BANK_TO_OFFSET(040), len);
				file.Close();
				// устанавливаем видеорежим.
				g_Platform.SetWord(REG_SCR_PGNUM_L0, 040);
				g_Platform.SetWord(REG_SCR_CSR, 063223);
			}
		}
	}

	// теперь надо загрузить в память нужные пзу.
	if (m_pAZIni->FindKeyStart(g_strAZIniRomSection, strKey, strValue))
	{
		do
		{
			// strKey - это номер слота. т.е. надо оттуда его выделить
			if (strKey.GetAt(0) == _T('R'))
			{
				const int nSlot = Global::ToInt(strKey.Mid(1));

				if (0 <= nSlot && nSlot < 64)
				{
					const int nOffset = 256 * 1024 + nSlot * 4 * 1024;
					// теперь надо выделить и сформировать путь.
					const fs::path strRomPath = ParseFilePathName({strValue.GetString()});
					CFile file;

					if (file.Open(strRomPath.c_str(), CFile::modeRead))
					{
						auto len = static_cast<UINT>(file.GetLength());
						const UINT readed = file.Read(pMemory + nOffset, len);
						file.Close();

						if (readed != len)
						{
							// не прочиталось. надо что-то делать
							ASSERT(false);
						}

						if (readed < 020000u)
						{
							// остаток забиваем нулями
							memset(pMemory + nOffset + readed, 0, 020000u - static_cast<size_t>(readed));
						}
					}
					else
					{
						CString strError(MAKEINTRESOURCE(IDS_ERROR_CANTOPENFILE));
						g_BKMsgBox.Show(strError + _T('\'') + CString(strRomPath.c_str()) + _T('\''), MB_OK | MB_ICONSTOP);
					}
				}
			}
		}
		while (m_pAZIni->FindKeyNext(strKey, strValue));
	}

	// первый проход, сперва узнаем количество дисков, и их номера
	m_nMaxUnit = 0;

	if (m_pAZIni->FindKeyStart(g_strAZIniDiskSection, strKey, strValue))
	{
		do
		{
			// strKey - это номер слота. т.е. надо оттуда его выделить
			if (strKey.GetAt(0) == _T('D'))
			{
				const int nNum = Global::ToInt(strKey.Mid(1));

				if (m_nMaxUnit < nNum)
				{
					m_nMaxUnit = nNum;
				}
			}
		}
		while (m_pAZIni->FindKeyNext(strKey, strValue));
	}

	m_nMaxUnit++; // сделаем не номер последнего юнита, а их количество
	m_pUnits = std::make_unique<AZ_UNIT[]>(m_nMaxUnit);

	if (m_pUnits)
	{
		// второй проход, заполняем массив юнитов
		if (m_pAZIni->FindKeyStart(g_strAZIniDiskSection, strKey, strValue))
		{
			do
			{
				// strKey - это номер слота. т.е. надо оттуда его выделить
				if (strKey.GetAt(0) == _T('D'))
				{
					const int nNum = Global::ToInt(strKey.Mid(1));

					if (nNum < m_nMaxUnit)
					{
						// теперь надо выделить и сформировать путь.
						m_pUnits[nNum].strOrigPathName = strValue.GetString();
						m_pUnits[nNum].strFileName = ParseFilePathName(m_pUnits[nNum].strOrigPathName);
						CFileStatus fst;

						if (CFile::GetStatus(m_pUnits[nNum].strFileName.c_str(), fst))
						{
							m_pUnits[nNum].nDate = (fst.m_ctime.GetDay() & 037) | ((fst.m_ctime.GetMonth() & 017) << 5) | ((fst.m_ctime.GetYear() - 1980) << 9);
							m_pUnits[nNum].nTime = (fst.m_ctime.GetHour() << 11) | (fst.m_ctime.GetMinute() << 5) | ((fst.m_ctime.GetSecond() / 2) & 037);
							m_pUnits[nNum].nAttr = fst.m_attribute;
							m_pUnits[nNum].bRO = !!(fst.m_attribute & CFile::Attribute::readOnly);
							m_pUnits[nNum].nSize = static_cast<uint32_t>(fst.m_size);
							m_pUnits[nNum].nBlkSize = static_cast<uint32_t>(fst.m_size + 0777) >> 9; //размер в блоках. блок - 512 байт
						}
					}
				}
			}
			while (m_pAZIni->FindKeyNext(strKey, strValue));
		}
	}
	else
	{
		// TODO
		ASSERT(false);
	}

	// теперь определим загрузочный юнит.
	// это не работает на БК, там загрузка всегда с 0,1,2,3 привода
	// по стратегии ПЗУ БК11
	if (m_pAZIni->FindKeyStart(g_strAZIniBootSection, strKey, strValue))
	{
		if (!strKey.IsEmpty() && strKey.GetAt(0) == _T('D'))
		{
			m_nBootUnit = Global::ToInt(strKey.Mid(1));
		}
	}

	// обновим параметры сети
	setNetParametersToIni();
	setMACAddressToIni();
	return true;
}

uint16_t CAZBKController::ReadCSR() const
{
	return azcs;
}

bool CAZBKController::WriteCSR(uint16_t v)
{
	azcs &= ~CS_WR_MASK;
	azcs |= v & CS_WR_MASK;
	az_start_io();
	return true;
}

uint16_t CAZBKController::ReadDR_int() const
{
	return azdr;
}

bool CAZBKController::ReadDR(uint16_t *v)
{
	if (azcs & CS_DONE) // регистр доступен только если контроллер готов
	{
		*v = az_read_data(&azdr, &dr_rd_data, &dr_wcnt);
		return true;
	}

	return false;
}

bool CAZBKController::WriteDR(uint16_t v)
{
	if (azcs & CS_DONE) // регистр доступен только если контроллер готов
	{
		az_write_data(&azdr, v, &dr_wr_data, &dr_wcnt);
		return true;
	}

	return false;
}

bool CAZBKController::ReadBR1(uint16_t *v) const
{
	if (azcs & CS_DONE)
	{
		*v = 0;
		return true;
	}

	return false;
}

bool CAZBKController::WriteBR1(uint16_t v) const
{
	if (azcs & CS_DONE)
	{
		return true;
	}

	return false;
}

bool CAZBKController::ReadBR2(uint16_t *v) const
{
	if (azcs & CS_DONE)
	{
		*v = 0776;
		return true;
	}

	return false;
}

bool CAZBKController::WriteBR2(uint16_t v) const
{
	if (azcs & CS_DONE)
	{
		return true;
	}

	return false;
}

/////////////////////////////////////////////////////////////////////////////////
//
// AZ Controller
//
/////////////////////////////////////////////////////////////////////////////////

void CAZBKController::az_set_done()
{
	azcs |= CS_DONE;

	switch (azcmd)
	{
		case CMD_READ:
		case CMD_WRITE:
			if (azcs & CS_IE) // если разрешено прерывание
			{
				g_Platform.VectorInterrupt(CS_VECTOR); // устанавливаем прерывание
			}

			break;
	}
}

void CAZBKController::az_start_io()
{
	uint32_t size;

	switch (AZ_CMD(azcs))
	{
		case CMD_RESET:

			// CLR_INT(AZ); // TODO: снимаем прерывание

			// закрываем все открытые файлы
			for (int unit = 0; unit < m_nMaxUnit; ++unit)
			{
				if (m_pUnits[unit].fFile.m_hFile != CFile::hFileNull)
				{
					//m_pUnits[unit].fFile.Flush();
					m_pUnits[unit].fFile.Close();
				}
			}

			// инициализируем временные переменнные
			dr_rd_data = dr_wr_data = nullptr;
			az_init_wcnt = AZ_BUF_SIZE;
			dr_wcnt = 0;
			azblkn = 0;
			azcs = CS_DONE; // снимаем флаг разрешения прерывания
			return;
	}

	if (azcs & CS_DONE)
	{
		azcmd = AZ_CMD(azcs); // вот такая команда
		azcs &= ~(CS_DONE | CS_BIG | CS_ERR | CS_CMD_MASK); // сбрасываем все флаги

		if (azcmd != CMD_READ_BUF)
		{
			az_init_wcnt = AZ_BUF_SIZE; // инициализируем значение при каждой команде , кроме CMD_READ_BUF
		}

		switch (azcmd)
		{
			case CMD_SETUNI:
			case CMD_SETBLK:
			case CMD_SETBLKX:
				switch (azcmd)
				{
					case CMD_SETUNI:
						if (m_pUnits[azunit].fFile.m_hFile != CFile::hFileNull)
						{
							m_pUnits[azunit].fFile.Close();
						}

						azunit = azdr & AZ_UNIT_MASK;

						if (azunit < m_nMaxUnit)
						{
							azblkn = 0;
							UINT nFlags = (m_pUnits[azunit].bRO) ? CFile::modeRead : CFile::modeReadWrite;

							// хоть это и учтено далее, файл нулевого размера и не существующий файл
							// вообще не будем открывать
							if (m_pUnits[azunit].nSize)
							{
								m_pUnits[azunit].fFile.Open(m_pUnits[azunit].strFileName.c_str(), nFlags | CFile::shareExclusive);
							}
							else
							{
								azcs |= CS_ERR;
							}
						}

						break;

					case CMD_SETBLK:
						azblkn = azdr & AZ_LSB_MASK;
						break;

					case CMD_SETBLKX:
						azblkn &= AZ_LSB_MASK;
						azblkn |= azdr << 16;
						break;
				}

				[[fallthrough]];
			case CMD_GET_SIZE:
			case CMD_GET_SIZEX:
			case CMD_READ:
			case CMD_WRITE:
				if (azunit >= m_nMaxUnit)
				{
					azcs |= CS_ERR;
					break;
				}

				size = m_pUnits[azunit].nBlkSize;

				if ((m_pUnits[azunit].fFile.m_hFile == CFile::hFileNull) ||
				        size <= azblkn)
				{
					azcs |= CS_ERR;
					break;
				}

				curr_rd_buf = azbuf;

				switch (azcmd)
				{
					case CMD_GET_SIZE:
						if (size > 65535)
						{
							azbuf[0] = 65535;
							azcs |= CS_BIG;
						}
						else
						{
							azbuf[0] = size;
						}

						dr_rd_data = azbuf;
						dr_wr_data = nullptr;
						dr_wcnt = 1;
						break;

					case CMD_GET_SIZEX:
						azbuf[0] = size & AZ_LSB_MASK;
						azbuf[1] = size >> 16;
						dr_rd_data = azbuf;
						dr_wr_data = nullptr;
						dr_wcnt = 2;
						break;

					case CMD_READ:  // прочитать в буфер блок
					{
						const LONGLONG seek = LONGLONG(azblkn) * 512;

						if (m_pUnits[azunit].fFile.Seek(seek, CFile::SeekPosition::begin) == seek)
						{
							constexpr UINT rd = AZ_BUF_SIZE * sizeof(uint16_t);

							if (m_pUnits[azunit].fFile.Read(azbuf, rd) == rd)
							{
								break;
							}
						}

						azcs |= CS_ERR;
					}
					break;

					case CMD_WRITE: // записать блок из буфера
						if (!m_pUnits[azunit].bRO)
						{
							const LONGLONG seek = LONGLONG(azblkn) * 512;

							if (m_pUnits[azunit].fFile.Seek(seek, CFile::SeekPosition::begin) == seek)
							{
								m_pUnits[azunit].fFile.Write(azbuf, AZ_BUF_SIZE * sizeof(uint16_t));
								break;
							}
						}

						azcs |= CS_ERR;
						break;
				}

				break;

			case CMD_READ_BUF:
				dr_wr_data = nullptr;
				dr_rd_data = curr_rd_buf;
				dr_wcnt = az_init_wcnt;
				break;

			case CMD_WRITE_BUF:
				dr_wr_data = azbuf;
				dr_rd_data = nullptr;
				dr_wcnt = az_init_wcnt;
				memset(azbuf, 0, sizeof(azbuf));
				break;

			case CMD_NET:
				if (azcs & CS_IE)
				{
					azcs &= ~CS_IE;
					return;
				}

				[[fallthrough]];
			case CMD_RTC_SET_TIME_NET: // не должно ничего делать. у нас и так время всегда установлено
			case CMD_NOP:
				break;

			case CMD_HFS_GET_AZUNITS:
				az_fill_table();
				az_init_wcnt = sizeof(az_assets) / 2;
				curr_rd_buf = reinterpret_cast<uint16_t *>(az_assets);
				break;

			case CMD_GET_FEAT:
				// выдача версии, первое слово: мл. байт - версия железа, ст. байт - версия прошивки STM
				azbuf[0] = (STM_version << 8) | AZ_version;
				// второе слово: максимальный монтируемый диск
				azbuf[1] = m_nMaxUnit - 1;
				dr_wr_data = nullptr;
				dr_rd_data = azbuf;
				dr_wcnt = 2;
				break;

			case CMD_GET_EEPROM:
				if (!az_read_eeprom(iobuf))
				{
					azcs |= CS_ERR;
				}

				break;

			case CMD_GET_IOBLOCK:
				dr_wr_data = nullptr;
				dr_rd_data = iobuf;
				dr_wcnt = AZ_BUF_SIZE;
				break;

			case CMD_PUT_IOBLOCK:
				dr_wr_data = iobuf;
				dr_rd_data = nullptr;
				dr_wcnt = AZ_BUF_SIZE;
				memset(iobuf, 0, sizeof(iobuf));
				break;

			case CMD_PUT_EEPROM:
				if (!az_write_eeprom(iobuf))
				{
					azcs |= CS_ERR;
				}

				break;

			case CMD_RTC_GET_TIME_NET:

			// получение времени из стека TCP/IP равнозначно получению вермени контроллера AZ
			// предполагается. что оно по умолчанию установлено и равно времени хоста
			case CMD_RTC_GET_TIME_HW:
				az_fill_timestamp(&m_timestamp);
				break;

			case CMD_RTC_READ_TIME:
				dr_wr_data = nullptr;
				dr_rd_data = reinterpret_cast<uint16_t *>(&m_timestamp);
				dr_wcnt = sizeof(az_timestamp) / sizeof(uint16_t);
				break;

			case CMD_RTC_WRITE_TIME:
				dr_wr_data = reinterpret_cast<uint16_t *>(&m_timestamp.year);
				dr_rd_data = nullptr;
				dr_wcnt = 7;
				break;

			case CMD_RTC_SET_TIME_BUF:
				az_set_time();
				break;

			case CMD_RTC_GET_TIME_NTP:
				// просто NOP ничего не делаем.
				break;

			case CMD_RESET_AZBK:
				g_Platform.GetMainPtr()->PostMessage(WM_COMMAND, ID_CPU_RESETCPU);
				Sleep(1000); // ненадёжное решение как предотвратить множественную посылку сообщений
				// т.к. посылать нужно строго 1 раз, иначе алгоритмы ломаются.
				break;

			case CMD_NET_GET_IP:
				getNetParametersFromIni(reinterpret_cast<uint8_t *>(ipaddrblock));
				break;

			case CMD_NET_READ_IP:
				dr_rd_data = ipaddrblock;
				dr_wr_data = nullptr;
				dr_wcnt = 12;
				break;

			case CMD_NET_GET_MAC:
				getMACAddressFromIni(reinterpret_cast<uint8_t *>(ipaddrblock));
				break;

			case CMD_IO_SET_RDFILE:
				m_strIoFileName = ParseFilePathName(Global::BKToUNICODE(reinterpret_cast<uint8_t *>(iobuf), AZ_BUF_SIZE * 2).GetString());
				m_nIoStatus = 1;
				break;

			case CMD_IO_GET_FILESTAT:
				switch (m_nIoStatus)
				{
					case 1:
					{
						if (fs::exists(m_strIoFileName))
						{
							*(reinterpret_cast<uint32_t *>(&iobuf[0])) = m_nIoFileSize = static_cast<int>(fs::file_size(m_strIoFileName));
							m_fileIo = _tfopen(m_strIoFileName.c_str(), _T("rb"));

							if (!m_fileIo)
							{
								m_nIoFileSize = 0;
								*(reinterpret_cast<uint32_t *>(&iobuf[0])) = (1 << 31) | 7;
							}
						}
						else
						{
							m_nIoFileSize = 0;
							*(reinterpret_cast<uint32_t *>(&iobuf[0])) = (1 << 31) | 4;
						}

						dr_rd_data = iobuf;
						dr_wr_data = nullptr;
						dr_wcnt = 2;
					}
					break;

					case -1:
						*(reinterpret_cast<uint32_t *>(&iobuf[0])) = m_nIoFileSize = 0;
						m_fileIo = _tfopen(m_strIoFileName.c_str(), _T("wb"));

						if (!m_fileIo)
						{
							*(reinterpret_cast<uint32_t *>(&iobuf[0])) = (1 << 31) | 7;
						}

						dr_rd_data = iobuf;
						dr_wr_data = nullptr;
						dr_wcnt = 2;
						break;

					default:
						azcs |= CS_ERR;
				}

				break;

			case CMD_IO_READ:
				if (m_nIoFileSize > 0)
				{
					const int toRead = (m_nIoFileSize > 512) ? 512 : m_nIoFileSize;
					const int read = static_cast<int>(fread(&iobuf[0], 1, toRead, m_fileIo));
					m_nIoFileSize -= toRead;

					if (m_nIoFileSize <= 0)
					{
						fclose(m_fileIo);
					}

					if (read != toRead)
					{
						azcs |= CS_ERR;
					}
				}

				break;

			case CMD_IO_SET_WRFILE:
				m_strIoFileName = ParseFilePathName(Global::BKToUNICODE(reinterpret_cast<uint8_t *>(iobuf), AZ_BUF_SIZE * 2).GetString());
				m_nIoStatus = -1;
				break;

			case CMD_IO_SET_FILESIZE:
				dr_wr_data = reinterpret_cast<uint16_t *>(&m_nIoFileSize);
				dr_rd_data = nullptr;
				dr_wcnt = 2;
				break;

			case CMD_IO_WRITE:
				if (m_nIoFileSize > 0)
				{
					const int toWrite = (m_nIoFileSize > 512) ? 512 : m_nIoFileSize;
					const int write = static_cast<int>(fwrite(&iobuf[0], 1, toWrite, m_fileIo));
					m_nIoFileSize -= toWrite;

					if (m_nIoFileSize <= 0)
					{
						fclose(m_fileIo);
					}

					if (write != toWrite)
					{
						azcs |= CS_ERR;
					}
				}

				break;

			case CMD_IO_GET_SDSIZE:
			{
				iobuf[0] = iobuf[1] = 0;
				std::error_code ec;
				const std::filesystem::space_info si = std::filesystem::space(m_strConfImgPath, ec);

				if (!ec)
				{
					int res = static_cast<int>(si.capacity / (1024ll * 1024));

					if (res > 65535)
					{
						iobuf[0] = 65535;
					}
					else
					{
						iobuf[0] = res & 0xffff;
					}

					res = static_cast<int>(si.available / (1024ll * 1024));

					if (res > 65535)
					{
						iobuf[1] = 65535;
					}
					else
					{
						iobuf[1] = res & 0xffff;
					}
				}
			}
			break;

			case CMD_IO_READ_SDSIZE:
				dr_rd_data = iobuf;
				dr_wr_data = nullptr;
				dr_wcnt = 2;
				break;

			default:
				azcs |= CS_ERR;
				break;
		}

		az_set_done();
	}

	azcs &= CS_RD_MASK; // оставляем только флаги
}

void CAZBKController::az_fill_table()
{
	memset(az_assets, 0, sizeof(az_assets));
	int as_unit = 0;    // в таблицу можем поместить только первые 8 псевдодисков

	for (int unit = 0; unit < m_nMaxUnit; ++unit)
	{
		if (m_pUnits[unit].nSize == 0 || m_pUnits[unit].strFileName.empty())
		{
			continue;
		}

		/* XXX: endian */
		az_assets[as_unit].size = m_pUnits[unit].nBlkSize;
		az_assets[as_unit].date = m_pUnits[unit].nDate;
		az_assets[as_unit].time = m_pUnits[unit].nTime;
		az_assets[as_unit].flag = UF_CFG | UF_MTD;
		az_assets[as_unit].attr = m_pUnits[unit].nAttr & 0xff;
		::WideCharToMultiByte(CP_ACP, 0, m_pUnits[unit].strOrigPathName.c_str(), -1, reinterpret_cast<LPSTR>(az_assets[as_unit].name), 130, nullptr, nullptr);

		if (++as_unit >= 8)
		{
			break;
		}
	}
}


uint16_t CAZBKController::az_read_data(uint16_t *reg, uint16_t **buff, int *wcnt)
{
	uint16_t data = *reg;

	if (*buff != nullptr)
	{
		data = *reg = **buff;
		(*buff)++;

		if (--(*wcnt) == 0)
		{
			*buff = nullptr;
		}
	}

	return data;
}

void CAZBKController::az_write_data(uint16_t *reg, uint16_t data, uint16_t **buff, int *wcnt)
{
	*reg = data;

	if (*buff != nullptr)
	{
		**buff = data;
		(*buff)++;

		if (--(*wcnt) == 0)
		{
			*buff = nullptr;
		}
	}
}

bool CAZBKController::az_read_eeprom(uint16_t *buff) const
{
	if (fs::exists(m_strConfImgPath) && fs::is_directory(m_strConfImgPath))
	{
		// если директория найдена
		fs::path strAZeeprom = m_strConfImgPath / g_strAZEEPROMFileName;

		if (fs::exists(strAZeeprom))
		{
			// если такой файл есть
			CFile f;

			if (f.Open(strAZeeprom.c_str(), CFile::modeRead | CFile::shareExclusive))
			{
				UINT readed = f.Read(&buff[1], AZ_EEPROM_SIZE * sizeof(uint16_t));

				if (readed == AZ_EEPROM_SIZE * sizeof(uint16_t))
				{
					// если всё прочиталось
					buff[0] = 0;
					f.Close();
					return true;
				}

				f.Close();
			}
		}
	}

	memset(buff, 0, AZ_BUF_SIZE * sizeof(uint16_t));
	buff[0] = 1;
	return false;
}

bool CAZBKController::az_write_eeprom(uint16_t *buff) const
{
	if (fs::exists(m_strConfImgPath) && fs::is_directory(m_strConfImgPath))
	{
		// если директория найдена
		fs::path strAZeeprom = m_strConfImgPath / g_strAZEEPROMFileName;
		// создаём файл, если можем
		CFile f;

		if (f.Open(strAZeeprom.c_str(), CFile::modeCreate | CFile::modeWrite | CFile::shareExclusive))
		{
			f.Write(buff, AZ_EEPROM_SIZE * sizeof(uint16_t));
			f.Close();
			return true;
		}
	}

	return false;
}

// функция форматирования даты в формат FAT
// Двухбайтовая отметка даты имеет следующий формат:
//    биты 0–4 — день месяца, допускаются значения 1–31;
//    биты 5–8 — месяц года, допускаются значения 1–12;
//    биты 9–15 — год, считая от 1980 года («эпоха MS-DOS»), возможны значения от 0 до 127 включительно, то есть 1980–2107 годы.
uint16_t CAZBKController::rtc_fat_date(std::tm *now)
{
	const int day = now->tm_mday, month = now->tm_mon + 1, year = now->tm_year + 1900;
	return (day + (month << 5) + ((year - 1980) << 9));
}

// функция форматирования времени в формат FAT
//Двухбайтовая отметка времени имеет следующий формат:
//    биты 0–4 — счётчик секунд (по две), допустимы значения 0–29, то есть 0–58 секунд;
//    биты 5–10 — минуты, допустимы значения 0–59;
//    биты 11–15 — часы, допустимы значения 0–23.
uint16_t CAZBKController::rtc_fat_time(std::tm *now)
{
	const int hour = now->tm_hour, min = now->tm_min, sec = now->tm_sec;
	return (sec / 2 + (min << 5) + (hour << 11));
}

// функция форматирования даты в формат RT11
uint16_t CAZBKController::rtc_rt11date(std::tm *now)
{
	int mday = now->tm_mday, mmonth = now->tm_mon + 1, myear = now->tm_year + 1900;
	myear = myear + 2000 - 1972;
	uint16_t rt11date = (myear / 32) << 14 | mmonth << 10 | mday << 5 | (myear % 32);
	//printf("rtc_rt11date: a.m.d.y=%d.%d.%d.%d > 0%06o\n\r", (myear / 32), mmonth, mday, (myear % 32), rt11date);
	return rt11date;
}

// функция форматирования времени в формат RT11 для таймера 50Гц
uint32_t CAZBKController::rtc_rt11time50(std::tm *now)
{
	const int hour = now->tm_hour, min = now->tm_min, sec = now->tm_sec;
	const uint32_t rt11time = hour * 3600 * 50 + min * 60 * 50 + sec * 50;
	//printf("rtc_rt11time50: %d  -> %06o . %06o  \n\r",rt11time , (unsigned short int) (rt11time>>16) , (unsigned short int) (rt11time & 0xFFFF) );
	return rt11time;
}

// функция форматирования времени в формат RT11 для таймера 60Гц
uint32_t CAZBKController::rtc_rt11time60(std::tm *now)
{
	const int hour = now->tm_hour, min = now->tm_min, sec = now->tm_sec;
	const uint32_t rt11time = hour * 3600 * 60 + min * 60 * 60 + sec * 60;
	//printf("rtc_rt11time60: %d  -> %06o . %06o  \n\r",rt11time , (unsigned short int) (rt11time>>16) , (unsigned short int) (rt11time & 0xFFFF) );
	return rt11time;
}

void CAZBKController::az_fill_timestamp(az_timestamp *t)
{
	std::time_t tim = std::time(nullptr);   // получим текущее время.
	tim += m_timeOffset;
	std::tm *now = std::localtime(&tim);    // расшифруем в человеческую форму
	t->rt11date = rtc_rt11date(now);
	t->rt11time50h = (rtc_rt11time50(now) >> 16) & 0xffff;
	t->rt11time50l = rtc_rt11time50(now) & 0xffff;
	t->rt11time60h = (rtc_rt11time60(now) >> 16) & 0xffff;
	t->rt11time60l = rtc_rt11time60(now) & 0xffff;
	t->fatdate = rtc_fat_date(now);
	t->fattime = rtc_fat_time(now);
	t->year = now->tm_year + 1900;
	t->month = now->tm_mon + 1;
	t->day = now->tm_mday;
	t->wday = now->tm_wday;
	t->hour = now->tm_hour;
	t->min = now->tm_min;
	t->sec = now->tm_sec;
}

void CAZBKController::az_set_time()
{
	// вот так вот создаётся time_t
	std::tm tm{};  // zero initialise
	tm.tm_year = m_timestamp.year > 1900 ? m_timestamp.year - 1900 : m_timestamp.year + 2000 - 1900;
	tm.tm_mon = m_timestamp.month - 1;
	tm.tm_mday = m_timestamp.day;
	tm.tm_hour = m_timestamp.hour;
	tm.tm_min = m_timestamp.min;
	tm.tm_sec = m_timestamp.sec;

	if (m_timestamp.wday)
	{
		tm.tm_wday = m_timestamp.wday == 7 ? 0 : m_timestamp.wday;
	}

	const std::time_t tim = std::time(nullptr);   // получим текущее время.
	const std::time_t t = std::mktime(&tm);
	m_timeOffset = t - tim;
}


