#include "pch.h"
#include "app.h"
#include "log.h"
#include "node_canvas.h"
#include "node_image_texture.h"

Node* NodeCanvas::clone_instantiate() const
{
    return new NodeCanvas();
}

void NodeCanvas::init()
{
    m_mouse_ignore = false;
    m_canvas = std::make_unique<Canvas>();
    m_canvas->create(CANVAS_RES, CANVAS_RES);
    m_canvas->m_unsaved = false;
    m_canvas->m_node = this;
    
    m_sampler.create();
    m_sampler.set_filter(GL_LINEAR, GL_NEAREST);
    
    m_sampler_linear.create(GL_LINEAR);
    m_sampler_stencil.create(GL_LINEAR, GL_REPEAT);
    m_face_plane.create<1>(2, 2);
    m_line.create();
    CanvasMode::node = this;
    for (int i = 0; i < (int)kCanvasMode::COUNT; i++)
        for (auto m : Canvas::modes[i])
            m->init();

    m_grid.create(1, 1, m_grid_divs);
}

void NodeCanvas::restore_context()
{
    Node::restore_context();
    m_canvas->create(CANVAS_RES, CANVAS_RES);

    m_sampler.create();

    m_sampler.set_filter(GL_LINEAR, GL_NEAREST);
    m_face_plane.create<1>(2, 2);
    m_canvas->snapshot_restore();
    CanvasMode::node = this;
    for (int i = 0; i < (int)kCanvasMode::COUNT; i++)
        for (auto m : Canvas::modes[i])
            m->init();
}

void NodeCanvas::clear_context()
{
    Node::clear_context();
    m_canvas->snapshot_save();
    m_canvas->clear_context();
    // TODO: clear CanvasMode objects
}

