/*
 *  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_PGP_2d.h>
#include <geogram/mesh/mesh.h>
#include <geogram/mesh/mesh_geometry.h>
#include <geogram/NL/nl.h>
#include <geogram/bibliography/bibliography.h>

#include <stack>

namespace {
    using namespace GEO;


    /**
     * \brief computes gradients in a triangle in 2D.
     */
    class ParamTrglGradient {
    public:
        ParamTrglGradient(
            const vec2& p1, const vec2& p2, const vec2& p3
        ) {
            vertex_[0] = p1 ;
            vertex_[1] = p2 ;
            vertex_[2] = p3 ;

            double x1 = p1.x ;
            double y1 = p1.y ;
            double x2 = p2.x ;
            double y2 = p2.y ;
            double x3 = p3.x ;
            double y3 = p3.y ;

            double d = x2*y3 - y2*x3 + x3*y1 - y3*x1 + x1*y2 - y1*x2 ;

            if(fabs(d) < 1e-10) {
                d = 1.0 ;
                is_flat_ = true ;
            } else {
                is_flat_ = false ;
            }

            TX_[0] = (y2 - y3)/d ;
            TX_[1] = (y3 - y1)/d ;
            TX_[2] = (y1 - y2)/d ;

            TY_[0] = -(x2 - x3)/d ;
            TY_[1] = -(x3 - x1)/d ;
            TY_[2] = -(x1 - x2)/d ;
        }

        double TX(int i) const {
            geo_debug_assert(i<3);
            return TX_[i];
        }

        double TY(int i) const {
            geo_debug_assert(i<3);
            return TY_[i];
        }

        bool is_flat() const {
            return is_flat_ ;
        }

    private:
        double TX_[3] ;
        double TY_[3] ;
        vec2 vertex_[3] ;
        bool is_flat_ ;
    } ;


    /**
     * \brief Retrieves a coordinate from an angle computed by PGP.
     * \param[in] alpha the input variable.
     * \param[in] ref the reference variable.
     * \return a number congruent to \p alpha modulo 2 pi in the inverval
     *  [ \p ref - M_PI, \p ref + M_PI]
     */
    double normalize_periodic_variable(
        double alpha, double ref
    ) {
        int count = 0;
        if(Numeric::is_nan(alpha)) {
            return 0.0 ;
        }
        if(Numeric::is_nan(ref)) {
            return 0.0 ;
        }
        double result = alpha ;
        count = 0 ;
        while(ref - result > M_PI) {
            result += 2.0 * M_PI ;
            count ++ ;
            if(count > 100) {
                return 0.0 ;
            }
        }
        count = 0 ;
        while(result - ref > M_PI) {
            result -= 2.0 * M_PI ;
            count ++ ;
            if(count > 100) {
                return 0.0 ;
            }
        }
        return result ;
    }
}

namespace GEO {

    namespace GlobalParam2d {

