//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Img3D/Model/Particles.cpp
//! @brief     Implements class Particle.
//!
//! @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/Model/Particles.h"
#include "Base/Const/PhysicalConstants.h"

namespace Img3D {

using namespace GeometricID;

PlotParticle::PlotParticle(Key key)
    : PlottableBody(key)
    , scale(F3(1, 1, 1))
{
}

void PlotParticle::set()
{
    transform(F3(0, 0, 0), F3(0, 0, 0));
}

void PlotParticle::transform(F3 rotate_, F3 translate_)
{
    PlottableBody::transform(turn, scale, (rotate = rotate_), offset + (translate = translate_));
}

void PlotParticle::addTransform(F3 rotate_, F3 translate_)
{
    PlottableBody::transform(turn, scale, (rotate = rotate + rotate_),
                             offset + (translate = translate + translate_));
}

void PlotParticle::addTranslation(F3 translate_)
{
    PlottableBody::transform(turn, scale, rotate, offset + (translate = translate + translate_));
}

void PlotParticle::addExtrinsicRotation(F3 rotateExtrinsic)
{
    PlottableBody::addExtrinsicRotation(turn, scale, rotate, rotateExtrinsic,
                                        (translate = offset + translate));
}

//------------------------------------------------------------------------------

namespace Particles {

static float const pi = static_cast<float>(PhysConsts::pi);
static float const sqrt2f = std::sqrt(2.f);
static float const sqrt3f = std::sqrt(3.f);

// see ~/BornAgain/GUI/ba3d/ba3d/model/geometry/ for BaseShape construction

// TODO : issue #269 : wrong BaseShape model. Now Pyramid2 always has top vertex. The true Pyramid2
// should have top edge if L!=W.
Pyramid2::Pyramid2(float L, float W, float H, float alpha)
    : PlotParticle(Key(BaseShape::Column, (1.0f - H / (std::min(L, W) / 2 * std::tan(alpha))), 4))
{
    isNull = (L <= 0 || W <= 0 || H <= 0 || alpha <= 0);
    turn = F3(0, 0, 45 * pi / 180.0f);
    scale = F3(L * sqrt2f, W * sqrt2f, H);
    offset = F3(0, 0, 0);
    set();
}

BarGauss::BarGauss(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Column, 1.0f, 4))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 45 * pi / 180.0f);
    scale = F3(L * sqrt2f, W * sqrt2f, H);
    offset = F3(0, 0, 0);
    set();
}

BarLorentz::BarLorentz(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Column, 1.0f, 4))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 45 * pi / 180.0f);
    scale = F3(L * sqrt2f, W * sqrt2f, H);
    offset = F3(0, 0, 0);
    set();
}

Box::Box(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Column, 1.0f, 4))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 45 * pi / 180.0f);
    scale = F3(L * sqrt2f, W * sqrt2f, H);
    offset = F3(0, 0, 0);
    set();
}

Cone::Cone(float R, float H, float alpha)
    : PlotParticle(Key(BaseShape::Column, (1.0f - H / (R * std::tan(alpha))), 0))
{
    isNull = (R <= 0 || H <= 0 || alpha <= 0);
    scale = F3(R * 2, R * 2, H);
    offset = F3(0, 0, 0);
    set();
}

Pyramid6::Pyramid6(float R, float H, float alpha)
    : PlotParticle(Key(BaseShape::Column, (1.0f - H / (R * sqrt3f / 2 * std::tan(alpha))), 6))
{
    isNull = (R <= 0 || H <= 0 || alpha <= 0);
    scale = F3(R * 2, R * 2, H);
    offset = F3(0, 0, 0);
    set();
}

Bipyramid4::Bipyramid4(float L, float H, float rH, float alpha)
    : PlotParticle(Key(BaseShape::Bipyramid4, rH, alpha, H / L))
{
    isNull = (L <= 0 || H <= 0 || rH <= 0 || alpha >= pi / 2);
    scale = F3(L, L, L);
    offset = F3(0, 0, 0);
    set();
}