void NodeCanvas::draw()
{
    GLint vp[4];
    GLfloat cc[4];
    glGetIntegerv(GL_VIEWPORT, vp);
    glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);

    float zoom = root()->m_zoom;
    auto box = m_clip * zoom;
    glm::ivec4 c = (glm::ivec4)glm::vec4(box.x, (int)(vp[3] - box.y - box.w), box.z, box.w);
    glClearColor(1, 1, 1, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    glViewport(c.x + App::I.off_x, c.y + App::I.off_y, c.z, c.w);

    //m_canvas->m_cam_rot = m_pan * 0.003f;

    glm::mat4 ortho_proj = glm::ortho(0.f, box.z, 0.f, box.w, -1000.f, 1000.f);
    glm::mat4 proj = glm::perspective(glm::radians(m_canvas->m_cam_fov), box.z / box.w, 0.001f, 1000.f);
    glm::mat4 camera = glm::translate(m_canvas->m_cam_pos) * m_canvas->m_cam_rot;

    m_canvas->m_mv = camera;
    m_canvas->m_proj = proj;
    m_canvas->m_box = box;
    m_canvas->m_vp = c;

    //         auto plane_mvp = proj * camera * transform *
    //             glm::scale(glm::vec3(sz, 1));


    m_sampler.bind(0);
    m_sampler.bind(1);
    m_sampler.bind(2);
    m_sampler.bind(3);
    m_sampler_stencil.bind(4);
    auto blend = glIsEnabled(GL_BLEND);
    auto depth = glIsEnabled(GL_DEPTH_TEST);


    float pitch = 0;
    if (auto slider = root()->find<NodeSliderH>("pitch-slider"))
        pitch = (slider->get_value() - 0.5) * glm::half_pi<float>();
    float yaw = 0;
    if (auto slider = root()->find<NodeSliderH>("yaw-slider"))
        yaw = (slider->get_value() - 0.5) * glm::half_pi<float>();
    float roll = 0;
    if (auto slider = root()->find<NodeSliderH>("roll-slider"))
        roll = (slider->get_value() - 0.5) * glm::half_pi<float>();

    // pre computed helpers
    for (int plane_index = 0; plane_index < 6; plane_index++)
    {
        //glm::mat4 plane_camera = glm::lookAt(m_canvas->m_plane_origin[plane_index], m_canvas->m_plane_normal[plane_index], m_canvas->m_plane_tangent[plane_index]);
        m_canvas->m_plane_unproject[plane_index] = glm::inverse(m_canvas->m_proj * m_canvas->m_mv * m_canvas->m_plane_transform[plane_index]);
        m_canvas->m_plane_dir[plane_index] = -(m_canvas->m_plane_transform[plane_index] * glm::vec4(m_canvas->m_plane_origin[plane_index], 1));
        // face is the 2d shape of the cube plane i projected onto the window space
        m_canvas->m_plane_shape[plane_index] = m_canvas->face_to_shape2D(plane_index);
    }

    // check if any layer use blend, otherwise draw directly on main framebuffer
    bool use_blend = false;
    for (size_t i = 0; i < m_canvas->m_order.size(); i++)
    {
        auto layer_index = m_canvas->m_order[i];
        use_blend |= m_canvas->m_layers[layer_index]->m_blend_mode != 0;
    }

    if (use_blend)
    {
        m_cache_rtt.bindFramebuffer();
        m_cache_rtt.clear({ 1, 1, 1, 0 });
    }
    else
    {
        // draw the grid
        for (int plane_index = 0; plane_index < 6; plane_index++)
        {
            auto plane_mvp = proj * camera *
                glm::scale(glm::vec3(m_canvas->m_order.size() + 500)) *
                m_canvas->m_plane_transform[plane_index] *
                glm::translate(glm::vec3(0, 0, -1));

            ShaderManager::use(kShader::Checkerboard);
            ShaderManager::u_int(kShaderUniform::Colorize, false);
            ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp);
            m_face_plane.draw_fill();
        }
    }

    // if not using shader blend, use gl rasterizer blend
    use_blend ? glDisable(GL_BLEND) : glEnable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);

    const auto& b = m_canvas->m_current_stroke->m_brush;

    for (size_t i = 0; i < m_canvas->m_order.size(); i++)
    {
        auto layer_index = m_canvas->m_order[i];
        for (int plane_index = 0; plane_index < 6; plane_index++)
        {
            if (!(m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index) && 
                (!m_canvas->m_layers[layer_index]->m_visible ||
                m_canvas->m_layers[layer_index]->m_opacity == .0f ||
                !m_canvas->m_layers[layer_index]->m_dirty_face[plane_index]))
                continue;

            if (use_blend)
            {
                m_blender_rtt.bindFramebuffer();
                m_blender_rtt.clear();
            }

            int z = (int)(m_canvas->m_order.size() - i);
            auto plane_mvp_z = proj * camera * 
                glm::scale(glm::vec3(z + 1)) * 
                glm::eulerAngleYXZ(yaw, pitch, roll) *
                m_canvas->m_plane_transform[plane_index] * 
                glm::translate(glm::vec3(0, 0, -1));
            
            if (m_canvas->m_current_stroke && m_canvas->m_current_mode == kCanvasMode::Erase && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
            {
                m_sampler.bind(0);
                ShaderManager::use(kShader::CompErase);
                ShaderManager::u_int(kShaderUniform::Tex, 0);
                ShaderManager::u_int(kShaderUniform::TexStroke, 1);
                ShaderManager::u_int(kShaderUniform::TexMask, 2);
                //ShaderManager::u_vec2(kShaderUniform::Resolution, zw(m_canvas->m_box) / zoom);
                ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index]->m_opacity);
                //ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index]->m_alpha_locked);
                ShaderManager::u_int(kShaderUniform::Mask, m_canvas->m_smask_active);
                ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
                glActiveTexture(GL_TEXTURE0);
                m_canvas->m_layers[layer_index]->m_rtt[plane_index].bindTexture();
                glActiveTexture(GL_TEXTURE1);
                m_canvas->m_tmp[plane_index].bindTexture();
                glActiveTexture(GL_TEXTURE2);
                m_canvas->m_smask.m_rtt[plane_index].bindTexture();
                m_face_plane.draw_fill();
                m_canvas->m_smask.m_rtt[plane_index].unbindTexture();
                glActiveTexture(GL_TEXTURE1);
                m_canvas->m_tmp[plane_index].unbindTexture();
                glActiveTexture(GL_TEXTURE0);
                m_canvas->m_layers[layer_index]->m_rtt[plane_index].unbindTexture();
            }
            else if(m_canvas->m_current_stroke && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
            {
                m_sampler.bind(0);
                
                glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale);
                if (b->m_pattern_flipx) patt_scale.x *= -1.f;
                if (b->m_pattern_flipy) patt_scale.y *= -1.f;
                
                ShaderManager::use(kShader::CompDraw);
                ShaderManager::u_int(kShaderUniform::Tex, 0);
                ShaderManager::u_int(kShaderUniform::TexStroke, 1);
                ShaderManager::u_int(kShaderUniform::TexMask, 2);
                ShaderManager::u_int(kShaderUniform::TexDual, 3);
                ShaderManager::u_vec2(kShaderUniform::Resolution, Canvas::I->m_size);
                ShaderManager::u_int(kShaderUniform::TexPattern, 4);
                ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index]->m_opacity);
                ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index]->m_alpha_locked);
                ShaderManager::u_int(kShaderUniform::Mask, m_canvas->m_smask_active);
                ShaderManager::u_int(kShaderUniform::UseFragcoord, false);
                ShaderManager::u_int(kShaderUniform::BlendMode, b->m_blend_mode);
                ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
                ShaderManager::u_int(kShaderUniform::UseDual, b->m_dual_enabled);
                ShaderManager::u_int(kShaderUniform::DualBlendMode, b->m_dual_blend_mode);
                ShaderManager::u_int(kShaderUniform::UsePattern, b->m_pattern_enabled && !b->m_pattern_eachsample);
                ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale);
                ShaderManager::u_float(kShaderUniform::PatternInvert, b->m_pattern_invert);
                ShaderManager::u_float(kShaderUniform::PatternBright, b->m_pattern_brightness);
                ShaderManager::u_float(kShaderUniform::PatternContrast, b->m_pattern_contrast);
                ShaderManager::u_float(kShaderUniform::PatternDepth, b->m_pattern_depth);
                ShaderManager::u_int(kShaderUniform::PatternBlendMode, b->m_pattern_blend_mode);
                ShaderManager::u_vec2(kShaderUniform::PatternOffset, Canvas::I->m_pattern_offset);
                ShaderManager::u_float(kShaderUniform::DualAlpha, b->m_dual_opacity);

                glActiveTexture(GL_TEXTURE0);
                m_canvas->m_layers[layer_index]->m_rtt[plane_index].bindTexture();
                glActiveTexture(GL_TEXTURE1);
                m_canvas->m_tmp[plane_index].bindTexture();
                glActiveTexture(GL_TEXTURE2);
                m_canvas->m_smask.m_rtt[plane_index].bindTexture();
                glActiveTexture(GL_TEXTURE3);
                if (b->m_dual_enabled)
                    m_canvas->m_tmp_dual[plane_index].bindTexture();
                glActiveTexture(GL_TEXTURE4);
                b->m_pattern_texture ?
                    b->m_pattern_texture->bind() :
                    glBindTexture(GL_TEXTURE_2D, 0);
                m_face_plane.draw_fill();
                glActiveTexture(GL_TEXTURE3);
                if (b->m_dual_enabled)
                    m_canvas->m_tmp_dual[plane_index].unbindTexture();
                glActiveTexture(GL_TEXTURE2);
                m_canvas->m_smask.m_rtt[plane_index].unbindTexture();
                glActiveTexture(GL_TEXTURE1);
                m_canvas->m_tmp[plane_index].unbindTexture();
                glActiveTexture(GL_TEXTURE0);
                m_canvas->m_layers[layer_index]->m_rtt[plane_index].unbindTexture();
            }
            else
            {
                m_sampler.bind(0);
                m_sampler_linear.bind(1);
                ShaderManager::use(kShader::TextureAlphaSep);
                ShaderManager::u_int(kShaderUniform::Tex, 0);
                ShaderManager::u_int(kShaderUniform::TexA, 1);
                ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index]->m_opacity);
                ShaderManager::u_int(kShaderUniform::Highlight, m_canvas->m_layers[layer_index]->m_hightlight);
                ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
                
                glActiveTexture(GL_TEXTURE0);
                m_canvas->m_layers[layer_index]->m_rtt[plane_index].bindTexture();
                glActiveTexture(GL_TEXTURE1);
                m_canvas->m_layers[layer_index]->m_rtt[plane_index].bindTexture();
                
                m_face_plane.draw_fill();
                
                glActiveTexture(GL_TEXTURE1);
                m_canvas->m_layers[layer_index]->m_rtt[plane_index].unbindTexture();
                glActiveTexture(GL_TEXTURE0);
                m_canvas->m_layers[layer_index]->m_rtt[plane_index].unbindTexture();
            }

            if (use_blend)
            {
                m_blender_rtt.unbindFramebuffer();
            }

            // draw the blended
            if (use_blend)
            {
                m_sampler.bind(0);
                m_sampler_linear.bind(1);
                m_sampler.bind(2);

                ShaderManager::use(kShader::TextureBlend);
                ShaderManager::u_int(kShaderUniform::Tex, 0);
                ShaderManager::u_int(kShaderUniform::TexA, 1);
                if (!ShaderManager::ext_framebuffer_fetch)
                    ShaderManager::u_int(kShaderUniform::TexBG, 2);
                ShaderManager::u_int(kShaderUniform::BlendMode, m_canvas->m_layers[layer_index]->m_blend_mode);
                ShaderManager::u_float(kShaderUniform::Alpha, 1.f);
                ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-1, 1, -1, 1));
                
                glActiveTexture(GL_TEXTURE0);
                m_blender_rtt.bindTexture();
                glActiveTexture(GL_TEXTURE1);
                m_blender_rtt.bindTexture();
                if (!ShaderManager::ext_framebuffer_fetch)
                {
                    glActiveTexture(GL_TEXTURE2);
                    m_blender_bg.bind();
                    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0,
                        m_blender_bg.size().x, m_blender_bg.size().y);
                }

                m_face_plane.draw_fill();

                if (!ShaderManager::ext_framebuffer_fetch)
                {
                    glActiveTexture(GL_TEXTURE2);
                    m_blender_bg.unbind();
                }
                glActiveTexture(GL_TEXTURE1);
                m_blender_rtt.unbindTexture();
                glActiveTexture(GL_TEXTURE0);
                m_blender_rtt.unbindTexture();
            }

