#include "pch.h"
#include "BK001001.h"
#include "FDDOptok.h"
#include "Platform.h"
#include "resource.h"

/*
Эмуляция 1818ВГ93 взята из XSpeccy: https://github.com/samstyle/Xpeccy
*/


// время перемещения на шаг для частоты 1МГц
// для 2 МГц - соответственно в 2 раза меньше
const std::array<int, 4> CFDDOptok::m_pauses = { 6000, 12000, 20000, 30000 }; // pause in ns for 1st type commands

IMPLEMENT_DYNAMIC(CFDDOptok, CFDDStd326)

CFDDOptok::CFDDOptok()
{
	vgComTab.push_back({ 0xf0, VG93_RESTORE,    &CFDDOptok::vgRest  });   // 0000xxxx - restore
	vgComTab.push_back({ 0xf0, VG93_SEEK,       &CFDDOptok::vgSeek  });   // 0001xxxx - seek
	vgComTab.push_back({ 0xe0, VG93_STEP,       &CFDDOptok::vgStep  });   // 001xxxxx - step
	vgComTab.push_back({ 0xe0, VG93_STEP_IN,    &CFDDOptok::vgStepF });   // 010xxxxx - step forward
	vgComTab.push_back({ 0xe0, VG93_STEP_OUT,   &CFDDOptok::vgStepB });   // 011xxxxx - step back
	vgComTab.push_back({ 0xe1, VG93_READ_REC,   &CFDDOptok::vgRdSec });   // 100xxxx0 - read sectors
	vgComTab.push_back({ 0xe0, VG93_WRITE_REC,  &CFDDOptok::vgWrSec });   // 101xxxxx - write sectors
	vgComTab.push_back({ 0xfb, VG93_READ_ADDR,  &CFDDOptok::vgRdAdr });   // 11000x00 - read address
	vgComTab.push_back({ 0xfb, VG93_READ_TRACK, &CFDDOptok::vgRdTrk });   // 11100x00 - read track
	vgComTab.push_back({ 0xfb, VG93_WRITE_TRACK, &CFDDOptok::vgWrTrk });  // 11110x00 - write track
	vgComTab.push_back({ 0x00, 0x00,            &CFDDOptok::vgStop  });   // othercom - do nothing
}

CFDDOptok::~CFDDOptok()
    = default;


bool CFDDOptok::InitialSetDevice()
{
	m_pMemoryMap.resize(DEC_BANKS_NUM);
	m_nMemorySize = 0100000;

	if (m_pMemoryMap.data())
	{
		InitMemory(BK_PORTSIO_AREA + 020);    // Инициализация модуля памяти
		MemoryManager();                // Начальная инициализация
		m_pFloppy[FDD_DRIVE::A] = std::make_unique<CFloppyVG93>();
		m_pFloppy[FDD_DRIVE::B] = std::make_unique<CFloppyVG93>();
		SetHD(false); // выключаем HD режим
		m_pFloppy[0]->SetGeometry(FLOPPY_TRACKS + 3, FLOPPY_SIDES, 9, FLOPPY_SECTOR_TYPE); // у оптока формат - 9 секторов на дорожке
		m_pFloppy[1]->SetGeometry(FLOPPY_TRACKS + 3, FLOPPY_SIDES, 9, FLOPPY_SECTOR_TYPE);
		return true;
	}

	g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	return false;
}

bool CFDDOptok::Interception() const
{
	return g_Platform.EmulateFDDOptok();
}

bool CFDDOptok::InitDevice(const uint16_t addrStart)
{
	// инициализация карты памяти
	bool bRet = OnInitDevice();

	if (bRet)
	{
		g_Platform.SetInterceptAddressOptok(0126260, 0126346);
	}

	Reset();
	return bRet;
}

bool CFDDOptok::OnInitDevice()
{
	static const std::array<CBKMPIDevice::BoardRomList_t, FDDRomListSize> m_arrFDDOptokRomList =
	{
		{
			// инициализируем список загружаемых ПЗУ
			{ IDS_INI_FDR_OPTOK, KNGMD_FDDROM_BNK }
		}
	};
	// инициализация карты памяти
	return InitMemoryModules(m_arrFDDOptokRomList);
}

bool CFDDOptok::IsEngineOn() const
{
	return m_motor;
}

void CFDDOptok::SetHD(bool bhd)
{
	m_hd = bhd;
	m_nByteDelay = bhd ? 16000 : 32000;
	dynamic_cast<CFloppyVG93 *>(m_pFloppy[0].get())->flp_set_hd(bhd);
	dynamic_cast<CFloppyVG93 *>(m_pFloppy[1].get())->flp_set_hd(bhd);
}

void CFDDOptok::OnReset()
{
	CFDDStd326::OnReset();
	m_regCSR = 0177777;
	vgReset();
}

