//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Img3D/Mesh/Box.cpp
//! @brief     Implements utility functions in ba3d 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 "Base/Util/Assert.h"
#include "Img3D/Model/Geometry.h"

namespace Img3D {

Geometry::Mesh Geometry::meshBox()
{
    float const D = .5f;

    Vertices vs_;
    vs_.reserve(8);
    for (float x : {-D, +D})
        for (float y : {-D, +D})
            for (float z : {-D, +D})
                vs_.append(F3(x, y, z));

    ASSERT(8 == vs_.count());

    Vertices vs;
    vs.reserve(36);

    vs.addQuad(vs_, 0, 2, 6, 4);
    vs.addQuad(vs_, 1, 5, 7, 3);
    vs.addQuad(vs_, 0, 1, 3, 2);
    vs.addQuad(vs_, 4, 6, 7, 5);
    vs.addQuad(vs_, 0, 4, 5, 1);
    vs.addQuad(vs_, 2, 3, 7, 6);

    ASSERT(36 == vs.count());

    return makeMesh(vs);
}

Geometry::Mesh Geometry::meshRoughBox(const double2d_t* topR, const double2d_t* bottomR,
                                      bool drawBottom)
{
    ASSERT(topR || bottomR);
    const double2d_t* base = topR ? topR : bottomR;
    ASSERT(base->size());

    const auto& t = topR ? *topR : double2d_t(base->size(), std::vector<double>(base[0].size()));
    const auto& b =
        bottomR ? *bottomR : double2d_t(base->size(), std::vector<double>(base[0].size()));

    float const D = 0.5f;

    const int M = t.size();
    const int N = t[0].size();
    int num_vertices = 6 * (M - 1) * (N - 1) + 12 * (M + N - 2);

    const float dx = 2 * D / (N - 1);
    const float dy = 2 * D / (M - 1);

    Vertices vs;
    vs.reserve(num_vertices);

    // top surface
    for (int j = 0; j < M - 1; j++) {
        const float y1 = -D + dy * j;
        const float y2 = y1 + dy;
        for (int i = 0; i < N - 1; i++) {
            const float x1 = -D + dx * i;
            const float x2 = x1 + dx;
            const F3 v1 = F3(x2, y1, D + t[i + 1][j]);
            const F3 v2 = F3(x2, y2, D + t[i + 1][j + 1]);
            const F3 v3 = F3(x1, y2, D + t[i][j + 1]);
            const F3 v4 = F3(x1, y1, D + t[i][j]);
            vs.addQuad(v1, v2, v3, v4); // visible from above
        }
    }

    // side walls
    for (int j = 0; j < M - 1; j++) {
        const float y1 = -D + dy * j;
        const float y2 = y1 + dy;
        {
            const F3 v1 = F3(-D, y1, -D + b[0][j]);
            const F3 v2 = F3(-D, y1, +D + t[0][j]);
            const F3 v3 = F3(-D, y2, +D + t[0][j + 1]);
            const F3 v4 = F3(-D, y2, -D + b[0][j + 1]);
            vs.addQuad(v1, v2, v3, v4);
        }
        {
            const F3 v1 = F3(D, y2, -D + b[N - 1][j + 1]);
            const F3 v2 = F3(D, y2, +D + t[N - 1][j + 1]);
            const F3 v3 = F3(D, y1, +D + t[N - 1][j]);
            const F3 v4 = F3(D, y1, -D + b[N - 1][j]);
            vs.addQuad(v1, v2, v3, v4);
        }
    }
    for (int i = 0; i < N - 1; i++) {
        const float x1 = -D + dx * i;
        const float x2 = x1 + dx;
        {
            const F3 v1 = F3(x2, -D, -D + b[i + 1][0]);
            const F3 v2 = F3(x2, -D, +D + t[i + 1][0]);
            const F3 v3 = F3(x1, -D, +D + t[i][0]);
            const F3 v4 = F3(x1, -D, -D + b[i][0]);
            vs.addQuad(v1, v2, v3, v4);
        }
        {
            const F3 v1 = F3(x1, D, -D + b[i][M - 1]);
            const F3 v2 = F3(x1, D, +D + t[i][M - 1]);
            const F3 v3 = F3(x2, D, +D + t[i + 1][M - 1]);
            const F3 v4 = F3(x2, D, -D + b[i + 1][M - 1]);
            vs.addQuad(v1, v2, v3, v4);
        }
    }
    ASSERT(num_vertices == vs.count());

    if (drawBottom) {
        // bottom plane only if there is no underlying layer
        if (!bottomR)
            vs.addQuad({+D, -D, -D}, {-D, -D, -D}, {-D, +D, -D},
                       {+D, +D, -D}); // visible from below
        else
            // bottom surface
            for (int j = 0; j < M - 1; j++) {
                const float y1 = -D + dy * j;
                const float y2 = y1 + dy;
                for (int i = 0; i < N - 1; i++) {
                    const float x1 = -D + dx * i;
                    const float x2 = x1 + dx;
                    const F3 v1 = F3(x2, y1, -D + b[i + 1][j]);
                    const F3 v2 = F3(x2, y2, -D + b[i + 1][j + 1]);
                    const F3 v3 = F3(x1, y2, -D + b[i][j + 1]);
                    const F3 v4 = F3(x1, y1, -D + b[i][j]);
                    vs.addQuad(v1, v2, v3, v4); // visible from above
                    vs.addQuad(v4, v3, v2, v1); // visible from below
                }
            }
    }

    return makeMesh(vs);
}

} // namespace Img3D
