#include <string.h>
#include "stdint.h"
#include <math.h>

#include "../Oni/Oni.h"

#include "../_Version.h"

#include "../Daodan.h"
#include "../Daodan_Config.h"
#include "Cheater.h"
#include "Input.h"
#include "Utility.h"

union MSVC_EVIL_FLOAT_HACK
{
   unsigned __int8 Bytes[4];
   float Value;
};
static union MSVC_EVIL_FLOAT_HACK INFINITY_HACK = {{0x00, 0x00, 0x80, 0x7F}};
#define DD_INFINITY (INFINITY_HACK.Value)

oniCheatCode DDr_CheatTable[] = {
	{ "shapeshifter",     "Change Characters Enabled",       "Change Characters Disabled", cheat_shapeshifter     },
	{ "liveforever",      "Invincibility Enabled",           "Invincibility Disabled",     cheat_liveforever      },
	{ "touchofdeath",     "Omnipotence Enabled",             "Omnipotence Disabled",       cheat_touchofdeath     },
	{ "canttouchthis",    "Unstoppable Enabled",             "Unstoppable Disabled",       cheat_canttouchthis    },
	{ "fatloot",          "Fat Loot Received",               NULL,                         cheat_fatloot          },
	{ "glassworld",       "Glass Furniture Enabled",         "Glass Furniture Disabled",   cheat_glassworld       },
	{ "winlevel",         "Instantly Win Level",             NULL,                         cheat_winlevel         },
	{ "loselevel",        "Instantly Lose Level",            NULL,                         cheat_loselevel        },
	{ "bighead",          "Big Head Enabled",                "Big Head Disabled",          cheat_bighead          },
	{ "minime",           "Mini Mode Enabled",               "Mini Mode Disabled",         cheat_minime           },
	{ "superammo",        "Super Ammo Mode Enabled",         "Super Ammo Mode Disabled",   cheat_superammo        },
	{ "thedayismine",     "Developer Access Enabled",        "Developer Access Disabled",  cheat_thedayismine     },
	{ "reservoirdogs",    "Last Man Standing Enabled",       "Last Man Standing Disabled", cheat_reservoirdogs    },
	{ "roughjustice",     "Gatling Guns Enabled",            "Gatling Guns Disabled",      cheat_roughjustice     },
	{ "chenille",         "Daodan Power Enabled",            "Daodan Power Disabled",      cheat_chenille         },
	{ "behemoth",         "Godzilla Mode Enabled",           "Godzilla Mode Disabled",     cheat_behemoth         },
	{ "elderrune",        "Regeneration Enabled",            "Regeneration Disabled",      cheat_elderrune        },
	{ "moonshadow",       "Phase Cloak Enabled",             "Phase Cloak Disabled",       cheat_moonshadow       },
	{ "munitionfrenzy",   "Weapons Locker Created",          NULL,                         cheat_munitionfrenzy   },
	{ "fistsoflegend",    "Fists Of Legend Enabled",         "Fists Of Legend Disabled",   cheat_fistsoflegend    },
	{ "killmequick",      "Ultra Mode Enabled",              "Ultra Mode Disabled",        cheat_killmequick      },
	{ "carousel",         "Slow Motion Enabled",             "Slow Motion Disabled",       cheat_carousel         },
	{ "bigbadboss",       "Boss Shield Enabled",             "Boss Shield Disabled",       cheat_bigbadboss       },
	{ "bulletproof",      "Force Field Enabled",             "Force Field Disabled",       cheat_bulletproof      },
	{ "kangaroo",         "Kangaroo Jump Enabled",	         "Kangaroo Jump Disabled",     cheat_kangaroo         },
	{ "marypoppins",      "Jet Pack Mode Enabled",           "Jet Pack Mode Disabled",     cheat_marypoppins      },
	{ "buddha",           "Unkillable Enabled",              "Unkillable Disabled",        cheat_buddha           },
	{ "shinobi",          "Ninja Mode Enabled (good luck!)", "Ninja Mode Disabled",        cheat_shinobi          },
	{ "x",                "Developer Access Enabled",        "Developer Access Disabled",  cheat_x                },
	{ "testcheat",        "Testing...",                      "",                           cheat_testcheat        },
	{ "tellmetheversion", "Daodan v."DAODAN_VERSION_STRING"",                    "",                           cheat_tellmetheversion },
	{0, 0, 0, 0}
};



// Just copied all these defines from the old daodan, they were originaly from SFeLi's code.

