/*
 *  Copyright (c) 2000-2022 Inria
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  * Neither the name of the ALICE Project-Team nor the names of its
 *  contributors may be used to endorse or promote products derived from this
 *  software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

#include <geogram/parameterization/mesh_global_param.h>
#include <geogram/mesh/mesh.h>
#include <geogram/mesh/mesh_geometry.h>
#include <geogram/mesh/mesh_frame_field.h>

#include <deque>
#include <stack>


namespace {
    using namespace GEO;

    /**
     * \brief Computes the angle between two vectors associated to two adjacent
     *  facets.
     * \param[in] mesh a pointer to a surface mesh
     * \param[in] f1 the first facet
     * \param[in] B1 the first 3d vector, in the plane of f1
     * \param[in] f2 the second facet
     * \param[in] B2 the second 3d vector, in the plane of f2
     * \return the angle in degrees between -180 and 180 required to
     *  transform vector \p B2 to vector \p B1 expressed in the local frame
     *  of facet f1 with the common edge of the two facets f1 and f2 as
     *  the X axis
     */
    double angle(
        Mesh* mesh, index_t f1, const vec3& B1, index_t f2, const vec3& B2
    ) {
        index_t lf2 = mesh->facets.find_adjacent(f1,f2);
        geo_assert(lf2 != NO_FACET);
        index_t c1 = mesh->facets.corners_begin(f1) + lf2;
        index_t c2 = mesh->facets.next_corner_around_facet(f1,c1);
        index_t v1 = mesh->facet_corners.vertex(c1);
        index_t v2 = mesh->facet_corners.vertex(c2);
        if(v2 < v1) {
            std::swap(v1,v2);
        }
        const vec3& p1 = mesh->vertices.point(v1);
        const vec3& p2 = mesh->vertices.point(v2);
        vec3 E = normalize(p2-p1);
        vec3 N1 = normalize(Geom::mesh_facet_normal(*mesh,f1));
        vec3 N2 = normalize(Geom::mesh_facet_normal(*mesh,f2));
        vec3 Y1 = cross(N1,E);
        vec3 Y2 = cross(N2,E);
        double x1 = dot(B1,E);
        double y1 = dot(B1,Y1);
        double x2 = dot(B2,E);
        double y2 = dot(B2,Y2);
        double a1 = atan2(y1,x1) * 180.0 / M_PI;
        double a2 = atan2(y2,x2) * 180.0 / M_PI;
        double result = a1-a2;

        while(result < -180.0) {
            result += 360.0;
        }
        while(result > 180.0) {
            result -= 360.0;
        }

        return result;
    }


    /**
     * \brief Gets the number of 90 degrees rotations required to minimize the
     *  angle between the vectors attached to two adjacent facets.
     * \param[in] mesh a pointer to a surface mesh
     * \param[in] f1 the first facet
     * \param[in] B1 the first 3D vector in the plane of f1
     * \param[in] f2 the second facet
     * \param[in] B2 the second 3D vector in the plane of f2
     * \return the number of times \p B2 should be rotated around the
     *  normal vector of \p f2 to minimize its angle with \p B1 in angus
     *  (in 0,1,2,3).
     */
    index_t Rij(
        Mesh* mesh, index_t f1, const vec3& B1, index_t f2, const vec3& B2
    ) {
        if(f1 > f2) {
            index_t result = Rij(mesh, f2, B2, f1, B1);
            return GlobalParam2d::Internal::inverse_R(result);
        }
        vec3 N2 = normalize(Geom::mesh_facet_normal(*mesh,f2));
        vec3 cur_B2 = B2;
        double best_angle = ::fabs(angle(mesh, f1, B1, f2, cur_B2));
        index_t best_i = 0;
        for(index_t i=1; i<4; ++i) {
            cur_B2 = cross(N2, cur_B2);
            double cur_angle = ::fabs(angle(mesh, f1, B1, f2, cur_B2));

            if(cur_angle < best_angle) {
                best_angle = cur_angle;
                best_i = i;
            }
        }
        return best_i;
    }

    /**
     * \brief Sets an attribute on both corners adjacent to the same edge.
     * \param[in] mesh a pointer to a surface mesh.
     * \param[out] attr a facet corner attribute
     * \param[in] f1 , f2 the two facets that share the edge
     * \param[in] val the new value of the attribute
     */
    void set_edge_attr(
        Mesh* mesh, Attribute<index_t>& attr,
        index_t f1, index_t f2, index_t val
    ) {
        index_t e1 = mesh->facets.find_adjacent(f1,f2);
        index_t e2 = mesh->facets.find_adjacent(f2,f1);
        attr[mesh->facets.corners_begin(f1)+e1] = val;
        attr[mesh->facets.corners_begin(f2)+e2] = val;
    }

}

