//=============================================================================
// Copyright (C) 2014, 2015 Graphics & Geometry Group, Bielefeld University
//=============================================================================

#include "Correspondences_node.h"

#include <graphene/gl/gl_includes.h>
#include <graphene/gl/Shader.h>
#include <graphene/macros.h>

#include <algorithm>


//=============================================================================


namespace graphene {
namespace scene_graph {


//=============================================================================


Correspondences_node::
Correspondences_node(std::vector<Correspondence>& correspondences, const std::string& _name)
    : Object_node(_name),
      correspondences_(correspondences),
      color_(0.0f)
{

    // init draw modes
    clear_draw_modes();

    // initialize GL buffers to zero
    vertex_array_object_ = 0;
    vertex_buffer_       = 0;
    normal_buffer_       = 0;
    color_buffer_        = 0;

}


//-----------------------------------------------------------------------------


Correspondences_node::
~Correspondences_node()
{
    // delete all OpenGL buffers
    if (vertex_buffer_)           glDeleteBuffers(1, &vertex_buffer_);
    if (normal_buffer_)           glDeleteBuffers(1, &normal_buffer_);
    if (color_buffer_ )           glDeleteBuffers(1, &color_buffer_);
    if (selection_index_buffer_)  glDeleteBuffers(1, &selection_index_buffer_);
    if (vertex_array_object_) glDeleteVertexArrays(1, &vertex_array_object_);

}


//-----------------------------------------------------------------------------


#if 0
bool
Correspondences_node::
load(const std::string& filename)
{
    fileinfo_ = filename;

    // extract file extension
    std::string::size_type dot(filename.rfind("."));
    if (dot == std::string::npos) return false;
    std::string ext = filename.substr(dot+1, filename.length()-dot-1);
    std::transform(ext.begin(), ext.end(), ext.begin(), tolower);

    bool ok = false;

    // extension determines reader
    if (ext == "xyz")
    {
        ok = point_set_.read(filename.c_str());
    }
    else if (ext == "cxyz")
    {
        ok = point_set_.read_cxyz(filename.c_str());
    }
    else if (ext == "txt")
    {
        ok = point_set_.read_metashape_txt(filename.c_str());
    }

    update_point_set();

    return ok;
}


//-----------------------------------------------------------------------------


bool
Correspondences_node::
save(const std::string& filename) const
{
    // extract file extension
    std::string::size_type dot(filename.rfind("."));
    if (dot == std::string::npos) return false;
    std::string ext = filename.substr(dot+1, filename.length()-dot-1);
    std::transform(ext.begin(), ext.end(), ext.begin(), tolower);

    bool ok = false;

    // extension determines reader
    if (ext == "xyz")
    {
        ok = point_set_.write(filename.c_str());
    }
    else if(ext == "cxyz")
    {
        ok = point_set_.write_cxyz(filename.c_str());
    }
    else if(ext == "txt")
    {
        ok = point_set_.write_metashape_txt(filename.c_str());
    }

    LOG(Log_info) << filename  << " saved!\n";

    return ok;
}
#endif

//-----------------------------------------------------------------------------


std::string
Correspondences_node::
info() const
{
    std::ostringstream s;
    s << correspondences_.size() << " correspondences";
    return s.str();
}


//-----------------------------------------------------------------------------


void
Correspondences_node::
draw(gl::GL_state* gl_state)
{
    if (!visible_)
    {
        return;
    }

    // setup Phong shader
    auto line_shader = gl_state->set_active_shader(gl::LINE_SHADER);
    line_shader->set_uniform("modelview_projection_matrix", gl_state->modelviewproj_);

    glBindVertexArray(vertex_array_object_);

    // TODO:What"s this
    // glDepthRange(0.002, 1.0);

    size_t arraySize = correspondences_.size() * 4;
    glDrawArrays(GL_LINES, 0, (GLsizei)arraySize);

    // reset stuff
    gl_state->set_active_shader(gl::PHONG_SHADER);
    glBindVertexArray(0);
    glPointSize(1.0);
    glDepthRange(0.0, 1.0);
}


//-----------------------------------------------------------------------------


geometry::Bounding_box
Correspondences_node::
bbox() const
{
    return bbox_;
}


//-----------------------------------------------------------------------------


void
Correspondences_node::
initialize_buffers()
{
    // initialize buffers
    if (vertex_array_object_ == 0)
    {
        glGenVertexArrays(1, &vertex_array_object_);
        glBindVertexArray(vertex_array_object_);
        glGenBuffers(1, &vertex_buffer_);
        glGenBuffers(1, &color_buffer_);
        glBindVertexArray(0);
    }
}


//-----------------------------------------------------------------------------



void
Correspondences_node::
update_correspondences()
{
    // initialize buffers
    if (vertex_array_object_ == 0)
    {
        //printf("[DEBUG] update_correspondences with uninitialized buffers\n");
        initialize_buffers();
    }
    else
    {
        //printf("[DEBUG] update_correspondences with initialized buffers\n");
    }


    std::vector<Point> points;
    std::vector<Vec4f> colors;
    points.reserve(correspondences_.size() * 4);
    colors.reserve(correspondences_.size() * 4);

    bbox_ = Bounding_box();

    //printf("[DEBUG] update_correspondences with %lu correspondences\n", correspondences_.size());

    double min_weight =  DBL_MAX;
    double max_weight = -DBL_MAX;
    for (const auto& c : correspondences_)
    {
        if (min_weight > c.weight)
            min_weight = c.weight;

        if (max_weight < c.weight)
            max_weight = c.weight;
    }

    for (const auto& c : correspondences_)
    {
        bbox_ += c.on_template;
        bbox_ += c.on_ps;

        points.push_back(c.on_template);
        points.push_back(c.on_ps);

        if (c.constr_dir == graphene::surface_mesh::corresp_dir_ps2mesh)
        {
            points.push_back(c.on_template_closest_face_barycenter);
            points.push_back(c.on_ps);
        }

        float alpha = 1.0f;
        if (std::abs(max_weight - min_weight) > 1e-6)
        {
            alpha = (float)((c.weight - min_weight) / (max_weight - min_weight));
            alpha = std::max(0.4f, alpha);
        }

        if (c.constr_dir == graphene::surface_mesh::corresp_dir_mesh2ps)
        {
            colors.push_back({0.3f, 0.8f, 0.8f, alpha});
            colors.push_back({0.8f, 0.3f, 0.8f, alpha});
        }
        else
        {
            colors.push_back({0.2f, 0.2f, 0.8f, alpha});
            colors.push_back({0.8f, 0.3f, 0.2f, alpha});
            colors.push_back({0.2f, 0.8f, 0.2f, alpha});
            colors.push_back({0.2f, 0.8f, 0.2f, alpha});
        }

    }

    glBindVertexArray(vertex_array_object_);

    // vertices
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
    glBufferData(GL_ARRAY_BUFFER, points.size()*3*sizeof(float), points.data(), GL_STATIC_DRAW);
    glVertexAttribPointer(gl::attrib_locations::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(gl::attrib_locations::VERTEX);

    // colors
    glBindBuffer(GL_ARRAY_BUFFER, color_buffer_);
    glBufferData(GL_ARRAY_BUFFER, colors.size()*4*sizeof(float), colors.data(), GL_STATIC_DRAW);
    glVertexAttribPointer(gl::attrib_locations::COLOR, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(gl::attrib_locations::COLOR);


    glBindVertexArray(0);
}


//=============================================================================
} // namespace scene_graph
} // namespace graphene
//=============================================================================
