#include "scripts/bpong/common.txt"
#include "scripts/Math.txt"
#include "scripts/misc/giblet.txt"

namespace BPong {
	class Ball {
		kActor @actor;
		kVec3 velocity;
		kVec3 maxVelocity(40.0f, 0.0f, 40.0f);
		float spinSpeed = 15.0f;
		float speedHitMultiplier = 1.1f;
		float spawnMinSpeed = 10.0f;
		float spawnMaxSpeed = 15.0f;
		bool showdownMode = false;
		bool multiMode = false;
		int showdownHits = 0;
		int id;
		//------------------------------------------------------------------------------------------------------------------------
		Ball(const int newID) {
			id = newID;
			velocity = kVec3(0.0f, 0.0f, 0.0f);
			@actor = ActorFactory.Spawn("Gib_Alien_Torso", 0.0f, 0.0f, 0.0f, 0, 0);
			Visible = false;
		}
		//------------------------------------------------------------------------------------------------------------------------
		~Ball() {
			
		}
		//------------------------------------------------------------------------------------------------------------------------
		kVec3 Pos {
			get const {
				return actor.Origin();
			}
			set {
				actor.Origin() = value;
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		kVec3 Scale {
			get const {
				return actor.Scale();
			}
			set {
				actor.Scale() = value;
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		bool Visible {
			get const {
				return (actor.Flags() & AF_NODRAW) == 0;
			}
			set {
				if (value) {
					actor.Flags() &= ~AF_NODRAW;
				}
				else {
					actor.Flags() |= AF_NODRAW;
				}
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void Spawn(const bool dirLeft, const bool randz) {
			Game.PlaySound("sounds/shaders/generic_157.ksnd");
			Visible = true;
			Pos = kVec3(BallSpawnX, BallSpawnY, randz ? Math::RandRange(BallSpawnMinZ, BallSpawnMaxZ) : BallSpawnCenterZ); //Position ball to the center of the screen
			showdownMode = false;
			multiMode = false;
			Game.SpawnFx("fx/freeze_explosion.kfx", Pos, 0);
			//if (id == 0) {
				//Game.SpawnFx("fx/freeze_explosion.kfx", Pos, 0);
			//} else {
				//Game.SpawnFx("fx/generic_108.kfx", Pos, 0);
			//}
			float angle = dirLeft ? Math::RandRange(200.0f, 340.0f) : Math::RandRange(20.0f, 160.0f);
			velocity = AngleToDirectionXZ(angle) * Math::RandRange(spawnMinSpeed, spawnMaxSpeed);
		}
		//------------------------------------------------------------------------------------------------------------------------
		// Converts an Angle to a kVec3 using x,y as direction with magnitude of 1
		//------------------------------------------------------------------------------------------------------------------------
		kVec3 AngleToDirectionXZ(const float value) {
			float r = Math::Deg2Rad(value);
			return kVec3(Math::Sin(r), 0.0f, Math::Cos(r));
		}
		//------------------------------------------------------------------------------------------------------------------------
		void HitPad() {
			PlayRandomSound(BallHitSounds);
			
			//if velocity reached maximum then ball goes to multi ball mode
			if ((velocity.z >= maxVelocity.z or velocity.z <= -maxVelocity.z) or (velocity.x >= maxVelocity.x or velocity.x <= -maxVelocity.x)) {
				multiMode = true;
			}
			
			//Random chance of the ball doing different behaviours:
			if (showdownMode) {
				if (showdownHits > 1) {
					showdownMode = false;
					velocity.z = (Math::RandRange(spawnMinSpeed, spawnMaxSpeed) / 2.0f) * ((Math::Rand() % -2) != 0 ? -1.0f : 1.0f);
					//float angle = GetSpawnAngle((Math::Rand() % -2) != 0);
					//velocity.z = (AngleToDirectionXZ(angle) * Math::RandRange(spawnMinSpeed, spawnMaxSpeed)).z;
				} else {
					showdownHits++;
				}
				velocity.x = Math::Clampf(-(velocity.x * speedHitMultiplier), -maxVelocity.x, maxVelocity.x);
			} else {
				int chance = Math::RandMax(101);
				if (chance > 95) {
					//up/down behaviour (makes the ball z speed be twice as fast as the x speed)
					velocity.x = Math::Clampf(-(velocity.x * speedHitMultiplier), -maxVelocity.x, maxVelocity.x);
					float vz = (velocity.x * 1.5f) * (velocity.z < 0.0f ? 1.0f : -1.0f);//
					velocity.z = Math::Clampf(vz, -maxVelocity.z, maxVelocity.z);
				} else if (chance > 90) {
					//showdown behaviour (makes the ball z speed be 0, after a few hits it returns to normal behaviour and sets x and z speed to rand spawn speed)
					showdownMode = true;
					showdownHits = 0;
					velocity.x = Math::Clampf(-(velocity.x * speedHitMultiplier), -maxVelocity.x, maxVelocity.x);
					velocity.z = 0.0f;
				} else if (chance > 85) {
					//reverse z behaviour (reverses the ball z speed)
					velocity.x = Math::Clampf(-(velocity.x * speedHitMultiplier), -maxVelocity.x, maxVelocity.x);
					velocity.z = Math::Clampf(-(velocity.z * speedHitMultiplier), -maxVelocity.z, maxVelocity.z);
				} else {
					//normal - reverse x velocity
					velocity.x = Math::Clampf(-(velocity.x * speedHitMultiplier), -maxVelocity.x, maxVelocity.x);
				}
			}
			Game.SpawnFx("fx/deathrain.kfx", Pos, 0);
		}
		//------------------------------------------------------------------------------------------------------------------------
		void UpdateActorGibTimer() {
			//Need to make sure gib actor (that we're using for ball) never goes away because the TurokGiblet Update removes it after so long.
			TurokGiblet @gib = cast<TurokGiblet@>(actor.ScriptObject().obj);
			gib.timer = 0;
		}
		//------------------------------------------------------------------------------------------------------------------------
		void Update(const Main@ main) {
			if (!Visible) {
				return;
			}
			Pos = Pos + velocity; //move ball by velocity
			
			//if hits top wall
			if (velocity.z > 0.0f and Pos.z >= TopWallZ) {
				PlayRandomSound(BallHitSounds);
				Game.SpawnFx("fx/deathrain.kfx", Pos, 0);
				velocity.z = Math::Clampf(-(velocity.z * speedHitMultiplier), -maxVelocity.z, maxVelocity.z);
			}
			//if hits bottom wall
			if (velocity.z < 0.0f and Pos.z <= BottomWallZ) {
				PlayRandomSound(BallHitSounds);
				Game.SpawnFx("fx/deathrain.kfx", Pos, 0);
				velocity.z = Math::Clampf(-(velocity.z * speedHitMultiplier), -maxVelocity.z, maxVelocity.z);
			}
			//if hits left side of screen (pad2 scores)
			if (Pos.x <= LeftGoalX) {
				if (id == 0) {
					Spawn(false, true);
				} else if (main.difficulty == 2 and id == 1) {
					Spawn(false, true);
				} else {
					Visible = false;
				}
				PlayRandomSound(P2GoalSounds);
				CameraShake();
				main.score1.RemoveLife();
			}
			//if hits right side of screen (pad1 scores)
			if (Pos.x >= RightGoalX) {
				if (id == 0) {
					Spawn(true, true);
				} else if (main.difficulty == 2 and id == 1) {
					Spawn(true, true);
				} else {
					Visible = false;
				}
				PlayRandomSound(P1GoalSounds);
				CameraShake();
				main.score2.RemoveLife();
			}
			//if hit pad1
			if (velocity.x < 0.0f and main.pad1.PointCollided(Pos)) {
				HitPad();
			}
			//if hit pad2
			if (velocity.x > 0.0f and main.pad2.PointCollided(Pos)) {
				HitPad();
			}
			//if hit pad2b
			if (velocity.x > 0.0f and main.pad2b.Visible and main.pad2b.PointCollided(Pos)) {
				HitPad();
			}
		
			//if (PlayLoop.Ticks() % 2 == 0) //Game.SpawnFx("fx/generic_5.kfx", Pos, 0);
			Game.SpawnFx("fx/drool.kfx", Pos + kVec3(0.0f, 0.0f, 10.0f), 0); //trail
			
			//spin the ball
			actor.Yaw() += Math::Deg2Rad(spinSpeed);
		}
		//------------------------------------------------------------------------------------------------------------------------
	}
}