namespace GEO {
    namespace GlobalParam2d {
        namespace Internal {

            void compute_R_ff(
                Mesh* mesh, Attribute<vec3>& B, Attribute<index_t>& R_ff
            ) {
                for(index_t f1: mesh->facets) {
                    FOR(e1, mesh->facets.nb_vertices(f1)) {
                        index_t f2 = mesh->facets.adjacent(f1,e1);
                        index_t c = mesh->facets.corners_begin(f1) + e1;
                        if(f2 != NO_FACET) {
                            index_t rij = Rij(mesh, f1, B[f1], f2, B[f2]);
                            index_t rji = Rij(mesh, f2, B[f2], f1, B[f1]);
                            geo_assert(rij == inverse_R(rji));
                            R_ff[c] = rij;
                        }
                    }
                }
            }


            void compute_R_fv(
                Mesh* mesh,
                Attribute<index_t>& R_ff, Attribute<index_t>& R_fv
            ) {
                // - Each vertex has a reference corner (v2c[v])
                // - Each corner c knows the number of rotations Rc[c] required
                //    to make the B of its triangle match the B of the triangle
                //    of the reference corner attached to its vertex (clear
                //    enough ?)

                // Step 1: Compute v2c
                //   Note: if there exists a corner that has his previous corner
                //   around the facet that is on the surface border for a
                //   given v, then use this one.
                //   Later, when we turn around the vertices,
                //   it will be easier to start from such an halfedge for all
                //   vertices that are on the border.

                vector<index_t> v2c(mesh->vertices.nb(), NO_CORNER);
                {
                    for(index_t c: mesh->facet_corners) {
                        index_t f = c/3;
                        index_t c_prev =
                            mesh->facets.prev_corner_around_facet(f,c);
                        if(mesh->facet_corners.adjacent_facet(c_prev) ==
                           NO_FACET
                          ) {
                            index_t v = mesh->facet_corners.vertex(c);
                            v2c[v] = c;
                        }
                    }
                    for(index_t c: mesh->facet_corners) {
                        index_t v = mesh->facet_corners.vertex(c);
                        if(v2c[v] == NO_CORNER) {
                            v2c[v] = c;
                        }
                    }
                }

                // Step 2: Compute Rv by turning around the facets that
                //  share a vertex.
                //   Yes, it is painful, I hate doing that, but R only works
                //   for pairs of *adjacent* facets (and I cannot think about
                //   a way of making work it *reliably* for any pair of facets).
                {
                    for(index_t v: mesh->vertices) {
                        index_t prev_c = NO_CORNER;
                        index_t c = v2c[v];

                        // Isolated vertex, ignore
                        if(c == NO_CORNER) {
                            continue;
                        }

                        do {
                            index_t
                                next_f = mesh->facet_corners.adjacent_facet(c);
                            index_t next_c = NO_CORNER;
                            if(next_f != NO_FACET) {
                                for(index_t c2: mesh->facets.corners(next_f)) {
                                    if(mesh->facet_corners.vertex(c2) == v) {
                                        next_c = c2;
                                        break;
                                    }
                                }
                                geo_assert(next_c != NO_CORNER);
                            }
                            if(prev_c != NO_CORNER) {
                                R_fv[c] = (R_fv[prev_c] + R_ff[prev_c]) % 4;
                            }
                            prev_c = c;
                            c = next_c;
                        } while(c != NO_CORNER && c != v2c[v]);
                    }
                }
            }