bool CFDDOptok::GetByteT(const uint16_t addr, uint8_t *pValue, int &nTC)
{
	uint16_t w{};
	bool b = GetWordT(addr, &w, nTC);
	*pValue = ((addr & 1) ? (w >> 8) : w) & 0377;
	return b;
}

bool CFDDOptok::GetWordT(const uint16_t addr, uint16_t *pValue, int &nTC)
{
	const BKMEMBank_t &memoryBank = m_pMemoryMap[KNGMD_FDDROM_BNK];

	// если бит 15 == 0, то ПЗУ по адресу 120000
	if (!(m_regCSR & 0100000) && (ADDRESS_TO_BANK(addr) == 5))
	{
		nTC = memoryBank.nTimingCorrection;
		// причём, первые 4кб ПЗУ дублируются во вторые 4 кб, когда drq==1
		uint16_t mask = m_drq ? 007776 : 017776;
		*pValue = *(reinterpret_cast<uint16_t *>(&m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & mask)]));
		return true;
	}

	// кусочек ПЗУ по адресам 177600-177630 должен быть доступен всегда
	if (0177600 <= addr && addr < m_nBKPortsIOArea)
	{
		nTC = memoryBank.nTimingCorrection;
		*pValue = *(reinterpret_cast<uint16_t *>(&m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & 017776)]));
		return true;
	}

	if (addr >= m_nBKPortsIOArea)
	{
		// затем проверка на попадание в регистры
		const uint16_t a = addr & 0177776;
		bool bRet = false;
		uint8_t res = 0;

		switch (a)
		{
			case FDC_CSR:
				res = 0; // m_regCSR; читаться должен всегда 0, иначе логика работы нарушается.
				bRet = true;
				break;

			case FDC_COM:
				m_status &= ~(VG93_STAT_NOT_READY | VG93_STAT_BUSY);

				if (!(m_pDrive && m_pDrive->isAttached()))
				{
					m_status |= VG93_STAT_NOT_READY;
				}

				if (!m_idle)
				{
					m_status |= VG93_STAT_BUSY;
				}

				if (m_fmode == 0)
				{
					m_status &= ~(VG93_STAT_WPROT | VG93_STAT_HLD | VG93_STAT_TRACK0 | VG93_STAT_INDEX);

					if (m_pDrive && m_pDrive->isReadOnly())
					{
						m_status |= VG93_STAT_WPROT;
					}

					if (m_motor)
					{
						m_status |= VG93_STAT_HLD;
					}

					if (m_pDrive && dynamic_cast<CFloppyVG93 *>(m_pDrive)->isTrack00())
					{
						m_status |= VG93_STAT_TRACK0;
					}

					if (m_pDrive && m_pDrive->isAttached() && m_motor && m_pDrive->isIndex())
					{
						m_status |= VG93_STAT_INDEX;
					}
				}
				else if (m_fmode == 1)
				{
					m_status &= ~VG93_STAT_DRQ;

					if (m_drq)
					{
						m_status |= VG93_STAT_DRQ;
					}
				}

				res = m_status & 0xff;
				bRet = true;
				break;

			case FDC_TRK:
				res = m_track;
				bRet = true;
				break;

			case FDC_SEC:
				res = m_sector;
				bRet = true;
				break;

			case FDC_DATA:
				res = m_data;
				m_drq = false;
				bRet = true;
				break;
		}

		if (bRet)   // если попали в порты - то корректируем тайминг
		{
			*pValue = ~res & 0377;
			nTC = REG_TIMING_CORR_VALUE;
			return true;
		}
	}

	nTC = 0; // иначе возвращаем ничего
	*pValue = 0;
	return false;
}

uint16_t CFDDOptok::GetDebugStatus() const
{
	uint16_t status = m_status;
	status &= ~(VG93_STAT_NOT_READY | VG93_STAT_BUSY);

	if (!(m_pDrive && m_pDrive->isAttached()))
	{
		status |= VG93_STAT_NOT_READY;
	}

	if (!m_idle)
	{
		status |= VG93_STAT_BUSY;
	}

	if (m_fmode == 0)
	{
		status &= ~(VG93_STAT_WPROT | VG93_STAT_HLD | VG93_STAT_TRACK0 | VG93_STAT_INDEX);

		if (m_pDrive && m_pDrive->isReadOnly())
		{
			status |= VG93_STAT_WPROT;
		}

		if (m_motor)
		{
			status |= VG93_STAT_HLD;
		}

		if (m_pDrive && dynamic_cast<CFloppyVG93 *>(m_pDrive)->isTrack00())
		{
			status |= VG93_STAT_TRACK0;
		}

		if (m_pDrive && m_pDrive->isAttached() && m_motor && m_pDrive->isIndex())
		{
			status |= VG93_STAT_INDEX;
		}
	}
	else if (m_fmode == 1)
	{
		status &= ~VG93_STAT_DRQ;

		if (m_drq)
		{
			status |= VG93_STAT_DRQ;
		}
	}

	return status & 0xff;
}


