#pragma once


#include "Config.h"
#include "MSFManager.h"
#include "Debugger.h"
#include "Screen.h"
#include "BkSound.h"
#include "Speaker.h"
#include "Covox.h"
#include "Menestrel.h"
#include "AYSnd.h"
#include "Tape.h"
#include "ScriptRunner.h"

#include "CPU.h"
#include "MainFrm.h"

// Целевой объект команды CPlatform
class CBKMPIDevice;
class CBK001001;
class CFDDStd326;
class CBKMSTD10;
class CIRPS;
class CAZBK;
class CFDDOptok;

//#include <thread>
// #include <mutex>
// #include <condition_variable>

constexpr DWORD CLI_KEY_B = (1 << 0);   // ключ /B - задать имя bin файла
constexpr DWORD CLI_KEY_M = (1 << 1);   // ключ /M - задать имя msf файла
constexpr DWORD CLI_KEY_T = (1 << 2);   // ключ /T - задать имя tap/wav файла
constexpr DWORD CLI_KEY_S = (1 << 3);   // ключ /S - задать имя script файла
constexpr DWORD CLI_KEY_C = (1 << 4);   // ключ /C - задать имя загружаемой конфигурации
constexpr DWORD CLI_KEY_P = (1 << 5);   // ключ /P - задать номера подключаемых страниц в конф. БК11(М)
constexpr DWORD CLI_KEY_L = (1 << 6);   // ключ /L - задать адрес загрузки bin файла
constexpr DWORD CLI_KEY_A = (1 << 7);   // ключ /A - задать адрес запуска bin файла
constexpr DWORD CLI_KEY_D = (1 << 8);   /* ключ /D - задать имя bin файла, который будет загружен с адреса,
                                        заданного ключом /L (если не задано, то по адресу из бин заголовка),
                                        и запустить его с адреса, заданного ключом /A (если не задано, то
                                        либо по адресу ключа /L, либо по адресу из бин заголовка) */
constexpr DWORD CLI_KEY_R = (1 << 9);   // ключ /R без параметров, запустить дамп после загрузки, иначе - не запускать
constexpr DWORD CLI_KEY_F = (1 << 10);  // ключ /F без параметров, загружать дамп не в формате бин.
constexpr DWORD CLI_KEY_P_PAGE0 = (1 << 16); // флаг задания параметра для окна 0 в ключе /P
constexpr DWORD CLI_KEY_P_PAGE1 = (1 << 17); // флаг задания параметра для окна 1 в ключе /P

struct CLI_PARAMETERS
{
	DWORD       nStatus;            // флаги наличия значений параметров
	fs::path    strBinFileName;     // /B
	fs::path    strMemFileName;     // /M
	fs::path    strTapeFileName;    // /T
	fs::path    strScriptFileName;  // /S
	CString     strPage0, strPage1; // номера страниц, для подключения на БК11(М) получаемые через командную строку
	uint16_t    nLoadAddr;          // /L
	uint16_t    nStartAddr;         // /A
	CLI_PARAMETERS()
		: nStatus(0)
		, nLoadAddr(0)
		, nStartAddr(0)
	{};
	void clear()
	{
		strBinFileName.clear();
		strMemFileName.clear();
		strTapeFileName.clear();
		strScriptFileName.clear();
		strPage0.Empty();
		strPage1.Empty();
		nLoadAddr = 0;
		nStartAddr = 0;
		nStatus = 0;
	}
	void clearBinFName()
	{
		strBinFileName.clear();
		nStatus &= ~CLI_KEY_B;
	}
	void clearScriptFName()
	{
		strScriptFileName.clear();
		nStatus &= ~CLI_KEY_S;
	}
};

// флаги m_nPlatformStatus
constexpr uint32_t CONFPLATFORM_11M    = (1 << 0);
constexpr uint32_t CONFPLATFORM_FDD    = (1 << 1);
constexpr uint32_t CONFPLATFORM_HDD    = (1 << 2);
constexpr uint32_t CONFPLATFORM_ALTPRO = (1 << 3);
constexpr uint32_t CONFPLATFORM_OPTOK  = (1 << 4);
constexpr uint32_t CONFPLATFORM_IRPS   = (1 << 5);
constexpr uint32_t CONFPLATFORM_AZBK   = (1 << 6);

// флаги выдачи указателей на устройства, означает что устройство уже найдено и не надо заново искать
constexpr uint32_t FOUND_BOARD = (1 << 0);
constexpr uint32_t FOUND_MSTD  = (1 << 1);
constexpr uint32_t FOUND_FDD   = (1 << 2);
constexpr uint32_t FOUND_IRPS  = (1 << 3);
constexpr uint32_t FOUND_AZBK  = (1 << 4);