            void mark_singular_vertices(
                Mesh* mesh,
                Attribute<index_t>& R_ff, Attribute<bool>& v_is_singular
            ) {
                vector<index_t> Rsum(mesh->vertices.nb(),0);
                for(index_t f: mesh->facets) {
                    for(index_t c: mesh->facets.corners(f)) {
                        if(
                            mesh->facet_corners.adjacent_facet(c) !=
                            NO_INDEX
                        ) {
                            index_t v = mesh->facet_corners.vertex(c);
                            Rsum[v] += R_ff[c];
                        }
                    }
                }
                for(index_t v: mesh->vertices) {
                    v_is_singular[v] = ((Rsum[v] % 4) != 0);
                }
                // Vertices on border can have non-zero Rsum without being
                // singular.
                for(index_t c: mesh->facet_corners) {
                    if(mesh->facet_corners.adjacent_facet(c) == NO_FACET) {
                        v_is_singular[mesh->facet_corners.vertex(c)] = false;
                    }
                }
            }

            void brush(Mesh* mesh, Attribute<vec3>& B) {
                std::vector<bool> visited(mesh->facets.nb(),false);
                std::deque<index_t> S;
                S.push_back(0);
                visited[0] = true;
                while(!S.empty()) {
                    index_t f1 = S.front();
                    S.pop_front();
                    FOR(e1, mesh->facets.nb_vertices(f1)) {
                        index_t f2 = mesh->facets.adjacent(f1,e1);
                        if(f2 != NO_FACET && !visited[f2]) {
                            vec3 N2 = normalize(
                                Geom::mesh_facet_normal(*mesh,f2)
                            );
                            index_t Rc1 = Rij(mesh,f1,B[f1],f2,B[f2]);
                            FOR(i, Rc1) {
                                B[f2] = cross(N2,B[f2]);
                            }
                            visited[f2] = true;
                            S.push_back(f2);
                        }
                    }
                }
            }


            void do_the_ball(
                Mesh* mesh,
                Attribute<index_t>& R_ff, Attribute<index_t>& c_on_border
            ) {
                for(index_t c: mesh->facet_corners) {
                    c_on_border[c] = 1;
                }

                // Covering tree

                std::vector<bool> visited(mesh->facets.nb(),false);
                std::deque<index_t> S;
                S.push_back(0);
                visited[0] = true;
                while(!S.empty()) {
                    index_t f1 = S.front();
                    S.pop_front();
                    for(index_t c1: mesh->facets.corners(f1)) {
                        index_t f2 = mesh->facet_corners.adjacent_facet(c1);
                        if(f2 != NO_FACET && !visited[f2] && R_ff[c1] == 0) {
                            set_edge_attr(mesh, c_on_border, f1, f2, 0);
                            visited[f2] = true;
                            S.push_back(f2);
                        }
                    }
                }

                // Zipping

                vector<index_t> v_nb_borders(mesh->vertices.nb(), 0);
                for(index_t c: mesh->facet_corners) {
                    if(c_on_border[c]) {
                        ++v_nb_borders[mesh->facet_corners.vertex(c)];
                    }
                }

                bool there_are_degree1_vertices = true;
                while(there_are_degree1_vertices) {
                    there_are_degree1_vertices = false;
                    for(index_t c1: mesh->facet_corners) {
                        index_t v1 = mesh->facet_corners.vertex(c1);
                        if(v_nb_borders[v1] == 1 &&
                           (c_on_border[c1] != 0) && R_ff[c1] == 0
                          ) {
                            index_t f2 = mesh->facet_corners.adjacent_facet(c1);
                            if(f2 != NO_FACET) {
                                there_are_degree1_vertices = true;
                                index_t f1 = c1/3;
                                index_t c2 = mesh->facets.corners_begin(f2) +
                                    mesh->facets.find_adjacent(f2,f1);
                                index_t v2 = mesh->facet_corners.vertex(c2);
                                c_on_border[c1] = 0;
                                c_on_border[c2] = 0;
                                --v_nb_borders[v1];
                                --v_nb_borders[v2];
                            }
                        }
                    }
                }
            }


