//== INCLUDES ===================================================================


#include "Blendshapes_generation.h"

#include <graphene/surface_mesh/eigen_algorithms/deformation_transfer/Deformation_transfer_botsch.h>
#include <graphene/surface_mesh/eigen_algorithms/deformation_transfer/Deformation_transfer_sumner.h>
#include <graphene/surface_mesh/algorithms/nonlinear_discrete_shell/Nonlinear_discrete_shell.h>
#include <graphene/surface_mesh/algorithms/smoothing/Smoother.h>
#include <graphene/surface_mesh/algorithms/surface_mesh_tools/misc.h>

#include <fstream>


//== NAMESPACES ================================================================


namespace graphene {
namespace surface_mesh {


//== IMPLEMENTATION ============================================================


Blendshapes_generation::
Blendshapes_generation()
{}

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


bool
Blendshapes_generation::
smooth_character_and_blendshapes(character::Character& character,
                                 const std::string& s_bodymesh,
                                 const std::string& fn_sel_vertices_to_smooth)
{
    const int  num_explicit_smoothing_iterations = 5;
    const bool use_uniform_laplace = true;

    // Load selection of vertices that should be smoothed
    std::vector<int> vertices_to_smooth;
    if (!graphene::surface_mesh::load_selection_from_file(vertices_to_smooth, fn_sel_vertices_to_smooth))
    {
        std::cerr << "Pipeline: [ERROR] Can't load selection for smoothing eye fold: " << fn_sel_vertices_to_smooth << std::endl;
        return false;
    }

    // Get bodymesh
    int bodymesh_index = character.get_skin_idx(s_bodymesh);
    if (bodymesh_index < 0)
    {
        std::cerr << "[ERROR] " << __FUNCTION__  << ": Cannot get bodymesh " << s_bodymesh << " from character" << std::endl;
        return false;
    }

    auto* mesh = character.skins()[bodymesh_index];

    auto selected_mesh = mesh->vertex_property<bool>("v:selected", false);
    for (auto idx : vertices_to_smooth)
        selected_mesh[Surface_mesh::Vertex(idx)] = true;

    Smoother smoother_bodymesh(*mesh);
    smoother_bodymesh.smooth(num_explicit_smoothing_iterations, use_uniform_laplace);
    mesh->remove_vertex_property<bool>(selected_mesh);

    // we have to smooth all blenshape targets of base mesh

    // get base mesh's blendshapes
    const std::vector<character::Blendshapes*>& blendshape_sets = character.blendshapes();
    character::Blendshapes* selected_bs_set = nullptr;
    for (size_t i = 0; i < blendshape_sets.size(); ++i)
    {
        character::Blendshapes* bs = blendshape_sets[i];
        if (bs->base() == mesh)
        {
            selected_bs_set = bs;
            break;
        }
    }

    if (selected_bs_set == nullptr)
    {
        std::cerr << "Pipeline: [ERROR] Can't find blendshape targets for base mesh!" << std::endl;
        return false;
    }

    // At this point 'selected_bs_set' are our base mesh's blendshapes

    // get blendshape targets of base mesh
    std::vector<graphene::character::Blendshapes::Target_shape*>& targets_body = selected_bs_set->targets();
    // update targets
    for (unsigned int bs = 0; bs < targets_body.size(); ++bs)
    {
        // First, for getting a Surface_mesh with connectivity (blendshape targets are stored without connectivity information),
        // copy the base mesh
        graphene::surface_mesh::Surface_mesh mesh_blendshape_target = *selected_bs_set->base();

        // Get vertex positions and normals from blendshape target
        auto target_points   = targets_body[bs]->get_vertex_property<Point>("v:point");
        auto target_vnormals = targets_body[bs]->get_vertex_property<Point>("v:normal");
        auto target_hnormals = targets_body[bs]->get_halfedge_property<Point>("h:normal");

        // Copy vertex positions and normals from blendshape target
        mesh_blendshape_target.get_vertex_property<Point>("v:point").vector()    = target_points.vector();
        mesh_blendshape_target.get_vertex_property<Point>("v:normal").vector()   = target_vnormals.vector();
        if (target_hnormals)
            mesh_blendshape_target.get_halfedge_property<Point>("h:normal").vector() = target_hnormals.vector();

        // smooth blendshape target
        auto selected_mesh_blendshape_target = mesh_blendshape_target.vertex_property<bool>("v:selected", false);
        for (auto idx : vertices_to_smooth)
            selected_mesh_blendshape_target[Surface_mesh::Vertex(idx)] = true;

        Smoother smoother_source_undeformed(mesh_blendshape_target);
        smoother_source_undeformed.smooth(num_explicit_smoothing_iterations, use_uniform_laplace);
        mesh_blendshape_target.remove_vertex_property<bool>(selected_mesh_blendshape_target);

        // Copy smoothed values back to blendshape target
        target_points.vector()   = mesh_blendshape_target.get_vertex_property<Point>("v:point").vector();
        target_vnormals.vector() = mesh_blendshape_target.get_vertex_property<Point>("v:normal").vector();
        if (target_hnormals)
            target_hnormals.vector() = mesh_blendshape_target.get_halfedge_property<Point>("h:normal").vector();

    }

    return true;
}

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


bool
Blendshapes_generation::
transfer_blendshapes(character::Character &ch, const std::string &base_mesh, const std::string& fn_nonlin_selection, bool non_linear)
{
    const std::vector<character::Blendshapes*>& blendshape_sets = ch.blendshapes();

    character::Blendshapes* selected_bs_set = nullptr;

    for (size_t i = 0; i < blendshape_sets.size(); ++i)
    {
        character::Blendshapes* bs = blendshape_sets[i];
        Surface_mesh::Mesh_property<std::string> mid = bs->base()->get_mesh_property<std::string>("m:id");
        if (mid && mid[0] == base_mesh)
        {
            selected_bs_set = bs;
            break;
        }
    }

    if (selected_bs_set == nullptr)
    {
        std::cerr << "Blendshapes_generation::transfer_blendshapes: [ERROR] Cannot transfer blendshapes. Did not find base mesh with blendshapes: \"" << base_mesh << "\"" << std::endl;
        return false;
    }

    Surface_mesh::Vertex_property<Point> undef_source_prop = selected_bs_set->base()->get_vertex_property<Point>("v:undef_point");
    if (!undef_source_prop)
    {
        std::cerr << "Blendshapes_generation::transfer_blendshapes: [ERROR] No undeformed data." << std::endl;
        return false;
    }

    //create new undeformed mesh
    Surface_mesh mesh_source_undeformed = *selected_bs_set->base();
    Surface_mesh::Vertex_property<Point> vp = mesh_source_undeformed.get_vertex_property<Point>("v:point");
    vp.vector() = undef_source_prop.vector();

    if (non_linear)
    {
        by_discrete_shell(mesh_source_undeformed,
                          *selected_bs_set->base(),
                          *selected_bs_set,
                          fn_nonlin_selection
                    );
    }
    else
    {
        by_deformation_transfer(mesh_source_undeformed, *selected_bs_set->base(), *selected_bs_set);
    }

    return true;
}

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


bool
Blendshapes_generation::
by_deformation_transfer(graphene::surface_mesh::Surface_mesh& mesh_source_undeformed,
                        graphene::surface_mesh::Surface_mesh& mesh_target_undeformed,
                        graphene::character::Blendshapes& blendshapes_body)
{
    // get blendshapes of base mesh
    std::vector<graphene::character::Blendshapes::Target_shape*>& targets_body = blendshapes_body.targets();

    // update targets
    for (unsigned int bs = 0; bs < targets_body.size(); ++bs)
    {
        // generate new blendshape by deformation transfer

        // we need the fitted neutral shape (target undeformed)
        // we need the undeformed source shape (source undeformed)
        // for every new blendshape, we need the old one (source deformed)

        // old blendshape (source deformed)
        graphene::surface_mesh::Surface_mesh& mesh_source_deformed = *targets_body[bs];

        // copy of fitted neutral shape
        // deformation transfer (Botsch), i.e., compute 'target deformed'
        graphene::surface_mesh::Surface_mesh mesh_target_deformed = mesh_target_undeformed;
        graphene::surface_mesh::Deformation_transfer_botsch deftrans;
        if (!deftrans.create_deformed_targets(mesh_source_undeformed, // initial old template   (source undeformed)
                                              mesh_source_deformed,   // current old blendshape (source deformed)
                                              mesh_target_deformed))  // copy of fitted neutral shape that (we want to change: target deformed)
        {
            std::cerr << "[ERROR] during deformation transfer: cannot deform target!" << std::endl;
            return false;
        }

        // deformation transfer (Sumner), i.e., compute 'target deformed'
        // graphene::surface_mesh::Deformation_transfer_sumner deftrans(mesh_target_undeformed);// copy of fitted neutral shape that (we want to change: target deformed)
        // auto points_mesh_source_undeformed = mesh_source_undeformed.get_vertex_property<Point>("v:point");
        // auto points_mesh_source_deformed   = mesh_source_deformed.get_vertex_property<Point>("v:point");
        // if (!deftrans.deform_target(points_mesh_source_undeformed.vector(), // initial old template   (source undeformed)
        //                             points_mesh_source_deformed.vector())   // current old blendshape (source deformed)
        //    )
        // {
        //     std::cerr << "[ERROR] during deformation transfer: cannot deform target!" << std::endl;
        //     return false;
        // }
        // graphene::surface_mesh::Surface_mesh mesh_target_deformed = deftrans.get_deformed_target();

        // update blendshape target
        for (auto v : targets_body[bs]->vertices())
        {
            targets_body[bs]->position(v) = mesh_target_deformed.position(v);
        }
        targets_body[bs]->update_face_normals();
        targets_body[bs]->update_vertex_normals();
    }

    return true;
}


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


bool
Blendshapes_generation::
by_discrete_shell(graphene::surface_mesh::Surface_mesh& mesh_source_undeformed,
                  graphene::surface_mesh::Surface_mesh& mesh_target_undeformed,
                  graphene::character::Blendshapes& blendshapes_body,
                  const std::string& filename_fixed_nonlindeftrans)
{
    // update targets
    std::vector<graphene::character::Blendshapes::Target_shape*>& targets_body = blendshapes_body.targets();
    for (unsigned int bs = 0; bs < targets_body.size(); ++bs)
    {
        // generate new blendshape by deformation transfer

        // we need the fitted neutral shape (target undeformed)
        // we need the undeformed source shape (source undeformed)
        // for every new blendshape, we need the old one (source deformed)
        graphene::surface_mesh::Surface_mesh& mesh_source_deformed = *targets_body[bs];

        // deformation transfer
        graphene::surface_mesh::Surface_mesh mesh_target_deformed = mesh_target_undeformed;

        auto region = mesh_target_deformed.vertex_property<Region_type>("v:region"); // TODO fix more points to speed up
        for (auto v : mesh_target_deformed.vertices())
        {
            region[v] = deformable;
        }
        // Surface_mesh::Vertex v0(11348);
        // region[v0] = fixed;
        // Surface_mesh::Vertex v1(11295);
        // region[v1] = fixed;
        // Surface_mesh::Vertex v2(11296);
        // region[v2] = fixed;
        // Surface_mesh::Vertex v3(689);
        // region[v3] = fixed;
        // Surface_mesh::Vertex v4(690);
        // region[v4] = fixed;
        // Surface_mesh::Vertex v5(738);
        // region[v5] = fixed;

        std::ifstream ifs;
        ifs.open(filename_fixed_nonlindeftrans.c_str());
        if (!ifs)
        {
            std::cerr << "[ERROR] Can't open selection of body." << std::endl;
            return false;
        }
        unsigned int idx;
        while (true)
        {
            ifs >> idx;
            if (ifs.eof()) break;

            Surface_mesh::Vertex v(idx);
            region[v] = fixed;
        }


        graphene::surface_mesh::Nonlinear_discrete_shell nonlin_ds;

        if (!nonlin_ds.set_base_mesh(mesh_target_deformed))
        {
            std::cerr << "[ERROR] Can't set base mesh." << std::endl;
            return false;
        }

        if (!nonlin_ds.set_stiffness(10000, 10))
        {
            std::cerr << "[ERROR] Can't set stiffness." << std::endl;
            return false;
        }

        if (!nonlin_ds.deformation_transfer(mesh_source_undeformed, mesh_source_deformed))
        {
            std::cerr << "[ERROR] Can't transfer deformation." << std::endl;
            return false;
        }


        // update blendshape target
        for (auto v : targets_body[bs]->vertices())
        {
            targets_body[bs]->position(v) = mesh_target_deformed.position(v);
        }
        targets_body[bs]->update_face_normals();
        targets_body[bs]->update_vertex_normals();
    }

    return true;
}


//=============================================================================
} // namespace surface_mesh
} // namespace graphene
//=============================================================================
