#include "Common.h"
#include "Scripting.h"
#include "Opcodes.h"
#include "Custom.h"

#define JMP_NEAR8 0xEBU

using namespace GTASA;

#define PED_TYPE_GEN 24
#define WMYCR 100
#define JETPACK 370
#define MAVERICK 487
#define BANSHEE 429
#define RAINY_COUNTRYSIDE 16
#define INTERIOR_OUTSIDE 0
#define INTERIOR_LIBERTY_CITY 1

static const Vector FourDragons = { 2034.408F, 997.6301F, 10.82031F };
static const Vector Bistro = { -750.7819F, 491.0268F, 1371.729F };
static const float BistroAzimuth = 215.0F;
static const Vector Ganton = { 2495.33F, -1682.016F, 13.33889F };
static const float GantonAzimuth = 0.0F;

static int checkScriptBuildError()
{
	if (theScript.hasOverflowed()) {
		strcpy(errorText, "Script: script size too big!");
		return 0;
	}
	if (theScript.getUnresolvedRefs()) {
		strcpy(errorText, "Script: script has unresolved labels");
		return 0;
	}
	if (theScript.hasForwardRefTooFar()) {
		strcpy(errorText, "Script: need to set forward script references to 32 bits");
		return 0;
	}
	if (theScript.hasConditionalError()) {
		strcpy(errorText, "Script: script has a conditional error");
		return 0;
	}
	if (theScript.hasNestingError()) {
		strcpy(errorText, "Script: script has a nesting error");
		return 0;
	}
	return 1;
}

