#include "pch.h"
#include "HSTraining.h"
#include <math.h>

#include "bakkesmod\wrappers\GameEvent\TutorialWrapper.h"
#include "bakkesmod\wrappers\GameObject\PriWrapper.h"
#include "bakkesmod\wrappers\GameObject\BallWrapper.h"
#include "bakkesmod\wrappers\GameObject\CarWrapper.h"
#include "bakkesmod\wrappers\ArrayWrapper.h"
#include "bakkesmod\wrappers\cvarmanagerwrapper.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <random>
#include <filesystem>
#include <vector>
#include <variant>
namespace fs = std::filesystem;
using namespace std;

BAKKESMOD_PLUGIN(HSTraining, "Heatseeker Training", plugin_version, PLUGINTYPE_CUSTOM_TRAINING)

std::shared_ptr<CVarManagerWrapper> _globalCvarManager;





  

void HSTraining::onLoad()
{
	using namespace std::placeholders;
	
	_globalCvarManager = cvarManager;
	
	
	
	//Binds set from the cfg file

	fs::create_directories(gameWrapper->GetDataFolder() / "HSShots");

	
	

	cvarManager->registerNotifier("controlInput", [this](std::vector<std::string> args) {
		controlInput();
		}, "", PERMISSION_ALL);
	
	cvarManager->registerNotifier("BallMove", [this](std::vector<std::string> args) {
		ballMove();
		}, "", PERMISSION_ALL);

	cvarManager->registerNotifier("CarMove", [this](std::vector<std::string> args) {
		carMove();
		}, "", PERMISSION_ALL);
	
	cvarManager->registerNotifier("BallVelocity", [this](std::vector<std::string> args) {
		ballVelocity();
		}, "", PERMISSION_ALL);
	
	cvarManager->registerNotifier("OverideShot", [this](std::vector<std::string> args) {
		overideShot();
		}, "", PERMISSION_ALL);
	
	cvarManager->registerNotifier("LoadShot", [this](std::vector<std::string> args) {
		loadShot();
		}, "", PERMISSION_ALL);
		
	cvarManager->registerNotifier("BallSpeed", [this](std::vector<std::string> args) {
		ballSpeed();
		}, "", PERMISSION_ALL);
		
	cvarManager->registerCvar("heat_enabled", "0", "Enable Heat", true, true, 0, true, 1)
		.addOnValueChanged([this](std::string oldValue, CVarWrapper cvar) {
			HeatEnabled = cvar.getBoolValue();
			});
	
	
	
	
	//manipulation of the bal cvar variables
	cvarManager->registerCvar("ball_speed", "(-6000.0, 6000)", "Velocity of the ball", true, true, 0.0f, true, 6000.0f);
	cvarManager->registerCvar("ball_location", "(0,6000)", "Location of the ball", true, true, 0.0f, true, 6000.0f);
	cvarManager->registerCvar("cool_height", "2044.0", "height to place the ball");
	cvarManager->registerCvar("cool_length", "4096.0", "length to place the ball");
	cvarManager->registerCvar("cool_width", "5120.0", "width to place the ball");
	cvarManager->registerCvar("cool_speedx", "6000.0", "set x value of speed of the ball");
	cvarManager->registerCvar("cool_speedy", "6000.0", "set y value of speed of the ball");
	cvarManager->registerCvar("cool_speedz", "6000.0", "set z value of speed of the ball");

	
	
	//manipulation of the car
	
	cvarManager->registerCvar("car_enabled", "0", "Enable Car Location Reset", true, true, 0, true, 1)
		.addOnValueChanged([this](std::string oldValue, CVarWrapper cvar) {
		carEnabled = cvar.getBoolValue();
			});
	
	cvarManager->registerCvar("car_location", "(0,6000)", "Location of the car", true, true, 0.0f, true, 6000.0f);
	cvarManager->registerCvar("car_rotation", "(0,6000)", "Location of the car", true, true, 0.0f, true, 6000.0f);
	cvarManager->registerCvar("carZ", "10.0", "height to place the car");
	cvarManager->registerCvar("carX", "4096.0", "length to place the car");
	cvarManager->registerCvar("carY", "5120.0", "width to place the car");
	cvarManager->registerCvar("carYaw", "2000", "Yaw of the car");
	cvarManager->registerCvar("carPitch", "2000", "Pitch of the car");
	cvarManager->registerCvar("carRoll", "2000", "Roll of the car");
	
	//Which Training pack is active
	cvarManager->registerCvar("TrainingPackNum", "0", "Which Training Pack is active");
	//variables for shot speed heatseaker
	cvarManager->registerCvar("speed_enabled", "1", "What speed value for a shot should it use");
	cvarManager->registerCvar("cool_speed", "3000", "speed of the ball");
	
	//shotname cvar variable declaration
	
	cvarManager->registerCvar("cool_shotname", "Shot 1", "Name of shot 1");

	cvarManager->registerCvar("shotNumber1", "1", "What shot was selected");

	CVarWrapper speedEnableCvar = cvarManager->getCvar("speed_enabled");
	speedEnableCvar.setValue(1);
	
	
}
HSTraining::~HSTraining() {

}

