#include "mumble.h"

#include <engine/shared/config.h>

#include <generated/protocol.h>

#include <game/client/gameclient.h>

// clang-format off
// needs to be kept after other includes
#include "mumblelinkedmem.h"
// clang-format on

CMumble::CMumble()
{
	m_pLinkedMem = nullptr;
#ifdef _WIN32
	HANDLE hMapObject = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"MumbleLink");
	if(hMapObject == NULL)
		return;

	m_pLinkedMem = (LinkedMem *)MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinkedMem));
	if(m_pLinkedMem == NULL)
	{
		CloseHandle(hMapObject);
		hMapObject = NULL;
		return;
	}
#else
	char memname[256];
	snprintf(memname, 256, "/MumbleLink.%d", getuid());

	int shmfd = shm_open(memname, O_RDWR, S_IRUSR | S_IWUSR);

	if(shmfd < 0)
		return;

	m_pLinkedMem = (LinkedMem *)(mmap(NULL, sizeof(struct LinkedMem), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0));

	if(m_pLinkedMem == (void *)(-1))
	{
		m_pLinkedMem = NULL;
		return;
	}
#endif
	OnReset();
}

void CMumble::OnStateChange(int NewState, int OldState)
{
	if(!m_pLinkedMem)
		return;
	LinkedMem *pLinkedMem = (LinkedMem *)m_pLinkedMem;

	if(NewState != IClient::STATE_ONLINE && OldState == IClient::STATE_ONLINE)
	{
		if(pLinkedMem->uiVersion != 2)
		{
			wcsncpy(pLinkedMem->name, L"CMClient", 256);
			wcsncpy(pLinkedMem->description, L"CMClient for DDNet/Teeworlds.", 2048);
			pLinkedMem->uiVersion = 2;
		}
		pLinkedMem->uiTick++;
		OnReset();
	}
}

void CMumble::OnReset()
{
	if(!m_pLinkedMem)
		return;
	LinkedMem *pLinkedMem = (LinkedMem *)m_pLinkedMem;
	pLinkedMem->fAvatarPosition[0] = 0.0f;
	pLinkedMem->fAvatarPosition[1] = 0.0f;
	pLinkedMem->fAvatarPosition[2] = 0.0f;
	pLinkedMem->fAvatarFront[0] = 0.0f;
	pLinkedMem->fAvatarFront[1] = 0.0f;
	pLinkedMem->fAvatarFront[2] = 0.0f;
	pLinkedMem->fAvatarTop[0] = 0.0f;
	pLinkedMem->fAvatarTop[1] = 0.0f;
	pLinkedMem->fAvatarTop[2] = 0.0f;
	pLinkedMem->fCameraPosition[0] = 0.0f;
	pLinkedMem->fCameraPosition[1] = 0.0f;
	pLinkedMem->fCameraPosition[2] = 0.0f;
	pLinkedMem->fCameraFront[0] = 0.0f;
	pLinkedMem->fCameraFront[1] = 0.0f;
	pLinkedMem->fCameraFront[2] = 0.0f;
	pLinkedMem->fCameraTop[0] = 0.0f;
	pLinkedMem->fCameraTop[1] = 0.0f;
	pLinkedMem->fCameraTop[2] = 0.0f;
	memset(pLinkedMem->identity, 0, sizeof(pLinkedMem->identity));
	memset(pLinkedMem->context, 0, sizeof(pLinkedMem->context));
	pLinkedMem->context_len = 0;
}

void CMumble::OnNewSnapshot()
{
	if(!m_pLinkedMem || Client()->State() != IClient::STATE_ONLINE)
		return;
	LinkedMem *pLinkedMem = (LinkedMem *)m_pLinkedMem;

	if(pLinkedMem->uiVersion != 2)
	{
		wcsncpy(pLinkedMem->name, L"CMClient", 256);
		wcsncpy(pLinkedMem->description, L"CMClient for DDNet/Teeworlds.", 2048);
		pLinkedMem->uiVersion = 2;
	}
	pLinkedMem->uiTick++;

	const CNetObj_Character *pChar = &GameClient()->m_Snap.m_aCharacters[GameClient()->m_aLocalIds[g_Config.m_ClDummy]].m_Cur;
	const vec2 Camera = GameClient()->m_Camera.m_Center;

	// assume 4 game tiles is 1 meter
	// y axis is inverted
	pLinkedMem->fAvatarPosition[0] = (float)pChar->m_X / (32.0f * 4.0f);
	pLinkedMem->fAvatarPosition[1] = -(float)pChar->m_Y / (32.0f * 4.0f);
	pLinkedMem->fAvatarPosition[2] = 0.0f;

	// player points at the screen
	pLinkedMem->fAvatarFront[0] = 0.0f;
	pLinkedMem->fAvatarFront[1] = 0.0f;
	pLinkedMem->fAvatarFront[2] = -1.0f;

	pLinkedMem->fAvatarTop[0] = 0.0f;
	pLinkedMem->fAvatarTop[1] = 1.0f;
	pLinkedMem->fAvatarTop[2] = 0.0f;

	// assume 4 game tiles is 1 meter
	// y axis is inverted
	pLinkedMem->fCameraPosition[0] = Camera.x / (32.0f * 4.0f);
	pLinkedMem->fCameraPosition[1] = -Camera.y / (32.0f * 4.0f);
	pLinkedMem->fCameraPosition[2] = -1.0f;

	// camera points into the world
	pLinkedMem->fCameraFront[0] = 0.0f;
	pLinkedMem->fCameraFront[1] = 0.0f;
	pLinkedMem->fCameraFront[2] = 1.0f;

	pLinkedMem->fCameraTop[0] = 0.0f;
	pLinkedMem->fCameraTop[1] = 1.0f;
	pLinkedMem->fCameraTop[2] = 0.0f;

	memset(pLinkedMem->identity, 0, sizeof(pLinkedMem->identity));
	int identityP = 0;
	CGameClient::CClientData *pClient = &GameClient()->m_aClients[GameClient()->m_aLocalIds[g_Config.m_ClDummy]];
	char aBuffer[1024];
	str_format(aBuffer, 1024, "%i\n%s\n%s\n%i %i %i %i %i\n", pClient->ClientId(), pClient->m_aName, pClient->m_aClan, pClient->m_Team, GameClient()->m_Teams.Team(pClient->ClientId()), pClient->m_Solo, pClient->m_Paused, pClient->m_Spec);
	const char *pBuffer = &aBuffer[0];
	while(*pBuffer)
	{
		pLinkedMem->identity[identityP] = (wchar_t)str_utf8_decode(&pBuffer);
		if(++identityP >= 255)
			return;
	}
	memset(pLinkedMem->context, 0, sizeof(pLinkedMem->context));
	const char *pAddr = Client()->ConnectAddressString();
	pLinkedMem->context_len = str_copy((char *)pLinkedMem->context, pAddr, sizeof(pLinkedMem->context));
}