        void PGP(
            Mesh* mesh,
            Attribute<vec3>& B, Attribute<vec2>& U,
            double scaling, bool constrain_hard_edges,
            bool use_direct_solver,
            double maximum_scaling_correction
        ) {
            geo_cite("DBLP:journals/tog/RayLLSA06");

            bool do_brush = true;

            // We use f = c/3
            // (could be fixed in the future, using a c2f array...).
            geo_assert(mesh->facets.are_simplices());

            // Step 0: Preparation

            scaling *= 2.0;
            scaling *= surface_average_edge_length(*mesh);

            // Step 0.1: Preparation / Brushing
            if(do_brush) {
                Internal::brush(mesh,B);
            }

            // Step 0.2: Preparation / Compute relative rotation of B between
            //   pairs of adjacent facets
            Attribute<index_t> R_ff(mesh->facet_corners.attributes(),"R");
            Internal::compute_R_ff(mesh,B,R_ff);
            Attribute<index_t> R_fv(mesh->facet_corners.attributes(),"R_fv");
            Internal::compute_R_fv(mesh,R_ff,R_fv);

            Attribute<double> CC;
            if(maximum_scaling_correction != 1.0) {
                Logger::out("PGP") << "Computing scaling correction"
                                   << std::endl;
                CC.bind(mesh->vertices.attributes(), "CC");
                Attribute<vec3> Bv(mesh->vertices.attributes(), "B");
                Internal::transfer_B_to_vertices(mesh, B, Bv, R_fv);
                curl_correction(
                    mesh, Bv, R_fv, CC,
                    use_direct_solver, maximum_scaling_correction
                );
            }


            Logger::out("PGP") << "Solving for PGP" << std::endl;


            // Step 1: Determine the structure of the problem:
            // - There are four variables per vertex (cu, su, cv, sv)
            // - 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 ?)

            // Rot[ R_ff[c] ][i][j] corresponds to the transform to
            // be applied to the variables associated with a
            // vertex to express their coordinates in the frame
            // of corner c (it corresponds to the same Rot[][][]
            // matrices as QuadCover, with the exception that each
            // coefficient 1 is replaced with the 2x2 identity matrix
            // and each coefficient -1 with Mat2x2(1,0,0,-1) (there is
            // only one "-1" coefficient because cos(-x) = cos(x) !!).
            // Note: in the PGP paper, the wrong coefficient is negated.

            static const double Rot[4][4][4] = {
                {{ 1, 0, 0, 0},
                 { 0, 1, 0, 0},
                 { 0, 0, 1, 0},
                 { 0, 0, 0, 1}},

                {{ 0, 0, 1, 0},
                 { 0, 0, 0, 1},
                 { 1, 0, 0, 0},
                 { 0,-1, 0, 0}},

                {{ 1, 0, 0, 0},
                 { 0,-1, 0, 0},
                 { 0, 0, 1, 0},
                 { 0, 0, 0,-1}},

                {{ 0, 0, 1, 0},
                 { 0, 0, 0,-1},
                 { 1, 0, 0, 0},
                 { 0, 1, 0, 0}}
            };

            static double Rotc2[4][4];

            // Step 2: setup and solve linear system
            {

                nlNewContext();

                nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE);
                nlSolverParameteri(
                    NL_NB_VARIABLES, NLint(mesh->vertices.nb()*4)
                );

                if(use_direct_solver) {
                    if(nlInitExtension("CHOLMOD")) {
                        nlSolverParameteri(NL_SOLVER, NL_CHOLMOD_EXT);
                    } else if(nlInitExtension("SUPERLU")) {
                        nlSolverParameteri(NL_SOLVER, NL_PERM_SUPERLU_EXT);
                    } else {
                        Logger::warn("PGP")
                            << "Could not initialize direct sovlver"
                            << std::endl;
                        Logger::warn("PGP")
                            << "Falling back to Jacobi pre-CG"
                            << std::endl;
                        use_direct_solver = false;
                    }
                }

                if(!use_direct_solver) {
                    // With the iterative solver, a very small threshold is
                    // needed, because of the very high scaling on the
                    // solution due to the varying modulus of the complex
                    // numbers far away from the constrained point.
                    nlSolverParameterd(NL_THRESHOLD, 1e-20);
                }

                nlBegin(NL_SYSTEM);

                if(constrain_hard_edges) {
                    for(index_t c: mesh->facet_corners) {
                        index_t cnstr =
                            Internal::get_edge_constraints(mesh, c, B);
                        index_t v = mesh->facet_corners.vertex(c);
                        // Inverse R, because when the axis turns
                        // clockwise, coordinates turn anticlockwise.
                        index_t Rcc = Internal::inverse_R(R_fv[c]);
                        if(cnstr & Internal::CNSTR_U) {
                            if(Rcc == 0 || Rcc == 2) {
                                nlLockVariable(4*v);
                                nlSetVariable(4*v,1e4);
                                nlLockVariable(4*v+1);
                                nlSetVariable(4*v+1,0.0);
                            } else {
                                nlLockVariable(4*v+2);
                                nlSetVariable(4*v+2,1e4);
                                nlLockVariable(4*v+3);
                                nlSetVariable(4*v+3,0.0);
                            }
                        }
                        if(cnstr & Internal::CNSTR_V) {
                            if(Rcc == 0 || Rcc == 2) {
                                nlLockVariable(4*v+2);
                                nlSetVariable(4*v+2,1e4);
                                nlLockVariable(4*v+3);
                                nlSetVariable(4*v+3,0.0);
                            } else {
                                nlLockVariable(4*v);
                                nlSetVariable(4*v,1e4);
                                nlLockVariable(4*v+1);
                                nlSetVariable(4*v+1,0.0);
                            }
                        }
                    }
                }

                // Lock at least one variable per connected component.
                {
                    std::vector<bool> f_visited(mesh->facets.nb(),false);
                    for(index_t f: mesh->facets) {
                        if(!f_visited[f]) {
                            index_t nb_locked=0;
                            index_t first_v = mesh->facets.vertex(f,0);
                            std::stack<index_t> S;
                            f_visited[f] = true;
                            S.push(f);
                            while(!S.empty()) {
                                index_t f_top = S.top();
                                S.pop();
                                FOR(le,mesh->facets.nb_vertices(f_top)) {
                                    index_t v = mesh->facets.vertex(f_top,le);
                                    if(
                                        nlVariableIsLocked(4*v) ||
                                        nlVariableIsLocked(4*v+2)) {
                                        ++nb_locked;
                                    }
                                    index_t f_neigh =
                                        mesh->facets.adjacent(f_top,le);

                                    if(f_neigh != NO_FACET &&
                                       !f_visited[f_neigh]
                                      ) {
                                        f_visited[f_neigh] = true;
                                        S.push(f_neigh);
                                    }
                                }
                            }

                            // Lock one of the points in each
                            // connected component to make sure that
                            // the minimum is well defined.
                            if(nb_locked == 0) {
                                nlLockVariable(4*first_v);
                                nlSetVariable(4*first_v,1e4);

                                nlLockVariable(4*first_v+1);
                                nlSetVariable(4*first_v+1,0.0);

                                nlLockVariable(4*first_v+2);
                                nlSetVariable(4*first_v+2,1e4);

                                nlLockVariable(4*first_v+3);
                                nlSetVariable(4*first_v+3,0.0);
                            }
                        }
                    }
                }

                nlBegin(NL_MATRIX);

                //  This one will be replaced in-place
                // with the rotation that encodes the
                // delta u and delta v along each edge.

                double RotDelta[4][4];
                FOR(i,4) {
                    FOR(j,4) {
                        RotDelta[i][j] = ((i==j) ? 1.0 : 0.0);
                    }
                }

                for(index_t f: mesh->facets) {

                    vec3 Bf, BTf;

                    for(index_t c1=mesh->facets.corners_begin(f);
                        c1 < mesh->facets.corners_end(f); ++c1) {

                        Internal::get_B_on_edge(mesh, B, R_ff, f, c1, Bf, BTf);

                        index_t c2 =
                            mesh->facets.next_corner_around_facet(f,c1);
                        index_t v1 = mesh->facet_corners.vertex(c1);
                        index_t v2 = mesh->facet_corners.vertex(c2);

                        vec3 E =
			    mesh->vertices.point(v2) -
                            mesh->vertices.point(v1) ;
                        double delta_u = 2.0 * M_PI * dot(E,Bf)/scaling;
                        double delta_v = 2.0 * M_PI * dot(E,BTf)/scaling;

                        if(CC.is_bound()) {
                            double s = (0.5*(CC[v1] + CC[v2]));
                            delta_u /= s;
                            delta_v /= s;
                        }

                        double sdu = sin(delta_u);
                        double cdu = cos(delta_u);
                        double sdv = sin(delta_v);
                        double cdv = cos(delta_v);

                        RotDelta[0][0] =  cdu;
                        RotDelta[0][1] = -sdu;
                        RotDelta[1][0] =  sdu;
                        RotDelta[1][1] =  cdu;

                        RotDelta[2][2] =  cdv;
                        RotDelta[2][3] = -sdv;
                        RotDelta[3][2] =  sdv;
                        RotDelta[3][3] =  cdv;

                        // Inverse R, because when the axis turns
                        // clockwise, coordinates turn anticlockwise.
                        index_t Rc1 = Internal::inverse_R(R_fv[c1]);
                        index_t Rc2 = Internal::inverse_R(R_fv[c2]);

                        // Compute the product of the "delta u, delta v"
                        // rotation with the Rc2 "90 degrees rotation" matrix,
                        // exactly like in the PGP article.

                        for(index_t i=0; i<4; ++i) {
                            for(index_t j=0; j<4; ++j) {
                                Rotc2[i][j] = 0.0;
                                for(index_t k=0; k<4; ++k) {
                                    Rotc2[i][j] +=
                                        RotDelta[i][k] * Rot[Rc2][k][j];
                                }
                            }
                        }

                        for(index_t i=0; i<4; ++i) {
                            nlBegin(NL_ROW);
                            for(index_t j=0; j<4; ++j) {
                                double a1 = Rot[Rc1][i][j];
                                if(a1 != 0.0) {
                                    nlCoefficient(v1*4+j, a1);
                                }
                            }
                            for(index_t j=0; j<4; ++j) {
                                double a2 = Rotc2[i][j];
                                if(a2 != 0.0) {
                                    nlCoefficient(v2*4+j, -a2);
                                }
                            }
                            nlEnd(NL_ROW);
                        }
                    }
                }

                nlEnd(NL_MATRIX);
                nlEnd(NL_SYSTEM);

                nlSolve();

                Attribute<double> PGP;
                PGP.bind_if_is_defined(mesh->facet_corners.attributes(),"PGP");
                if(!PGP.is_bound()) {
                    PGP.create_vector_attribute(
                        mesh->facet_corners.attributes(), "PGP", 4
                    );
                }

                for(index_t c: mesh->facet_corners) {
                    index_t v = mesh->facet_corners.vertex(c);
                    // Inverse R, because when the axis turns
                    // clockwise, coordinates turn anticlockwise.
                    index_t Rcc = Internal::inverse_R(R_fv[c]);
                    double vars[4];
                    for(index_t i=0; i<4; ++i) {
                        vars[i] = 0.0;
                        for(index_t j=0; j<4; ++j) {
                            vars[i] += Rot[Rcc][i][j] * nlGetVariable(4*v+j);
                        }
                    }
                    double s = sqrt(vars[0]*vars[0]+vars[1]*vars[1]);
                    vars[0] /= s;
                    vars[1] /= s;

                    s = sqrt(vars[2]*vars[2]+vars[3]*vars[3]);
                    vars[2] /= s;
                    vars[3] /= s;

                    U[c].x = atan2(vars[1], vars[0]);
                    U[c].y = atan2(vars[3], vars[2]);

                    FOR(i,4) {
                        PGP[4*c+i] = vars[i];
                    }
                }

                Attribute<index_t> singular(
                    mesh->facets.attributes(),"is_singular"
                );

                for(index_t f: mesh->facets) {
                    singular[f] = false;
                    for(index_t c1=mesh->facets.corners_begin(f);
                        c1<mesh->facets.corners_end(f); ++c1) {
                        index_t c2 =
                            mesh->facets.next_corner_around_facet(f,c1);

                        index_t v1 = mesh->facet_corners.vertex(c1);
                        index_t v2 = mesh->facet_corners.vertex(c2);
                        vec3 E =
			    mesh->vertices.point(v2) -
                            mesh->vertices.point(v1);

                        vec3 Bf, BTf;
                        Internal::get_B_on_edge(mesh, B, R_ff, f, c1, Bf, BTf);

                        double delta_u = 2.0 * M_PI * dot(E,Bf)/scaling;
                        double delta_v = 2.0 * M_PI * dot(E,BTf)/scaling;

                        if(CC.is_bound()) {
                            double s = (0.5*(CC[v1] + CC[v2]));
                            delta_u /= s;
                            delta_v /= s;
                        }

                        double expected_u = U[c1].x-delta_u;
                        double expected_v = U[c1].y-delta_v;
                        vec2 uv(
                            normalize_periodic_variable(U[c2].x,expected_u),
                            normalize_periodic_variable(U[c2].y,expected_v)
                        );
                        if(c2 == mesh->facets.corners_begin(f)) {
                            singular[f] =
                                (length(uv - U[c2]) > 1e-20) ? 1 : 0;
                        } else {
                            U[c2] = uv;
                        }

                    }
                }

                for(index_t c: mesh->facet_corners) {
                    U[c].x /= M_PI;
                    U[c].y /= M_PI;
                    Internal::snap_tex_coord(U[c].x);
                    Internal::snap_tex_coord(U[c].y);
                }

                nlDeleteContext(nlGetCurrent());
            }


        }