            void do_the_ball_no_brush_no_zip(
                Mesh* mesh, Attribute<index_t>& c_on_border
            ) {
                for(index_t c: mesh->facet_corners) {
                    c_on_border[c] = 1;
                }

                // Covering tree

                std::vector<bool> visited(mesh->facets.nb(),false);
                std::deque<index_t> S;
                S.push_back(0);
                visited[0] = true;
                while(!S.empty()) {
                    index_t f1 = S.front();
                    S.pop_front();
                    for(index_t c1: mesh->facets.corners(f1)) {
                        index_t f2 = mesh->facet_corners.adjacent_facet(c1);
                        if(f2 != NO_FACET && !visited[f2]) {
                            set_edge_attr(mesh, c_on_border, f1, f2, 0);
                            visited[f2] = true;
                            S.push_back(f2);
                        }
                    }
                }
            }

            void get_B_on_edge(
                Mesh* mesh, Attribute<vec3>& B, Attribute<index_t>& R_ff,
                index_t f, index_t c,
                vec3& Bc, vec3& BTc
            ) {
                vec3 Nf = normalize(Geom::mesh_facet_normal(*mesh,f));
                Bc = B[f];
                BTc = cross(Nf,Bc);
                index_t f2 = mesh->facet_corners.adjacent_facet(c);
                if(f2 != NO_FACET) {
                    vec3 N2 = normalize(Geom::mesh_facet_normal(*mesh,f2));
                    vec3 B2 = B[f2];
                    vec3 BT2 = cross(N2,B2);
                    FOR(i,R_ff[c]) {
                        B2 = cross(N2,B2);
                        BT2 = cross(N2,BT2);
                    }
                    Bc += B2;
                    BTc += BT2;
                }
                Bc = normalize(Bc);
                BTc = normalize(BTc);
            }

            void get_constraints(
                Mesh* mesh, Attribute<vec3>& B, Attribute<index_t>& R_ff,
                Attribute<index_t>& constraint
            ) {

                geo_argused(R_ff);

                for(index_t c: mesh->facet_corners) {
                    constraint[c] = CNSTR_NONE;
                }

                for(index_t c: mesh->facet_corners) {
                    index_t edge_constraints = get_edge_constraints(mesh,c,B);
                    index_t f = c/3;
                    index_t c2 = mesh->facets.next_corner_around_facet(f,c);
                    constraint[c] |= edge_constraints;
                    constraint[c2] |= edge_constraints;
                }

                return;

                /*
                //   Propagate the constraints: all the corners incident to a
                // vertex that is itself incident to a constrained edge are
                // constrained.
                //   Normally, I think this would not be required, since the
                // (u,v) compatibility constraint + the mutiplity constraint of
                // one of the (u,v)'s imply that all the corners incident to the
                // considered vertex should have integer coordinates,
                // however if I do not set this constraint I observed that it
                // does not work as expected (note: interestingly, when I
                // added the wheel compatibility constraint there was a big
                // improvement, but it did not solve all issues).
                std::stack<index_t> S;
                std::vector<bool> is_visited(mesh->facet_corners.nb(),false);

                for(index_t c=0; c<mesh->facet_corners.nb(); ++c) {
                if(constraint[c] != CNSTR_NONE) {
                S.push(c);
                is_visited[c] = true;
                }
                }

                while(!S.empty()) {
                index_t c = S.top();
                S.pop();
                index_t f = c/3;
                index_t cprev = mesh->facets.prev_corner_around_facet(f,c);
                index_t fneigh = mesh->facet_corners.adjacent_facet(cprev);
                if(fneigh == NO_FACET) {
                continue;
                }
                index_t eneigh = mesh->facets.find_adjacent(fneigh,f);
                index_t cneigh =
                mesh->facets.corners_begin(fneigh) + eneigh;

                if(is_visited[cneigh]) {
                continue;
                }

                bool cu = (constraint[c] & CNSTR_U) != 0;
                bool cv = (constraint[c] & CNSTR_V) != 0;

                index_t Rij = R_ff[cprev];

                // If rotation is 90 degrees or 270 degrees, then
                // u and v are swapped.
                if((Rij & 1) != 0) {
                std::swap(cu,cv);
                }

                if(cu) {
                constraint[cneigh] |= CNSTR_U;
                }

                if(cv) {
                constraint[cneigh] |= CNSTR_V;
                }

                is_visited[cneigh]=true;
                S.push(cneigh);
                }*/
            }

