//#####################################################################
// Copyright 2002-2007, Robert Bridson, Ronald Fedkiw, Eran Guendelman, Geoffrey Irving, Nipun Kwatra, Andrew Selle, Jonathan Su, Joseph Teran.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
// Class TRIANGLE_COLLISIONS
//#####################################################################
#ifndef __TRIANGLE_COLLISIONS__
#define __TRIANGLE_COLLISIONS__

#include <Core/Basic_Geometry/BASIC_SIMPLEX_POLICY.h>
#include <Core/Data_Structures/SPARSE_UNION_FIND.h>
#include <Core/Spatial_Acceleration/BOX_HIERARCHY.h>
#include <Deformables/Collisions_And_Interactions/TRIANGLE_REPULSIONS_AND_COLLISIONS_GEOMETRY.h>
namespace PhysBAM{

template<class TV> class TRIANGLE_COLLISION_PARAMETERS;

template<class TV>
class TRIANGLE_COLLISIONS
{
    typedef typename TV::SCALAR T;
    typedef typename BASIC_SIMPLEX_POLICY<TV,TV::m-1>::SIMPLEX T_FACE;
    typedef typename BASIC_SIMPLEX_POLICY<TV,TV::m-1>::SIMPLEX_FACE T_EDGE;
public:
    TRIANGLE_REPULSIONS_AND_COLLISIONS_GEOMETRY<TV>& geometry;
    const ARRAY<T>& repulsion_thickness;
    T final_repulsion_youngs_modulus;
    T final_repulsion_limiter_fraction;
    T collision_thickness;
    T restitution_coefficient;
    bool compute_point_face_collisions,compute_edge_edge_collisions;
    int nonrigid_collision_attempts,rigid_collision_attempts;
    bool limit_rigid_collision_attempts;
    bool output_collision_results;
    ARRAY<VECTOR<int,TV::m+1> > point_face_pairs, edge_edge_pairs;
    bool use_gauss_jacobi;

    // [0 = full, 1 = partial][0 = point, 1 = edge, 2 = face]
    BOX_HIERARCHY<TV> hierarchy[2][TV::m];
    ARRAY<VECTOR<int,1> > elements_point[2];
    ARRAY<VECTOR<int,2> > elements_edge[2];
    ARRAY<VECTOR<int,3> > elements_face[2];

    struct GAUSS_JACOBI_DATA
    {
        GAUSS_JACOBI_DATA(TV& target_impulse_input,VECTOR<T,TV::m+1>& target_weight_input,TV& target_normal_input,T& old_speed_input)
            :target_impulse(target_impulse_input),target_weight(target_weight_input),target_normal(target_normal_input),old_speed(old_speed_input)
        {}
        TV& target_impulse;
        VECTOR<T,TV::m+1>& target_weight;
        TV& target_normal;
        T& old_speed;
    };
private:
    int final_point_face_repulsions,final_point_face_collisions,final_edge_edge_repulsions,final_edge_edge_collisions;
    ARRAY<bool> recently_modified_full;

    ARRAY<TV> impulse_velocities;
    ARRAY<TV> pf_target_impulses;
    ARRAY<VECTOR<T,TV::m+1> > pf_target_weights;
    ARRAY<T> pf_old_speeds;
    ARRAY<TV> pf_normals;
    ARRAY<TV> ee_target_impulses;
    ARRAY<VECTOR<T,TV::m+1> > ee_target_weights;
    ARRAY<T> ee_old_speeds;
    ARRAY<TV> ee_normals;
public:

    TRIANGLE_COLLISIONS(TRIANGLE_REPULSIONS_AND_COLLISIONS_GEOMETRY<TV>& geometry,const ARRAY<T>& repulsion_thickness);
    TRIANGLE_COLLISIONS(const TRIANGLE_COLLISIONS&) = delete;
    void operator=(const TRIANGLE_COLLISIONS&) = delete;
    ~TRIANGLE_COLLISIONS();

    void Set_Collision_Thickness(const T thickness=1e-6)
    {collision_thickness=thickness;}

    void Set_Restitution_Coefficient(const T restitution_coefficient_input=0) // default is inelastic
    {restitution_coefficient=restitution_coefficient_input;}

    void Compute_Point_Face_Collisions(const bool compute=true,const int nonrigid_collision_attempts=3)
    {compute_point_face_collisions=compute;Set_Attempts_For_Nonrigid_Collisions(nonrigid_collision_attempts);}

