// SCGMS includes, helpers and so on
#include "../../common/rtl/scgmsLib.h"
#include "../../common/rtl/FilterLib.h"
#include "../../common/rtl/FilesystemLib.h"
#include "../../common/rtl/referencedImpl.h"
#include "../../common/rtl/SolverLib.h"
#include "../../common/rtl/UILib.h"
#include "../../common/utils/winapi_mapping.h"
#include "../../common/utils/string_utils.h"
#include "../../common/rtl/Dynamic_Library.h"

class SCGMS_Game
{
	// typedefs
	using scgms_game_wrapper_t = void*;
	using game_create_t = scgms_game_wrapper_t(IfaceCalling *)(uint16_t config_class, uint16_t config_id, uint32_t stepping_ms, const char* log_file_path);
	using game_step_t = BOOL(IfaceCalling *)(scgms_game_wrapper_t wrapper, GUID* input_signal_ids, double* input_signal_levels, double* input_signal_times, uint32_t input_signal_count, double* bg, double* ig, double* iob, double* cob);
	using game_terminate_t = BOOL(IfaceCalling *)(scgms_game_wrapper_t wrapper);

	private:
		CDynamic_Library mSCGMS_Game_Wrapper_Lib;

		game_create_t mFnc_Create;
		game_step_t mFnc_Step;
		game_terminate_t mFnc_Terminate;

		scgms_game_wrapper_t mGame_Instance;

		std::vector<GUID> mInput_Signal_Ids;
		std::vector<double> mInput_Signal_Levels;
		std::vector<double> mInput_Signal_Times;

		struct {
			double bg;
			double ig;
			double iob;
			double cob;
		} mPatient_State;

	public:
		SCGMS_Game(uint16_t configClass, uint16_t configId, uint32_t steppingMs, const std::string& log_file_path)
		{
			if (!mSCGMS_Game_Wrapper_Lib.Load(CDynamic_Library::Append_Library_Extension("game-wrapper")))
				throw new std::runtime_error{ "Could not load SCGMS game wrapper" };

			mFnc_Create = mSCGMS_Game_Wrapper_Lib.Resolve<game_create_t>("scgms_game_create");
			mFnc_Step = mSCGMS_Game_Wrapper_Lib.Resolve<game_step_t>("scgms_game_step");
			mFnc_Terminate = mSCGMS_Game_Wrapper_Lib.Resolve<game_terminate_t>("scgms_game_terminate");

			if (mFnc_Create == nullptr || mFnc_Step == nullptr || mFnc_Terminate == nullptr)
				throw new std::runtime_error{ "Could not resolve SCGMS game wrapper interface functions" };

			mGame_Instance = mFnc_Create(configClass, configId, steppingMs, log_file_path.c_str());

			if (!mGame_Instance)
				throw new std::runtime_error{ "Could not create game instance" };
		}

		virtual ~SCGMS_Game()
		{
			Terminate();
		}

		void Schedule_Signal(const GUID& signalId, const double level, const double time)
		{
			mInput_Signal_Ids.push_back(signalId);
			mInput_Signal_Levels.push_back(level);
			mInput_Signal_Times.push_back(time);
		}

		void Schedule_Insulin_Bolus(const double level, const double time)
		{
			Schedule_Signal(scgms::signal_Requested_Insulin_Bolus, level, time);
		}

		void Schedule_Insulin_Basal_Rate(const double level, const double time)
		{
			Schedule_Signal(scgms::signal_Requested_Insulin_Basal_Rate, level, time);
		}

		void Schedule_Carb_Intake(const double level, const double time)
		{
			Schedule_Signal(scgms::signal_Carb_Intake, level, time);
		}

		void Schedule_Carb_Rescue(const double level, const double time)
		{
			Schedule_Signal(scgms::signal_Carb_Rescue, level, time);
		}

		void Schedule_Physical_Activity(const double level, const double time)
		{
			Schedule_Signal(scgms::signal_Physical_Activity, level, time);
		}

		bool Step()
		{
			if (!mGame_Instance)
				return false;

			auto result = mFnc_Step(mGame_Instance, mInput_Signal_Ids.data(), mInput_Signal_Levels.data(), mInput_Signal_Times.data(), (uint32_t)mInput_Signal_Ids.size(), &mPatient_State.bg, &mPatient_State.ig, &mPatient_State.iob, &mPatient_State.cob);

			mInput_Signal_Ids.clear();
			mInput_Signal_Levels.clear();
			mInput_Signal_Times.clear();

			return (result != FALSE);
		}

		bool Terminate()
		{
			if (!mGame_Instance)
				return false;

			auto result = mFnc_Terminate(mGame_Instance);

			mGame_Instance = nullptr;

			return (result != FALSE);
		}

		double Get_BG() const
		{
			return mPatient_State.bg;
		}

		double Get_IG() const
		{
			return mPatient_State.ig;
		}
		double Get_IOB() const
		{
			return mPatient_State.iob;
		}
		double Get_COB() const
		{
			return mPatient_State.cob;
		}
};