int buildScript()
{
	theScript.initialize();

#if SCRIPT_SELECT == 1
	/*
	 * This script says:
	 *   text_styled 'HEIST_5' 1000 ms 2
	 *   force_weather 16
	 *   wait 20000
	 */
	theScript << text_styled << "HEIST_5" << 1000 << 2;
	theScript << force_weather << RAINY_COUNTRYSIDE;
	theScript << wait << 20000;
	theScript.setRepeatCount(-1);	// repeat forever
#elif SCRIPT_SELECT == 2
	theScript << request_model << WMYCR;
	theScript << load_requested_models;
	theScript << create_actor << PED_TYPE_GEN << WMYCR << FourDragons << L(0);
	theScript.setRepeatCount(1);
#elif SCRIPT_SELECT == 3
	G PLAYER_ACTOR(3);
	L VTRIP(31), VACTIVE(30), VCAR(29), VOWNCAR(28), VCARMODEL(27);
	L VINTERIOR(10), VX(11), VY(12), VZ(13), VA(14);
	Label lab1, lab2, lab3;
	Vector BistroCar(Bistro);
	Vector delta = { 6.0F, -7.0F, 0.0F };
	float BistroCarAzimuth = BistroAzimuth - 80.0F;
	BistroCar += delta;

	theScript << If << eli << VTRIP << 1 << And << eli << VACTIVE << 0 << Then;
	theScript	<< get_active_interior << VINTERIOR;
	theScript	<< select_interior << INTERIOR_LIBERTY_CITY;
	theScript	<< link_actor_to_interior << PLAYER_ACTOR << INTERIOR_LIBERTY_CITY;
//	theScript	<< opcode(0x0A0B) << Bistro << 82.0F;
//	theScript	<< opcode(0x0792) << PLAYER_ACTOR;
	theScript	<< If << is_actor_driving << PLAYER_ACTOR << Then;
	theScript		<< setli << VOWNCAR << 1;
	theScript		<< get_actor_car << PLAYER_ACTOR << VCAR;
	theScript		<< get_car_coords << VCAR << VX << VY << VZ;
	theScript		<< get_car_z_angle << VCAR << VA;
	theScript		<< link_car_to_interior << VCAR << INTERIOR_LIBERTY_CITY;
	theScript		<< put_car_at << VCAR << BistroCar;
	theScript		<< set_car_z_angle << VCAR << BistroCarAzimuth;
	theScript	<< Else;
	theScript		<< setli << VOWNCAR << 0;
	theScript		<< get_actor_coords << PLAYER_ACTOR << VX << VY << VZ;
	theScript		<< get_actor_z_angle << PLAYER_ACTOR << VA;
	theScript		<< put_actor_at << PLAYER_ACTOR << Bistro;
	theScript		<< set_actor_z_angle << PLAYER_ACTOR << BistroAzimuth;
	theScript		<< request_model << JETPACK;
	theScript		<< request_model << BANSHEE /* MAVERICK */;
	theScript		<< load_requested_models;
	theScript		<< put_jetpack_on_actor << PLAYER_ACTOR;
	theScript		<< create_car << BANSHEE /* MAVERICK */ << BistroCar << VCAR;
	theScript		<< set_car_z_angle << VCAR << BistroCarAzimuth;
	theScript		<< link_car_to_interior << VCAR << INTERIOR_LIBERTY_CITY;
	theScript	<< EndIf;
	theScript	<< refresh_screen << Bistro.X << Bistro.Y;
	theScript	<< setli << VACTIVE << 1;
	theScript	<< jump << lab3;
	theScript << BareElseIf << eli << VTRIP << 2 << And << !eli << VACTIVE << 0 << Then;
	theScript	<< select_interior << VINTERIOR;
	theScript	<< link_actor_to_interior << PLAYER_ACTOR << VINTERIOR;
	theScript	<< If << !is_actor_driving_car << PLAYER_ACTOR << VCAR << Then;
	theScript		<< gosub << lab1;
	theScript	<< EndIf;
	theScript	<< If << is_actor_driving << PLAYER_ACTOR << Then;
	theScript		<< If << eli << VOWNCAR << 0 << Then;
	theScript			<< get_actor_car << PLAYER_ACTOR << VCAR;
	theScript			<< remove_actor_from_car_and_put_at << PLAYER_ACTOR << Bistro;
	theScript			<< gosub << lab1;
	theScript			<< jump << lab2;
	theScript			>> lab1;
	theScript			<< get_car_model << VCAR << VCARMODEL;
	theScript			<< destroy_car << VCAR;
	theScript			<< release_model << VCARMODEL;
	theScript			<< _return;
	theScript		<< BareElse;
	theScript			<< get_actor_car << PLAYER_ACTOR << VCAR;
	theScript			<< link_car_to_interior << VCAR << VINTERIOR;
	theScript			<< put_car_at << VCAR << VX << VY << VZ;
	theScript			<< set_car_z_angle << VCAR << VA;
	theScript		<< EndIf;
	theScript	<< Else;
	theScript			>> lab2;
	theScript			<< put_actor_at << PLAYER_ACTOR << VX << VY << VZ;
	theScript			<< set_actor_z_angle << PLAYER_ACTOR << VA;
	theScript	<< EndIf;
	theScript	<< If << eli << VOWNCAR << 0 << Then;
	theScript		<< release_model << JETPACK;
	theScript	<< EndIf;
	theScript	<< refresh_screen << VX << VY;
	theScript	<< setli << VACTIVE << 0;
	theScript	>> lab3;
	theScript	<< set_camera_behind_player;
	theScript	<< setli << VTRIP << 0;
	theScript << EndIf;
	theScript.setRepeatCount(-1);
#elif SCRIPT_SELECT == 4
	G PLAYER_ACTOR(3);
	L VMARKER(0), VCOLOR(1);
	Label lab1, lab2, lab3;
	Vector MarkerLocation(FourDragons);
	Vector delta = { 0.0F, 0.0F, 1.5F };
	Vector radius = { 1.2F, 1.2F, 1.2F };
	MarkerLocation += delta;

	theScript << wait << 2000;
	theScript << setli << VCOLOR << 267;	// Yellow
#ifdef COLOR_SEARCH
	theScript >> lab1;
#endif
	theScript << create_entrance_marker_at << MarkerLocation << VCOLOR << VMARKER;
	theScript << refresh_screen << MarkerLocation.X << MarkerLocation.Y;
#ifdef COLOR_SEARCH
	theScript >> lab2;
	theScript << wait << 0;
	theScript << If << is_key_pressed << 0 << 16 << Then;	// sprint key
	theScript	<< destroy_entrance_marker << VMARKER;
	theScript	<< addli << VCOLOR << 1;
	theScript	<< jump << lab1;
	theScript << EndIf;
	theScript << If << is_key_pressed << 0 << 18 << Then;	// crouch key
	theScript	<< destroy_entrance_marker << VMARKER;
	theScript	<< subli << VCOLOR << 1;
	theScript	<< jump << lab1;
	theScript << EndIf;
	theScript << jump << lab2;
#endif
	theScript >> lab3;
	theScript << wait << 0;
	theScript << If << is_actor_near_point_3d_on_foot << PLAYER_ACTOR << FourDragons << radius << 0 << Then;
	theScript	<< put_actor_at << PLAYER_ACTOR << Ganton;
	theScript	<< refresh_screen << Ganton.X << Ganton.Y;
	theScript	<< destroy_entrance_marker << VMARKER;
	theScript << Else;
	theScript <<   jump << lab3;
	theScript << EndIf;
#else
#error no script!
#endif

	if (!checkScriptBuildError()) {
		theScript.invalidate();
		return 0;
	}
	theScript.complete();
	return 1;
}

static void flipGravity()
{
	static float fGravity = 0.0F;
	DWORD d;

	if (!VirtualProtect(GA(GRAVITY), sizeof(FLOAT), PAGE_READWRITE, &d))
		return;
	if (fGravity == 0.0F) {
		fGravity = *GA(GRAVITY);
		*GA(GRAVITY) = fGravity / 50.0F;
	} else {
		*GA(GRAVITY) = fGravity;
		fGravity = 0.0F;
	}
	VirtualProtect(GA(GRAVITY), sizeof(FLOAT), d, &d);
}