bool CFDDOptok::SetByteT(const uint16_t addr, uint8_t value, int &nTC)
{
	auto w = uint16_t(value);

	if (addr & 1)
	{
		w <<= 8;
	}

	return SetWordT(addr, w, nTC);
}

bool CFDDOptok::SetWordT(const uint16_t addr, uint16_t value, int &nTC)
{
	bool bRet = false;

	if (addr >= m_nBKPortsIOArea)
	{
		const uint16_t a = addr & 0177776;
		uint8_t val = ~value & 0377;

		switch (a)
		{
			case FDC_CSR:
				m_regCSR = value;

				if (g_Platform.GetBoardPtr())
				{
					if (m_regCSR & 0100000)
					{
						// включаем ПЗУ бейсика БК10
						g_Platform.GetBoardPtr()->BasicRomOFF(false);
					}
					else
					{
						// выключаем ПЗУ бейсика БК10
						g_Platform.GetBoardPtr()->BasicRomOFF(true);
					}
				}

				if (!(m_regCSR & (1 << 10))) // если бит 10 == 0
				{
					m_pDrive = m_pFloppy[0].get(); // то выбран привод 0
				}
				else if (!(m_regCSR & (1 << 13))) // если бит 13 == 0
				{
					m_pDrive = m_pFloppy[1].get(); // то выбран привод 1
				}
				else
				{
					m_pDrive = nullptr;     // иначе - никакой привод не выбран
				}

				m_side = (m_regCSR & (1 << 11)) ? 0 : 1; // если бит 11 == 0, выбор стороны 1, если 1, то стороны 0
				bRet = true;
				break;

			case FDC_COM:
				m_debugCMD = val;
				vgExec(val);
				bRet = true;
				break;

			case FDC_TRK:
				m_track = val;
				bRet = true;
				break;

			case FDC_SEC:
				m_sector = val;
				bRet = true;
				break;

			case FDC_DATA:
				m_data = val;
				m_drq = false;
				bRet = true;
				break;
		}
	}

	nTC = bRet ? REG_TIMING_CORR_VALUE : 0;
	return bRet;
}

bool CFDDOptok::GetByteIndirect(const uint16_t addr, uint8_t *pValue)
{
	uint16_t w{};
	bool b = CFDDStd326::GetWordIndirect(addr, &w);
	*pValue = ((addr & 1) ? (w >> 8) : w) & 0377;
	return b;
}

bool CFDDOptok::GetWordIndirect(const uint16_t addr, uint16_t *pValue)
{
	const BKMEMBank_t &memoryBank = m_pMemoryMap[KNGMD_FDDROM_BNK];

	// если бит 15 == 0, то ПЗУ по адресу 120000
	if (!(m_regCSR & 0100000) && (ADDRESS_TO_BANK(addr) == 5))
	{
		// причём, первые 4кб ПЗУ дублируются во вторые 4 кб, когда drq==1
		uint16_t mask = m_drq ? 007776 : 017776;
		*pValue = *(reinterpret_cast<uint16_t *>(&m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & mask)]));
		return true;
	}

	// кусочек ПЗУ по адресам 177600-177630 должен быть доступен всегда
	if (0177600 <= addr && addr < m_nBKPortsIOArea)
	{
		*pValue = *(reinterpret_cast<uint16_t *>(&m_pMemory[static_cast<size_t>(memoryBank.nOffset) + (addr & 017776)]));
		return true;
	}

	if (addr >= m_nBKPortsIOArea)
	{
		// затем проверка на попадание в регистры
		const uint16_t a = addr & 0177776;
		bool bRet = false;
		uint8_t res = 0;

		switch (a)
		{
			case FDC_CSR:
				*pValue = m_regCSR; //тут читаем содержимое.
				return true;

			case FDC_COM:
				res = m_status & 0xff;
				bRet = true;
				break;

			case FDC_TRK:
				res = m_track;
				bRet = true;
				break;

			case FDC_SEC:
				res = m_sector;
				bRet = true;
				break;

			case FDC_DATA:
				res = m_data;
				bRet = true;
				break;
		}

		if (bRet)   // если попали в порты - то корректируем тайминг
		{
			*pValue = ~res;
			return true;
		}
	}

	// иначе возвращаем ничего
	*pValue = 0;
	return false;
}


void CFDDOptok::Periodic()
{
	if (m_pDrive)
	{
		fdcSync(32000);
	}
}


//////////////////////////////////////////////////////////////////////////
// 1818ВГ93

void CFDDOptok::fdcSync(int ns)
{
	if (m_plan.empty())
	{
		return;    // fdc does nothing
	}

	m_wait -= ns;

	while ((m_wait < 0) && (!m_plan.empty()))
	{
		if (m_plan[m_pos])
		{
			(this->*m_plan[m_pos])();
		}
		else
		{
			m_plan.clear();
			break; // сразу выйдем из цикла, не будем проверять условия цикла
		}
	}
}

