// Copyright 2016-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#define MUMBLE_ALLOW_DEPRECATED_LEGACY_PLUGIN_API
#include "mumble_legacy_plugin.h"

#include "mumble_positional_audio_main.h"  // Include standard positional audio header.
#include "mumble_positional_audio_utils.h" // Include positional audio header for special functions, like "escape".

#include <cmath>

// Offset values can be obtained from:
//   http://xivapp.com/api/structures?patchVersion=latest&platform=x86
//   http://xivapp.com/api/structures?patchVersion=latest&platform=x64
// Y is North/South, South is increasing values
// X is East/West, West is increasing values
// Heading is in radians, 0 faces south

#ifdef FFXIV_USE_x64
// Memory offsets
const procptr_t camera_ptr    = 0x1673350;
const procptr_t avatar_ptr    = 0x1674950;
const procptr_t state_offset  = 0x1641990;
const procptr_t map_id_offset = 0x16409E8;
// Avatar struct offsets
const procptr_t identity_offset       = 48;  // Name
const procptr_t avatar_pos_offset     = 176; // X, Z, Y
const procptr_t avatar_azimuth_offset = 192; // Heading (-pi to pi)
// Camera struct offsets
const procptr_t camera_is_free_offset   = 272; // 0: First person mode; 1: 3rd person
const procptr_t camera_pos_offset       = 80;  // X, Z, Y
const procptr_t camera_azimuth_offset   = 304; // (-pi to pi)
const procptr_t camera_elevation_offset = 308; // (-pi to pi)
// Module names
const wchar_t *exe_name = L"ffxiv_dx11.exe";
// Plugin long description
static const std::wstring longdesc() {
	return std::wstring(
		L"Supports Final Fantasy XIV X64 version 2016.11.11.0000.0000 with context and identity support.");
}
// Plugin short description
static std::wstring description(L"Final Fantasy XIV X64 (2016.11.11.0000.0000)");
#else
// Memory offsets
const procptr_t camera_ptr    = 0x1045C40;
const procptr_t avatar_ptr    = 0x10468EC;
const procptr_t state_offset  = 0x1048C60;
const procptr_t map_id_offset = 0x10210B0;
// Avatar struct offsets
const procptr_t identity_offset       = 48;  // Name
const procptr_t avatar_pos_offset     = 160; // X, Z, Y
const procptr_t avatar_azimuth_offset = 176; // Heading (-pi to pi)
// Camera struct offsets
const procptr_t camera_is_free_offset   = 256; // 0: First person mode; 1: 3rd person
const procptr_t camera_pos_offset       = 64;  // X, Z, Y
const procptr_t camera_azimuth_offset   = 288; // (-pi to pi)
const procptr_t camera_elevation_offset = 292; // (-pi to pi)
// Module names
const wchar_t *exe_name = L"ffxiv.exe";
// Plugin long description
static const std::wstring longdesc() {
	return std::wstring(L"Supports Final Fantasy XIV version 2016.11.11.0000.0000 with context and identity support.");
}
// Plugin short description
static std::wstring description(L"Final Fantasy XIV (2016.11.11.0000.0000)");
#endif
// Plugin short name
static std::wstring shortname(L"Final Fantasy XIV");