static BYTE bHydraSpeedPatchValue = 0;
static BYTE bJetpackHeightPatchValue = 0;
static BYTE bAircraftHeightPatchValue = 0;

/*
 * iAction - 0 = remove, 1 = set, 2 = flip
 */
static void setPatch(BYTE& bPatchValue, PBYTE pAddress, int iAction)
{
	DWORD d;

	switch (iAction) {
	case 0:
		if (!bPatchValue)
			return;
		break;
	case 1:
		if (bPatchValue)
			return;
		break;
	case 2:
		iAction = !bPatchValue;
		break;
	}
	if (!VirtualProtect(pAddress, sizeof(BYTE), PAGE_READWRITE, &d))
		return;
	if (iAction) {
		bPatchValue = *pAddress;
		*pAddress = JMP_NEAR8;
	} else {
		*pAddress = bPatchValue;
		bPatchValue = 0;
	}
	VirtualProtect(pAddress, sizeof(BYTE), d, &d);
}

#ifdef DO_CUSTOM_PRESENT
void customPresent(
	CONST RECT* pSourceRect,
	CONST RECT* pDestRect,
	HWND hDestWindowOverride,
	CONST RGNDATA* pDirtyRegion)
{
}
#endif /* DO_CUSTOM_PRESENT */

#ifdef DO_CUSTOM_SCRIPT_HOOK
/*
 * Note: if you want to create additional
 *   scripts and execute them here, timerDelta
 *   is the amount of time to be added
 *   to the local timers at each tick.
 *   Call Script::adjustLocalTimers()
 *   to update them if you need them.
 */
void customScriptHook(DWORD timerDelta)
{
}
#endif /* DO_CUSTOM_SCRIPT_HOOK */

#ifdef DO_GXT_HOOK
/*
 * You can use this to either add your own custom
 *   GXT labels, or replace the game's own labels.
 *   - don't need to check if gxtLabel is NULL, the
 *     stub already checks it.
 *   - If you return NULL, the game's native read_GXT
 *     will be used.
 *   - Otherwise your return string will be used.  Make
 *     sure it's in static data or system allocated memory.
 */
const char* __stdcall customStrings(const char* gxtLabel)
{
	if (!strncmp(gxtLabel, "HEIST_5", 8))
		return "Down, Down and Out!";	// originally: Up, Up and Away!
	return NULL;
}
#endif /* DO_GXT_HOOK */

#ifdef DO_FULL_THREAD_CONTROL
/*
 * Use this to filter which native threads
 *   the game runs and which it doesn't.
 *   pGst is a pointer to a thread the game wants
 *   to execute.
 *   - return true if you want the thread executed,
 *     false otherwise.
 *   - Usually, you can use pGst->strName to identify
 *     the thread by name.  Unnamed thread use "noname",
 *     so you can possibly use pGst->dwScriptIP
 *     to identify them if you know where the thread
 *     code is in main.scm.  Use GA(SCRIPT_BASE) to
 *     get the base of main.scm.
 *     Mission threads have pGst->dwBaseIP == 0xA7A6A0.
 *     External script threads have a non-zero pGst->dwBaseIP
 *     (they're in a dynamically allocated buffer).
 *   - "return false;" will disable main.scm completely.
 */
bool customThreadFilter(PGTASA_SCRIPT_THREAD pGst)
{
	return true;
}
#endif /* DO_FULL_THREAD_CONTROL */

#ifdef DO_CUSTOM_KEYS
/*
 * This is a hook to the window procedure,
 *   so you get raw keystrokes here, not
 *   game controls.
 *   - return true if you handle the keypress
 *     and don't want the game to process it.
 *   - return false to pass the keypress to the game.
 */
bool customKeyHook(
	HWND hWnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam)
{
	/*
	 * Check that ALT is held and that this is the 1st rep
	 */
	if ((lParam & 0x60000000U) != 0x20000000U)
		return false;
	switch (wParam) {
	case VK_F10:
		/*
		 * Alt-F10
		 */
		flipGravity();
		return true;
	case VK_F9:
		/*
		 * Alt-F9
		 */
		setPatch(bHydraSpeedPatchValue, GA(HYDRA_SPEED_PATCH), 2);
		return true;
	case VK_F8:
		/*
		 * Alt-F8
		 */
		setPatch(bAircraftHeightPatchValue, GA(AIRCRAFT_HEIGHT_PATCH), 2);
		return true;
#if SCRIPT_SELECT == 3
	case 'L':
		/*
		 * Alt-L
		 */
		theScript.setLocalVar(31, 1);
		setPatch(bJetpackHeightPatchValue, GA(JETPACK_HEIGHT_PATCH), 1);
		return true;
	case 'G':
		/*
		 * Alt-G
		 */
		setPatch(bJetpackHeightPatchValue, GA(JETPACK_HEIGHT_PATCH), 0);
		theScript.setLocalVar(31, 2);
		return true;
#endif
	}
	return false;
}
#endif /* DO_CUSTOM_KEYS */