#include "Player.h"
#include "Camera.h"
#include "LevelMap.h"
#include "PlayerInputComponent.h"
#include <iostream>

Player::Player(glm::vec3 position, glm::vec3 rotation, std::vector<Sector>& sectors, GLFWwindow* Window)
    : GameObject(position, rotation, glm::vec3(1.0f, 1.0f, 1.0f)),
    PlayerCamera(std::make_unique<Camera>(Window)),
	cPlayerInput(std::make_unique<PlayerInputComponent>(Window, *this)),
    LevelSectors(sectors),
	CurrentSector(&sectors[0])
{
	PlayerCamera->SetOffset(glm::vec3(0.0f, 1.0f, 0.0f));
	PlayerCamera->MoveCamera(Position);
	PlayerCamera->Rotation = Rotation;
}

void Player::Update(float DeltaTime)
{
	GameObject::Update(DeltaTime);
	cPlayerInput->Update(DeltaTime);

	if (bJumping)
	{
		Position.y += JumpVelocity * DeltaTime;
		JumpVelocity -= GRAVITY * DeltaTime;

		if (Position.y <= CurrentSector->floor_height)
		{
			JumpVelocity = 0.0f;
			Position.y = CurrentSector->floor_height;
			bJumping = false;
			std::cout << "STOP JUMPING" << std::endl;
		}
	}
	else if (Position.y > CurrentSector->floor_height)
	{
		Position.y -= GRAVITY * DeltaTime;
	}
	else
	{
		Position.y = CurrentSector->floor_height;
		bJumping = false;
	}

	PlayerCamera->MoveCamera(Position);
}

void Player::Move(EInputDirection Direction, float Delta)
{
	float Velocity = bIsRunning ? RunSpeed : WalkSpeed;
	Velocity = Velocity * Delta;
	glm::vec3 newPosition = Position;
	if (Direction == FORWARD)
		newPosition += Front * Velocity;
	if (Direction == BACKWARD)
		newPosition -= Front * Velocity;
	if (Direction == LEFT)
		newPosition -= Right * Velocity;
	if (Direction == RIGHT)
		newPosition += Right * Velocity;

	if (!CrossingWall(newPosition))
	{
		Position = newPosition;
	}

	for (Sector& Sector : LevelSectors)
	{
		glm::vec2 playerPoint = { Position.x, Position.z };
		if (PointInPolygon(playerPoint, Sector.walls))
		{
			/*if (!bJumping)
				Position.y = Sector.floor_height;*/
			if (CurrentSector != &Sector)
			{
				std::cout << "Updating CurrentSector ("
					<< (CurrentSector ? CurrentSector->index : -1)
					<< ") to Sector (" << Sector.index << ")" << std::endl;
				CurrentSector = &Sector;
			}
			break;
			// TO DO: Pretty sure this limits me for sectors that contain sectors, i.e. if I want multiple floor/ceiling heights within a sector it will have to be made of multiple sectors
			//std::cout << "Player in: " << sector.index << " | Player Y: " << sector.floor_height << std::endl;
		}
	}
}

void Player::Jump()
{
	if (bJumping) return;

	//std::cout << "JUMP!" << std::endl;
	bJumping = true;
	JumpVelocity = JumpForce;
}

void Player::Sprint()
{
	bIsRunning = true;
}

void Player::StopSprint()
{
	bIsRunning = false;
}

void Player::Look(float Yaw, float Pitch, GLboolean bConstrainPitch)
{
	Yaw *= MOUSE_SENSITIVITY;
	Pitch *= MOUSE_SENSITIVITY;

	Rotation.y += Yaw;

	PlayerCamera->RotateCamera(Yaw, Pitch);
	UpdateVectors();
}

glm::mat4 Player::GetViewMatrix() const
{
	return PlayerCamera->GetViewMatrix();
}

bool Player::CrossingWall(glm::vec3 NewPosition)
{
    // Convert 3D positions to 2D (XZ plane) for wall collision
    glm::vec2 CurrentPosition = glm::vec2(Position.x, Position.z);
    glm::vec2 NextPosition = glm::vec2(NewPosition.x, NewPosition.z);

	//bool bCrossed = false;

	// Check each wall in the sector
	for (const Wall& Wall : CurrentSector->walls)
	{
		glm::vec2 WallStart = Wall.a;
		glm::vec2 WallEnd = Wall.b;

		// Check if movement line segment intersects with wall line segment
		if (LineSegmentsIntersect(CurrentPosition, NextPosition, WallStart, WallEnd))
		{
			if (Wall.portal == 0)
			{
				return true; // Collision detected
			}
			else if (CanFitInNextSector(LevelSectors[Wall.portal-1]))// check if valid portal (floor height < player y and floor and ceiling tall enough for player)
			{
				CurrentSector = &LevelSectors[Wall.portal - 1];
				return false;
			}
			else
			{
				return true;
			}
			//return true; // Collision detected
		}
	}

	//if (bCrossed) return bCrossed; // breakout early, attempting to move through wall

    // Check outward facing sectors to ensure not moving into pillars
    for (const Sector& Sector : LevelSectors)
    {
		if (!Sector.outward_facing)
		{
			continue;
		}

        // Check each wall in the sector
        for (const Wall& Wall : Sector.walls)
        {
            glm::vec2 WallStart = Wall.a;
            glm::vec2 WallEnd = Wall.b;

            // Check if movement line segment intersects with wall line segment
            if (LineSegmentsIntersect(CurrentPosition, NextPosition, WallStart, WallEnd))
            {
                //if (Wall.portal != 0)
                //{
                //    return false; // Collision but to another sector
                //}
                return true; // Collision detected
            }
        }
    }

    return false; // No collisions detected
}