// add byte to CRC
void CFDDOptok::add_crc_16(uint8_t val)
{
	unsigned int tkk = m_crc ^ (val << 8);

	for (int i = 8; i; i--)
	{
		if ((tkk <<= 1) & 0x10000)
		{
			tkk ^= 0x1021;
		}
	}

	m_crc = tkk & 0xffff;
}

// read FCRC from disk (hi-low)
void CFDDOptok::rdFCRC()
{
	m_fcrc = (dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpRd() << 8);
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
	m_fcrc |= dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpRd();
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
}

// save CRC to disk (hi-low)
void CFDDOptok::wrCRC()
{
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpWr((m_crc >> 8) & 0xff);
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpWr(m_crc & 0xff);
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
}

// ищем данные заголовка ADR
// выход: 0 : не нашли ADR, 1 : нашли ADR, 2 : зона индексного отверстия IDX
int CFDDOptok::waitADR()
{
	m_wait += m_nByteDelay;

	if (dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext())
	{
		return 2;   // зона индексного отверстия IDX
	}

	if (dynamic_cast<CFloppyVG93 *>(m_pDrive)->getField() == 1)
	{
		return 1;   // зона данных заголовка
	}

	return 0;       // 0:всё остальное
}

// ждём маркер заголовка ADR и читаем его в буфер BUF[4] и его CRC
// выход: 0 : не нашли ADR, 1 : нашли ADR, прочитали и посчитали CRC, 2 : зона индексного отверстия IDX
int CFDDOptok::seekADR()
{
	int res = waitADR();

	if (res == 1) // если нашли
	{
		m_crc = 0xcdb4; // читаем и считаем CRC
		add_crc_16(0xfe);

		for (int i = 0; i < 4; i++)
		{
			m_buf[i] = dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpRd();
			add_crc_16(m_buf[i]);
			dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
		}

		rdFCRC();
		m_wait += (6 * m_nByteDelay);    // delay : 6 byte reading
	}

	return res;
}

// send byte FDC->Floppy (wr commands)
bool CFDDOptok::vgSendByte()
{
	if (m_drq)
	{
		// time to write byte, but isn't get it from CPU
		m_status |= VG93_STAT_LOST_DATA;
		m_wait += m_nByteDelay;
		return false;
	}

	add_crc_16(m_data);
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpWr(m_data);
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
	m_drq = true;
	m_wait += m_nByteDelay;
	return true;
}

// get byte Floppy->FDC (rd commands)
bool CFDDOptok::vgGetByte()
{
	if (m_drq)
	{
		m_status |= VG93_STAT_LOST_DATA;    // data lost - time to read next byte, but previous is not transfered
		m_wait += m_nByteDelay;
		return false;
	}

	m_data = dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpRd();
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
	m_drq = true;
	m_wait += m_nByteDelay;
	return true;
}

//!!! ошибки: Оказалось, что в любой момент текущий привод может перестать им быть
//!!! а алгоритмы этого не предусматривают и тут вообще не делается проверок.

// wait fdc.cnt ms & spin flop if motor is on
void CFDDOptok::vgwait()
{
	m_cnt -= m_nByteDelay;

	if (m_cnt < 0)
	{
		m_pos++;
	}
	else
	{
		m_wait += m_nByteDelay;

		if (m_motor)
		{
			dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
		}
	}
}

// idle : wait 15 IDX pulses & stop motor
void CFDDOptok::vgstp00()
{
	m_idle = true;

	if (m_motor && (m_pDrive && m_pDrive->isAttached()))
	{
		m_cnt = 15;
		m_pos++;
	}
	else
	{
		m_plan.clear();
	}
}

void CFDDOptok::vgstp01()
{
	m_wait += m_nByteDelay;

	if (dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext())
	{
		// turn floppy to next byte, check IDX
		m_cnt--;

		if (m_cnt < 1)
		{
			// check 15 IDX pulses
			m_motor = false;  // and off
			m_plan.clear();
		}
	}
}

void CFDDOptok::vgStop()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgstp00);
	m_plan.push_back(&CFDDOptok::vgstp01);
	m_plan.push_back(nullptr);
	m_pos = 0;
}

// ================================
// check head pos (h=1, v=1, motor)

// prepare : check if check is on, if disk inserted, set IDX count to 9
void CFDDOptok::vgchk00()
{
	if ((m_com & (CMD_H | CMD_V)) != (CMD_H | CMD_V))
	{
		// if (h=0 || v=0), stops
		vgStop();
	}
	else if (!(m_pDrive && m_pDrive->isAttached()))
	{
		// if no disk, seek error, stop
		m_status |= VG93_STAT_SEEK_ERROR;
		vgStop();
	}
	else
	{
		m_cnt = 9;        // try seek ADR in 9 spins
		m_pos++;
	}
}

