//=============================================================================
#ifndef GRAPHENE_TEMPLATE_FIT_H
#define GRAPHENE_TEMPLATE_FIT_H
//=============================================================================

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


#include <string>
#include <tuple>

#include <graphene/surface_mesh/data_structure/Surface_mesh.h>
#include <graphene/character/data_structure/Character.h>
#include <graphene/geometry/Point_set.h>
#include <graphene/geometry/Bounding_box.h>

#include "my_types.h"
#include "Landmarks_manager.h"
#include "Energy.h"
#include "types/Fitting_target.h"


//== NAMESPACE ================================================================


namespace graphene {
namespace surface_mesh {


//== CLASS DEFINITION =========================================================


enum Transformation_direction { trans_dir_mesh2ps, trans_dir_ps2mesh };


class Template_fit
{
private: //------------------------------------------------------- private data

    Energy                              energy_;

    //
    // TODO(swenninger):
    //  We could refactor this code, to not explicitely store
    //  'point_set_' here, but only work with fitting targets
    //
    graphene::geometry::Point_set*      point_set_;             // the actual point set (possibly filtered / possibly subsampled / possibly transformed)
    Surface_mesh*                       template_mesh_;
    graphene::character::Character*     character_;

    std::string                         fn_folder_fieldliner_;

    // bounding mesh used for collision handling
    Surface_mesh                        bounding_mesh_;

    // helper for using old laplacian to a certain extent
    bool                                helper_use_old_laplacian_;

    // data if we fit to multiple targets simultaneously
    std::vector<Fitting_target> fitting_targets_;

    // Whether or not to correct joint positions after shape change.
    // Default is true, but after correcting eyes, we want to keep
    // all joint positions.
    bool correct_joint_positions_;

    // Whether or not the new fitting_target API was used
    // TODO(swenninger): Remove once every Pipeline and EnergyTerm
    //                   has been ported to new fitting target API
    bool fitting_target_api_used_;

    // For visualizing intermediate steps
    std::function<void(const std::string&)> render_callback_;


public: //--------------------------------------------------------- public data

    static int DEBUG_ITER_COUNT;

public: //---------------------------------------------------- public functions

    // constructor
    Template_fit();

    // clear
    void clear();

    Energy& energy() { return energy_; }


//--------------------------------------------------------------- loading stuff


    void set_point_set(graphene::geometry::Point_set* point_set);
    void set_template_mesh(Surface_mesh* template_mesh);
    void set_character(graphene::character::Character* character);
    void add_fitting_target(const Fitting_target& fitting_target);

    bool load_selection_into_bool_property(const std::string& filename, const std::string& property_name);

    bool load_selection_to_omit(const std::string& filename);
    bool load_selection_to_omit(const std::string& filename,
                                const std::string& filename_mapping);
    void clear_selection_to_omit();
    bool load_selection_to_keep(const std::string& filename, bool add = false);
    bool load_selection_to_keep(const std::string& filename, const std::string& filename_mapping, const bool add = false);
    void clear_selection_to_keep();
    bool load_selection_ground(const std::string& filename);
    void clear_selection_ground();
    bool load_mouth_shut_selection(const std::string& filename);
    bool load_mouth_shut_selection(const std::string& filename, const std::string& filename_mapping);
    bool load_fieldlines(const std::string& filename);
    bool update_local_weights_flcs();
    bool load_bounding_mesh(const std::string& filename);

    bool load_ear_landmarks_template(const std::string& filename);
    bool load_ear_landmarks_template(const std::string& filename, const std::string& filename_mapping);
    bool load_ear_landmarks_pointset(const std::string& filename);

    bool load_vertexweighting(const std::string& filename, const std::string& property_name, bool multiply=true);
    bool load_vertexweighting(const std::string& filename, const std::string& filename_mapping, const std::string& property_name, bool multiply=true);
    bool clear_vertexweighting(const std::string& property_name);

    void set_folder_fieldliner(const std::string dirname)
    {
        fn_folder_fieldliner_ = dirname;
    }

    void set_correct_joint_positions(bool correct_joint_positions) { correct_joint_positions_ = correct_joint_positions; }
    void set_render_callback(std::function<void(const std::string&)> render_callback) { render_callback_ = render_callback; }

//---------------------------- initial transformations based on landmarks


    // transforms 'mesh 2 pointset' OR 'pointset 2 mesh'
    bool do_translation_transformation_lm(const Transformation_direction trans_dir, const std::string& lm_prop_name, const std::string& lm_weight_prop_name);
    bool do_similarity_transformation_lm(const Transformation_direction trans_dir, const std::string& lm_prop_name, const std::string& lm_weight_prop_name);

    bool do_similarity_transformation_cpc(const Transformation_direction trans_dir,
                                          const Correspondences_settings& corresp_settings,
                                          double& relative_change);


//-------------------------------------------------------------- nonrigid stuff

    void start(Correspondences_settings& corresp_settings,
               const unsigned int max_iter,
               const double conv_mean_diff,
               const double until_lambda,
               const bool make_plastic = true,
               const bool no_decrease  = false,
               const bool use_dualquaternion_skinning = false
               );


//-------------------------------------------------------------- misc/testing stuff


    void update_restpose();
    void write_mesh_backtransformed( const std::string& filename_mesh );
    double evaluate_fitting();


//---------------------------------------------------------------------- getter


    geometry::Point_set& get_point_set()     { return *point_set_; }
    Surface_mesh&        get_template_mesh() { return *template_mesh_; }

    Landmarks_manager&   get_landmarks_manager() { return energy_.energy_term_data().landmarks_manager; }

    PCA&                 get_pca() { return energy_.energy_term_data().pca; }

private: //-------------------------------------------------- private functions

    void sum_correspondences(const std::vector<Correspondence>& correspondences);
    void sum_correspondences();

    void update_area_property();
    void update_cotan_property();
    void update_laplaciancoordinate_property();
    void update_aniso_laplace_properties();

    void setup_energy_fitting_targets();


    void transform_points(const Mat4f& M);

    void clean_up_mesh_properties();

    unsigned int update_collision_correspondences_v_vs_tri();
    unsigned int update_collision_correspondences_tri_vs_p();

public:

    void collision_resolve_v_vs_tri();
    void collision_resolve_tri_vs_p();

};


//=============================================================================
} // namespace surface_mesh
} // namespace graphene
//=============================================================================
#endif // GRAPHENE_TEMPLATE_FIT_H
//=============================================================================