#define GSA_camera        (0x00000080)
#define GSA_player        (0x000000AC)
#define GSA_carousel      (0x00000104) /* char */
#define GSA_slowmotimer   (0x00000108)
#define GSA_splashscreen  (0x00000118) /* char */

#define CHR_flags         (0x00000004)
#define CHR_flags2        (0x00000008)
#define CHR_oncc          (0x0000000C)
#define CHR_team          (0x00000012) /* short */
#define CHR_name          (0x00000014) /* char[32] */
#define CHR_scalemodel    (0x00000034)
#define CHR_weapon1       (0x00000194)
#define CHR_weapon2       (0x00000198)
#define CHR_weapon3       (0x0000019C)
#define CHR_shield_curr   (0x000001B6) /* short */
#define CHR_shield        (0x000001B8) /* short */
#define CHR_phasecloak    (0x000001BA) /* short */
#define CHR_stats_kills   (0x00001670)
#define CHR_stats_damage  (0x00001674)

#define ONCC_jet_accel    (0x00000010) /* float */
#define ONCC_jet_timer    (0x00000016) /* short */
#define ONCC_height1      (0x00000018) /* float */
#define ONCC_height2      (0x0000001C) /* float */
#define ONCC_bodysize_min (0x00000C58) /* float */
#define ONCC_bodysize_max (0x00000C8C) /* float */

#define kangaroo_h     (short)(60)
#define kangaroo_jp    (float)(0.06)
#define marypoppins_jp (float)(0.14)

uint16_t cheat_oldshield = 0;
int32_t cheat_oldhealth = 1;
int32_t cheat_oldmaxhealth = 1;
float cheat_oldjet_accel = 0.03f;
uint16_t cheat_oldjet_timer = 20;
float cheat_oldheight1 = 45;
float cheat_oldheight2 = 135;
bool inc_fallingframes = true;