// read ADR mark in cnt spins, change side every IDX, read ADR to buf
void CFDDOptok::vgchk01()
{
	int res = seekADR();

	if (res == 2) // зона индексного отверстия
	{
		//fdc.side ^= 1;
		m_cnt--;

		if (m_cnt < 1)
		{
			m_status |= VG93_STAT_SEEK_ERROR;   // SEEK error
			vgStop();
		}
	}
	else if ((res == 1) && (m_buf[0] == m_track))
	{
		// если заголовок прочитан и дорожка та, что надо
		m_pos++;
	}
}

// check CRC
void CFDDOptok::vgchk02()
{
	if (m_crc != m_fcrc)
	{
		m_status |= VG93_STAT_CRC_ERROR;    // crc error
	}

	m_pos++;
}

// prepare, seek/read ADR, check CRC, stop
void CFDDOptok::vgchk()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgchk00);
	m_plan.push_back(&CFDDOptok::vgchk01);
	m_plan.push_back(&CFDDOptok::vgchk02);
	m_plan.push_back(&CFDDOptok::vgStop);
	m_pos = 0;
}

// =======
// restore

// prepare, do start delay (h=1)
void CFDDOptok::vgres00()
{
	m_fmode = 0;
	m_track = 0xff;
	m_cnt = 1000000;      // delay for BV

	if (m_com & CMD_H)
	{
		// if h=1 : start motor, pause 15 ms
		m_wait += 15000;
		m_motor = true;
	}

	m_pos++;
}

// do step in until TRK0 or Rtrk=0
void CFDDOptok::vgres01()
{
	if ((dynamic_cast<CFloppyVG93 *>(m_pDrive)->isTrack00()) || (m_track == 0))
	{
		if (!dynamic_cast<CFloppyVG93 *>(m_pDrive)->isTrack00())
		{
			m_status |= VG93_STAT_SEEK_ERROR;
		}

		m_track = 0;
		m_pos++;
	}
	else
	{
		m_wait += m_pauses[m_com & CMD_SPD];
		m_track--;
		dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpStep(FLP_STEP::FLP_BACK);
	}
}

void CFDDOptok::vgRest()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgres00);
	m_plan.push_back(&CFDDOptok::vgwait);
	m_plan.push_back(&CFDDOptok::vgres01);
	m_plan.push_back(&CFDDOptok::vgchk);
	m_pos = 0;
}

// ====
// seek

void CFDDOptok::vgseek00()
{
	m_fmode = 0;
	m_cnt = 1000000;      // 1ms delay for BV :)

	if (m_com & CMD_H)
	{
		m_wait += 15000;
		m_motor = true;

		if (m_pDrive)
		{
			m_pDrive->PrepareTrack(m_track, m_side);
		}
	}

	m_pos++;
}

void CFDDOptok::vgseek01()
{
	if (m_track == m_data)
	{
		m_wait += m_pauses[m_com & CMD_SPD];
		m_pos++;
	}
	else if (m_track < m_data)
	{
		dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpStep(FLP_STEP::FLP_FORWARD);
		m_track++;
		m_wait += m_nByteDelay;
	}
	else
	{
		dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpStep(FLP_STEP::FLP_BACK);
		m_track--;
		m_wait += m_nByteDelay;
	}
}

void CFDDOptok::vgSeek()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgseek00);
	m_plan.push_back(&CFDDOptok::vgwait);
	m_plan.push_back(&CFDDOptok::vgseek01);
	m_plan.push_back(&CFDDOptok::vgchk);
	m_pos = 0;
}

// ===========================
// step/step forward/step back

void CFDDOptok::vgstpf()
{
	m_step = FLP_STEP::FLP_FORWARD;
	m_pos++;
}

void CFDDOptok::vgstpb()
{
	m_step = FLP_STEP::FLP_BACK;
	m_pos++;
}

void CFDDOptok::vgstep()
{
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpStep(m_step);
	m_wait += m_pauses[m_com & CMD_SPD];

	if (m_com & CMD_U)
	{
		if (m_step == FLP_STEP::FLP_FORWARD)
		{
			m_track++;
		}
		else
		{
			m_track--;
		}
	}

	m_pos++;
}

void CFDDOptok::vgStepF()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgseek00);
	m_plan.push_back(&CFDDOptok::vgstpf);
	m_plan.push_back(&CFDDOptok::vgstep);
	m_plan.push_back(&CFDDOptok::vgchk);
	m_pos = 0;
}

void CFDDOptok::vgStepB()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgseek00);
	m_plan.push_back(&CFDDOptok::vgstpb);
	m_plan.push_back(&CFDDOptok::vgstep);
	m_plan.push_back(&CFDDOptok::vgchk);
	m_pos = 0;
}

