#include "lab_m1/tema1/tema1.h"

#include <vector>
#include <iostream>

#include "lab_m1/tema1/transform2D.h"
#include "lab_m1/tema1/object2Dtema.h"

using namespace std;
using namespace m1;

Tema1::Tema1()
    : tankX(100.0f), tankAngle(0.0f), turretAngle(glm::radians(0.0f)), moveSpeed(100.0f), rotationSpeed(100.0f)
{
}

Tema1::~Tema1()
{
}

float gen_y(float x) {
    x /= 50;
    return 1.5 * sin(0.5 * x) + 1.5 * sin(0.6 * x) + 0.4 * sin(1.5 * x);
} 

void Tema1::Init()
{
    glm::ivec2 resolution = window->GetResolution();
    auto camera = GetSceneCamera();
    camera->SetOrthographic(0, (float)resolution.x, 0, (float)resolution.y, 0.01f, 400);
    camera->SetPosition(glm::vec3(0, 0, 50));
    camera->SetRotation(glm::vec3(0, 0, 0));
    camera->Update();
    GetCameraInput()->SetActive(false);

    glm::vec3 corner = glm::vec3(0, 0, 0);
    float squareSide = 1;

    // Set ground color to brown
    Mesh* square = object2Dtema::CreateSquare("square", corner, squareSide, glm::vec3(0.7098f, 0.7412f, 0.5373f), true);
    AddMeshToList(square);

    numSquare = 1280; // Set the desired number of squares
    int step = resolution.x / numSquare;
    for (size_t i = 0; i < numSquare + 1; ++i) {
        xPositions.push_back(i * step);
        heightMap.push_back(30 * gen_y(i * step) + 300);
    }

    InitTank();

}

void Tema1::InitTank() {
    // Construiește baza tancului cu două trapeze
	tankWidth = 100.0f;
    Mesh* base1 = object2Dtema::CreateTrapezoid("base1", glm::vec3(0, 10, 0), 80.0f, 100.0f, 20.0f, glm::vec3(0.5f, 0.5f, 0.5f), true);
    Mesh* base2 = object2Dtema::CreateTrapezoid("base2", glm::vec3(20, 0, 0), 70.0f, 60.0f, 10.0f, glm::vec3(0.3f, 0.3f, 0.3f), true);

    // Creează turela și tunul
    Mesh* turret = object2Dtema::CreateCircle("turret", glm::vec3(50, 30, 0), 17.5f, glm::vec3(0.5f, 0.5f, 0.5f), true);
    Mesh* barrel = object2Dtema::CreateTrapezoid("barrel", glm::vec3(0, 0, 0), 40, 40, 5, glm::vec3(0.3f, 0.3f, 0.3f), true);

    // Adăugare în lista de mesh-uri
    AddMeshToList(base1);
    AddMeshToList(base2);
    AddMeshToList(turret);
    AddMeshToList(barrel);

	Mesh* linieTraiectorie = object2Dtema::CreateTrapezoid("line", glm::vec3(0, 0, 0), 1, 1, 1, glm::vec3(0.0f, 1.0f, 1.0f), true);
    AddMeshToList(linieTraiectorie);
	// Creează un cerc pentru proiectil
	Mesh* projectile = object2Dtema::CreateCircle("projectile", glm::vec3(0, 0, 0), 5.0f, glm::vec3(1.0f, 0.0f, 0.0f), true);
	AddMeshToList(projectile);
}