void HSTraining::onUnload() {
	
	
	cvarManager->executeCommand("unbind " + controllerKeys[controllerKeybind]);
	cvarManager->executeCommand("unbind " + KeyBoardKeys[KeyBoardKeybind]);
	trainingActive = false;
	
}




//internal cooldown for button presses.
bool HSTraining::delayRepeat() {
	d2 = std::chrono::system_clock::now();
	std::chrono::duration<float> difference = d2 - d1;
	const float milliseconds = difference.count() * 1000.0;
	if (milliseconds >= 60) {
		d1 = d2;
		return true;
	}
	else {
		return false;
	}
}
//adds speed to the ball
void HSTraining::ballVelocity() {
	

	if (gameWrapper->IsInOnlineGame()) { return; }
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }
	
	BallWrapper ball = server.GetBall();
	if (!ball) { return; }
	float ballRadius = ball.GetRadius();

	if (!HeatEnabled) { return; }

	Vector ballSpeed = {
		cvarManager->getCvar("ball_speed").getFloatValue(),
		cvarManager->getCvar("ball_speed").getFloatValue(),
		cvarManager->getCvar("ball_speed").getFloatValue()
	};
	
	ballSpeed.X = speedX;
	ballSpeed.Y = speedY;
	ballSpeed.Z = speedZ;

	ball.SetHitTeamNum(1);//2
	auto godball = BallGodWrapper(ball.memory_address);
	if (ball.IsGodBall()) {
		
		//
		godball.SetCarHitTeamNum(1);
		godball.OnHitTeamNumChanged();
		godball.UpdateColor();
		godball.ShouldDrawTrajectory();
	}
	
	ball.SetVelocity(ballSpeed);
	
}
//moves the ball
void HSTraining::ballMove() {
	
	if (gameWrapper->IsInOnlineGame()) { return; }
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }
	
	BallWrapper ball = server.GetBall();
	if (!ball) { return; }

	if (!HeatEnabled) { return; }
	float ballRadius = ball.GetRadius();
	
	ball.Stop();
	ball.SetHitTeamNum(255);

	Vector ballLocation = {
		cvarManager->getCvar("ball_location").getFloatValue(),
		cvarManager->getCvar("ball_location").getFloatValue(),
		cvarManager->getCvar("ball_location").getFloatValue()
	};
	ballLocation.X = length;
	ballLocation.Y = width;
	ballLocation.Z = height;
	
	ball.SetLocation(ballLocation);
	ball.Stop();
}
//moves the car
void HSTraining::carMove() {

	if (gameWrapper->IsInOnlineGame()) { return; }
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }

	if(!carEnabled) { return; }
	if (!HeatEnabled) { return; }

	CarWrapper car = server.GetGameCar();
	if (!car) { return; }
	
	

	Vector carLocation = {
		cvarManager->getCvar("car_location").getFloatValue(),
		cvarManager->getCvar("car_location").getFloatValue(),
		cvarManager->getCvar("car_location").getFloatValue()
	};
	

	carLocation.X = carX;
	carLocation.Y = carY;
	carLocation.Z = carZ;

	Rotator carRotation;
	carRotation.Pitch = carPitch;
	carRotation.Yaw = carYaw;
	carRotation.Roll = carRoll;

	car.SetCarRotation(carRotation);
	car.SetLocation(carLocation);
	car.Stop();
}

