#include "pch.h"

#include "legacy_canvas_projection_services.h"

#include "canvas.h"

namespace pp::panopainter {

bool legacy_canvas_point_trace(Canvas& canvas, glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
    glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id)
{
    legacy_canvas_point_unproject(loc, { 0, 0, zw(canvas.m_box) }, canvas.m_mv, canvas.m_proj, ray_origin, ray_dir);
    glm::vec3 hit;
    float hit_t;
    for (int i = 0; i < 6; i++)
    {
        if (ray_intersect(ray_origin, ray_dir, canvas.m_plane_origin[i], canvas.m_plane_normal[i], canvas.m_plane_tangent[i], hit, hit_t))
        {
            glm::mat4 plane_camera = glm::lookAt(canvas.m_plane_origin[i], canvas.m_plane_normal[i], canvas.m_plane_tangent[i]);
            glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1);
            if (glm::abs(plane_local.x) < 1.f && glm::abs(plane_local.y) < 1.f)
            {
                fb_pos.x = -(plane_local.x * 0.5f - 0.5f) * canvas.m_width;
                fb_pos.y = (plane_local.y * 0.5f + 0.5f) * canvas.m_height;
                hit_pos = hit;
                hit_normal = canvas.m_plane_normal[i];
                out_plane_id = i;
                return true;
            }
        }
    }
    return false;
}

bool legacy_canvas_point_trace_plane(Canvas& canvas, glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
    glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id)
{
    legacy_canvas_point_unproject(loc, { 0, 0, zw(canvas.m_box) }, canvas.m_mv, canvas.m_proj, ray_origin, ray_dir);
    glm::vec3 hit;
    float hit_t;
    if (ray_intersect(ray_origin, ray_dir, canvas.m_plane_origin[plane_id],
        canvas.m_plane_normal[plane_id], canvas.m_plane_tangent[plane_id], hit, hit_t))
    {
        glm::mat4 plane_camera = glm::lookAt(canvas.m_plane_origin[plane_id], canvas.m_plane_normal[plane_id], canvas.m_plane_tangent[plane_id]);
        glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1);
        hit_pos = hit;
        hit_normal = canvas.m_plane_normal[plane_id];
        hit_fb_pos.x = -(plane_local.x * 0.5f - 0.5f);
        hit_fb_pos.y = (plane_local.y * 0.5f + 0.5f);
        return true;
    }
    return false;
}

void legacy_canvas_point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj,
    glm::vec3& out_origin, glm::vec3& out_dir)
{
    auto clip_space = glm::vec2(loc.x, vp.w - loc.y - 1.f) / zw(vp) * 2.f - 1.f;
    auto inv = glm::inverse(proj * camera);
    auto wp0 = inv * glm::vec4(clip_space, 0, 1);
    auto wp1 = inv * glm::vec4(clip_space, .5, 1);
    out_origin = xyz(wp0 / wp0.w);
    out_dir = glm::normalize(xyz(wp1 / wp1.w) - out_origin);
}

void legacy_canvas_point_unproject(Canvas& canvas, glm::vec2 loc, glm::vec3& out_origin, glm::vec3& out_dir)
{
    auto clip_space = glm::vec2(loc.x, canvas.m_vp.w - loc.y - 1.f) / zw(canvas.m_vp) * 2.f - 1.f;
    auto inv = glm::inverse(canvas.m_proj * canvas.m_mv);
    auto wp0 = inv * glm::vec4(clip_space, 0, 1);
    auto wp1 = inv * glm::vec4(clip_space, .5, 1);
    out_origin = xyz(wp0 / wp0.w);
    out_dir = glm::normalize(xyz(wp1 / wp1.w) - out_origin);
}

glm::vec3 legacy_canvas_point_trace(Canvas& canvas, glm::vec2 loc)
{
    glm::vec3 ray_origin;
    glm::vec3 ray_dir;
    glm::vec3 hit_pos;
    glm::vec3 hit_normal;
    glm::vec2 fb_pos;
    int plane_id;
    if (legacy_canvas_point_trace(canvas, loc, ray_origin, ray_dir, hit_pos, fb_pos, hit_normal, plane_id))
        return hit_pos;
    return glm::vec3(0);
}

void legacy_canvas_project_2d_points(Canvas& canvas, std::vector<vertex_t>& vertices)
{
    for (auto& p : vertices)
    {
        glm::vec3 ro, rd, hit_o, hit_d;
        glm::vec2 hit_fb;
        int plane_id;
        if (legacy_canvas_point_trace(canvas, p.pos, ro, rd, hit_o, hit_fb, hit_d, plane_id))
            p.pos = glm::vec4(hit_o, 1);
    }
}

glm::vec3 legacy_canvas_project_2d_point(Canvas& canvas, glm::vec2 pt)
{
    glm::vec3 ro, rd, hit_o, hit_d;
    glm::vec2 hit_fb;
    int plane_id;
    if (legacy_canvas_point_trace(canvas, pt, ro, rd, hit_o, hit_fb, hit_d, plane_id))
        return glm::vec4(hit_o, 1);
    return glm::vec3(0);
}