void CFDDOptok::vgStep()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgseek00);
	m_plan.push_back(&CFDDOptok::vgstep);
	m_plan.push_back(&CFDDOptok::vgchk);
	m_pos = 0;
}

// ============
// read sectors

// prepare
void CFDDOptok::vgrds00()
{
	m_fmode = 1;

	if (m_pDrive && m_pDrive->isAttached())
	{
		m_pDrive->PrepareTrack(m_track, m_side);
		m_motor = true;

		if (m_com & CMD_E)
		{
			m_wait += 15000;    // if (e=1) pause 15ms
		}

		m_cnt = 5;                // seek sector in 5 spins
		m_pos++;
	}
	else
	{
		vgStop();
	}
}

// seek right sector ADR
void CFDDOptok::vgrds01()
{
	int res = seekADR();

	if (res == 2)
	{
		m_cnt--;

		if (m_cnt < 1)
		{
			m_status |= VG93_STAT_NOT_FOUND;    // sector not found
			vgStop();
		}
	}
	else if (res == 1)
	{
		if ((m_buf[0] == m_track) && (m_buf[2] == m_sector))
		{
			// check TRK,SEC
			if ((~m_com & CMD_C) || (m_buf[1] == ((m_com & CMD_S) ? 1 : 0)))
			{
				// check HEAD (s, if c=1)
				if (m_crc != m_fcrc)
				{
					// check CRC
					m_status |= VG93_STAT_CRC_ERROR;    // ADR crc error
					vgStop();
				}
				else
				{
					m_cnt = 52;                   // DATA must be in next (22 + 30) bytes
					m_pos++;                      // ADR found, next step
				}
			}
		}
	}
}

// seek sector DATA in next CNT bytes, else - array not found
void CFDDOptok::vgrds02()
{
	if (m_cnt > 0)
	{
		uint8_t tmp = dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpRd();
		dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
		m_wait += m_nByteDelay;

		if ((dynamic_cast<CFloppyVG93 *>(m_pDrive)->getField() == 2) || (dynamic_cast<CFloppyVG93 *>(m_pDrive)->getField() == 3))
		{
			m_buf[4] = tmp;
			m_pos++;
			m_wait = 0;
		}

		m_cnt--;
	}
	else
	{
		m_status |= VG93_STAT_NOT_FOUND;    // sector not found (array not found)
		vgStop();
	}
}

// init crc, set sector size
void CFDDOptok::vgrds03()
{
	if (m_buf[4] == 0xf8)
	{
		m_status |= VG93_STAT_REC_TYPE;
	}

	m_crc = 0xcdb4;
	add_crc_16(m_buf[4]);                   // add DATA mark (F8 | FB)
	m_cnt = (0x80 << (m_buf[3] & 3));   // sector size (128,256,512,1024)
	m_drq = false;
	m_wait += m_nByteDelay;
	m_pos++;
}

// transfer CNT bytes flp->cpu
void CFDDOptok::vgrds04()
{
	if (vgGetByte())
	{
		add_crc_16(m_data);
		m_cnt--;

		if (m_cnt < 1)
		{
			m_wait += m_nByteDelay;
			m_pos++;
		}
	}
	else
	{
		m_wait = 0;
	}
}

// end: read, compare CRC, check multisector, stop
void CFDDOptok::vgrds05()
{
	rdFCRC();
	m_wait += (2 * m_nByteDelay);

	if (m_crc != m_fcrc)
	{
		m_status |= VG93_STAT_CRC_ERROR;
		m_pos++;
	}
	else if (m_com & CMD_M)
	{
		m_sector++;
		m_pos = 1;
		m_cnt = 5;
	}
	else
	{
		m_pos++;
	}
}

void CFDDOptok::vgRdSec()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgrds00);  // подготовка
	m_plan.push_back(&CFDDOptok::vgrds01);  // поиск сектора 5 оборотов диска
	m_plan.push_back(&CFDDOptok::vgrds02);  // поиск начала данных сектора
	m_plan.push_back(&CFDDOptok::vgrds03);  // подготовка CRC
	m_plan.push_back(&CFDDOptok::vgrds04);  // чтение данных сектора
	m_plan.push_back(&CFDDOptok::vgrds05);  // чтение и проверка CRC, проверка на мультисекторную операцию
	m_plan.push_back(&CFDDOptok::vgStop);       // останов двигателя
	m_pos = 0;
}
// =============
// write sectors

// check write protect
void CFDDOptok::vgwrs00()
{
	if (m_pDrive->isReadOnly())
	{
		m_status |= VG93_STAT_WPROT;
		vgStop();
	}
	else
	{
		m_drq = true;
		m_pos++;
	}
}

// init crc, set F8/F9 (need roll flp back), prepare to write data
void CFDDOptok::vgwrs01()
{
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpPrev();     // back to DATA mark
	m_crc = 0xcdb4;
	uint8_t tmp = (m_com & CMD_A) ? 0xf8 : 0xfb;
	add_crc_16(tmp);
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpWr(tmp);
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext();
	m_cnt = 0x80 << (m_buf[3] & 3); // sector size
	m_wait += m_nByteDelay;
	m_pos++;
}