//calls the various functions in order to reset the shot
void HSTraining::controlInput()
{
	if (gameWrapper->IsInOnlineGame()) { return; }
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }
	
	if (!HeatEnabled) { return; }
	if (setMaxScore) {
		if (maxScore == 0) {
			maxScore = server.GetMaxScore();
		}
		server.SetMaxScore(100);
	}
	if (trainingActive) {
		HSTraining::shotRandom();
	}
	BallWrapper ball = server.GetBall();
	if (!ball) { return; }

	if (!noShot) {
		t2 = std::chrono::system_clock::now();
		t1 = t2;
		lineStart = true;
		HSTraining::carMove();
		HSTraining::ballMove();
		HSTraining::ballSpeed();
		float delay = .2f;
		gameWrapper->SetTimeout([this](GameWrapper* gw) {
			HSTraining::ballVelocity();
			}, delay);
	}
}
void HSTraining::Rep() {
	if (gameWrapper->IsInOnlineGame()) { return; }
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }

	if (!HeatEnabled) { return; }
	t2 = std::chrono::system_clock::now();
	std::chrono::duration<float> difference = t2 - t1;
	const float milliseconds = difference.count() * 1000.0;
	if (milliseconds >= repshot) {
		t1 = t2;
		HSTraining::controlInput();
	}
};
//makes the ball go faster based off of user input
void HSTraining::ballSpeed() {
	if (gameWrapper->IsInOnlineGame()) { return; }
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }

	if (!HeatEnabled) { return; }

	CVarWrapper speedEnableCvar = cvarManager->getCvar("speed_enabled");
	int speedEnable = speedEnableCvar.getIntValue();
	CVarWrapper speedBallCvar = cvarManager->getCvar("cool_speed");
	int speedBall = speedBallCvar.getIntValue();

	auto ball = gameWrapper->GetCurrentGameState().GetBall();
	if (!ball || !ball.IsGodBall()) return;
	auto godball = BallGodWrapper(ball.memory_address);
	
	//by players choice whether the speed of the ball comes from the file or the slider or neither
	if (speedEnable == 2) {
		godball.SetTargetSpeed(speedBall);
		godball.UpdateColor();
	}
	else if(speedEnable == 1){
		godball.SetTargetSpeed(SSpeed);
		godball.UpdateColor();
	}
	
	
	
}


//Overides an existing shot
void HSTraining::overideShot() {
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }
	
	CVarWrapper speedBallCvar = cvarManager->getCvar("cool_speed");
	if (!speedBallCvar) { return; }
	int speedBall = speedBallCvar.getIntValue();

	CVarWrapper shotNumberCvar = cvarManager->getCvar("shotNumber1");
	if (!shotNumberCvar) { return; }
	int shotNumber = shotNumberCvar.getIntValue();
	if (ShotData.size() > shotNumber) {
		ShotData[shotNumber].setShot(height, length, width, speedX, speedY, speedZ, carX, carY, carZ, carYaw, shotName, speedBall, carPitch, carRoll);
	}
	
	
}