Cylinder::Cylinder(float R, float H)
    : PlotParticle(Key(BaseShape::Column, 1.0f, 0))
{
    isNull = (R <= 0 || H <= 0);
    scale = F3(R * 2, R * 2, H);
    offset = F3(0, 0, 0);
    set();
}

Dodecahedron::Dodecahedron(float L)
    : PlotParticle(Key(BaseShape::Dodecahedron))
{
    isNull = (L <= 0);
    float R = L / DodecahedronL2R;
    scale = F3(R * 2, R * 2, R * 2);
    offset = F3(0, 0, 0);
    set();
}

EllipsoidalCylinder::EllipsoidalCylinder(float Ra, float Rb, float H)
    : PlotParticle(Key(BaseShape::Column, 1.0f, 0))
{
    isNull = (Ra <= 0 || Rb <= 0 || H <= 0);
    scale = F3(Ra * 2, Rb * 2, H);
    offset = F3(0, 0, 0);
    set();
}

Sphere::Sphere(float R)
    : PlotParticle(Key(BaseShape::Sphere, 0, 0.5f))
{
    isNull = (R <= 0);
    float D = 2 * R;
    scale = F3(D, D, D);
    offset = F3(0, 0, 0);
    set();
}

Spheroid::Spheroid(float R, float H)
    : PlotParticle(Key(BaseShape::Sphere, 0, 0.5f))
{
    isNull = (R <= 0 || H <= 0);
    scale = F3(R * 2, R * 2, H);
    offset = F3(0, 0, 0);
    set();
}

HemiEllipsoid::HemiEllipsoid(float Ra, float Rb, float H)
    : PlotParticle(Key(BaseShape::Sphere, .5f, 0.0f))
{
    isNull = (Ra <= 0 || Rb <= 0 || H <= 0);
    scale = F3(Ra * 2, Rb * 2, H * 2);
    offset = F3(0, 0, 0);
    set();
}

Icosahedron::Icosahedron(float L)
    : PlotParticle(Key(BaseShape::Icosahedron))
{
    isNull = (L <= 0);
    scale = F3(L, L, L);
    offset = F3(0, 0, 0);
    set();
}

Prism3::Prism3(float L, float H)
    : PlotParticle(Key(BaseShape::Column, 1.0f, 3))
{
    isNull = (L <= 0 || H <= 0);
    float D = L / sqrt3f;
    scale = F3(D * 2, D * 2, H);
    offset = F3(0, 0, 0);
    set();
}

Prism6::Prism6(float R, float H)
    : PlotParticle(Key(BaseShape::Column, 1.0f, 6))
{
    isNull = (R <= 0 || H <= 0);
    scale = F3(R * 2, R * 2, H);
    offset = F3(0, 0, 0);
    set();
}

Pyramid4::Pyramid4(float L, float H, float alpha)
    : PlotParticle(Key(BaseShape::Column, (1.0f - H / (L / 2 * std::tan(alpha))), 4))
{
    isNull = (L <= 0 || H <= 0 || alpha <= 0);
    float L2 = L * sqrt2f;
    turn = F3(0, 0, 45 * pi / 180.0f);
    scale = F3(L2, L2, H);
    offset = F3(0, 0, 0);
    set();
}

CosineRippleBox::CosineRippleBox(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Ripple, 0, 0))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 0);
    scale = F3(L, W, H);
    offset = F3(0, 0, 0);
    set();
}

CosineRippleGauss::CosineRippleGauss(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Ripple, 0, 0))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 0);
    scale = F3(L, W, H);
    offset = F3(0, 0, 0);
    set();
}

CosineRippleLorentz::CosineRippleLorentz(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Ripple, 0, 0))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 0);
    scale = F3(L, W, H);
    offset = F3(0, 0, 0);
    set();
}

