#pragma once

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

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


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

#include <thread>
#include <vector>
using namespace std;

class CPlatform
{
		CScreen            *m_pScreen;          //модуль подсистемы вывода на экран
		CBkSound            m_Sound;            //модуль звуковой подсистемы

		CSpeaker            m_Speaker;          //объект пищалка
		CCovox              m_Covox;            //объект ковокс
		CEMU2149            m_AY8910;           //объект сопроцессор Ay8910-3
		CTape               m_Tape;             //объект обработчика кассет
		CScriptRunner       m_Script;           //объект обработчика скриптов
		// Эмулятор
		CDebugger           m_Debugger;         //отладчик.
		CCPU                m_CPU;              // Процессор 1801ВМ1

		std::vector<CBKMPIDevice *> m_pMPIDevices;   //список объектов БК

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

		//имена файлов, получаемых через ДнД или командную строку
		CString             m_strBinFileName;
		CString             m_strMemFileName;
		CString             m_strTapeFileName;
		CString             m_strScriptFileName;

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

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

		// Debug section
		uint16_t            m_nPreviousPC;  //предыдущее значение регистра РС
		int                 m_nGotoAddress; //адрес, на котором будет сделан отладочный останов
		bool                m_bAskForBreak; //при чтении/записи из несуществующего адреса не прерываться. (полезно для отладки и не полезно для работы эмулятора)

		int                 m_nMediaTicksPerFrame; //количество медиатактов во фрейме.
		int                 m_nCPUFreq_prev;
		int                 m_nCPUFreq;     //текущая частота работы.
		int                 m_nCPUTicks;    //текущий счётчик тактов процессора
		double              m_fMediaTicks;  //счётчик медиа тактов, происходящих за одну инструкцию
		double              m_fMemoryTicks;
		double              m_fFDDTicks;

		double              m_fCpuTickTime;
		double              m_fMemory_Mod;
		double              m_fFDD_Mod;
		double              m_fMedia_Mod;

		//переменные для эмуляции луча ЭЛТ
		uint16_t            m_nVideoAddress; //видео адрес, младшие 6 бит - счётчик строк внутри строки
		bool                m_bHgate;  //флаг отсчёта служебных видеоциклов в строке
		bool                m_bVgate;  //флаг отсчёта служебных строк
		int                 m_nVGateCounter; //дополнительный счётчик служебных строк
		int                 m_nLineCounter; //счётчик видео строк

		//переменные для медиатактов
		int                 m_nMediaTickCount;  //счётчик медиа тактов (их должно быть не более g_Config.m_nSoundSampleRate/CPU_FRAMES_PER_SECOND в текущем фрейме)
		uint8_t            *m_pSoundBuffer;     //звуковой буфер
		int                 m_nSoundBufferLen;  //длина звукового буфера
		int                 m_nBufPos;          //позиция в звуковом буфере

		bool m_bBoardFound;  //флаг, что поиск проводили
		CBK001001 *m_pBoard; //то, что нашлось, или nullptr, если такого устройства нет
		bool m_bMSTDFound;  //флаг, что поиск проводили
		CBKMSTD10 *m_pMSTD; //то, что нашлось, или nullptr, если такого устройства нет
		bool m_bFDDFound;   //флаг, что поиск проводили
		CFDDStd326 *m_pFDD; //то, что нашлось, или nullptr, если такого устройства нет
		bool m_bIRPSFound;   //флаг, что поиск проводили
		CIRPS *m_pIRPS; //то, что нашлось, или nullptr, если такого устройства нет

		uint16_t            m_nFDDCatchAddr, m_nFDDExitCatchAddr;

	public:
		struct BKKeyPrs_t
		{
			int nScanCode;  //скан код первой нажатой клавиши из всех нажатых
			bool bProcessed; //скан код обработали, можно отменять
			bool bKeyPressed; //флаг нажатия и удержания любой алфавитно-цифровой клавиши (для отключения автоповтора)
			void clear()
			{
				nScanCode = -1; //-1 - нет нажатых клавиш
				bProcessed = bKeyPressed = false;
			}
		};
		BKKeyPrs_t          m_kprs; //вектор нажатых клавиш. Значение не важно, важен факт наличия чего-либо в векторе.