//loads the shot
void HSTraining::loadShot() {
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }

	CVarWrapper shotNumberCvar = cvarManager->getCvar("shotNumber1");
	if (!shotNumberCvar) { return; }
	int shotNumber = shotNumberCvar.getIntValue();
	if(ShotData.size() <= shotNumber|| shotNumber < 0){ return; }
	
	int ranInt = 0;
	int ranSpawn = 0;
	int splitRanCarInt = 0;
	int splitRanInt = 0;
	int ranCarInt = 0;
	
	height = ShotData[shotNumber].getHeight();
	length = ShotData[shotNumber].getLength();
	
	ranInt = 0;
	if (randomizer && mirBall) {
		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist6(0, 1);
		ranInt = dist6(rng);
		if (ranInt) {
			length = -length;
			splitRanCarInt = ranInt;
		}
		if (!splitBall) {
			splitRanInt = ranInt;
		}
	}
	width = ShotData[shotNumber].getWidth();
	
	speedX = ShotData[shotNumber].getSpeedX() ;
	
	ranInt = 0;
	if (randomizer && mirBall) {
		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist6(0, 1);
		ranInt = dist6(rng);
		if (!splitBall) {
			ranInt = splitRanInt;
		}
		if (ranInt) {
			speedX = -speedX;
		}
	}
	speedY = ShotData[shotNumber].getSpeedY();
	speedZ = ShotData[shotNumber].getSpeedZ();
	carX = ShotData[shotNumber].getCarX();

	ranInt = 0;
	if (randomizer && mirCar) {
		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist6(0, 1);
		ranInt = dist6(rng);
		if (!splitCar) {
			ranInt = splitRanCarInt;
		}
		else {
			ranCarInt = ranInt;
		}
		if (ranInt) {
			carX = -carX;
		}
		
	}

	carY = ShotData[shotNumber].getCarY();

	
	
	carZ = ShotData[shotNumber].getCarZ();

	ranInt = 0;
	if (randomizer && makeCeiling && makeWall) {
		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist6(0, 20);
		ranInt = dist6(rng);

		
		if (!isRan) {
			if (ranInt >= 8) {
				carZ = 2024;
				ranSpawn = 1;
			}
			else{
				if (carZ <= 1000 || carZ >= 1500) {
					carZ = 1200;
				}
				if (carX <= -2200 || carX >= 2200) {
					carX = 0;
				}
				carY = -5180;
				ranSpawn = 2;
			}
		}
		else {
			if (ranInt == 1|| ranInt == 2 ) {
					carZ = 2024;
					ranSpawn = 1;
			}
			if (ranInt == 3) {
				if (carZ <= 1000 || carZ >= 1500) {
					carZ = 1200;
				}
				if (carX <= -2200 || carX >= 2200) {
					carX = 0;
				}
				carY = -5180;
				ranSpawn = 2;
			}
		}


	}
	else if (randomizer && makeCeiling ) {
		ranInt = 0;
		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist6(0, 10);
		ranInt = dist6(rng);

		
		if (!isRan) {
			carZ = 2024;
			ranSpawn = 1;
		}
		else {
			if (ranInt == 1|| ranInt == 2) {
				carZ = 2024;
				ranSpawn = 1;
			}
		}
		
		
	}
	else if (randomizer && makeWall) {
		ranInt = 0;
		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist6(0, 10);
		ranInt = dist6(rng);

		
		if (!isRan) {
			if (carZ <= 1000 || carZ >= 1500) {
				carZ = 1200;
			}
			if (carX <= -2200 || carX >= 2200) {
				carX = 0;
			}
			carY = -5180;
			ranSpawn = 2;
		}
		else {
			if (ranInt == 1) {
				if (carZ <= 1000||carZ >=1500) {
					carZ = 1200;
				}
				if (carX <= -2200 || carX >= 2200) {
					carX = 0;
				}
				carY = -5180;
				ranSpawn = 2;
			}
		}


	}
	
	carYaw = ShotData[shotNumber].getCarYaw() + ranInt;

	ranInt = 0;
	if (randomizer && mirCar) {
		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist6(0, 1);
		ranInt = dist6(rng);
		if (!splitCar) {
			ranInt = splitRanCarInt;
		}
		else {
			ranInt = ranCarInt;
		}
		if (ranInt) {
			if (carYaw < 0) {
				carYaw = -1 * (32764 - abs(carYaw));
			}
			else {
				carYaw = 32764 - carYaw;
			}
		}
	}


	carPitch = ShotData[shotNumber].getCarPitch() ;
	carRoll = ShotData[shotNumber].getCarRoll() ;
	
	if (randomizer && makeCeiling|| makeWall) {
		
		if (ranSpawn == 1) {
			carPitch = 0;
			carRoll = 32764;
		}
		if (ranSpawn == 2) {
			carRoll = 16100;
			carYaw = 0;
			if (ranInt >= 0) {
				carPitch = 16100;
			}
			else {
				carPitch = -16100;
			}

		}
	}

	SSpeed = ShotData[shotNumber].getBallSpeed();
	
	shotName = ShotData[shotNumber].getShotName();
}
//randomizes the shot
void HSTraining::shotRandom() {
	
	if (gameWrapper->IsInOnlineGame()) { return; }
	ServerWrapper server = gameWrapper->GetCurrentGameState();
	if (!server) { return; }
	
	noShot = false;
	
	
	CVarWrapper shotNumberCvar = cvarManager->getCvar("shotNumber1");
	if (!shotNumberCvar) { return; }
	int shotNumber = shotNumberCvar.getIntValue();
	if (isShotActive.empty()) {
		LOG("emptyShotActive");
		return;
	}
	int ranInt;
	int i = 0;
	
	
	int checkRand = 0;
	bool runOnce = true;
	
	while (checkRand == 0) {
		
		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist6(0, isShotActive.size() - 1);
		ranInt = dist6(rng);
		i = 0;
		
		for(bool isActiveShot:isShotActive) {
			if (ranInt == i && isShotActive[i]) {
				shotNumberCvar.setValue(ranInt);
				checkRand = 1;
			}
			i += 1;

		}
		i = 0;
		while (i < isShotActive.size() && runOnce) {
			if (isShotActive[i]) {
				runOnce = false;
			}
			if (i == isShotActive.size() - 1 && runOnce) {
				checkRand = 1;
				noShot = true;
			}
			i += 1;
		}
		
	}
	
	HSTraining::loadShot();
}