#ifdef _DEBUG
            // draw dirty area
            {
                auto bb = m_canvas->m_layers[layer_index]->m_dirty_box[plane_index] / (float)m_canvas->m_layers[layer_index]->w;
                glm::vec2 bbmin = xy(bb);
                glm::vec2 bbsz = zw(bb) - xy(bb);
                ShaderManager::use(kShader::Color);
                ShaderManager::u_vec4(kShaderUniform::Col, { 1, 0, 0, 1 });
                ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z 
                    * glm::translate(glm::vec3(bbmin * 2.f, 0))
                    * glm::translate(glm::vec3(-1, -1, 0))
                    * glm::scale(glm::vec3(bbsz, 1))
                    * glm::translate(glm::vec3(1, 1, 0))
                );
                m_face_plane.draw_stroke();
            }
#endif
        }
    }
    if (use_blend)
    {
        m_cache_rtt.unbindFramebuffer();
    }

    // draw the grid behind the layers using a temporary copy
    if (use_blend)
    {
        glEnable(GL_BLEND);
        
        //draw the grid
        for (int plane_index = 0; plane_index < 6; plane_index++)
        {
            auto plane_mvp = proj * camera *
                glm::scale(glm::vec3(m_canvas->m_order.size() + 500.f)) *
                m_canvas->m_plane_transform[plane_index] *
                glm::translate(glm::vec3(0, 0, -1.f));

            ShaderManager::use(kShader::Checkerboard);
            ShaderManager::u_int(kShaderUniform::Colorize, false);
            ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp);
            m_face_plane.draw_fill();
        }

        // draw the layers
        m_sampler.bind(0);
        glActiveTexture(GL_TEXTURE0);
        m_cache_rtt.bindTexture();
        ShaderManager::use(kShader::Texture);
        ShaderManager::u_int(kShaderUniform::Tex, 0);
        ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho<float>(-1, 1, -1, 1));
        m_face_plane.draw_fill();
        m_cache_rtt.unbindTexture();
    }

    glDisable(GL_DEPTH_TEST);
    if (m_canvas->m_smask_active || m_canvas->m_current_mode == kCanvasMode::Copy || m_canvas->m_current_mode == kCanvasMode::Cut)
    {
        m_canvas->modes[(int)kCanvasMode::MaskFree][0]->on_Draw(ortho_proj, proj, camera);
        m_canvas->modes[(int)kCanvasMode::MaskLine][0]->on_Draw(ortho_proj, proj, camera);
    }
    
    // keep drawing the grids
    if (m_canvas->m_current_mode != kCanvasMode::Grid)
        for (auto& mode : Canvas::modes[(int)kCanvasMode::Grid])
            mode->on_Draw(ortho_proj, proj, camera);

    App::I.grid->draw_heightmap(proj, camera, false);

    for (auto& mode : *m_canvas->m_mode)
        mode->on_Draw(ortho_proj, proj, camera);

    blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
    depth ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST);
    m_sampler.unbind();
    glViewport(vp[0], vp[1], vp[2], vp[3]);
    glClearColor(cc[0], cc[1], cc[2], cc[3]);
}

