#pragma once

// Hyperbolic tetrahedron volume formulas.
// Ported from de.varylab.discreteconformal.functional.HyperIdealUtility (Java).

#include "clausen.hpp"
#include "constants.hpp"

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

namespace conformallab {

// Volume of a generalized hyperbolic tetrahedron with dihedral angles A..F.
// Formula: Meyerhoff / Ushijima (Springer 2006).
// Corresponds to Java HyperIdealUtility.calculateTetrahedronVolume().
inline double calculateTetrahedronVolume(double A, double B, double C,
                                         double D, double E, double F) {
    // PI from constants.hpp (conformallab::PI)

    // Degenerate if any angle equals pi.
    if (A == PI || B == PI || C == PI || D == PI || E == PI || F == PI)
        return 0.0;

    const double sA = std::sin(A), sB = std::sin(B), sC = std::sin(C);
    const double sD = std::sin(D), sE = std::sin(E), sF = std::sin(F);
    const double cA = std::cos(A), cB = std::cos(B), cC = std::cos(C);
    const double cD = std::cos(D), cE = std::cos(E), cF = std::cos(F);

    // Unit complex numbers e^(i*angle).
    using Cx = std::complex<double>;
    auto polar = [](double angle) { return std::polar(1.0, angle); };

    Cx ad = polar(A + D), be = polar(B + E), cf = polar(C + F);
    Cx abc = polar(A + B + C), abf = polar(A + B + F);
    Cx ace = polar(A + C + E), aef = polar(A + E + F);
    Cx bcd = polar(B + C + D), bdf = polar(B + D + F);
    Cx def = polar(D + E + F), cde = polar(C + D + E);
    Cx abde = ad * be, acdf = ad * cf, bcef = be * cf;
    Cx abcdef = abc * def;

    Cx z = ad + be + cf + abf + ace + bcd + def + abcdef;

    // Gram matrix of the tetrahedron.
    Eigen::Matrix4d G;
    G <<  1.0, -cA, -cB, -cF,
         -cA,  1.0, -cC, -cE,
         -cB, -cC,  1.0, -cD,
         -cF, -cE,  -cD,  1.0;

    Cx sqrtG = std::sqrt(Cx(G.determinant(), 0.0));

    Cx f   = Cx(sA*sD + sB*sE + sC*sF, 0.0);
    Cx f1  = f - sqrtG;
    Cx f2  = f + sqrtG;
    Cx z1  = -2.0 * f1 / z;
    Cx z2  = -2.0 * f2 / z;

    auto U = [&](Cx zi) {
        return 0.5 * (
            + ImLi2(zi)
            + ImLi2(abde  * zi)
            + ImLi2(acdf  * zi)
            + ImLi2(bcef  * zi)
            - ImLi2(-abc  * zi)
            - ImLi2(-aef  * zi)
            - ImLi2(-bdf  * zi)
            - ImLi2(-cde  * zi)
        );
    };

    return (U(z1) - U(z2)) / 2.0;
}

// Volume of a hyperideal tetrahedron with one ideal vertex (at gamma).
// Dihedral angles at the ideal vertex: gamma1, gamma2, gamma3.
// Dihedral angles at opposite edges: alpha23, alpha31, alpha12.
// Formula: Kolpakov–Mednykh (arxiv math/0603097).
// Corresponds to Java HyperIdealUtility.calculateTetrahedronVolumeWithIdealVertexAtGamma().
inline double calculateTetrahedronVolumeWithIdealVertexAtGamma(
    double gamma1, double gamma2, double gamma3,
    double alpha23, double alpha31, double alpha12)
{
    // PI from constants.hpp (conformallab::PI)
    auto L = [](double x) { return Lobachevsky(x); };

    double result = L(gamma1) + L(gamma2) + L(gamma3);

    result += L((PI + alpha31 - alpha12 - gamma1) / 2.0);
    result += L((PI + alpha12 - alpha23 - gamma2) / 2.0);
    result += L((PI + alpha23 - alpha31 - gamma3) / 2.0);

    result += L((PI - alpha31 + alpha12 - gamma1) / 2.0);
    result += L((PI - alpha12 + alpha23 - gamma2) / 2.0);
    result += L((PI - alpha23 + alpha31 - gamma3) / 2.0);

    result += L((PI + alpha31 + alpha12 - gamma1) / 2.0);
    result += L((PI + alpha12 + alpha23 - gamma2) / 2.0);
    result += L((PI + alpha23 + alpha31 - gamma3) / 2.0);

    result += L((PI - alpha31 - alpha12 - gamma1) / 2.0);
    result += L((PI - alpha12 - alpha23 - gamma2) / 2.0);
    result += L((PI - alpha23 - alpha31 - gamma3) / 2.0);

    return result / 2.0;
}

} // namespace conformallab