std::vector<glm::vec2> legacy_canvas_face_to_shape_2d(const Canvas& canvas, int plane_index)
{
    static std::array<glm::vec4, 4> corners{
        glm::vec4(-1.f, +1.f, -1.f, 1.f),
        glm::vec4(+1.f, +1.f, -1.f, 1.f),
        glm::vec4(+1.f, -1.f, -1.f, 1.f),
        glm::vec4(-1.f, -1.f, -1.f, 1.f),
    };

    std::vector<glm::vec3> pt_cam;
    for (auto c : corners)
    {
        auto pt_world = canvas.m_plane_transform[plane_index] * c;
        pt_cam.push_back(canvas.m_mv * pt_world);
    }

    pt_cam = poly_clip_near(pt_cam, 0.01f);

    std::vector<glm::vec2> points;
    for (auto p : pt_cam)
    {
        auto pt_clip = canvas.m_proj * glm::vec4(p, 1);
        pt_clip = pt_clip / pt_clip.w;
        glm::vec2 pt_screen = (glm::vec2(pt_clip) * 0.5f + 0.5f) * zw(canvas.m_vp);
        pt_screen.y = canvas.m_vp.w - pt_screen.y - 1;
        points.push_back(pt_screen);
    }
    return points;
}

void legacy_canvas_push_camera(Canvas& canvas)
{
    canvas.m_camera_stack.push(legacy_canvas_get_camera(canvas));
}

void legacy_canvas_pop_camera(Canvas& canvas)
{
    if (!canvas.m_camera_stack.empty())
    {
        legacy_canvas_set_camera(canvas, canvas.m_camera_stack.top());
        canvas.m_camera_stack.pop();
    }
}

CameraData legacy_canvas_get_camera(const Canvas& canvas)
{
    CameraData camera;
    camera.m_box = canvas.m_box;
    camera.m_mv = canvas.m_mv;
    camera.m_pan = canvas.m_pan;
    std::copy_n(canvas.m_plane_dir, 6, camera.m_plane_dir);
    std::copy_n(canvas.m_plane_unproject, 6, camera.m_plane_unproject);
    camera.m_proj = canvas.m_proj;
    camera.m_vp = canvas.m_vp;
    return camera;
}

void legacy_canvas_set_camera(Canvas& canvas, const CameraData& camera)
{
    canvas.m_box = camera.m_box;
    canvas.m_mv = camera.m_mv;
    canvas.m_pan = camera.m_pan;
    std::copy_n(camera.m_plane_dir, 6, canvas.m_plane_dir);
    std::copy_n(camera.m_plane_unproject, 6, canvas.m_plane_unproject);
    canvas.m_proj = camera.m_proj;
    canvas.m_vp = camera.m_vp;
}

} // namespace pp::panopainter

bool Canvas::point_trace(
    glm::vec2 loc,
    glm::vec3& ray_origin,
    glm::vec3& ray_dir,
    glm::vec3& hit_pos,
    glm::vec2& fb_pos,
    glm::vec3& hit_normal,
    int& out_plane_id)
{
    return pp::panopainter::legacy_canvas_point_trace(*this, loc, ray_origin, ray_dir, hit_pos, fb_pos, hit_normal, out_plane_id);
}

bool Canvas::point_trace_plane(
    glm::vec2 loc,
    glm::vec3& ray_origin,
    glm::vec3& ray_dir,
    glm::vec3& hit_pos,
    glm::vec3& hit_normal,
    glm::vec2& hit_fb_pos,
    int plane_id)
{
    return pp::panopainter::legacy_canvas_point_trace_plane(*this, loc, ray_origin, ray_dir, hit_pos, hit_normal, hit_fb_pos, plane_id);
}

void Canvas::point_unproject(
    glm::vec2 loc,
    glm::vec4 vp,
    glm::mat4 camera,
    glm::mat4 proj,
    glm::vec3& out_origin,
    glm::vec3& out_dir)
{
    pp::panopainter::legacy_canvas_point_unproject(loc, vp, camera, proj, out_origin, out_dir);
}

void Canvas::point_unproject(glm::vec2 loc, glm::vec3& out_origin, glm::vec3& out_dir)
{
    pp::panopainter::legacy_canvas_point_unproject(*this, loc, out_origin, out_dir);
}

glm::vec3 Canvas::point_trace(glm::vec2 loc)
{
    return pp::panopainter::legacy_canvas_point_trace(*this, loc);
}

void Canvas::project2Dpoints(std::vector<vertex_t>& vertices)
{
    pp::panopainter::legacy_canvas_project_2d_points(*this, vertices);
}

glm::vec3 Canvas::project2Dpoint(glm::vec2 pt)
{
    return pp::panopainter::legacy_canvas_project_2d_point(*this, pt);
}
