#include <controls/cam_control.hpp>

void CameraController::turn(float xoffset, float yoffset)
{
    if (xoffset == 0 && yoffset == 0)
        return;

    auto vertical_rot = glm::angleAxis(-yoffset, bound_node->getRotationAxisXWorld());
    auto horizontal_rot = glm::angleAxis(xoffset, glm::vec3(0.0f, 1.0f, 0.0f));

    auto R = glm::toMat3(glm::angleAxis(-yoffset, bound_node->getRotationAxisXWorld()));
    glm::vec3 rotated_y = glm::normalize(R * bound_node->getRotationAxisYWorld());
    
    auto dprod = glm::dot(rotated_y, glm::vec3(0.0f, 1.0f, 0.0f));

    if (dprod > 0.0f)
    {
        bound_node->rotateRotationQuat(vertical_rot);
        bound_node->rotateRotationQuat(horizontal_rot);
    }
}

void CameraController::setSens(float x_size, float y_size)
{
    sens.x = sens_multiplier * x_size;
    sens.y = sens_multiplier * y_size;
}

std::pair<float, float> CameraController::getMouseDelta(const osi::Mouse &mouse, const osi::Window &window, bool &editor_running)
{

    if (!editor_running || (editor_running && window.is_resized()))
    {
        setSens(window.pixel_size_in_meters().x,
                            window.pixel_size_in_meters().y);
        editor_running = true;
    }

    glm::i32vec2 offset = mouse.pos_delta();
    float xoffset = static_cast<float>(offset.x) * getSens().x;
    float yoffset = static_cast<float>(offset.y) * getSens().y;

    return { xoffset, yoffset };
}

/* FREE FLY CAMERA METHODS */

void FreeFlyCameraController::forward()
{
    bound_node->translatePosVec(step * getFrontVec());
}
void FreeFlyCameraController::backward()
{
    bound_node->translatePosVec(-step * getFrontVec());
}
void FreeFlyCameraController::left()
{
    bound_node->translatePosVec(-step * glm::normalize(glm::cross(getFrontVec(), getUpVec())));
}
void FreeFlyCameraController::right()
{
    bound_node->translatePosVec(step * glm::normalize(glm::cross(getFrontVec(), getUpVec())));
}
void FreeFlyCameraController::up()
{
    bound_node->translatePosVec(step * getUpVec());
}
void FreeFlyCameraController::down()
{
    bound_node->translatePosVec(-step * getUpVec());
}

void FreeFlyCameraController::keyboardMove(const std::unordered_set<std::string> &held_keys, const std::unordered_set<std::string> &pressed_keys)
{
    if (held_keys.contains("W"))
        forward();
    if (held_keys.contains("S"))
        backward();
    if (held_keys.contains("A"))
        left();
    if (held_keys.contains("D"))
        right();
    if (held_keys.contains("R"))
        up();
    if (held_keys.contains("F"))
        down();
}

void FreeFlyCameraController::mouseMove(const osi::Mouse &mouse, const osi::Window &window, bool &editor_running)
{
    if (mouse.down().contains("MouseRight"))
    {
        auto [xoffset, yoffset] = getMouseDelta(mouse, window, editor_running);
        turn(xoffset, yoffset);
    }
}

/* ORBIT CAMERA METHODS */

void OrbitCameraController::forward()
{
    auto front_vec = getFrontVec();
    auto length_orig = glm::length(front_vec);

    front_vec.y = 0.0f;
    front_vec *= length_orig / glm::length(front_vec);;

    translateTarget(step * front_vec);
}
void OrbitCameraController::backward()
{
    auto front_vec = getFrontVec();
    auto length_orig = glm::length(front_vec);

    front_vec.y = 0.0f;
    front_vec *= length_orig / glm::length(front_vec);

    translateTarget(-step * front_vec);
}
void OrbitCameraController::left()
{
    translateTarget(-step * glm::normalize(glm::cross(getFrontVec(), getUpVec())));
}
void OrbitCameraController::right()
{
    translateTarget(step * glm::normalize(glm::cross(getFrontVec(), getUpVec())));
}
void OrbitCameraController::up()
{
    translateTarget(step * getUpVec());
}
void OrbitCameraController::down()
{
    translateTarget(-step * getUpVec());
}

void OrbitCameraController::zoomCamera(const osi::Mouse &mouse)
{
    if (mouse.just_pressed().contains("MouseWheelUp"))
        zoom = (zoom - 0.5f) < 1.0f ? 1.0f : zoom - 0.5f; 

    if (mouse.just_pressed().contains("MouseWheelDown"))
        zoom = (zoom + 0.5f) > 20.0f ? 20.0f : zoom + 0.5f; 
}

void OrbitCameraController::keyboardMove(const std::unordered_set<std::string> &held_keys, const std::unordered_set<std::string> &pressed_keys)
{
    // these move the target vector, the camera node is moved in mouseMove method
    if (held_keys.contains("W"))
        forward();
    if (held_keys.contains("S"))
        backward();
    if (held_keys.contains("A"))
        left();
    if (held_keys.contains("D"))
        right();
    if (held_keys.contains("R"))
        up();
    if (held_keys.contains("F"))
        down();
}
void OrbitCameraController::mouseMove(const osi::Mouse &mouse, const osi::Window &window, bool &editor_running)
{   
    zoomCamera(mouse);

    if (mouse.down().contains("MouseRight"))
    {
        auto [phi, theta] = getMouseDelta(mouse, window, editor_running);
        turn(-phi, theta);
    }

    auto front_vector = -bound_node->getRotationAxisZWorld();
    bound_node->setPosVec(getTarget() - (zoom * front_vector));
}

