#include <numbers>

using std::numbers::pi;

#include "Base/Vector/RotMatrix.h"
#include "Sample/Lattice/BakeLattice.h"
#include "Sample/Lattice/Lattice3D.h"
#include "Tests/GTestWrapper/google_test.h"

// tests volume of the unit cell
TEST(Lattice, volume)
{
    R3 a1(4, 0, 0);
    R3 a2(0, 2.1, 0);
    R3 a3(0, 0, 1);

    Lattice3D l1(a1, a2, a3);
    EXPECT_EQ(8.4, l1.unitCellVolume()); // 8.4 is the expected volume for the given lattice vectors
}

// tests whether reciprocal lattice basis vectors have been initialized or not
TEST(Lattice, reciprocal)
{
    R3 a1(1, 0, 0);
    R3 a2(0, 1, 0);
    R3 a3(0, 0, 1);
    Lattice3D l1(a1, a2, a3);

    R3 b1, b2, b3, m_ra, m_rb, m_rc;

    // computing expected reciprocal lattice vectors
    R3 a23 = a2.cross(a3);
    R3 a31 = a3.cross(a1);
    R3 a12 = a1.cross(a2);
    m_ra = (2 * pi) / a1.dot(a23) * a23;
    m_rb = (2 * pi) / a2.dot(a31) * a31;
    m_rc = (2 * pi) / a3.dot(a12) * a12;

    l1.reciprocalLatticeBasis(b1, b2, b3);
    EXPECT_EQ(m_ra, b1);
    EXPECT_EQ(m_rb, b2);
    EXPECT_EQ(m_rc, b3);
}

// tests whether Lattice has been rotated correctly
TEST(Lattice, transform)
{
    R3 a1(1, 0, 0);
    R3 a2(0, 1, 0);
    R3 a3(0, 0, 1);
    Lattice3D l1(a1, a2, a3);

    // use rotation by 90 degrees around z axis as a transformation
    RotMatrix tr = RotMatrix::AroundZ((2 * pi) / 4);
    Lattice3D ltr = l1.rotated(tr);

    // use EXPECT_NEAR as transform (matrix multiplication) uses double value for rotation angle
    // e.g. Rotating the vector (1,0,0) by 2*PI about z would give something like (0.99999,0,0)
    const double epsilon = 1e-12;
    EXPECT_NEAR(a2.x(), ltr.basisVectorA().x(), epsilon);
    EXPECT_NEAR(a2.y(), ltr.basisVectorA().y(), epsilon);
    EXPECT_NEAR(a2.z(), ltr.basisVectorA().z(), epsilon);
    EXPECT_NEAR(-a1.x(), ltr.basisVectorB().x(), epsilon);
    EXPECT_NEAR(-a1.y(), ltr.basisVectorB().y(), epsilon);
    EXPECT_NEAR(-a1.z(), ltr.basisVectorB().z(), epsilon);
    EXPECT_NEAR(a3.x(), ltr.basisVectorC().x(), epsilon);
    EXPECT_NEAR(a3.y(), ltr.basisVectorC().y(), epsilon);
    EXPECT_NEAR(a3.z(), ltr.basisVectorC().z(), epsilon);
}

// tests the nearest REC. LATTICE point to a given REC. SPACE vector
TEST(Lattice, NearestI3)
{
    R3 a1(1, 0, 0);
    R3 a2(0, 1, 0);
    R3 a3(0, 0, 1);
    Lattice3D l1(a1, a2, a3);

    // vector_in is in REC. SPACE coordinates
    R3 vector_in(2.8 * (2 * pi), 0, 0);

    // point_expected is in REC. LATTICE coordinates
    I3 point_expected(3, 0, 0);

    EXPECT_EQ(point_expected, l1.nearestI3(vector_in));
}