void Tema1::FrameStart()
{
    // Set the background color to blue for the sky
    glClearColor(0.529f, 0.808f, 0.922f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glm::ivec2 resolution = window->GetResolution();
    // Sets the screen area where to draw
    glViewport(0, 0, resolution.x, resolution.y);
}

float Tema1::CalculateTankY(float tankX) {
    // Căutăm punctele A și B din heightMap care încadrează x-ul tancului
    for (size_t i = 0; i < numSquare; ++i) {
        float x1 = xPositions[i];
        float y1 = heightMap[i];
        float x2 = xPositions[i + 1];
        float y2 = heightMap[i + 1];

        if (tankX >= x1 && tankX <= x2) {
            // Calculăm t conform formulei
            float t = (tankX - x1) / (x2 - x1);

            // Calculăm coordonata y a tancului
            return y1 + t * (y2 - y1);
        }
    }
    return 0; // În cazul în care tankX este în afara intervalului de puncte (nu ar trebui să se întâmple)
}

float Tema1::getAngle(float tankX) {
    for (size_t i = 0; i < numSquare; ++i) {
        float x1 = xPositions[i];
        float y1 = heightMap[i];
        float x2 = xPositions[i + 1];
        float y2 = heightMap[i + 1];

        if (tankX >= x1 && tankX <= x2) 
            return atan2(y2 - y1, (x2 - x1));
    }
    return 0;
}

std::vector<glm::vec2> Tema1::CalculTraiectorie(float vitezaInitiala, float unghiTurret, float gravitatie, int numPuncte, float intervalTimp, glm::vec2 tipTurret) {
    std::vector<glm::vec2> puncteTraiectorie;

    // Start from the tip of the turret
    glm::vec2 pozitieVarfTun = tipTurret;

    // Generate trajectory points
    for (int i = 0; i < numPuncte; ++i) {
        float t = i * intervalTimp;

        // Calculate the position on trajectory at time t
        glm::vec2 pozitie;
        pozitie.x = pozitieVarfTun.x + vitezaInitiala * cos(unghiTurret) * t;
        pozitie.y = pozitieVarfTun.y + vitezaInitiala * sin(unghiTurret) * t - 0.5f* gravitatie * t * t;

        // Add the point to trajectory
        puncteTraiectorie.push_back(pozitie);

        // Stop if the point goes below the terrain
        if (pozitie.y < 0) break;
    }

    return puncteTraiectorie;
}


void Tema1::DrawTerrain() {
    glm::mat3 modelMatrix;
    for (size_t i = 0; i < numSquare; ++i) {
        float x1 = xPositions[i];
        float y1 = heightMap[i];
        float x2 = xPositions[i + 1];
        float y2 = heightMap[i + 1];

        modelMatrix = glm::mat3(1);
        modelMatrix *= transform2D::Translate(x1, y1);
        modelMatrix *= transform2D::Shear((y2 - y1) / (x2 - x1));
        modelMatrix *= transform2D::Scale(x2 - x1, max(y1, y2));
        modelMatrix *= transform2D::Translate(0, -1);

        RenderMesh2D(meshes["square"], shaders["VertexColor"], modelMatrix);
    }
}

void Tema1::Update(float deltaTimeSeconds) {
    // Codul existent pentru redarea terenului și a tancului
    DrawTerrain();

    // Calculează coordonata y și unghiul de înclinare al tancului
    tankY = CalculateTankY(tankX);
    float angle = getAngle(tankX);

    // Aplica transformările pentru tanc
    modelMatrix = glm::mat3(1);
    modelMatrix *= transform2D::Translate(tankX, tankY);
    modelMatrix *= transform2D::Rotate(angle);
    modelMatrix *= transform2D::Translate(-tankWidth / 2, 0);

    // Redare baza și turela tancului
    RenderMesh2D(meshes["base1"], shaders["VertexColor"], modelMatrix);
    RenderMesh2D(meshes["base2"], shaders["VertexColor"], modelMatrix);
    RenderMesh2D(meshes["turret"], shaders["VertexColor"], modelMatrix);

    // Rotația și poziția tunului
    glm::mat3 barrelMatrix = modelMatrix;
    barrelMatrix *= transform2D::Translate(50, 30);   // Mutăm originea la vârful turelei
    barrelMatrix *= transform2D::Rotate(turretAngle); // Aplicăm rotația turelei

    // Redare tun
    RenderMesh2D(meshes["barrel"], shaders["VertexColor"], barrelMatrix);

    // Calculează traiectoria proiectilului pe baza noii poziții și a unghiului tunului

    //std::vector<glm::vec2> puncteTraiectorie = CalculTraiectorie(vitezaInitiala, turretAngle + angle, gravitatie, numPuncte, intervalTimp);

    glm::vec2 tipTurret(tankX + cos(angle) - 30 * sin(angle), tankY + sin(angle) + 30 * cos(angle));
    tipTurret += glm::vec2(cos(turretAngle + angle) * 40, sin(turretAngle + angle) * 40);

    std::vector<glm::vec2> puncteTraiectorie = CalculTraiectorie(vitezaInitiala, turretAngle + angle, gravitatie, numPuncte, intervalTimp, tipTurret);
    // Desenează traiectoria actualizată
    for (size_t i = 0; i < puncteTraiectorie.size() - 1; ++i) {
        glm::vec2 punct1 = puncteTraiectorie[i];
        glm::vec2 punct2 = puncteTraiectorie[i + 1];

        // Creează o linie între punctele succesive ale traiectoriei
        modelMatrix = glm::mat3(1);
        modelMatrix *= transform2D::Translate(punct1.x, punct1.y);
		modelMatrix *= transform2D::Rotate(atan2(punct2.y - punct1.y, punct2.x - punct1.x));
		modelMatrix *= transform2D::Scale(glm::distance(punct1, punct2), 1);

        RenderMesh2D(meshes["line"], shaders["VertexColor"], modelMatrix);
    }

	// Desenează proiectilele
    // Update and render projectiles
    for (auto& proj : proiectile) {
        if (!proj.active) continue;

        //// Update projectile position

        proj.position += proj.velocity * deltaTimeSeconds;
        // Apply gravity
        proj.velocity.y -= gravitatie * deltaTimeSeconds;
        // Deactivate projectile if it goes below the terrain
        if (proj.position.y < CalculateTankY(proj.position.x)) {
            // deform terrain
            // Deformare teren

            // Dezactivăm proiectilul
            proj.active = false;
        }

        // Render active projectile
        if (proj.active) {
            modelMatrix = glm::mat3(1);
            modelMatrix *= transform2D::Translate(proj.position.x, proj.position.y);
            RenderMesh2D(meshes["projectile"], shaders["VertexColor"], modelMatrix);
        }
    }
    DrawTerrain();
}






void Tema1::FrameEnd()
{
}


/*
 *  These are callback functions. To find more about callbacks and
 *  how they behave, see input_controller.h.
 */


void Tema1::OnInputUpdate(float deltaTime, int mods) {
    if (window->KeyHold(GLFW_KEY_D) || window->KeyHold(GLFW_KEY_RIGHT)) {
        tankX += moveSpeed * deltaTime;
    }
    if (window->KeyHold(GLFW_KEY_A) || window->KeyHold(GLFW_KEY_LEFT)) {
        tankX -= moveSpeed * deltaTime;
    }
    if (window->KeyHold(GLFW_KEY_W) || window->KeyHold(GLFW_KEY_UP)) {
		turretAngle += glm::radians(rotationSpeed) * deltaTime;
    }
    if (window->KeyHold(GLFW_KEY_S) || window->KeyHold(GLFW_KEY_DOWN)) {
		turretAngle -= glm::radians(rotationSpeed) * deltaTime;
    }
}



void Tema1::OnKeyPress(int key, int mods)
{
    if (key == GLFW_KEY_SPACE) {
        // Calculate position and velocity of the projectile
        float angle = getAngle(tankX);
        glm::vec2 tipTurret(tankX + cos(angle) - 30 * sin(angle), tankY + sin(angle) + 30 * cos(angle));
        tipTurret += glm::vec2(cos(turretAngle + angle) * 40, sin(turretAngle + angle) * 40);

        glm::vec2 initialVelocity = glm::vec2(vitezaInitiala * cos(turretAngle+angle), vitezaInitiala * sin(turretAngle+angle));

        Proiectil newProiectil = { tipTurret, initialVelocity, true };
        proiectile.push_back(newProiectil);
    }
}


void Tema1::OnKeyRelease(int key, int mods)
{
    // Add key release event
}


void Tema1::OnMouseMove(int mouseX, int mouseY, int deltaX, int deltaY)
{
    // Add mouse move event
}


void Tema1::OnMouseBtnPress(int mouseX, int mouseY, int button, int mods)
{
    // Add mouse button press event
}


void Tema1::OnMouseBtnRelease(int mouseX, int mouseY, int button, int mods)
{
    // Add mouse button release event
}


void Tema1::OnMouseScroll(int mouseX, int mouseY, int offsetX, int offsetY)
{
}


void Tema1::OnWindowResize(int width, int height)
{
}



Detecție coliziune proiectil-teren
O coliziune are loc dacă un proiectil se intersectează cu suprafața terenului. Pentru simplitate, putem considera că o coliziune are loc atunci când proiectilul se află la o distanță mai mică de un prag (ales de voi) de punctul aflat la aceeași coordonată x de pe teren. Dacă acest punct nu se află direct în harta de inălțime, el poate fi aflat prin metoda descrisă mai sus, utilizată pentru calcularea poziției unui tanc pe teren.

După cum se poate vedea în imaginea de mai jos, pe baza componentei x a coordonatei P a proiectilului, se poate calcula componenta y a proiecției proiectilului pe teren. Se consideră că proiectilul instersectează terenul, când coordonata proiecției pe teren se află la o distanță față de poziția propriu-zisă a proiectilului ce este mai mică decât un prag ales de voi.

 

Putem afla `t` - coeficientul de interpolare ca fiind procentul pe care îl reprezintă (x - x1) din (x2 - x1). Folosind acest coeficient, putem afla coordonatele lui I prin interpolarea între punctele P1 și P2. Coliziunea se intamplă când (x - xi) scade sub un prag, care poate fi chiar 0.

t=x−x1x2−x1I=f(P1,P2,t)f(a,b,t)=a∗(1−t)+b∗t

În formula de mai sus, f este o interpolare liniară.

Deformare teren
În caz de coliziune, terenul va fi deformat. Se va utiliza un parametru radius
 , care reprezintă raza de efect a exploziei proiectilului. Efectul exploziei trebuie să fie coborârea nivelurilor din harta de înălțime, în zonele afectate, astfel încât terenul să ajungă în afara razei de efect radius
 .


implementeaza coliziunea cu terenul si gaura pe care o lasa