    void Compute_Edge_Edge_Collisions(const bool compute=true,const int nonrigid_collision_attempts=3)
    {compute_edge_edge_collisions=compute;Set_Attempts_For_Nonrigid_Collisions(nonrigid_collision_attempts);}

    void Set_Attempts_For_Nonrigid_Collisions(const int attempts=3) // after this switch to rigid collisions
    {nonrigid_collision_attempts=attempts;}

    void Set_Attempts_For_Rigid_Collisions(const bool limit_attempts=true,const int attempts=10)
    {limit_rigid_collision_attempts=limit_attempts;rigid_collision_attempts=attempts;}

    void Output_Collision_Results(const bool output=true)
    {output_collision_results=output;}

    void Set_Gauss_Jacobi(const bool use_gauss_jacobi_input=false)
    {use_gauss_jacobi=use_gauss_jacobi_input;}

//#####################################################################
    int Adjust_Velocity_For_Self_Collisions(const T dt,const T time,const bool exit_early,const bool change_positions_only=false);
    void Initialize(TRIANGLE_COLLISION_PARAMETERS<TV>& triangle_collision_parameters);
    void Update_Swept_Hierachies_And_Compute_Pairs(ARRAY_VIEW<const TV> X,ARRAY_VIEW<const TV> X_self_collision_free,ARRAY_VIEW<bool> recently_modified,const T detection_thickness,bool all_moved);
private:
    // TODO: rename from "get" to "append"...
    void Scale_And_Apply_Impulses();
    int Adjust_Velocity_For_Point_Face_Collision(const T dt,const bool rigid,SPARSE_UNION_FIND<>& union_find,const ARRAY<VECTOR<int,TV::m+1> >& pairs,
        const T attempt_ratio,const bool final_repulsion_only,const bool exit_early);
    bool Point_Face_Collision(GAUSS_JACOBI_DATA& pf_data,const VECTOR<int,TV::m+1>& nodes,const T dt,const T repulsion_thickness,T& collision_time,const T attempt_ratio,const bool exit_early);
    void Point_Face_Pull_In(const VECTOR<int,TV::m+1>& nodes,ARRAY_VIEW<TV> V,const T dt,const T repulsion_thickness);
    bool Point_Face_Final_Repulsion(GAUSS_JACOBI_DATA& pf_data,const VECTOR<int,TV::m+1>& nodes,const T dt,const T repulsion_thickness,T& collision_time,const T attempt_ratio,const bool exit_early);
    int Adjust_Velocity_For_Edge_Edge_Collision(const T dt,const bool rigid,SPARSE_UNION_FIND<>& union_find,const ARRAY<VECTOR<int,TV::m+1> >& pairs,
        const T attempt_ratio,const bool final_repulsion_only,const bool exit_early);
    int Prune_Non_Intersecting_Pairs(const T dt,ARRAY<VECTOR<int,TV::m+1> >& point_face_pairs,ARRAY<VECTOR<int,TV::m+1> >& edge_edge_pairs,const T attempt_ratio);
    bool Edge_Edge_Collision(GAUSS_JACOBI_DATA& ee_data,const VECTOR<int,TV::m+1>& nodes,const T dt,T& collision_time,const T attempt_ratio,const bool exit_early);
    bool Edge_Edge_Final_Repulsion(GAUSS_JACOBI_DATA& ee_data,const VECTOR<int,TV::m+1>& nodes,const T dt,T& collision_time,const T attempt_ratio,const bool exit_early);
    void Apply_Rigid_Body_Motions(const T dt,const SPARSE_UNION_FIND<>& union_find);
    void Rebuild_Hierarchy(ARRAY_VIEW<const TV> X0,ARRAY_VIEW<const TV> X1);
    void Rebuild_Partial_Hierarchy(ARRAY_VIEW<const TV> X0,ARRAY_VIEW<const TV> X1,ARRAY_VIEW<bool> recently_modified);
    template<int d> void Refresh_Hierarchy(ARRAY_VIEW<const TV> X0,ARRAY_VIEW<const TV> X1,BOX_HIERARCHY<TV>& hierarchy,const ARRAY<VECTOR<int,d> >& elements,bool reorder);
//#####################################################################
};
}
#endif