// write CNT bytes
void CFDDOptok::vgwrs02()
{
	if (vgSendByte())
	{
		m_cnt--;

		if (m_cnt < 1)
		{
			m_drq = false;
			m_pos++;
		}
	}
	else
	{
		// надо  обнулить, а то зацикливается
		m_wait = 0;
	}
}

// write crc, check multisector, end
void CFDDOptok::vgwrs03()
{
	wrCRC();
	m_wait += (2 * m_nByteDelay);

	if (m_com & CMD_M)
	{
		m_sector++;
		m_pos = 2;
		m_cnt = 5;
	}
	else
	{
		m_pos++;
	}
}

void CFDDOptok::vgWrSec()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgrds00);  // подготовка.
	m_plan.push_back(&CFDDOptok::vgwrs00);  // проверка на защиту от записи
	m_plan.push_back(&CFDDOptok::vgrds01);  // ищем нужный сектор 5 оборотов диска
	m_plan.push_back(&CFDDOptok::vgrds02);  // ищем начало данных сектора
	m_plan.push_back(&CFDDOptok::vgwrs01);  // корректируем маркер данных и подготовка CRC
	m_plan.push_back(&CFDDOptok::vgwrs02);  // запись сектора
	m_plan.push_back(&CFDDOptok::vgwrs03);  // запись CRC, проверка на мультисекторную операцию
	m_plan.push_back(&CFDDOptok::vgStop);       // останов двигателя
	m_pos = 0;
}

// ============
// read address

void CFDDOptok::vgrda00()
{
	int res = waitADR();

	if (res == 2)
	{
		m_cnt--;

		if (m_cnt < 1)
		{
			m_status |= VG93_STAT_NOT_FOUND;    // sector not found
			vgStop();
		}
	}
	else if (res == 1)
	{
		m_fcrc = 0;
		m_crc = 0xcdb4;
		add_crc_16(0xfe);               // add ADR mark
		m_cnt = 6;                    // send 6 bytes FDC->CPU
		m_drq = false;
		m_wait += m_nByteDelay;
		m_pos++;
	}
}

// read CNT bytes, last 2 bytes is CRC
void CFDDOptok::vgrda01()
{
	if (vgGetByte())
	{
		m_cnt--;

		switch (m_cnt)
		{
			case 0:
				m_fcrc |= (m_data & 0xff);
				m_wait += m_nByteDelay;
				m_pos++;
				break;

			case 1:
				m_fcrc = (m_data << 8) & 0xff00;
				break;

			case 5:
				m_sector = m_data;
				[[fallthrough]];

			default: add_crc_16(m_data);
				break;
		}
	}
}

void CFDDOptok::vgRdAdr()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgrds00);
	m_plan.push_back(&CFDDOptok::vgrda00);
	m_plan.push_back(&CFDDOptok::vgrda01);
	m_plan.push_back(&CFDDOptok::vgchk02);
	m_plan.push_back(&CFDDOptok::vgStop);
	m_pos = 0;
}

// ==========
// read track

// wait IDX
void CFDDOptok::vgrdt00()
{
	m_wait += m_nByteDelay;

	if (dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext())
	{
		m_drq = false;
		m_pos++;
	}
}

void CFDDOptok::vgrdt01()
{
	if (vgGetByte())
	{
		m_data = ~m_data;

		if (dynamic_cast<CFloppyVG93 *>(m_pDrive)->getDataPtr() == 0)
		{
			m_pos++;    // end of trk (same as IDX)
		}
	}
}

void CFDDOptok::vgRdTrk()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgrds00);
	m_plan.push_back(&CFDDOptok::vgrdt00);
	m_plan.push_back(&CFDDOptok::vgrdt01);
	m_plan.push_back(&CFDDOptok::vgStop);
	m_pos = 0;
}

// ==========
// write track

void CFDDOptok::vgwrt00()
{
	m_wait += m_nByteDelay;

	if (dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpNext())
	{
		m_pos++;
		m_wait = m_nByteDelay;
	}
}

void CFDDOptok::vgwrt01()
{
	//m_data = ~m_data; // если при записи дорожки диск будет портиться - значит надо инвертировать
	if (vgSendByte())
	{
		if (dynamic_cast<CFloppyVG93 *>(m_pDrive)->getDataPtr() == 0)
		{
			m_drq = false;
			m_pos++;
		}
	}
}

void CFDDOptok::vgwrt02()
{
	dynamic_cast<CFloppyVG93 *>(m_pDrive)->flpFillFields(true);
	m_pos++;
}