bool Player::LineSegmentsIntersect(const glm::vec2& StartPosition, const glm::vec2& EndPosition, const glm::vec2& WallPointA, const glm::vec2& WallPointB)
{
	// Implementation of the cross product test for line segment intersection
	auto cross = [](const glm::vec2& a, const glm::vec2& b) {
		return a.x * b.y - a.y * b.x;
		};

	glm::vec2 PlayerNormal = EndPosition - StartPosition;
	glm::vec2 WallNormal = WallPointB - WallPointA;
	glm::vec2 WallToPlayerNormal = WallPointA - StartPosition;

	float PlayerWallCross = cross(PlayerNormal, WallNormal);

	// If lines are not parallel
	if (std::abs(PlayerWallCross) >= std::numeric_limits<float>::epsilon())
	{
		float t = cross(WallToPlayerNormal, PlayerNormal) / PlayerWallCross;
		float u = cross(WallToPlayerNormal, WallNormal) / PlayerWallCross;

		// If intersection point is on both segments
		if ((t >= 0 && t <= 1) && (u >= 0 && u <= 1))
			return true;
	}

	// If no intersection, check distance from EndPosition to wall segment
	return PointToLineSegmentDistance(EndPosition, WallPointA, WallPointB) <= 1.0f;
}

float Player::PointToLineSegmentDistance(const glm::vec2& point, const glm::vec2& segA, const glm::vec2& segB)
{
	glm::vec2 segVec = segB - segA;
	float segLengthSquared = glm::dot(segVec, segVec);

	// If segment is actually a point
	if (segLengthSquared < std::numeric_limits<float>::epsilon())
		return glm::distance(point, segA);

	// Project point onto the segment
	float t = glm::dot(point - segA, segVec) / segLengthSquared;
	t = glm::clamp(t, 0.0f, 1.0f);

	glm::vec2 projection = segA + t * segVec;
	return glm::distance(point, projection);
}


// Checking if a point is inside a polygon
bool Player::PointInPolygon(glm::vec2 point, std::vector<Wall> polygon)
{
	int num_vertices = polygon.size();
	double x = point.x, y = point.y;
	bool inside = false;

	// Store the first point in the polygon and initialize
	// the second point
	glm::vec2 p1 = polygon[0].a, p2;

	// Loop through each edge in the polygon
	for (int i = 1; i <= num_vertices; i++) {
		// Get the next point in the polygon
		p2 = polygon[i % num_vertices].a;

		// Check if the point is above the minimum y
		// coordinate of the edge
		if (y > std::min(p1.y, p2.y)) {
			// Check if the point is below the maximum y
			// coordinate of the edge
			if (y <= std::max(p1.y, p2.y)) {
				// Check if the point is to the left of the
				// maximum x coordinate of the edge
				if (x <= std::max(p1.x, p2.x)) {
					// Calculate the x-intersection of the
					// line connecting the point to the edge
					double x_intersection
						= (y - p1.y) * (p2.x - p1.x)
						/ (p2.y - p1.y)
						+ p1.x;

					// Check if the point is on the same
					// line as the edge or to the left of
					// the x-intersection
					if (p1.x == p2.x
						|| x <= x_intersection) {
						// Flip the inside flag
						inside = !inside;
					}
				}
			}
		}

		// Store the current point as the first point for
		// the next iteration
		p1 = p2;
	}

	// Return the value of the inside flag
	return inside;
}

bool Player::CanFitInNextSector(Sector Sector)
{
	std::cout << "Checking sector " << Sector.index << " | Floor height : " << Sector.floor_height << " | Player Y : " << Position.y << " | Sector height : " << Sector.ceiling_height - Sector.floor_height << std::endl;
	if (Position.y < Sector.floor_height - STEP_HEIGHT)
	{
		return false;
	}

	if (Sector.ceiling_height - Sector.floor_height < PLAYER_HEIGHT)
	{
		return false;
	}

	return true;
}