void NodeCanvas::handle_resize(glm::vec2 old_size, glm::vec2 new_size)
{
    if (new_size.x != m_canvas->m_width || new_size.y != m_canvas->m_height)
    {
        // actual screen size
        new_size = new_size * root()->m_zoom;
#if defined(__IOS__) || defined(__ANDROID__)
        m_canvas->m_mixer.create((int)new_size.x * m_canvas->m_mixer_scale,
            (int)new_size.y * m_canvas->m_mixer_scale, -1, GL_RGBA16F);
#else
        m_canvas->m_mixer.create((int)new_size.x * m_canvas->m_mixer_scale,
            (int)new_size.y * m_canvas->m_mixer_scale, -1, GL_RGBA32F);
#endif
        m_blender_rtt.create((int)new_size.x, (int)new_size.y, -1, GL_RGBA8);
        m_cache_rtt.create((int)new_size.x, (int)new_size.y, -1, GL_RGBA8);
        m_blender_bg.create((int)new_size.x, (int)new_size.y, GL_RGBA8);
        if (auto img = root()->find<NodeImageTexture>("tex-debug"))
            img->tex.assign(m_canvas->m_mixer.getTextureID());
        //             m_canvas->resize((int)new_size.x, (int)new_size.y);
        //             m_canvas->clear();
    }
}