void CFDDOptok::vgWrTrk()
{
	m_plan.clear();
	m_plan.push_back(&CFDDOptok::vgrds00);
	m_plan.push_back(&CFDDOptok::vgwrs00);
	m_plan.push_back(&CFDDOptok::vgwrt00);
	m_plan.push_back(&CFDDOptok::vgwrt01);
	m_plan.push_back(&CFDDOptok::vgwrt02);
	m_plan.push_back(&CFDDOptok::vgStop);
	m_pos = 0;
}

void CFDDOptok::vgExec(uint8_t com)
{
#ifdef _DEBUG
	DecodeCommand(com);
#endif

	if ((com & 0xf0) == VG93_FORCE_INTR)
	{
		m_wait = 0;
		vgStop();
	}
	else if (m_idle)
	{
		// if FDC is idle
		m_com = com;

		for (auto &idx : vgComTab)
		{
			// idx.mask != 0
			if ((com & idx.mask) == idx.val)
			{
				(this->*idx.plan)(); // заполняем вектор цепочкой команд
				m_wait = 0;
				m_idle = false;
				m_status = 0;
				return;
			}
		}

		TRACE("OPTOK:: Неизвестная команда, не выполняем: 0%o\n", com);
	}
}

void CFDDOptok::vgReset()
{
	m_track = 0;
	m_sector = 0;
	m_data = 0;
	m_status = 0;
	m_idle = false;
	m_drq = false;
	m_side = 0;
	m_plan.clear();
	m_pos = 0;
	m_wait = -1;
	m_fmode = 0;
	m_cnt = 0;
}

#ifdef _DEBUG
void CFDDOptok::DecodeCommand(uint8_t com)
{
	std::string str;
	// расшифровка команд, чтобы было понятней

	switch (com & 0300)
	{
		case 0000:
			if (com & 040)
			{
				str = ("Шаг");
			}
			else
			{
				if (com & 020)
				{
					str = ("Поиск");
				}
				else
				{
					str = ("Восстановление");
				}
			}

			break;

		case 0100:
			if (com & 040)
			{
				str = ("Шаг назад");
			}
			else
			{
				str = ("Шаг вперёд");
			}

			break;

		case 0200:
			if (com & 040)
			{
				str = ("Запись сектора");
			}
			else
			{
				str = ("Чтение сектора");
			}

			if (com & 020)
			{
				str += (", мультисекторная");
			}

			str += (", сторона ");
			str += (com & 010) ? ("1") : ("0");

			if (com & 4)
			{
				str += ("\n\tзадержка 15мс перед выдачей HLD после появления HRDY");
			}

			if (com & 2)
			{
				str += ("\n\tнужна проверка стороны");
			}

			str += ("\n\tкод ");
			str += (com & 1) ? ("F8") : ("FB");
			break;

		case 0300:
			switch (com & 060)
			{
				case 0000:
					str = ("Чтение адреса");
					break;

				case 0020:
					str = ("Принудительное прерывание");
					break;

				case 0040:
					str = ("Чтение дорожки");
					break;

				case 0060:
					str = ("Запись дорожки");
					break;
			}

			if ((com & 060) == 020)
			{
				if ((com & 017) == 0)
				{
					str += (" текущей команды, INTRQ не выдаётся");
				}
				else
				{
					if (com & 1)
					{
						str += ("\n\tпосле перехода сигнала CPRDY из низкого уровня в высокий");
					}

					if (com & 2)
					{
						str += ("\n\tпри переходе сигнала CPRDY из высокого уровня в низкий");
					}

					if (com & 4)
					{
						str += ("\n\tпо приходу индексного импульса ~IP");
					}

					if (com & 8)
					{
						str += ("\n\tнемедленно");
					}

					str += ("\n\tINTRQ выдаётся");
				}
			}
			else
			{
				if (com & 4)
				{
					str += ("\n\tзадержка 15мс перед выдачей HLD после появления HRDY");
				}
			}

			break;
	}

	switch (com & 0340)
	{
		case 0040:
		case 0100:
		case 0140:
			if (com & 020)
			{
				str += ("\n\tРг.Дор изменяется на один бит");
			}
			else
			{
				str += ("\n\tРг.Дор не изменяется");
			}

			[[fallthrough]];

		case 0000:
			if (com & 010)
			{
				str += ("\n\tголовка устанавливается на диск");
			}
			else
			{
				str += ("\n\tголовка не устанавливается");
			}

			if (com & 04)
			{
				str += ("\n\tпроверка номера дорожки");
			}
			else
			{
				str += ("\n\tдорожка не проверяется");
			}

			str += ("\n\tзадержка ");

			switch (com & 3)
			{
				case 0:
					str += ("6мс");
					break;

				case 1:
					str += ("126мс");
					break;

				case 2:
					str += ("20мс");
					break;

				case 3:
					str += ("30мс");
					break;
			}

			break;
	}

	TRACE("OPTOK debug:: %0o - %s\n", com, str.c_str());
}
#endif
