#pragma once
// Port of the static helper
//   HyperIdealVisualizationPlugin.getEuclideanCircleFromHyperbolic()
// from de.varylab.discreteconformal.plugin.
//
// Converts a hyperbolic circle (center + radius in the hyperboloid model)
// to its Euclidean representation (cx, cy, r) in the Poincaré disk model.
//
// Mathematical background
// -----------------------
// Hyperboloid model: points (x,y,z,w) with w²-x²-y²-z²=1, w>0.
// Metric signature: g = diag(+1,+1,+1,−1)  (spatial-first, time-last).
//
// Hyperbolic translation from the origin e₄=(0,0,0,1) to p=(a,b,c,d):
//   T = [ I₃ + p'·p'ᵀ/(d+1)   p' ]      p' = (a,b,c)
//       [        p'ᵀ            d  ]
// This is the standard Lorentz boost; it is in O(3,1) and maps e₄ → p.
//
// Poincaré disk projection (jReality convention):
//   (x,y,z,w)  →  (x,y) / (w+1)
//
// The three reference points on the unit hyperbolic circle (at origin) are
//   p1 = (sinh r, 0, 0, cosh r)
//   p2 = (0, sinh r, 0, cosh r)
//   p3 = (-sinh r, 0, 0, cosh r)
// After translation and projection to the Poincaré disk their circumcircle
// equals the image of the original hyperbolic circle.

#include <Eigen/Dense>
#include <array>
#include <cmath>

namespace conformallab {

// ---------------------------------------------------------------------------
// Circumcenter of three 2-D points
// ---------------------------------------------------------------------------
inline Eigen::Vector2d circumcenter2d(
    const Eigen::Vector2d& a,
    const Eigen::Vector2d& b,
    const Eigen::Vector2d& c)
{
    double ax = a.x(), ay = a.y();
    double bx = b.x(), by = b.y();
    double cx = c.x(), cy = c.y();

    double D = 2.0 * (ax*(by - cy) + bx*(cy - ay) + cx*(ay - by));
    double a2 = ax*ax + ay*ay;
    double b2 = bx*bx + by*by;
    double c2 = cx*cx + cy*cy;

    double ux = (a2*(by - cy) + b2*(cy - ay) + c2*(ay - by)) / D;
    double uy = (a2*(cx - bx) + b2*(ax - cx) + c2*(bx - ax)) / D;
    return {ux, uy};
}

// ---------------------------------------------------------------------------
// 4×4 Lorentz boost: maps the hyperboloid origin e₄=(0,0,0,1) to `center`.
// `center` must lie on the hyperboloid: center[3]² - ‖center.head<3>()‖² = 1.
// ---------------------------------------------------------------------------
inline Eigen::Matrix4d hyperboloidTranslation(const Eigen::Vector4d& center)
{
    Eigen::Vector3d p = center.head<3>();
    double          d = center(3);

    Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
    // Upper-left 3×3 block: I + p'·p'ᵀ / (d+1)
    T.block<3,3>(0,0) += p * p.transpose() / (d + 1.0);
    // Right column and bottom row
    T.block<3,1>(0,3) = p;
    T.block<1,3>(3,0) = p.transpose();
    T(3,3) = d;
    return T;
}

// ---------------------------------------------------------------------------
// Project a hyperboloid point to the Poincaré disk (jReality convention:
// add 1 to the w-coordinate, then dehomogenize the spatial part).
// ---------------------------------------------------------------------------
inline Eigen::Vector2d toPoincareDisk(const Eigen::Vector4d& x)
{
    double w = x(3) + 1.0;
    return {x(0) / w, x(1) / w};
}

// ---------------------------------------------------------------------------
// getEuclideanCircleFromHyperbolic
//
// Inputs
//   center  – point on the hyperboloid, e.g. (0,0,0,1) for the origin
//   radius  – hyperbolic radius (real number > 0)
//
// Output
//   { euclidean_cx, euclidean_cy, euclidean_radius }
//   describing the circle in the Poincaré disk that corresponds to the
//   given hyperbolic circle.
//
// Port of HyperIdealVisualizationPlugin.getEuclideanCircleFromHyperbolic()
// ---------------------------------------------------------------------------
inline std::array<double,3> getEuclideanCircleFromHyperbolic(
    const Eigen::Vector4d& center, double radius)
{
    const double s  = std::sinh(radius);
    const double ch = std::cosh(radius);

    // Three points on the hyperbolic circle centered at the origin
    Eigen::Vector4d p1( s, 0.0, 0.0, ch);
    Eigen::Vector4d p2(0.0,  s, 0.0, ch);
    Eigen::Vector4d p3(-s, 0.0, 0.0, ch);

    // Apply the hyperbolic translation to the target center
    const Eigen::Matrix4d T = hyperboloidTranslation(center);
    p1 = T * p1;
    p2 = T * p2;
    p3 = T * p3;

    // Project to the Poincaré disk
    const Eigen::Vector2d q1 = toPoincareDisk(p1);
    const Eigen::Vector2d q2 = toPoincareDisk(p2);
    const Eigen::Vector2d q3 = toPoincareDisk(p3);

    // Euclidean circumcircle of the three projected points
    const Eigen::Vector2d ec = circumcenter2d(q1, q2, q3);
    const double           r  = (ec - q1).norm();

    return {ec.x(), ec.y(), r};
}

} // namespace conformallab