        void curl_correction(
            Mesh* mesh, Attribute<vec3>& Bv,
            Attribute<index_t>& R_fv, Attribute<double>& CC,
            bool use_direct_solver, double max_scaling_correction
        ) {
            geo_assert(Bv.manager() == &mesh->vertices.attributes());
            nlNewContext();

            nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE);
            nlSolverParameteri(
                NL_NB_VARIABLES, NLint(mesh->vertices.nb()*4)
            );

            if(use_direct_solver) {
                if(nlInitExtension("CHOLMOD")) {
                    nlSolverParameteri(NL_SOLVER, NL_CHOLMOD_EXT);
                } else if(nlInitExtension("SUPERLU")) {
                    nlSolverParameteri(NL_SOLVER, NL_PERM_SUPERLU_EXT);
                } else {
                    Logger::warn("PGP")
                        << "Could not initialize direct solver"
                        << std::endl;
                    Logger::warn("PGP")
                        << "Falling back to Jacobi pre-CG"
                        << std::endl;
                    use_direct_solver = false;
                }
            }

            if(!use_direct_solver) {
                // With the iterative solver, a very small threshold is
                // needed, because of the very high scaling on the
                // solution due to the varying modulus of the complex
                // numbers far away from the constrained point.
                nlSolverParameterd(NL_THRESHOLD, 1e-20);
            }