kEventResult NodeCanvas::handle_event(Event* e)
{
    static bool stylus_eraser = false;
    Node::handle_event(e);
    MouseEvent* me = static_cast<MouseEvent*>(e);
    KeyEvent* ke = static_cast<KeyEvent*>(e);
    GestureEvent* ge = static_cast<GestureEvent*>(e);
    auto loc = (me->m_pos - m_pos) * root()->m_zoom;

    switch (e->m_type)
    {
    case kEventType::MouseMove:
        if (stylus_eraser != me->m_eraser)
        {
            Canvas::set_mode(me->m_eraser ?
                kCanvasMode::Erase :
                kCanvasMode::Draw);
            stylus_eraser = me->m_eraser;
        }
    case kEventType::MouseScroll:
    case kEventType::MouseDownL:
    case kEventType::MouseUpL:
    case kEventType::MouseDownR:
    case kEventType::MouseUpR:
    case kEventType::MouseCancel:
        m_canvas->m_cur_pos = loc;
        for (auto& mode : *m_canvas->m_mode)
            mode->on_MouseEvent(me, loc);
        break;
    case kEventType::KeyDown:
        if (ke->m_key == kKey::KeyE)
            Canvas::set_mode(kCanvasMode::Erase);
        if (ke->m_key == kKey::AndroidBack)
            if (!ActionManager::empty())
                ActionManager::undo();

        break;
    case kEventType::KeyUp:
        if (ke->m_key == kKey::KeyE)
            Canvas::set_mode(kCanvasMode::Draw);
        if (ke->m_key == kKey::KeyTab)
            App::I.toggle_ui();
        if (ke->m_key == kKey::KeyZ && App::I.keys[(int)kKey::KeyCtrl])
            App::I.keys[(int)kKey::KeyShift] ? ActionManager::redo() : ActionManager::undo();
        if (ke->m_key == kKey::KeyS && App::I.keys[(int)kKey::KeyCtrl] && !App::I.keys[(int)kKey::KeyShift])
        {
            if (Canvas::I->m_newdoc)
            {
                App::I.dialog_save();
            }
            else if (Canvas::I->m_unsaved)
            {
                Canvas::I->project_save();
            }
        }
        if (ke->m_key == kKey::KeyS && App::I.keys[(int)kKey::KeyCtrl] && App::I.keys[(int)kKey::KeyShift])
        {
            if (Canvas::I->m_newdoc)
            {
                App::I.dialog_save();
            }
            else if (Canvas::I->m_unsaved)
            {
                App::I.dialog_save_ver();
            }
        }
        break;
    case kEventType::GestureStart:
        mouse_capture();
        for (auto& mode : *m_canvas->m_mode)
            mode->on_GestureEvent(ge);
        break;
    case kEventType::GestureMove:
        for (auto& mode : *m_canvas->m_mode)
            mode->on_GestureEvent(ge);
        break;
    case kEventType::GestureEnd:
        mouse_release();
        for (auto& mode : *m_canvas->m_mode)
            mode->on_GestureEvent(ge);
        break;
    default:
        return kEventResult::Available;
        break;
    }
    return kEventResult::Consumed;
}

void NodeCanvas::reset_camera()
{
    m_canvas->m_cam_rot = glm::mat4(1);
    m_canvas->m_cam_pos = {0, 0, 0};
    m_canvas->m_cam_fov = 85;
    m_canvas->m_pan = {0, 0};
}

void NodeCanvas::destroy_immediate()
{
    Node::destroy_immediate();
    m_blender_rtt.destroy();
    m_cache_rtt.destroy();
    m_blender_bg.destroy();
    m_face_plane.destroy();
    m_line.destroy();
    m_grid.destroy();
}