class CPlatform
{
		uint32_t            m_nPlatformStatus;      // флаги наличия разных устройств, чтобы лишних действий не делать там, где не надо бы
// /------------------------------------------------
// media sound system
		std::unique_ptr<CBkSound> m_pSound;         // модуль звуковой подсистемы
		std::unique_ptr<CSpeaker> m_pSpeaker;       // объект пищалка
		std::unique_ptr<CCovox> m_pCovox;           // объект ковокс
		std::unique_ptr<CMenestrel> m_pMenestrel;   // объект менестрель
		std::unique_ptr<CAYSnd> m_pAYSnd;           // объект мульти сопроцессор Ay8910-3
		std::unique_ptr<CTape> m_pTape;             // объект обработчика кассет
		double              m_fCpuTickTime;
		mutable double      m_fMediaTicks;        // счётчик медиа тактов, происходящих за одну инструкцию
		double              m_fMedia_Mod;

		struct ThrMTVars_t
		{
			// переменные для медиатактов
			int     nMediaTicksPerFrame; // количество медиатактов во фрейме.
			int     nMediaTickCount;    // счётчик медиа тактов (их должно быть не более g_Config.m_nSoundSampleRate/CPU_FRAMES_PER_SECOND в текущем фрейме)
			std::unique_ptr<SAMPLE_INT[]> pSoundBuffer;   // звуковой буфер
			SAMPLE_INT *pBufPos;        // текущая позиция в звуковом буфере

			void clear()
			{
				nMediaTicksPerFrame = 0;
				nMediaTickCount = 0;
				pSoundBuffer = nullptr;
				pBufPos = nullptr;
			}
		};

		mutable ThrMTVars_t m_sMTV;
		void                MediaTick() const;
// /------------------------------------------------
// screen func
		CScreen            *m_pScreen;          // модуль подсистемы вывода на экран
		mutable double      m_fMemoryTicks;
		double              m_fMemory_Mod;
		void                Make_One_Screen_Cycle() const;

		struct ThrOSCVars_t
		{
			// переменные для эмуляции луча ЭЛТ
			uint16_t nVideoAddress;     // видео адрес, младшие 6 бит - счётчик строк внутри строки
			bool    bHgate;             // флаг отсчёта служебных видеоциклов в строке
			bool    bVgate;             // флаг отсчёта служебных строк
			int     nVGateCounter;      // дополнительный счётчик служебных строк
			int     nLineCounter;       // счётчик видео строк

			void clear()
			{
				nVideoAddress = 0; // видео адрес, младшие 6 бит - счётчик строк внутри строки
				bHgate = false;
				bVgate = true;
				nVGateCounter = 64;
				nLineCounter = 0; // счётчик видео строк
			}

			ThrOSCVars_t &operator = (const ThrOSCVars_t &src)
			{
				memcpy(this, &src, sizeof(ThrOSCVars_t));
				return *this;
			}
			ThrOSCVars_t &operator = (const ThrOSCVars_t *src)
			{
				memcpy(this, src, sizeof(ThrOSCVars_t));
				return *this;
			}
		};

		mutable ThrOSCVars_t m_sOSCV;

		mutable double		m_fAZVideoTicks;
		double				m_fAZVideo_mod;

// /------------------------------------------------
// periodic functions
		mutable double      m_fFDDTicks;
		double              m_fFDD_Mod;

// /------------------------------------------------

		std::unique_ptr<CScriptRunner> m_pScript; // объект обработчика скриптов
		// Эмулятор
		std::unique_ptr<CCPU> m_pCPU;           // Процессор 1801ВМ1
		std::unique_ptr<CDebugger> m_pDebugger; // отладчик.
		std::vector<std::unique_ptr<CBKMPIDevice>> m_pMPIDevices;   // список объектов БК

		CMainFrame         *m_pMainFrame;       // класс главное окно.

		// разные параметры, получаемые через ДнД или командную строку
		CLI_PARAMETERS      m_cli;

		mutable LockVarType m_bBreaked;         // флаг состояния - процессор приостановлен для отладки
		mutable LockVarType m_bRunning;         // флаг состояния - процессор работает/стоит

		int                 m_nLowBound;        // нижняя граница, меньше которой не может быть частота
		int                 m_nHighBound;       // верхняя граница, больше которой не может быть частота

		int                 m_nCPUFreq;         // текущая частота работы.
		int                 m_nCPUFreq_prev;
		int                 m_nBaseCPUFreq;     // базовая частота работы, по которой вычисляется множитель

