/*
 * Copyright (C) 2012-2026 MotionSystems
 *
 * This file is part of ForceSeatMI SDK.
 *
 * www.motionsystems.eu
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#define _USE_MATH_DEFINES

#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <string>
#include <chrono>
#include <thread>

#pragma comment(lib, "Winmm.lib")

#include "ForceSeatMI_Functions.h"

namespace
{
	static const int64_t INTERVAL = 4;

	static double CalcSin(double t_s, double amplitude, double hz)
	{
		return amplitude * sin((2.0 * M_PI) * hz * t_s);
	}

	static double Deg2Rad(double deg)
	{
		return deg * (M_PI / 180.0);
	}

	class Timer
	{
	public:
		typedef std::chrono::high_resolution_clock Clock;

		Timer()
		{
			m_start = Clock::now();
		}

		int64_t Elapsed_ms() const
		{
			auto now = Clock::now();
			std::chrono::duration<double, std::milli> elapsed(now - m_start);
			return static_cast<int64_t>(elapsed.count());
		}

		double Elapsed_s() const
		{
			auto now = Clock::now();
			std::chrono::duration<double> elapsed(now - m_start);
			return elapsed.count();
		}

		void Wait(int64_t duration)
		{
			if (duration > 0)
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(duration));
			}
		}

	private:
		Clock::time_point m_start;
	};
}

static void work(FSMI_Handle api)
{
	// This is needed to increase accuracy of Thread.Sleep. You can find more
	// about it in MSDN.
	timeBeginPeriod(1);

	printf("Giving time for the client to connect...\n");
	Sleep(3000);

	// Communication structure
	FSMI_TopTablePositionPhysical pos;
	FSMI_SFX sfx;

	// Demo data
	unsigned int sfxIterator = 0;

	// Prepare structure by clearing it and setting correct size
	memset(&pos, 0, sizeof(pos));
	pos.structSize = sizeof(pos);
	memset(&sfx, 0, sizeof(sfx));
	sfx.structSize = sizeof(sfx);

	// The demo program is able to provide pause, position and speed limit
	pos.mask     = FSMI_POS_BIT_STATE | FSMI_POS_BIT_POSITION | FSMI_POS_BIT_MAX_SPEED;
	pos.maxSpeed = 65535;

	// Let's start the motion
	ForceSeatMI_BeginMotionControl(api);

	printf("SIM started...\n");
	printf("Press 'q' to exit\n");

	// Configure SFX
	// Level 2 is supported by all motion platforms, Level 3 and 4 are supported by "QS" motion platforms.
	sfx.effects[0].type = FSMI_SFX_EffectType_SinusLevel2; 
	sfx.effects[0].area = FSMI_SFX_AreaFlags_FrontLeft;
	sfx.effects[0].amplitude = 0.05f;
	sfx.effects[0].frequency = 0;
	sfx.effectsCount = 1;

	Timer stopWatch;

	while (GetKeyState('Q') == 0)
	{
		pos.state = FSMI_STATE_NO_PAUSE;

		// Fill demo data
		pos.pitch = 0; // in rad
		pos.roll  = (float)CalcSin(stopWatch.Elapsed_s(), Deg2Rad(5), 0.5/*hz*/);
		pos.yaw   = 0; // in rad
		pos.sway  = 0; // in mm
		pos.surge = 0; // in mm
		pos.heave = 0; // in mm
	
		sfx.effects[0].frequency = sfxIterator / 10;

		ForceSeatMI_SendTopTablePosPhy3(api, &pos, &sfx, nullptr, nullptr);

		// Change values somehow
		++sfxIterator;
		if (sfxIterator >= 500)
		{
			sfxIterator = 0;
		}


		// Without timeBeginPeriod(1), this sleep is not accurate (due to Windows scheduler policy).
		// For more advanced application (e.g. with UI), this code should be moved to background 
		// task/thread and calling intervals should be controlled more precisely.
		stopWatch.Wait(INTERVAL);
	}
	ForceSeatMI_EndMotionControl(api);
	printf("SIM ended...\n");

	timeEndPeriod(1);
}

int main(int argc, wchar_t* argv[])
{
	FSMI_Handle api = ForceSeatMI_Create();
	if (! api)
	{
		wprintf(L"ForceSeatMI DLL was not loaded, so the game will not send anything to the ForceSeatPM\n");
		return -1;
	}

	// Activate appropriate ForceSeatPM profile
	ForceSeatMI_SetAppID(api, ""); // If you have dedicated app id, remove ActivateProfile calls from your code
	ForceSeatMI_ActivateProfile(api, "SDK - Positioning");

	work(api);
	ForceSeatMI_Delete(api);
	return 0;
}
