//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Img3D/Type/InsideForm.cpp
//! @brief     Implements Img3D::BuilderUtils namespace
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Img3D/Type/InsideForm.h"
#include "Base/Const/Units.h"
#include "Sample/HardParticle/HardParticles.h"
#include <numbers>
using std::numbers::pi;

bool Img3D::insideForm(const IFormFactor* ff, R3 position)
{
    bool check(false);
    if (const auto* f = dynamic_cast<const Pyramid2*>(ff)) {
        double L = f->length();
        double W = f->width();
        double H = f->height();
        double alpha = f->alpha();

        double l_z =
            L / 2 - position.z() / std::tan(alpha); // half-length of rectangle at a given height
        double w_z =
            W / 2 - position.z() / std::tan(alpha); // half-width of rectangle at a given height
        if (std::abs(position.x()) <= l_z && std::abs(position.y()) <= w_z
            && (position.z() >= 0 && position.z() <= H))
            check = true;
    } else if (const auto* f = dynamic_cast<const BarGauss*>(ff)) {
        double L = f->length();
        double W = f->width();
        double H = f->height();

        if (std::abs(position.x()) <= L / 2 && std::abs(position.y()) <= W / 2
            && (position.z() >= 0 && position.z() <= H))
            check = true;
    } else if (const auto* f = dynamic_cast<const BarLorentz*>(ff)) {
        double L = f->length();
        double W = f->width();
        double H = f->height();

        if (std::abs(position.x()) <= L / 2 && std::abs(position.y()) <= W / 2
            && (position.z() >= 0 && position.z() <= H))
            check = true;
    } else if (const auto* f = dynamic_cast<const Box*>(ff)) {
        double L = f->length();
        double W = f->width();
        double H = f->height();

        if (std::abs(position.x()) <= L / 2 && std::abs(position.y()) <= W / 2
            && (position.z() >= 0 && position.z() <= H))
            check = true;
    } else if (const auto* f = dynamic_cast<const Cone*>(ff)) {
        double R = f->radius();
        double H = f->height();
        double alpha = f->alpha();

        if (std::abs(position.x()) > R || std::abs(position.y()) > R || position.z() < 0
            || position.z() > H)
            return check;

        double R_z = R - position.z() / std::tan(alpha);
        if (std::pow(position.x() / R_z, 2) + std::pow(position.y() / R_z, 2) <= 1)
            check = true;
    } else if (const auto* f = dynamic_cast<const Pyramid6*>(ff)) {
        double B = f->baseEdge();
        double H = f->height();
        double alpha = f->alpha();

        if (std::abs(position.x()) > B || std::abs(position.y()) > B || position.z() < 0
            || position.z() > H)
            return check;

        double l_z = B - position.z() / std::tan(alpha); // edge of hexagon at a given height
        double theta_prime = 0; // angle between position & x-axis in position.z() plane
        if (position.x() != 0 || position.y() != 0)
            theta_prime = Units::rad2deg(
                std::asin(std::abs(position.y())
                          / std::sqrt(std::pow(position.x(), 2) + std::pow(position.y(), 2))));
        int c = static_cast<int>(theta_prime / 60); // multiplication constant
        double theta = Units::deg2rad(theta_prime - c * 60);
        double k_z = l_z / (std::cos(theta) + std::sin(theta) / std::tan(pi / 3));

        if (std::pow(position.x(), 2) + std::pow(position.y(), 2) <= std::pow(k_z, 2))
            check = true;
    } else if (const auto* f = dynamic_cast<const Bipyramid4*>(ff)) {
        double L = f->length();
        double H = f->base_height();
        double rH = f->heightRatio();
        double alpha = f->alpha();

        double total_Height = H + rH * H;

        if (std::abs(position.x()) > L / 2 || std::abs(position.y()) > L / 2 || position.z() < 0
            || position.z() > total_Height)
            return check;

        // half-length of square (i.e. horizontal cross-section of Bipyramid4) at a given height
        double l_z = L / 2 - std::abs(H - position.z()) / std::tan(alpha);
        if (std::abs(position.x()) <= l_z && std::abs(position.y()) <= l_z)
            check = true;
    } else if (const auto* f = dynamic_cast<const Cylinder*>(ff)) {
        double R = f->radius();
        double H = f->height();

        if (std::abs(position.x()) > R || std::abs(position.y()) > R || position.z() < 0
            || position.z() > H)
            return check;

        if (std::pow(position.x() / R, 2) + std::pow(position.y() / R, 2) <= 1)
            check = true;
    } else if (dynamic_cast<const Dodecahedron*>(ff)) {
        // TODO: Implement Dodecahedron
        std::ostringstream ostr;
        ostr << "Sorry, outer shape Dodecahedron not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (const auto* f = dynamic_cast<const EllipsoidalCylinder*>(ff)) {
        double a = f->radiusX(); // semi-axis length along x
        double b = f->radiusY(); // semi-axis length along y
        double H = f->height();

        if (std::abs(position.x()) > a || std::abs(position.y()) > b || position.z() < 0
            || position.z() > H)
            return check;

        if (std::pow(position.x() / a, 2) + std::pow(position.y() / b, 2) <= 1)
            check = true;
    } else if (const auto* f = dynamic_cast<const Sphere*>(ff)) {
        double R = f->radius();

        if (std::abs(position.x()) > R || std::abs(position.y()) > R || position.z() < 0
            || position.z() > 2 * R)
            return check;

        if (std::pow(position.x() / R, 2) + std::pow(position.y() / R, 2)
                + std::pow((position.z() - R) / R, 2)
            <= 1)
            check = true;
    } else if (const auto* f = dynamic_cast<const Spheroid*>(ff)) {
        double a = f->radius(); // semi-axis length along x and y
        double H = f->height();
        double c = H / 2; // semi-axis length along z

        if (std::abs(position.x()) > a || std::abs(position.y()) > a || position.z() < 0
            || position.z() > H)
            return check;

        if (std::pow(position.x() / a, 2) + std::pow(position.y() / a, 2)
                + std::pow((position.z() - c) / c, 2)
            <= 1)
            check = true;
    } else if (const auto* f = dynamic_cast<const HemiEllipsoid*>(ff)) {
        double a = f->radiusX(); // semi-axis length along x
        double b = f->radiusY(); // semi-axis length along y
        double c = f->height();  // semi-axis length along z

        if (std::abs(position.x()) > a || std::abs(position.y()) > b || position.z() < 0
            || position.z() > c)
            return check;

        if (std::pow(position.x() / a, 2) + std::pow(position.y() / b, 2)
                + std::pow(position.z() / c, 2)
            <= 1)
            check = true;
    } else if (dynamic_cast<const Icosahedron*>(ff)) {
        // TODO: Implement Icosahedron
        std::ostringstream ostr;
        ostr << "Sorry, outer shape Icosahedron not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (const auto* f = dynamic_cast<const Prism3*>(ff)) {
        double B = f->baseEdge();
        double H = f->height();

        double l = B * std::sin(pi / 3);
        double x_shift = B / 2 * std::tan(pi / 6);

        if (position.x() + x_shift < 0 || position.x() + x_shift > l
            || std::abs(position.y()) > B / 2 || position.z() < 0 || position.z() > H)
            return check;

        double theta = 0; // angle between position & x-axis in position.z() plane
        if (position.x() + x_shift != 0 || position.y() != 0)
            theta = std::asin(
                std::abs(position.y())
                / std::sqrt(std::pow(position.x() + x_shift, 2) + std::pow(position.y(), 2)));

        double k = l / (std::sin(theta) / std::tan(pi / 6) + std::cos(theta));

        if (std::pow(position.x() + x_shift, 2) + std::pow(position.y(), 2) <= std::pow(k, 2))
            check = true;
    } else if (const auto* f = dynamic_cast<const Prism6*>(ff)) {
        double B = f->baseEdge();
        double H = f->height();

        if (std::abs(position.x()) > B || std::abs(position.y()) > B || position.z() < 0
            || position.z() > H)
            return check;

        double theta_prime = 0; // angle between position & x-axis in position.z() plane
        if (position.x() != 0 || position.y() != 0)
            theta_prime = Units::rad2deg(
                std::asin(std::abs(position.y())
                          / std::sqrt(std::pow(position.x(), 2) + std::pow(position.y(), 2))));
        int c = static_cast<int>(theta_prime / 60); // multiplicative constant
        double theta = Units::deg2rad(theta_prime - c * 60);
        double k_z = B / (std::cos(theta) + std::sin(theta) / std::tan(pi / 3));

        if (std::pow(position.x(), 2) + std::pow(position.y(), 2) <= std::pow(k_z, 2))
            check = true;
    } else if (const auto* f = dynamic_cast<const Pyramid4*>(ff)) {
        double B = f->baseEdge();
        double H = f->height();
        double alpha = f->alpha();

        double l_z =
            B / 2 - position.z() / std::tan(alpha); // half-length of square at a given height
        if (std::abs(position.x()) <= l_z && std::abs(position.y()) <= l_z
            && (position.z() >= 0 && position.z() <= H))
            check = true;
    } else if (dynamic_cast<const CosineRippleBox*>(ff)) {
        // TODO: Implement CosineRippleBox
        std::ostringstream ostr;
        ostr << "Sorry, outer shape CosineRippleBox not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const CosineRippleGauss*>(ff)) {
        // TODO: Implement CosineRippleGauss
        std::ostringstream ostr;
        ostr << "Sorry, outer shape CosineRippleGauss not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const CosineRippleLorentz*>(ff)) {
        // TODO: Implement CosineRippleLorentz
        std::ostringstream ostr;
        ostr << "Sorry, outer shape CosineRippleLorentz not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const SawtoothRippleBox*>(ff)) {
        // TODO: Implement SawtoothRippleBox
        std::ostringstream ostr;
        ostr << "Sorry, outer shape SawtoothRippleBox not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const SawtoothRippleGauss*>(ff)) {
        // TODO: Implement SawtoothRippleGauss
        std::ostringstream ostr;
        ostr << "Sorry, outer shape SawtoothRippleGauss not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const SawtoothRippleLorentz*>(ff)) {
        // TODO: Implement SawtoothRippleLorentz
        std::ostringstream ostr;
        ostr << "Sorry, outer shape SawtoothRippleLorentz not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (const auto* f = dynamic_cast<const Pyramid3*>(ff)) {
        double B = f->baseEdge();
        double H = f->height();
        double alpha = f->alpha();

        double B_z = B - position.z() * 2 / std::tan(alpha); // edge of triangle at a given height

        double l = B_z * std::sin(pi / 3);
        double x_shift = B_z / 2 * std::tan(pi / 6);

        if (position.x() + x_shift < 0 || position.x() + x_shift > l
            || std::abs(position.y()) > B_z / 2 || position.z() < 0 || position.z() > H)
            return check;

        double theta = 0; // angle between position & x-axis in position.z() plane
        if (position.x() + x_shift != 0 || position.y() != 0)
            theta = std::asin(
                std::abs(position.y())
                / std::sqrt(std::pow(position.x() + x_shift, 2) + std::pow(position.y(), 2)));

        double k = l / (std::sin(theta) / std::tan(pi / 6) + std::cos(theta));

        if (std::pow(position.x() + x_shift, 2) + std::pow(position.y(), 2) <= std::pow(k, 2))
            check = true;
    } else if (dynamic_cast<const TruncatedCube*>(ff)) {
        // TODO: Implement Truncated cube
        std::ostringstream ostr;
        ostr << "Sorry, outer shape Truncated cube not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (const auto* f = dynamic_cast<const TruncatedSphere*>(ff)) {
        double R = f->radius();
        double H = f->untruncated_height();
        double deltaH = f->removedTop();
        if (std::abs(position.x()) > R || std::abs(position.y()) > R || position.z() < 0
            || position.z() > (H - deltaH))
            return check;

        if (std::pow(position.x() / R, 2) + std::pow(position.y() / R, 2)
                + std::pow((position.z() - (H - R)) / R, 2)
            <= 1)
            check = true;
    } else if (dynamic_cast<const TruncatedSpheroid*>(ff)) {
        // TODO: Implement Truncated spheroid
        std::ostringstream ostr;
        ostr << "Sorry, outer shape Truncated spheroid not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const CantellatedCube*>(ff)) {
        // TODO: Implement Cantellated cube
        std::ostringstream ostr;
        ostr << "Sorry, outer shape Cantellated cube not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const HorizontalCylinder*>(ff)) {
        // TODO: Implement Horizontal cylinder
        std::ostringstream ostr;
        ostr << "Sorry, outer shape Horizontal cylinder not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const PlatonicOctahedron*>(ff)) {
        // TODO: Implement Platonic octahedron
        std::ostringstream ostr;
        ostr << "Sorry, outer shape Platonic octahedron not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    } else if (dynamic_cast<const PlatonicTetrahedron*>(ff)) {
        // TODO: Implement Platonic tetrahedron
        std::ostringstream ostr;
        ostr << "Sorry, outer shape Platonic tetrahedron not yet implemented for Mesocrystal";
        ostr << "\n\nStay tuned!";
        throw std::runtime_error(ostr.str());
    }
    return check;
}