uint8_t ONICALL DDrCheater(uint32_t cheat)
{
	switch (cheat)
	{
		case cheat_bigbadboss:
		{
			Character* player = ONgGameState->PlayerCharacter;
			//char* player = *((char**)(ONgGameState + GSA_player));
			if (player->Flags & chr_bossshield)
			{
				player->Flags = player->Flags & ~chr_bossshield;
				return 0;
			}
			else
			{
				player->Flags = player->Flags | chr_bossshield;
				return 1;
			}
		}
		case cheat_bulletproof:
		{
			Character* player = ONgGameState->PlayerCharacter;
			if (player->Flags  & chr_weaponimmune)
			{
				player->Flags  = player->Flags  & ~chr_weaponimmune;
				player->Inventory.ShieldUsed  = cheat_oldshield;
				return 0;
			}
			else
			{
				player->Flags  |=  chr_weaponimmune;
				cheat_oldshield = player->Inventory.ShieldUsed;
				player->Inventory.ShieldUsed = 100;
				return 1;
			}
		}
		case cheat_kangaroo:
		{
			Character* player = ONgGameState->PlayerCharacter;
			//char* oncc = *(char**)(player + CHR_oncc);
			if (!inc_fallingframes)
				inc_fallingframes = true;
			if (player->ONCC->JetpackTimer == kangaroo_h)
			{
				player->ONCC->JumpAcceleration = cheat_oldjet_accel;
				player->ONCC->JetpackTimer  = cheat_oldjet_timer;
				player->ONCC->MaxFallingHeightWithoutDamage = cheat_oldheight1;
				player->ONCC->MaxFallingHeightWithDamage = cheat_oldheight2;
				return 0;
			}
			else if ((unsigned short)player->ONCC->JetpackTimer == 0xFFFF)
			{
				player->ONCC->JumpAcceleration = kangaroo_jp;
				player->ONCC->JetpackTimer  = kangaroo_h;
				player->ONCC->MaxFallingHeightWithoutDamage = DD_INFINITY;
				player->ONCC->MaxFallingHeightWithDamage  = DD_INFINITY;
				return 1;
			}
			else
			{
				cheat_oldjet_accel = player->ONCC->JumpAcceleration;
				cheat_oldjet_timer = player->ONCC->JetpackTimer;
				cheat_oldheight1 = player->ONCC->MaxFallingHeightWithoutDamage ;
				cheat_oldheight2 = player->ONCC->MaxFallingHeightWithDamage;
				player->ONCC->JumpAcceleration = kangaroo_jp;
				player->ONCC->JetpackTimer = kangaroo_h;
				player->ONCC->MaxFallingHeightWithoutDamage  = DD_INFINITY;
				player->ONCC->MaxFallingHeightWithDamage  = DD_INFINITY;
				return 1;
			}
		}
		case cheat_marypoppins:
		{
			Character* player = ONgGameState->PlayerCharacter;
			if (!inc_fallingframes)
			{
				inc_fallingframes = true;
				if ((unsigned short)player->ONCC->JetpackTimer == 0xFFFF)
				{
					player->ONCC->JumpAcceleration = cheat_oldjet_accel;
					player->ONCC->JetpackTimer = cheat_oldjet_timer;
					player->ONCC->MaxFallingHeightWithoutDamage = cheat_oldheight1;
					player->ONCC->MaxFallingHeightWithDamage = cheat_oldheight2;
				}
				return 0;
			}
			else if (player->ONCC->JetpackTimer == kangaroo_h)
			{
				player->ONCC->JumpAcceleration = marypoppins_jp;
				player->ONCC->JetpackTimer = 0xFFFF;
				player->ONCC->MaxFallingHeightWithoutDamage = 0x7f800000;
				player->ONCC->MaxFallingHeightWithDamage = 0x7f800000;
				inc_fallingframes = false;
				return 1;
			}
			else
			{
				cheat_oldjet_accel = player->ONCC->JumpAcceleration;
				cheat_oldjet_timer = player->ONCC->JetpackTimer;
				cheat_oldheight1 = player->ONCC->MaxFallingHeightWithoutDamage;
				cheat_oldheight2 = player->ONCC->MaxFallingHeightWithDamage ;
				player->ONCC->JumpAcceleration = marypoppins_jp;
				player->ONCC->JetpackTimer = 0xFFFF;
				player->ONCC->MaxFallingHeightWithoutDamage = 0x7f800000;
				player->ONCC->MaxFallingHeightWithDamage = 0x7f800000;
				inc_fallingframes = false;
				return 1;
			}
		}
		case cheat_buddha:
		{
			Character* player = ONgGameState->PlayerCharacter;
			if (player->Flags& chr_unkillable)
			{
				player->Flags= player->Flags& ~chr_unkillable;
				return 0;
			}
			else
			{
				player->Flags= player->Flags| chr_unkillable;
				return 1;
			}
		}
		case cheat_shinobi:
		{
			Character* player = ONgGameState->PlayerCharacter;
			if (player->MaxHealth == 1)
			{
				player->Health = cheat_oldhealth;
				player->MaxHealth = cheat_oldmaxhealth;
				player->Flags = player->Flags & ~(chr_bossshield | chr_weaponimmune);
				ai2_deaf = 0;
				return 0;
			}
			else
			{
				cheat_oldhealth = player->Health;
				cheat_oldmaxhealth = player->MaxHealth;
				player->Health = 1;
				player->MaxHealth = 1;
				player->Flags = player->Flags | chr_bossshield | chr_weaponimmune;
				ai2_deaf = 1;
				return 1;
			}
			
		}
		case cheat_testcheat:
		{
			Character* player = ONgGameState->PlayerCharacter;
			player->Flags = player->Flags | chr_noncombatant;
			return 1;
		}
		case cheat_elderrune:
		{
			if (DDrConfig_GetOptOfType("modding.d_regen", C_BOOL)->value.intBoolVal)
			{
				int* Regeneration = &ONgGameState->PlayerCharacter->RegenHax;
				if(*Regeneration)
				{
					*Regeneration = 0;
					return 0;
				}
				else
				{
					*Regeneration = 1;
					return 1;
				}
			}
			else
			{
				return ONrCheater(cheat_elderrune);
			}
		}

		case cheat_tellmetheversion:
			return 1;
		case cheat_x:
			return ONrCheater(cheat_thedayismine);
		default:
			return ONrCheater(cheat);
	}
}

void ONICALL DDrCheater_LevelLoad()
{
	inc_fallingframes = true;
}

// Biggest hack in the entire thing ^_^
void __stdcall FallingFrames(void* Ebp)
{
	if (inc_fallingframes)
		++*((unsigned int*)((char*)Ebp + 0xf6));
}


static void BindableCheatCallback (intptr_t cheatnum) {
	uint8_t res = DDrCheater (cheatnum);
	if (res)
		COrMessage_Print(DDr_CheatTable[cheatnum].message_on, 0, 240);
	else
		COrMessage_Print(DDr_CheatTable[cheatnum].message_off, 0, 240);
}

void InitBindableCheats() {
	oniCheatCode* cur;
	for (cur = DDr_CheatTable; cur->name != 0; cur++) {
		DDrInput_RegisterCustomAction(cur->name, DDcEventType_KeyPress,
		                              BindableCheatCallback, cur->func);
	}
}

