#include "pch.h"

#include "canvas.h"

#include "app.h"
#include "renderer_gl/opengl_capabilities.h"

namespace {

GLint current_canvas_stroke_internal_format()
{
    const auto renderer_features = ShaderManager::render_device_features();
    if (renderer_features.float32_linear_filtering)
        return static_cast<GLint>(pp::renderer::gl::rgba32f_internal_format());
    if (renderer_features.float16_render_targets)
        return static_cast<GLint>(pp::renderer::gl::rgba16f_internal_format());
    return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
}

GLint rgba8_internal_format()
{
    return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
}

} // namespace

void Canvas::pick_start()
{
    for (int i = 0; i < 6; i++)
        m_pick_ready[i] = false;
}

void Canvas::pick_update(int plane)
{
    if (m_pick_ready[plane])
        return;

    App::I->render_task([this, plane]
    {
        std::array<bool, 6> faces{ false };
        faces[plane] = true;
        draw_merge(true, faces);

        int i = plane;
        if (!m_pick_data[plane])
            m_pick_data[plane] = std::make_unique<glm::u8vec4[]>(m_width * m_height);
        m_layers_merge.rtt(i).readPixelsRgba8(
            0,
            0,
            m_width,
            m_height,
            m_pick_data[plane].get());
    });

    m_pick_ready[plane] = true;
}

glm::vec4 Canvas::pick_get(glm::vec2 canvas_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 (point_trace(canvas_loc, ray_origin, ray_dir, hit_pos, fb_pos, hit_normal, plane_id)) {
        pick_update(plane_id);
        int i = (int)fb_pos.x + (int)fb_pos.y * m_width;
        return glm::vec4(m_pick_data[plane_id][i]) / 255.f;
    }
    return { 0, 0, 0, 1 };
}

void Canvas::pick_end()
{
    for (int i = 0; i < 6; i++)
        m_pick_data[i].release();
}

void Canvas::clear(const glm::vec4& c/*={0,0,0,1}*/)
{
    auto a = new ActionLayerClear;
    a->m_layer = m_layers[m_current_layer_idx];
    a->m_frame = layer().m_frame_index;
    a->m_snap = std::make_shared<LayerFrame::Snapshot>(a->m_layer->snapshot());
    a->m_color = c;
    ActionManager::add(a);

    m_layers[m_current_layer_idx]->clear(c);
    m_unsaved = true;
}

void Canvas::clear_all()
{
    for (auto& l : m_layers)
        l->clear({ 0, 0, 0, 0 });
}

void Canvas::layer_add(std::string name, std::shared_ptr<Layer> layer /*= nullptr*/, int index /*= 0*/)
{
    LOG("canvas layer_add %s", name.c_str());
    if (layer) {
        m_layers.insert(m_layers.begin() + index, layer);
    } else {
        m_layers.insert(m_layers.begin() + index, std::make_unique<Layer>());
        m_layers[index]->create(m_width, m_height, name);
    }
    m_current_layer_idx = index;
}

std::shared_ptr<Layer> Canvas::layer_with_id(uint32_t id)
{
    auto layer = std::find_if(m_layers.begin(), m_layers.end(),
        [id](const auto& l) { return l->id == id; });
    if (layer != m_layers.end())
        return *layer;
    return nullptr;
}

void Canvas::layer_remove(int idx) // m_order index
{
    LOG("canvas layer_remove %d", idx);
    m_layers.erase(m_layers.begin() + idx);
    m_current_layer_idx = std::min<int>((int)m_layers.size() - 1, idx);
}

void Canvas::layer_order(int idx, int pos) // m_order index
{
    auto from = m_layers.begin() + idx;
    auto to = m_layers.begin() + pos;

    if (pos < idx)
        std::rotate(to, from, from + 1);
    if (pos > idx)
        std::rotate(from, from + 1, to + 1);

    if (m_current_layer_idx == idx)
        m_current_layer_idx = pos;
    else if (m_current_layer_idx == pos)
        m_current_layer_idx = idx;
}

int Canvas::anim_duration() const noexcept
{
    int frames = 1;
    for (auto& l : m_layers)
        frames = glm::max(frames, l->total_duration());
    return frames;
}

void Canvas::anim_update() noexcept
{
    for (auto& l : m_layers)
        l->goto_frame(m_anim_frame);
}

void Canvas::anim_goto_frame(int frame) noexcept
{
    m_anim_frame = frame;
    for (auto& l : m_layers)
        l->goto_frame(frame);
}

void Canvas::anim_goto_next() noexcept
{
    anim_goto_frame((m_anim_frame + 1) % anim_duration());
}

void Canvas::anim_goto_prev() noexcept
{
    int k = anim_duration();
    anim_goto_frame((m_anim_frame - 1 + k) % k);
}

void Canvas::resize(int width, int height)
{
    m_width = width;
    m_height = height;
    m_size = { width, height };
    for (int i = 0; i < 6; i++) {
        const auto stroke_format = current_canvas_stroke_internal_format();
        m_tmp[i].create(width, height, -1, stroke_format);
        m_tmp_dual[i].create(width, height, -1, stroke_format);
        m_tex[i].create(width, height, rgba8_internal_format());
        m_tex2[i].create(width, height, rgba8_internal_format());
    }
    for (auto& l : m_layers)
        l->resize(width, height);
    m_smask.create(width, height, "mask");
    m_layers_merge.resize(width, height);
    m_merge_rtt.create(width, height);
    m_merge_tex.create(width, height);
    m_unsaved = true;
}

void Canvas::snapshot_save()
{
    LOG("SAVE SNAPSHOT");
    m_layers_snapshot.clear();
    m_layers_snapshot.resize(m_layers.size());
    for (int i = 0; i < m_layers.size(); i++)
        m_layers_snapshot[i] = m_layers[i]->snapshot();
}

void Canvas::snapshot_restore()
{
    LOG("RESTORE SNAPSHOT");
    for (int i = 0; i < m_layers.size(); i++)
        m_layers[i]->restore(m_layers_snapshot[i]);
    m_layers_snapshot.clear();
}