SawtoothRippleBox::SawtoothRippleBox(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Ripple, 0, 0))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 0);
    scale = F3(L, W, H);
    offset = F3(0, 0, 0);
    set();
}

SawtoothRippleGauss::SawtoothRippleGauss(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Ripple, 0, 0))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 0);
    scale = F3(L, W, H);
    offset = F3(0, 0, 0);
    set();
}

SawtoothRippleLorentz::SawtoothRippleLorentz(float L, float W, float H)
    : PlotParticle(Key(BaseShape::Ripple, 0, 0))
{
    isNull = (L < 0 || W < 0 || H < 0) || (L <= 0 && W <= 0 && H <= 0);
    turn = F3(0, 0, 0);
    scale = F3(L, W, H);
    offset = F3(0, 0, 0);
    set();
}

Pyramid3::Pyramid3(float L, float H, float alpha)
    : PlotParticle(Key(BaseShape::Column, (1.0f - H / (L / (2 * sqrt3f) * std::tan(alpha))), 3))
{
    isNull = (L <= 0 || H <= 0 || alpha <= 0);
    float D = L / sqrt3f;
    scale = F3(D * 2, D * 2, H);
    offset = F3(0, 0, 0);
    set();
}

TruncatedCube::TruncatedCube(float L, float t)
    : PlotParticle(Key(BaseShape::TruncatedBox, 2 * t / L))
{
    isNull = (L <= 0);
    scale = F3(L, L, L);
    offset = F3(0, 0, 0);
    set();
}

TruncatedSphere::TruncatedSphere(float R, float H, float deltaH)
    : PlotParticle(Key(BaseShape::Sphere, 1 - H / R / 2, (H - R) / R / 2, deltaH / R / 2))
{
    isNull = (R <= 0 || H <= 0);
    float D = 2 * R;
    scale = F3(D, D, D);
    offset = F3(0, 0, 0);
    set();
}

TruncatedSpheroid::TruncatedSpheroid(float R, float H, float fp, float deltaH)
    : PlotParticle(Key(BaseShape::Sphere, 1 - H / fp / R / 2, (H - fp * R) / fp / R / 2,
                       deltaH / fp / R / 2))
{
    isNull = (R <= 0 || H <= 0 || fp <= 0);
    scale = F3(R * 2, R * 2, fp * R * 2);
    offset = F3(0, 0, 0);
    set();
}

// TODO : issue #269 : change base shape to cantellated box
CantellatedCube::CantellatedCube(float L, float t)
    : PlotParticle(Key(BaseShape::TruncatedBox, 2 * t / L))
{
    isNull = (L <= 0);
    scale = F3(L, L, L);
    offset = F3(0, 0, 0);
    set();
}

HorizontalCylinder::HorizontalCylinder(float R, float L, float s_b, float s_t)
    : PlotParticle(Key(BaseShape::Column, 1.0f, 0))
{
    isNull = (R <= 0 || L <= 0 || s_b >= s_t);
    turn = F3(90 * pi / 180.0f, 90 * pi / 180.0f, 0);
    scale = F3(L, R * 2, R * 2);
    offset = F3(0, 0, 0);
    set();
}

PlatonicOctahedron::PlatonicOctahedron(float L)
    : PlotParticle(
          Key(BaseShape::Bipyramid4, 1.0f, std::asin(std::sqrt(2.0f / 3)), 1.0f / std::sqrt(2.0f)))
{
    isNull = (L <= 0);
    scale = F3(L, L, L);
    offset = F3(0, 0, 0);
    set();
}

PlatonicTetrahedron::PlatonicTetrahedron(float L)
    : PlotParticle(Key(BaseShape::Column, 0, 3))
{
    isNull = (L <= 0);
    float D = L / sqrt3f;
    scale = F3(D * 2, D * 2, L * std::sqrt(6.f) / 3);
    offset = F3(0, 0, 0);
    set();
}

} // namespace Particles
} // namespace Img3D