// tests the list of REC. LATTICE vectors (in REC. SPACE coords) computed within a specified
// radius of a given REC. SPACE vector
TEST(Lattice, reciprocalLatticeVectorsWithinRadius)
{
    R3 a1(1, 0, 0);
    R3 a2(0, 1, 0);
    R3 a3(0, 0, 1);
    Lattice3D l1(a1, a2, a3);

    R3 b1, b2, b3;
    l1.reciprocalLatticeBasis(b1, b2, b3);

    // vector_in is in REC. SPACE coordinates
    R3 vector_in(2.5 * (2 * pi), 0, 0);

    // list of REC. LATTICE vectors expected within given radius
    std::vector<R3> vectors_expected;
    R3 expected_1 = 2 * b1;
    R3 expected_2 = 3 * b1;

    vectors_expected.push_back(expected_1);
    vectors_expected.push_back(expected_2);

    EXPECT_EQ(vectors_expected, l1.reciprocalLatticeVectorsWithinRadius(vector_in, (2 * pi)));

    EXPECT_EQ(vectors_expected, l1.reciprocalLatticeVectorsWithinRadius(vector_in, (2 * pi) - 0.1));
}

// tests FCC lattice creation
TEST(Lattice, FCCLattice)
{
    // creates FCC lattice onto a new Lattice instance l1
    Lattice3D l1 = bake::FCCLattice(1);

    R3 fcc1(0, 0.5, 0.5);
    R3 fcc2(0.5, 0, 0.5);
    R3 fcc3(0.5, 0.5, 0);

    EXPECT_EQ(fcc1, l1.basisVectorA());
    EXPECT_EQ(fcc2, l1.basisVectorB());
    EXPECT_EQ(fcc3, l1.basisVectorC());
}

// tests hexagonal lattice creation
TEST(Lattice, HexagonalLattice2D)
{
    Lattice3D l1 = bake::HexagonalLattice(1, 4);

    R3 tri1(1, 0.0, 0.0);
    R3 tri2(-1 / 2.0, std::sqrt(3.0) * 1 / 2.0, 0);
    R3 tri3(0.0, 0.0, 4);

    EXPECT_EQ(tri1, l1.basisVectorA());
    EXPECT_EQ(tri2, l1.basisVectorB());
    EXPECT_EQ(tri3, l1.basisVectorC());
}

// tests whether basis and reciprocal vectors are returned correctly when the basis
// vectors are manually changed using the setVectorValue method
TEST(Lattice, onChange)
{
    R3 a1(1, 0, 0);
    R3 a2(0, 1, 0);
    R3 a3(0, 0, 1);
    Lattice3D l1(a1, a2, a3);

    R3 b1, b2, b3, m_ra, m_rb, m_rc;

    // computing expected reciprocal lattice vectors
    R3 a23 = a2.cross(a3);
    R3 a31 = a3.cross(a1);
    R3 a12 = a1.cross(a2);
    m_ra = (2 * pi) / a1.dot(a23) * a23;
    m_rb = (2 * pi) / a2.dot(a31) * a31;
    m_rc = (2 * pi) / a3.dot(a12) * a12;

    l1.reciprocalLatticeBasis(b1, b2, b3);
    EXPECT_EQ(m_ra, b1);
    EXPECT_EQ(m_rb, b2);
    EXPECT_EQ(m_rc, b3);

    // The new changed lattice vectors
    R3 c1(2, 0, 0), c2(0, 2, 0), c3(0, 0, 2);
    Lattice3D l2(c1, c2, c3);

    EXPECT_EQ(c1, l2.basisVectorA());
    EXPECT_EQ(c2, l2.basisVectorB());
    EXPECT_EQ(c3, l2.basisVectorC());

    R3 d1, d2, d3, mc_ra, mc_rb, mc_rc;

    // computing the expected changed reciprocal lattice vectors
    R3 c23 = c2.cross(c3);
    R3 c31 = c3.cross(c1);
    R3 c12 = c1.cross(c2);
    mc_ra = (2 * pi) / c1.dot(c23) * c23;
    mc_rb = (2 * pi) / c2.dot(c31) * c31;
    mc_rc = (2 * pi) / c3.dot(c12) * c12;

    l2.reciprocalLatticeBasis(d1, d2, d3);
    EXPECT_EQ(mc_ra, d1);
    EXPECT_EQ(mc_rb, d2);
    EXPECT_EQ(mc_rc, d3);
}