            index_t get_edge_constraints(
                Mesh* mesh, index_t c, Attribute<vec3>& B
            ) {
                index_t result = 0;

                index_t f = c/3;
                vec3 N = normalize(Geom::mesh_facet_normal(*mesh,f));

                index_t f2 = mesh->facet_corners.adjacent_facet(c);
                if(f2 != NO_FACET) {
                    if(
                        ::fabs(Geom::mesh_normal_angle(*mesh,c)) * 180.0 / M_PI
                        < 45.0
                    ) {
                        return 0;
                    }
                }

                index_t v1 = mesh->facet_corners.vertex(c);
                index_t c2 = mesh->facets.next_corner_around_facet(c/3,c);
                index_t v2 = mesh->facet_corners.vertex(c2);
                vec3 E = mesh->vertices.point(v2) - mesh->vertices.point(v1);
                vec3 Bf = normalize(B[f]);
                vec3 Bfrot = cross(N,Bf);

                double a1 = (Geom::angle(E,Bf)) * 180.0 / M_PI;
                a1 = std::min(a1, 180.0-a1);
                if(a1 < 10.0) {
                    result |= GlobalParam2d::Internal::CNSTR_V;
                }

                double a2 = (Geom::angle(E,Bfrot)) * 180.0 / M_PI;
                a2 = std::min(a2, 180.0-a2);
                if(a2 < 10.0) {
                    result |= GlobalParam2d::Internal::CNSTR_U;
                }

                // geo_assert!(a1 < 10.0 && a2 < 10.0));
                // Should not occur...
                if(a1 < 10.0 && a2 < 10.0) {
                    result = 0;
                }

                return result;
            }


            void snap_tex_coord(double& coord) {
                double snapped = GEO::round(coord);
                if(std::fabs(coord - snapped) < 0.05) {
                    coord = snapped;
                }
            }

            index_t inverse_R(index_t R) {
                geo_assert(R < 4);
                static index_t inverse[4] = {
                    0,
                    3,
                    2,
                    1
                };
                return inverse[R];
            }


            void transfer_B_to_vertices(
                Mesh* mesh,
                Attribute<vec3>& B, Attribute<vec3>& Bv,
                Attribute<index_t>& R_fv
            ) {
                for(index_t v: mesh->vertices) {
                    Bv[v] = vec3(0.0, 0.0, 0.0);
                }
                for(index_t c: mesh->facet_corners) {
                    index_t v = mesh->facet_corners.vertex(c);
                    index_t f = c/3;
                    vec3 Bf = normalize(B[f]);
                    vec3 N = normalize(Geom::mesh_facet_normal(*mesh, f));
                    FOR(k, R_fv[c]) {
                        Bf = cross(N,Bf);
                    }
                    Bv[v] += Bf;
                }
                for(index_t v: mesh->vertices) {
                    Bv[v] = normalize(Bv[v]);
                }
            }
        } // namespace Internal

        void frame_field(
            Mesh* mesh, Attribute<vec3>& B,
            double hard_angle_threshold
        ) {
            FrameField FF;
            // We will directly query the field on the facets, no need
            // for the KD-tree.
            FF.set_use_spatial_search(false);
            FF.create_from_surface_mesh(*mesh,false,hard_angle_threshold);
            const vector<double>& frames = FF.frames();
            for(index_t f: mesh->facets) {
                B[f] = vec3(
                    frames[9*f+0],
                    frames[9*f+1],
                    frames[9*f+2]
                );
            }
        }

    } // namespace GlobalParam2d
} // namespace OGF
