#include "pch.h"

#include "AZBK.h"
#include "AZBK\AZBK_Network.h"
#include "BKMessageBox.h"
#include "Ini.h"
#include "MFCStrUtil.h"
#include "Platform.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()
{
	g_strConfImgPath = g_Config.GetConfCurrPath() / g_strAZBKSDCardDir; // предустановленный путь.
	m_pAZIni = std::make_unique<CIni>();

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

CAZBKController::~CAZBKController()
{
	for (int unit = 0; unit < m_nMaxUnit; ++unit)
	{
		m_pUnits[unit].Close();
	}

	m_pUnits.reset();
	m_pAZIni->Clear();
}

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

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


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

	if (!strVal.IsEmpty())
	{
		volatile ULONGLONG m = std::stoull(strVal.GetString(), nullptr, 16);
		auto b = reinterpret_cast<volatile 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.data());

	if (AZNetwork::getMACAddress(buf, g_Config.m_strAZNetworkAdapter))
	{
		volatile ULONGLONG m = 0;
		auto b = reinterpret_cast<volatile 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) const
{
	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(const uint8_t *buf, int nOffset) const
{
	CString str;
	const 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
{
	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.data());

	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(const uint8_t *pMemory)
{
	m_pAZMainMemory = pMemory;

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

	const fs::path strAZini = g_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() && isLogoKey(strKey.GetAt(0)))
		{
			// есть логотип, теперь надо выделить и сформировать путь.
			AZ_UNIT file;

			// если читается, то читаем как есть, иначе - ничего не делаем
			if (file.Open(fs::path(strValue.GetString()), AZIO_OPEN_RD))
			{
				const auto len = file.getSize();
				const auto readed = file.ReadFile(m_pAZMainMemory + 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 (isRomKey(strKey.GetAt(0)))
			{
				const int nSlot = Global::ToInt(strKey.Mid(1));

				if (0 <= nSlot && nSlot < 64)
				{
					const int nOffset = 256 * 1024 + nSlot * 4 * 1024;
					// теперь надо выделить и сформировать путь.
					AZ_UNIT file;
					// это для обычных, setup rom могут быть гораздо большей длины
					constexpr auto MaxRomSize = 020000u;

					if (file.Open(fs::path(strValue.GetString()), AZIO_OPEN_RD))
					{
						const auto len = file.getSize();
						const auto readed = file.ReadFile(m_pAZMainMemory + nOffset, len);
						file.Close();

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

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

	ReadDisksFromIni();

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

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

void CAZBKController::ReadDisksFromIni()
{
	CString strKey, strValue;
	// первый проход, сперва узнаем количество дисков, и их номера
	m_nMaxUnit = 0;

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

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

	m_nMaxUnit++; // сделаем не номер последнего юнита, а их количество (допускаются пустые слоты)

	// и не допустим, чтобы их количество превышало максимум
	// т.е. в списке может быть сколько угодно, но обрабатываются только номера с 0 по 31
	if (m_nMaxUnit > AZ_UNIT_NUMBER)
	{
		m_nMaxUnit = AZ_UNIT_NUMBER;
	}

	if (m_pUnits)
	{
		m_pUnits.reset();
	}

	m_pUnits = std::make_unique<AZ_UNIT[]>(m_nMaxUnit);

	if (m_pUnits)
	{
		// второй проход, заполняем массив юнитов
		if (m_pAZIni->FindKeyStart(g_strAZIniDiskSection, strKey, strValue))
		{
			do
			{
				ProcessDiskFromIni(strKey, strValue);
			}
			while (m_pAZIni->FindKeyNext(strKey, strValue));
		}
	}
	else
	{
		// TODO
		ASSERT(false);
	}
}


bool CAZBKController::isRomKey(const WCHAR ch) const
{
	return ch == _T('R') || ch == _T('r');
}

bool CAZBKController::isLogoKey(const WCHAR ch) const
{
	return ch == _T('L') || ch == _T('l');
}

bool CAZBKController::isDiskKey(const WCHAR ch) const
{
	return ch == _T('D') || ch == _T('d');
}


bool CAZBKController::ProcessDiskFromIni(const CString &strKey, const CString &strValue) const
{
	// strKey - это номер слота. т.е. надо оттуда его выделить
	if (isDiskKey(strKey.GetAt(0)))
	{
		const int nNum = Global::ToInt(strKey.Mid(1));

		if (nNum < m_nMaxUnit)
		{
			// просто заполняем пути, без открытия файла
			m_pUnits[nNum].setFulPathName(strValue.GetString());
			m_pUnits[nNum].FillFileParameters(); // получим параметры файла, если он есть
			return true;
		}
	}

	return false;
}

CString CAZBKController::RepackDiskKey(const CString &strKey) const
{
	CString strRet;

	if (isDiskKey(strKey.GetAt(0)))
	{
		const int nNum = Global::ToInt(strKey.Mid(1));
		strRet.Format(_T("D%d"), nNum);
	}

	return strRet;
}

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)
			//{
			//  m_pUnits[unit].Close();
			//} НЕ будем ка закрывать.
			// инициализируем временные переменные
			dr_rd_data = dr_wr_data = nullptr;
			az_init_wcnt = AZ_IOBUF_SIZE_W;
			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_IOBUF_SIZE_W; // инициализируем значение при каждой команде , кроме CMD_READ_BUF
		}

		switch (azcmd)
		{
			case CMD_SETUNI:
			case CMD_SETBLK:
			case CMD_SETBLKX:
				switch (azcmd)
				{
					case CMD_SETUNI:
					{
						const int curr_unit = azunit; // текущий открытый образ
						azunit = azdr & AZ_UNIT_MASK; // получаем номер нового образа

						if (0 <= azunit && azunit < m_nMaxUnit) // если верный
						{
							azblkn = 0; // инициализируем блок

							if (azunit != curr_unit) // если новый, то открываем, если такой же, то ничего не делаем
							{
								if (0 <= curr_unit && curr_unit < m_nMaxUnit) // закрываем текущий открытый образ
								{
									m_pUnits[curr_unit].Close();
								}

								const auto nFlags = (m_pUnits[azunit].isReadOnly()) ? AZIO_OPEN_RD : AZIO_OPEN_RW;

								if (!m_pUnits[azunit].Open(nFlags))
								{
									azcs |= CS_ERR;
								}
							}
						}
						else
						{
							// если номер неверный, то возвращаем старый
							azunit = curr_unit;
							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 (0 > azunit || azunit >= m_nMaxUnit)
				{
					azcs |= CS_ERR;
					break;
				}

				size = m_pUnits[azunit].getBlkSize();

				if (size <= azblkn)
				{
					azcs |= CS_ERR;
					break;
				}

				curr_rd_buf = az_iobuf.data();

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

						dr_rd_data = az_iobuf.data();
						dr_wr_data = nullptr;
						dr_wcnt = 1;
						break;

					case CMD_GET_SIZEX:
						az_iobuf[0] = size & AZ_LSB_MASK;
						az_iobuf[1] = size >> 16;
						dr_rd_data = az_iobuf.data();
						dr_wr_data = nullptr;
						dr_wcnt = 2;
						break;

					case CMD_READ:  // прочитать в буфер блок
						if (!m_pUnits[azunit].ReadBlock(azblkn, az_iobuf.data()))
						{
							azcs |= CS_ERR;
						}

						break;

					case CMD_WRITE: // записать блок из буфера
						if (!m_pUnits[azunit].isReadOnly())
						{
							if (m_pUnits[azunit].WriteBlock(azblkn, az_iobuf.data()))
							{
								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 = az_iobuf.data();
				dr_rd_data = nullptr;
				dr_wcnt = az_init_wcnt;
				az_iobuf.fill(0);
				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.data());
				break;

			case CMD_HFS_OPEN_CAT:
				if (!az_open_catHFS())
				{
					azcs |= CS_ERR;
				}

				break;

			case CMD_HFS_READ_CATREC:
				if (!az_read_itemHFS())
				{
					azcs |= CS_ERR;
				}

				break;

			case CMD_HFS_MOUNT_UNIT:
				if (!az_mount_unit())
				{
					azcs |= CS_ERR;
				}

				break;

			case CMD_HFS_UMOUNT_UNIT:
				if (!az_umount_unit())
				{
					azcs |= CS_ERR;
				}

				break;

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

			case CMD_GET_EEPROM:
				if (!az_read_eeprom(az_cmosmem.data()))
				{
					azcs |= CS_ERR;
				}

				break;

			case CMD_GET_IOBLOCK:
				dr_wr_data = nullptr;
				dr_rd_data = az_cmosmem.data();
				dr_wcnt = AZ_IOBUF_SIZE_W;
				break;

			case CMD_PUT_IOBLOCK:
				dr_wr_data = az_cmosmem.data();
				dr_rd_data = nullptr;
				dr_wcnt = AZ_IOBUF_SIZE_W;
				az_cmosmem.fill(0);
				break;

			case CMD_PUT_EEPROM:
				if (!az_write_eeprom(az_cmosmem.data()))
				{
					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.data()));
				break;

			case CMD_NET_READ_IP:
				dr_rd_data = ipaddrblock.data();
				dr_wr_data = nullptr;
				dr_wcnt = AZ_IPBUF_SIZE;
				break;

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

			case CMD_SCREENSHOT:
				if (!cmd044func())
				{
					azcs |= CS_ERR;
				}

				break;

			case CMD_IO_SET_RDFILE:
				m_azFileIORD.setFulPathName(MFCStrUtil::Str2WideStr(reinterpret_cast<char *>(az_cmosmem.data()), AZ_IOBUF_SIZE_W * 2).GetString());
				m_nIoStatus = 1;
				break;

			case CMD_IO_SET_WRFILE:
				m_azFileIOWR.setFulPathName(MFCStrUtil::Str2WideStr(reinterpret_cast<char *>(az_cmosmem.data()), AZ_IOBUF_SIZE_W * 2).GetString());
				m_nIoStatus = -1;
				break;

			case CMD_IO_GET_FILESTAT:
				switch (m_nIoStatus)
				{
					case 1:
					{
						if (m_azFileIORD.Open(AZIO_OPEN_RD))
						{
							*(reinterpret_cast<uint32_t *>(az_cmosmem.data())) = m_nIoFileSizeRd = m_azFileIORD.getSize();
						}
						else
						{
							m_nIoFileSizeRd = 0;
							*(reinterpret_cast<uint32_t *>(az_cmosmem.data())) = (1 << 31) | 4;
						}

						dr_rd_data = az_cmosmem.data();
						dr_wr_data = nullptr;
						dr_wcnt = 2;
					}
					break;

					case -1:
						*(reinterpret_cast<uint32_t *>(az_cmosmem.data())) = m_nIoFileSizeWr = 0;

						if (!m_azFileIOWR.Open(AZIO_OPEN_CR | AZIO_OPEN_WR))
						{
							*(reinterpret_cast<uint32_t *>(az_cmosmem.data())) = (1 << 31) | 7;
						}

						dr_rd_data = az_cmosmem.data();
						dr_wr_data = nullptr;
						dr_wcnt = 2;
						break;

					default:
						azcs |= CS_ERR;
				}

				break;

			case CMD_IO_READ:
				if (m_nIoFileSizeRd > 0)
				{
					const int toRead = (m_nIoFileSizeRd > AZIO_BLK_SIZE) ? AZIO_BLK_SIZE : m_nIoFileSizeRd;
					const int read = m_azFileIORD.ReadFile(reinterpret_cast<uint8_t *>(az_cmosmem.data()), toRead);
					m_nIoFileSizeRd -= toRead;

					if (m_nIoFileSizeRd <= 0)
					{
						m_azFileIORD.Close();
					}

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

				break;

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

			case CMD_IO_WRITE:
				if (m_nIoFileSizeWr > 0)
				{
					const int toWrite = (m_nIoFileSizeWr > AZIO_BLK_SIZE) ? AZIO_BLK_SIZE : m_nIoFileSizeWr;
					const auto write = m_azFileIOWR.WriteFile(reinterpret_cast<uint8_t *>(az_cmosmem.data()), toWrite);
					m_nIoFileSizeWr -= toWrite;

					if (m_nIoFileSizeWr <= 0)
					{
						m_azFileIOWR.Close();
					}

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

				break;

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

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

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

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

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

			case CMD_IO_READ_SDSIZE:
				dr_rd_data = az_sizecard.data();
				dr_wr_data = nullptr;
				dr_wcnt = 2;
				break;

			case CMD_IO_READ_FILE:
			{
				// получаем адрес для чтения
				const int nReadAddr = *(reinterpret_cast<int *>(az_cmosmem.data()));

				if (m_nIoFileSizeRd > 0)
				{
					const int read = m_azFileIORD.ReadFile(m_pAZMainMemory + nReadAddr * 2, m_nIoFileSizeRd);
					m_azFileIORD.Close();

					if (read == m_nIoFileSizeRd)
					{
						break;
					}
				}

				azcs |= CS_ERR;
			}
			break;

			default:
				azcs |= CS_ERR;
				break;
		}

		az_set_done();
	}

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

void CAZBKController::az_fill_table()
{
	az_assets.fill({});

	for (int as_unit = 0; as_unit < AZ_UNIT_NUMBER; ++as_unit)
	{
		if (as_unit >= m_nMaxUnit)
		{
			break;
		}

		if (m_pUnits[as_unit].getBlkSize() && !m_pUnits[as_unit].getFulPathName().empty())
		{
			/* XXX: endian */
			az_assets[as_unit].size = m_pUnits[as_unit].getBlkSize();
			az_assets[as_unit].date = m_pUnits[as_unit].getDate();
			az_assets[as_unit].time = m_pUnits[as_unit].getTime();
			az_assets[as_unit].flag = UF_CFG | UF_MTD;
			az_assets[as_unit].attr = m_pUnits[as_unit].getAttr() & 0xff;
			MFCStrUtil::WideStr2Str(m_pUnits[as_unit].getFulPathName().c_str(), std::span{az_assets[as_unit].name.begin(), AZ_FNAME_BUFSIZE - 2});
		}
	}
}


bool CAZBKController::az_mount_unit() const
{
	// в azbuf - строка == ключ в секции = путь, всё в точности как в ини файле.
	// нужно разбить строку на ключ, значение, добавить в ини, и перечитать секцию
	// и сделать flushini
	CString strCmd = MFCStrUtil::Str2WideStr(reinterpret_cast<LPCCH>(az_iobuf.data()), AZ_FNAME_BUFSIZE * 2);
	int pos = strCmd.Find(_T('='));

	if (pos >= 0)
	{
		CString strKey = strCmd.Left(pos++);
		strKey.Trim();
		strKey = RepackDiskKey(strKey);
		CString strValue = strCmd.Mid(pos);
		strValue.Trim();

		if (m_pAZIni->SetValueString(g_strAZIniDiskSection, strKey, strValue))
		{
			if (ProcessDiskFromIni(strKey, strValue))
			{
				m_pAZIni->FlushIni();
				return true;
			}
		}
	}

	return false;
}

bool CAZBKController::az_umount_unit() const
{
	// в azbuf - номер диска == ключ в секции
	// нужно найти в ини файле этот ключ, удалить его и сделать flushini
	CString strKey = MFCStrUtil::Str2WideStr(reinterpret_cast<LPCCH>(az_iobuf.data()), 4);
	strKey = RepackDiskKey(strKey);

	if (isDiskKey(strKey.GetAt(0)))
	{
		const int nNum = Global::ToInt(strKey.Mid(1));

		if (nNum < m_nMaxUnit)
		{
			if (m_pAZIni->DeleteKey(g_strAZIniDiskSection, strKey))
			{
				m_pAZIni->FlushIni();
				m_pUnits[nNum].Close();
				m_pUnits[nNum].setFulPathName(_T(""));
				return true;
			}
		}
	}

	return false;
}

bool CAZBKController::az_open_catHFS()
{
	// в azbuf - путь.
	const CString str = MFCStrUtil::Str2WideStr(reinterpret_cast<LPCCH>(az_iobuf.data()), AZ_FNAME_BUFSIZE * 2);
	return m_DirHFS.OpenDir(str.GetString());
}

bool CAZBKController::az_read_itemHFS()
{
	// поместить в azbuf очередную запись о файле
	auto p = reinterpret_cast<az_HFSitem *>(az_iobuf.data());
	memset(p, 0, sizeof(az_HFSitem));
	return m_DirHFS.ReadDir(p);
}

uint16_t CAZBKController::az_read_data(uint16_t *reg, uint16_t **buff, int *wcnt) const
{
	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) const
{
	*reg = data;

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

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

bool CAZBKController::az_read_eeprom(uint16_t *buff) const
{
	memset(buff, 0, AZ_IOBUF_SIZE_W * sizeof(uint16_t));
	buff[0] = 1;

	if (fs::exists(g_strConfImgPath) && fs::is_directory(g_strConfImgPath))
	{
		// если директория найдена
		const fs::path strAZeeprom = g_strConfImgPath / g_strAZEEPROMFileName;

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

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

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

	return false;
}

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

		if (f.Open(strAZeeprom.c_str(), CFile::modeCreate | CFile::modeWrite | CFile::shareExclusive))
		{
			f.Write(buff, AZ_EEPROM_SIZE_W * 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(const std::tm *now) const
{
	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(const std::tm *now) const
{
	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(const std::tm *now) const
{
	const int mday = now->tm_mday, mmonth = now->tm_mon + 1, myear = now->tm_year + 1900 + 2000 - 1972;
	const 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(const std::tm *now) const
{
	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(const std::tm *now) const
{
	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) const
{
	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;
}