		mutable int         m_nCPUTicks;        // текущий счётчик тактов процессора

		// Debug section
		mutable int         m_nGotoAddress;     // адрес, на котором будет сделан отладочный останов

		uint32_t            m_nFoundFlags;      // флаги, что поиск проводили
		CBK001001          *m_pBoard;           // то, что нашлось, или nullptr, если такого устройства нет
		CBKMSTD10          *m_pMSTD;            // то, что нашлось, или nullptr, если такого устройства нет
		CFDDStd326         *m_pFDD;             // то, что нашлось, или nullptr, если такого устройства нет
		CIRPS              *m_pIRPS;            // то, что нашлось, или nullptr, если такого устройства нет
		CAZBK              *m_pAZBK;            // то, что нашлось, или nullptr, если такого устройства нет

		uint16_t            m_nFDDCatchAddr, m_nFDDExitCatchAddr; // диапазон перехвата эмуляции функций дисковода
		static const int    m_arPageCodes[8];

		std::vector<WindowParam_t> m_vWindows;  // вектор окон для дампера.
		uint16_t			m_nKbdIrqReq;		// флаг, что есть запрос на прерывание пока прерывание от клавиатуры запрещено
	public:
		CPlatform();
		virtual ~CPlatform();
		enum : int
		{
			ADDRESS_NONE = -1
		};

		inline const uint32_t GetPlatformStatus() const
		{
			return m_nPlatformStatus;
		}
		// выдача указателей на объекты всем желающим
		inline CBkSound    *GetBKSoundPtr() const
		{
			return m_pSound.get();
		}
		inline CSpeaker    *GetSpeakerPtr() const
		{
			return m_pSpeaker.get();
		}
		inline CCovox      *GetCovoxPtr() const
		{
			return m_pCovox.get();
		}
		inline CMenestrel  *GetMenestrelPtr() const
		{
			return m_pMenestrel.get();
		}
		inline CAYSnd    *GetAYSndPtr() const
		{
			return m_pAYSnd.get();
		}
		inline CTape       *GetTapePtr() const
		{
			return m_pTape.get();
		}
		inline CScriptRunner *GetScriptRunnerPtr() const
		{
			return m_pScript.get();
		}
		inline CCPU        *GetCPUPtr() const
		{
			return m_pCPU.get();
		}

		void                ClearDevicesFounds();
		void                ClearDevicesVector();
		CBK001001          *GetBoardPtr();
		CFDDStd326         *GetFDDPtr();
		CBKMSTD10          *GetMSTDPtr();
		CIRPS              *GetIRPSPtr();
		CAZBK              *GetAZBKPtr();

		inline CDebugger   *GetDebuggerPtr() const
		{
			return m_pDebugger.get();
		}

		inline void         SetScreen(CScreen *scr)
		{
			m_pScreen = scr;   // задание объекта экранной подсистемы. Он создаётся в другом месте
		}

		inline CScreen     *GetScreenPtr() const
		{
			return m_pScreen;
		}

		VWinParam          *GetWndVectorPtr()
		{
			return &m_vWindows;
		}

		inline void         SetMainPtr(CMainFrame *ptr)
		{
			m_pMainFrame = ptr;
		}
		inline CMainFrame  *GetMainPtr() const
		{
			return m_pMainFrame;
		}

		inline CLI_PARAMETERS *GetCLI()
		{
			return &m_cli;
		}
		inline bool isBinFileNameSet() const
		{
			return !!(m_cli.nStatus & CLI_KEY_B);
		}
		fs::path GetStrBinFileName()
		{
			fs::path str = m_cli.strBinFileName;
			m_cli.clearBinFName();
			return str;
		}
		void                InitEmulator();
		void                InitObjects();
		bool                StartPlayTape() const;
		bool                StartPlayTape(const fs::path &strPath) const;
		void                ResetBoard(bool &bLongreset, const bool bIsSuPress);
		bool                InitPlatform();
		void                ResetPlatform();

		bool                DropFiles(CString *str);
		void                ParseCommandLineParameters(CString strCommands);
		bool                SetCmdLParameters(TCHAR command, CString &strParam);
		void                SetScript(const bool bXlat) const;
		void                StopAll();          // остановить всё - процессор, BKTIMER_UI_REFRESH
		void                StartAll();         // запустить всё - процессор, BKTIMER_UI_REFRESH
		bool                SaveMemoryState(const fs::path &strPath);
		bool                RestoreState(CMSFManager *pMsf);
		bool                LoadMemoryState(CMSFManager *pMsf);

		bool                CreateBoard(CONF_BKMODEL nConf);
		void                DeleteBoard();
		void                RunBoard();