		CPlatform();
		virtual ~CPlatform();
		enum BREAK_ADDRESS : int { ADDRESS_NONE = -1, GO_INTO = -2, GO_OUT = -3 };

		//выдача указателей на объекты всем желающим
		inline CBkSound     *GetBKSoundPtr()
		{
			return &m_Sound;
		}
		inline CSpeaker     *GetSpeakerPtr()
		{
			return &m_Speaker;
		}
		inline CCovox       *GetCovoxPtr()
		{
			return &m_Covox;
		}
		inline CEMU2149     *GetAY8910Ptr()
		{
			return &m_AY8910;
		}
		inline CTape        *GetTapePtr()
		{
			return &m_Tape;
		}
		inline CScriptRunner *GetScriptRunnerPtr()
		{
			return &m_Script;
		}
		inline CCPU  *GetCPUPtr()
		{
			return &m_CPU;
		}

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

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

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

		inline CString *GetStrBinFileName()
		{
			return &m_strBinFileName;
		}
		inline CString *GetStrMemFileName()
		{
			return &m_strMemFileName;
		}
		inline CString *GetStrTapeFileName()
		{
			return &m_strTapeFileName;
		}
		inline CString *GetScriptFileName()
		{
			return &m_strScriptFileName;
		}

		void                InitEmulator();
		void                InitObjects();
		void                StartPlayTape();
		void                StartPlayTape(const CString &strPath);
		void                ResetBoard(bool &bLongreset, bool bIsSuPress);
		bool                InitPlatform();
		void                ResetPlatform();

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

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

		void                RunInto();
		void                RunOver();
		void                RunOut();
		void                RunToAddr(uint16_t addr);
		void                RunCPU(bool bUnbreak = true); //запуск. по умолчанию сбрасывается отладочный приостанов
		void                StopCPU(bool bUnbreak = true); //остановка. по умолчанию сбрасывается отладочный приостанов
		inline bool         IsCPURun();
		void                BreakCPU();
		void                UnbreakCPU(int nGoto);
		inline bool         IsCPUBreaked();

		uint8_t             GetByte(uint16_t addr);
		uint16_t            GetWord(uint16_t addr);
		void                SetByte(uint16_t addr, uint8_t b);
		void                SetWord(uint16_t addr, uint16_t w);

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

		void                FrameParam();
		void                KeyboardInterrupt(uint16_t interrupt);

		inline void         EnableAskForBreak(bool bEnable)
		{
			m_bAskForBreak = bEnable;
		}
		inline bool         IsAskForBreak()
		{
			return m_bAskForBreak;
		}
		void                AccelerateCPU();
		void                SlowdownCPU();
		void                NormalizeCPU();
		bool                CanAccelerate();
		bool                CanSlowDown();
		int                 GetLowBound();
		int                 GetHighBound();
		void                SetCPUBaseFreq(int frq); //установка базовой частоты
		void                SetCPUFreq(int frq); //установка текущей частоты
		int                 GetCPUFreq(); //выдача текущей частоты
		int                 GetCPUSpeed();   //выдача текущей частоты для конфигурации

		bool                EmulateLoadTape();
		bool                EmulateSaveTape();

		void                SetInterseptAddress(uint16_t nBegin, uint16_t nEnd);
		void                EmulateFDD();

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

		CBKMPIDevice       *CreateDevice(BK_BOARD_MODEL device);

		bool                RestorePlatform(CMSFManager *pMsf, MSF_PLATFORM_DATA **pPD);
		bool                RestorePreview(CMSFManager *pMsf);
		bool                RestoreConfig(CMSFManager *pMsf);
		bool                RestoreCPURegisters(CMSFManager *pMsf);

		void                ClearProcessingFiles();

		void                MediaTick();
		void                Make_One_Screen_Cycle();
		void                DrawDebugScreen();

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

};

extern CPlatform g_Platform;