static int fetch(float *avatar_pos, float *avatar_front, float *avatar_top, float *camera_pos, float *camera_front,
				 float *camera_top, std::string &context, std::wstring &identity) {
	for (int i = 0; i < 3; i++) {
		avatar_pos[i] = avatar_front[i] = avatar_top[i] = camera_pos[i] = camera_front[i] = camera_top[i] = 0.0f;
	}

	// Boolean value to check if game addresses retrieval is successful
	bool ok;
	// Create containers to stuff our raw data into, so we can convert it to Mumble's coordinate system
	float avatar_pos_corrector[3], camera_pos_corrector[3], avatar_azimuth, camera_azimuth, camera_elevation;
	// Values for extra features
	char player[64];
	unsigned int map_id;
	// State
	unsigned char state, camera_is_free;

	// Retrieve Avatar and Camera addresses
#ifdef FFXIV_USE_x64
	procptr_t avatar_address = peekProcPtr(pModule + avatar_ptr);
	procptr_t camera_address = peekProcPtr(pModule + camera_ptr);
#else
	procptr_t avatar_address = peekProcPtr(pModule + avatar_ptr);
	procptr_t camera_address = peekProcPtr(pModule + camera_ptr);
#endif
	if (!avatar_address || !camera_address)
		return false;

	// Peekproc and assign game addresses to our containers, so we can retrieve positional data
	ok = peekProc(pModule + state_offset, &state, 1)
		 && // Magical state value: 0 or 255 when in main menu and 1 when in-game.
		 peekProc(camera_address + camera_pos_offset, &camera_pos_corrector, 12)
		 &&                                                                      // Camera Position values (X, Z and Y).
		 peekProc(camera_address + camera_azimuth_offset, &camera_azimuth, 4) && // Camera azimuth float.
		 peekProc(camera_address + camera_elevation_offset, &camera_elevation, 4) && // Camera elevation float.
		 peekProc(camera_address + camera_is_free_offset, &camera_is_free, 1) &&     // Camera is in first person mode
		 peekProc(avatar_address + avatar_pos_offset, &avatar_pos_corrector, 12)
		 &&                                                                      // Avatar Position values (X, Z and Y).
		 peekProc(avatar_address + avatar_azimuth_offset, &avatar_azimuth, 4) && // Avatar azimuth float.
		 peekProc(pModule + map_id_offset, &map_id, 4) &&                        // Map id.
		 peekProc(avatar_address + identity_offset, player);                     // Player name.

	// This prevents the plugin from linking to the game in case something goes wrong during values retrieval from
	// memory addresses.
	if (!ok)
		return false;

	// State
	if (state != 1) {     // If not in-game
		context.clear();  // Clear context
		identity.clear(); // Clear identity
		// Set vectors values to 0.
		for (int i = 0; i < 3; i++) {
			avatar_pos[i] = avatar_front[i] = avatar_top[i] = camera_pos[i] = camera_front[i] = camera_top[i] = 0.0f;
		}

		return true; // This tells Mumble to ignore all vectors.
	}

	// Begin context
	std::ostringstream ocontext;
	ocontext << "{";

	// Map id
	ocontext << "Map: " << map_id << ""; // Set map id in identity.

	ocontext << "}";
	context = ocontext.str();
	// End context

	// Begin identity
	std::wostringstream oidentity;
	oidentity << "{";

	// Map id
	oidentity << "Map: " << map_id << ", "; // Set map id in identity.

	// Player name
	escape(player, sizeof(player));
	if (strcmp(player, "") != 0) {
		oidentity << "Player: \"" << player << "\""; // Set player name in identity.
	} else {
		oidentity << "Player: null";
	}

	oidentity << "}";
	identity = oidentity.str();
	// End identity

	/*
	Mumble | Game (in L hand coordinate system)
	X      | -X
	Y      | Y
	Z      | Z
	*/
	avatar_pos[0] = -avatar_pos_corrector[0];
	avatar_pos[1] = avatar_pos_corrector[1];
	avatar_pos[2] = avatar_pos_corrector[2];

	camera_pos[0] = -camera_pos_corrector[0];
	camera_pos[1] = camera_pos_corrector[1];
	camera_pos[2] = camera_pos_corrector[2];

	avatar_front[0] = static_cast< float >(std::sin(-avatar_azimuth));
	avatar_front[1] = 0.0f;
	avatar_front[2] = static_cast< float >(std::cos(-avatar_azimuth));

	camera_front[0] = static_cast< float >(std::cos(camera_elevation) * std::sin(-camera_azimuth));
	camera_front[1] = static_cast< float >(std::sin(camera_elevation));
	camera_front[2] = static_cast< float >(std::cos(camera_elevation) * std::cos(-camera_azimuth));

	if (camera_is_free) {
		camera_front[0] *= -1;
		camera_front[2] *= -1;
	}

	// Convert from yards (yalms) to meters
	for (int i = 0; i < 3; i++) {
		avatar_pos[i] *= 0.9144f;
		camera_pos[i] *= 0.9144f;
	}

	return true;
}

static int trylock(const std::multimap< std::wstring, unsigned long long int > &pids) {
	if (!initialize(pids, exe_name)) { // Retrieve "exe_name" module's memory address
		return false;
	}

	// Check if we can get meaningful data from it
	float apos[3], afront[3], atop[3], cpos[3], cfront[3], ctop[3];
	std::wstring sidentity;
	std::string scontext;

	if (fetch(apos, afront, atop, cpos, cfront, ctop, scontext, sidentity)) {
		return true;
	} else {
		generic_unlock();
		return false;
	}
}

static int trylock1() {
	return trylock(std::multimap< std::wstring, unsigned long long int >());
}

static MumblePlugin ffxivplug = { MUMBLE_PLUGIN_MAGIC, description, shortname, nullptr, nullptr, trylock1,
								  generic_unlock,      longdesc,    fetch };

static MumblePlugin2 ffxivplug2 = { MUMBLE_PLUGIN_MAGIC_2, MUMBLE_PLUGIN_VERSION, trylock };

extern "C" MUMBLE_PLUGIN_EXPORT MumblePlugin *getMumblePlugin() {
	return &ffxivplug;
}

extern "C" MUMBLE_PLUGIN_EXPORT MumblePlugin2 *getMumblePlugin2() {
	return &ffxivplug2;
}