		void                RunInto() const;
		void                RunOver();
		void                RunOut() const;
		void                RunToAddr(int addr) const;
		void                RunCPU(bool bUnbreak = true); // запуск. по умолчанию сбрасывается отладочный приостанов
		void                StopCPU(bool bUnbreak = true) const; // остановка. по умолчанию сбрасывается отладочный приостанов
		inline bool         IsCPURun() const
		{
			return m_bRunning.IsLocked();
		}

		void                BreakCPU() const;
		void                UnbreakCPU(int nGoto = ADDRESS_NONE) const;
		inline bool         IsCPUBreaked() const
		{
			return m_bBreaked.IsLocked();
		}

		uint8_t             GetByteT(const uint16_t addr, int &nTC) const;
		uint16_t            GetWordT(const uint16_t addr, int &nTC) const;
		void                SetByteT(const uint16_t addr, uint8_t b, int &nTC) const;
		void                SetWordT(const uint16_t addr, uint16_t w, int &nTC) const;
		uint8_t             GetByte(const uint16_t addr) const;
		uint16_t            GetWord(const uint16_t addr) const;
		void                SetByte(const uint16_t addr, uint8_t b) const;
		void                SetWord(const uint16_t addr, uint16_t w) const;

		uint8_t             GetByteIndirect(const uint16_t addr) const;
		uint16_t            GetWordIndirect(const uint16_t addr) const;
		void                SetByteIndirect(const uint16_t addr, uint8_t b) const;
		void                SetWordIndirect(const uint16_t addr, uint16_t w) const;

		void                FrameParam();
		void                DelayedKeyboardInterrupt();
		void                KeyboardInterrupt(const uint16_t interrupt);
		void                VectorInterrupt(const uint16_t interrupt) const;

		void                AccelerateCPU();
		void                SlowdownCPU();
		void                NormalizeCPU();
		bool                CanAccelerate() const;
		bool                CanSlowDown() const;
		int                 GetLowBound() const;
		int                 GetHighBound() const;
		void                SetCPUBaseFreq(const int frq); // установка базовой частоты
		void                ResetToCPUBaseFreq(); // установка базовой частоты
		void                SetCPUFreq(int frq); // установка текущей частоты
		int                 GetCPUFreq() const; // выдача текущей частоты
		int                 GetCPUSpeed() const;   // выдача текущей частоты для конфигурации

		void                SetMemPages(const int pg0, const int pg1);
		void                RestoreMemPages();


		bool                EmulateLoadTape();
		bool                EmulateSaveTape() const;

		bool                EmulateLoadTape11();
		bool                EmulateSaveTape11() const;

		bool                EmulateLoadTape11M();
		bool                EmulateSaveTape11M() const;

		struct LTF11Param
		{
			uint16_t abp;
			uint16_t nPageCode;
			uint16_t nSysPageCode;
			uint16_t nErrAddr;
			uint16_t nCRCAddr;
			uint16_t nFictAddr;
			uint16_t nOkExitAddr;
			uint16_t nErrExitAddr;
			bool bReadNameOnly;
			bool bFictive;
		};

		bool                LoadTapeFunc11(LTF11Param p);
		bool                SaveTapeFunc(uint16_t abp, uint16_t nPageCode, uint16_t nSysPageCode, uint16_t fileAddr, uint16_t fileSize, uint16_t &cs) const;

		void                SetInterseptAddress(uint16_t nBegin, uint16_t nEnd);
		void                SetInterseptAddressOptok(uint16_t nBegin, uint16_t nEnd);
		bool                EmulateFDD();
		bool                EmulateFDDOptok();

	protected:
		int                 CalcStep() const;
		void                StopBoard();
		void                StartBoard();

		bool                CreateDevice(const BK_DEV_MPI device);

		bool                RestorePlatform(CMSFManager *pMsf, std::unique_ptr<MSF_PLATFORM_DATA> &pPD) const;
		bool                RestorePreview(CMSFManager *pMsf) const;
		bool                RestoreConfig(CMSFManager *pMsf);
		bool                RestoreCPURegisters(CMSFManager *pMsf) const;

		void                ClearProcessingFiles();

		void                DrawDebugScreen() const;

		// поток с точным таймером
		void                TimerThreadFunc() const;  // собственно функция
		std::thread         m_TimerThread;
		mutable std::mutex          m_mutLockTimerThread; // флаг работы потока.
		mutable std::mutex          m_mutRunLock;       // блокировка работы фрейма.
		mutable volatile bool       m_bKillTimerEvent;  // флаг для остановки потока.
		void                StopTimerThread();
		bool                StartTimerThread();

};

extern CPlatform g_Platform;

