#ifndef __SPLATTER__
#define __SPLATTER__

class Options;

#include "Utilities/BBoxMap.h"

// External libraries
#include <GEL/CGLA/Vec4d.h>
#include <GEL/Geometry/XForm.h>
#include <GEL/HMesh/Manifold.h>

/** Class constructed to handle splatting of surfaces to volumetric mesh.
 *
 * This class is designed to construct the interface mesh, where all selected
 * surfaces from the StreamSurfaces are extended with the underlying material
 * thickness.
 *
 * Default class usage:
 * @code {.cpp}
 * Splatter spl(options);
 * spl.import_surfaces();
 * spl.splat_surfaces();
 * spl.splat_solid();
 * spl.extract_iso_contour();
 * spl.save_mesh();
 * @endcode
 *
 * See protected attributes for modifiable settings.
 */
class Splatter {

    /** HMesh manifold version with colors. */
    struct MeshVCol {
        HMesh::Manifold                           m;
        HMesh::VertexAttributeVector<CGLA::Vec3d> vcol;

        MeshVCol() {}
    };

    /** Single triangles from the mesh. Mostly used for interpolation. */
    class Triangle {
        std::array<CGLA::Vec3d, 3> vert;

      public:
        Triangle(const CGLA::Vec3d &p0, const CGLA::Vec3d &p1,
                 const CGLA::Vec3d &p2)
            : vert({p0, p1, p2}) {}

        std::pair<CGLA::Vec3d, CGLA::Vec3d> bbox(double rad);
        CGLA::Vec4d                         bary_dist(const CGLA::Vec3d &p);
    };

    struct MicroGrid {
        CGLA::Vec3i        lower, upper;
        std::vector<float> dist;
        size_t             idx = 0;

        MicroGrid(const CGLA::Vec3i &_lower, const CGLA::Vec3i &_upper)
            : lower(_lower), upper(_upper) {
            const CGLA::Vec3i dim = upper - lower;
            dist.resize(dim[0] * dim[1] * dim[2], 0);
        }

        void push(float d);
        void store(Geometry::RGrid<float> &grid,
                   Geometry::RGrid<float> &wgrid) const;
    };

  protected:
    // =====================================================================  //
    // List of options and variables

    // ...................................................................... //
    // Specification of input and output names and folders

    std::string data_name       = "";
    std::string data_location   = "../../../Data/";
    std::string info_file       = "";
    std::string density_file    = "../densities.csv";
    std::string options_file    = "options.cfg";
    std::string export_location = "result/";

    // ...................................................................... //
    // Splatter Specific Options

    double splatter_min_scaling   = 1;
    double splatter_max_scaling   = DBL_MAX;
    double splatter_simplify_to   = 5e7;
    double splatter_solid_overlap = 0.05;

    // ...................................................................... //
    // Toggles

    bool debug           = false;
    bool splatter_toggle = false;

    // ...................................................................... //
    // Settings carried over from surface generation

    double desired_spacing = 10;
    double threshold_void  = 0.0;
    double threshold_solid = DBL_MAX;

  private:
    // ====================================================================== //
    // Data fields

    BBox                     bb;          // Bounding box of domain
    std::vector<std::string> files;       // List of surface files
    std::vector<MeshVCol>    mesh_vec;    // List of mesh to be splatted
    Geometry::RGrid<float>   global_grid; // Interpolation grid
    Geometry::XForm          xform;       // Mapping object from domain to grid
    HMesh::Manifold          mesh;        // Final interface mesh

  public:
    // ====================================================================== //
    // Constructors

    /** Construct a new Splatter object.
     *
     * The Options class is used to specify input data and settings.
     *
     * @param options see protected attributes for modifiable Options.
     */
    Splatter(Options &options);
    /** Construct a new Splatter object.
     *
     * Construct a Splatter object with Options specified directly from a
     * file.
     *
     * @param options_filename default is "options.cfg".
     */
    Splatter(std::string options_filename = "options.cfg");
    /** Destructor */
    ~Splatter();

    /** Import surfaces based on the given folder.
     *
     * @param folder if empty we use options to find optimal surfaces.
     */
    void import_surfaces(std::string folder = "");

    // ====================================================================== //
    // Splatting functions

    /** Surface Splatting Function
     *
     * This is the function that will splat surfaces into the underlying
     * interpolation grid.
     *
     * The grid is constructed with a smooth step function to vary smoothly from
     * 0.0 to 1.0. By default the isocontour obaying the volume constraint
     * should be at 0.5.
     */
    void splat_surfaces();
    /** Solid block insertion
     *
     * By default, the surface generator export a list of densities for each
     * element. If these densities are above the solid threshold parameter, we
     * will add a square of material in the interpolation grid.
     */
    void splat_solid();

    // ====================================================================== //
    // IO functions

    /** Extraction of surface mesh.
     *
     * This function will extract the surface representing the outer interface
     * of the structure. By default the volume constraint should be located on
     * the 0.5 iso contour of the underlying grid.
     *
     * @param iso value to be extracted.
     */
    void extract_iso_contour(double iso = 0.5);

    /** Export the mesh.
     *
     * This function exports the mesh constructed with the extract_iso_contour.
     * In addition, a number of vertices can be specified, we will then attempt
     * to simplify the mesh to this number of vertices.
     *
     * We will simplify to the smallest number specified here, or in the options
     * file.
     *
     * @param simplify_to
     */
    void save_mesh(double simplify_to = DBL_MAX);

  private:
    // ====================================================================== //
    // Internal functions

    /** Import options to the class */
    void read_options(Options &opt);

    /** Construction */
    void constructor(Options &opt);

    /** Preparation function of grids etc. */
    void prepare_splatter();

    // ====================================================================== //
    // Internal utility functions

    /** Smooth Step Function
     *
     * This function compute the smooth step between a and b by the following:
     *
     * @code {.cpp}
     *       x = (t - a) / (b - a)
     *   ss(x) = 3 * x^2 - 2 * x^3
     * @endcode
     *
     * @param a Lower limit.
     * @param b Upper limit.
     * @param t Position.
     * @return double 0.0 (t<a), ss(x) (a<t<b), 1.0 (b<t).
     */
    inline double smooth_step(double a, double b, double t);
    /** Remove trailing whitespace from string. */
    void right_trim(std::string &str);
    /** Alternative to get_line. */
    std::istream &get_multi_line(std::istream &is, std::string &buf);
    /** Estimate the volume fraction inside the interface. */
    void estiamte_volume_fraction(double tau);
    /** Load objfile and save color information in vcol. */
    bool vcol_obj_load(const std::string &filename, HMesh::Manifold &m,
                       HMesh::VertexAttributeVector<CGLA::Vec3d> &vcol);
};

#endif /* __SPLATTER__ */