            const double locked_value = 1.0;
            const double solver_scale = 1e3;

            nlBegin(NL_SYSTEM);
            nlLockVariable(0u);
            nlSetVariable(0u,locked_value);
            nlBegin(NL_MATRIX);
            for(index_t f: mesh->facets) {

                index_t va = mesh->facets.vertex(f,0);
                index_t vb = mesh->facets.vertex(f,1);
                index_t vc = mesh->facets.vertex(f,2);

                vec3 N = normalize(Geom::mesh_facet_normal(*mesh,f));

                vec3 fieldA3d = normalize(Bv[va]);
                vec3 fieldB3d = normalize(Bv[vb]);
                vec3 fieldC3d = normalize(Bv[vc]);

                FOR(i, Internal::inverse_R(R_fv[f*3])) {
                    fieldA3d = cross(N,fieldA3d);
                }

                FOR(i, Internal::inverse_R(R_fv[f*3+1])) {
                    fieldB3d = cross(N,fieldB3d);
                }

                FOR(i, Internal::inverse_R(R_fv[f*3+2])) {
                    fieldC3d = cross(N,fieldC3d);
                }

                vec3 U = normalize(cross(cross(N, fieldA3d),N));
                vec3 V = normalize(cross(N, U));


                // fieldx is the 2d field at point x. The fields are
                // rotated in a coherent way to take the modulus into
                // account notice that it will be better if the field
                // is rotated to the facet instead of being projected
                vec2 fieldA(1.0,0.0);
                vec2 fieldB(dot(fieldB3d, U), dot(fieldB3d, V)) ;
                fieldB = normalize(fieldB);

                vec2 fieldC (dot(fieldC3d, U), dot(fieldC3d, V)) ;
                fieldC = normalize(fieldC);

                vec3 A3d = mesh->vertices.point(va);
                vec3 B3d = mesh->vertices.point(vb);
                vec3 C3d = mesh->vertices.point(vc);

                vec3 AB3d = B3d-A3d;
                vec3 AC3d = C3d-A3d;

                vec2 A(0.0,0.0);
                vec2 B(dot(AB3d,U), dot(AB3d,V));
                vec2 C(dot(AC3d,U), dot(AC3d,V));

                // hummm... flat triangles ?
                double a=0;
                double b=0;
                ParamTrglGradient trg(A,B,C);
                if (trg.is_flat() ||
                    length(cross(AB3d,AC3d))<1e-10 ||
                    ::fabs(fieldB.x) < 1e-20 ||
                    ::fabs(fieldC.x) < 1e-20
                   ) {
                    std::cerr<<"bad triangle ..." << std::endl ;

                    nlBegin(NL_ROW);
                    nlCoefficient(va,1e-2) ;
                    nlCoefficient(vb,-1e-2) ;
                    nlEnd(NL_ROW);

                    nlBegin(NL_ROW);
                    nlCoefficient(va,1e-2) ;
                    nlCoefficient(vc,-1e-2) ;
                    nlEnd(NL_ROW);

                    nlBegin(NL_ROW);
                    nlCoefficient(vc,1e-2) ;
                    nlCoefficient(vb,-1e-2) ;
                    nlEnd(NL_ROW);
                    continue ;
                } else {

                    // the direction field is represented by angles
                    //  double alpha_A = 0;
                    //  double alpha_B = atan(fieldB.y/fieldB.x);
                    //  double alpha_C = atan(fieldC.y/fieldC.x);

                    // Order 1 Taylor Expansion ....
                    double alpha_A = 0;
                    double alpha_B = fieldB.y/fieldB.x;
                    double alpha_C = fieldC.y/fieldC.x;

                    vec2 grad_alpha (
                        trg.TX(0)*alpha_A +
                        trg.TX(1)*alpha_B +
                        trg.TX(2)*alpha_C ,
                        trg.TY(0)*alpha_A +
                        trg.TY(1)*alpha_B +
                        trg.TY(2)*alpha_C
                    );
                    a = grad_alpha.x;
                    b = grad_alpha.y;
                }

                double sqrt_area =  ::sqrt(Geom::mesh_facet_area(*mesh,f)) ;

                if(
                    Numeric::is_nan(sqrt_area) ||
                    Numeric::is_nan(trg.TX(0)) ||
                    Numeric::is_nan(trg.TX(1)) ||
                    Numeric::is_nan(trg.TX(2)) ||
                    Numeric::is_nan(trg.TY(0)) ||
                    Numeric::is_nan(trg.TY(1)) ||
                    Numeric::is_nan(trg.TY(2)) ||
                    Numeric::is_nan(a) ||
                    Numeric::is_nan(b)
                ) {
                    std::cerr << "Found NAN !!" << std::endl ;
                }


                nlRowScaling(sqrt_area) ;
                nlBegin(NL_ROW);
                nlCoefficient(va, trg.TX(0)) ;
                nlCoefficient(vb, trg.TX(1)) ;
                nlCoefficient(vc, trg.TX(2)) ;
                nlRightHandSide(solver_scale*b);
                nlEnd(NL_ROW);

                nlRowScaling(sqrt_area) ;
                nlBegin(NL_ROW);
                nlCoefficient(va, trg.TY(0)) ;
                nlCoefficient(vb, trg.TY(1)) ;
                nlCoefficient(vc, trg.TY(2)) ;
                nlRightHandSide(solver_scale*-a);
                nlEnd(NL_ROW);
            }
            nlEnd(NL_MATRIX);
            nlEnd(NL_SYSTEM);

            nlSolve();
            double min_val = Numeric::max_float64();
            double max_val = Numeric::min_float64();
            for(index_t v: mesh->vertices) {
                CC[v] = ::exp(
                    (nlGetVariable(v)-locked_value)/solver_scale)
                    ;
                min_val = std::min(min_val,CC[v]);
                max_val = std::max(max_val,CC[v]);
            }
            double avg_val = 0.5 * (min_val + max_val);
            double min_limit = 1.0 / ::sqrt(max_scaling_correction);
            double max_limit = ::sqrt(max_scaling_correction);
            for(index_t v: mesh->vertices) {
                CC[v] = CC[v] / avg_val;
                geo_clamp(CC[v], min_limit, max_limit);
            }
            nlDeleteContext(nlGetCurrent());
        }

    } // namespace GlobalParam2d
} // namespace OGF
