//Copyright (c) 2018 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#ifndef INFILL_SUBDIVCUBE_H
#define INFILL_SUBDIVCUBE_H

#include "types/Ratio.h"
#include "types/LayerIndex.h"
#include "utils/IntPoint.h"
#include "utils/Point3.h"

namespace cura52
{
    class Polygons;
    class SliceMeshStorage;

    class SubDivCube
    {
    public:
        /*!
         * Constructor for SubDivCube. Recursively calls itself eight times to flesh out the octree.
         * \param mesh contains infill layer data and settings
         * \param my_center the center of the cube
         * \param depth the recursion depth of the cube (0 is most recursed)
         */
        SubDivCube(SliceMeshStorage& mesh, Point3& center, size_t depth);

        ~SubDivCube(); //!< destructor (also destroys children)

        /*!
         * Precompute the octree of subdivided cubes
         * \param mesh contains infill layer data and settings
         */
        static void precomputeOctree(SliceMeshStorage& mesh, const Point& infill_origin);

        /*!
         * Generates the lines of subdivision of the specific cube at the specific layer. It recursively calls itself, so it ends up drawing all the subdivision lines of sub-cubes too.
         * \param z the specified layer height
         * \param result (output) The resulting lines
         */
        void generateSubdivisionLines(const coord_t z, Polygons& result);
    private:
        /*!
         * Generates the lines of subdivision of the specific cube at the specific layer. It recursively calls itself, so it ends up drawing all the subdivision lines of sub-cubes too.
         * \param z the specified layer height
         * \param result (output) The resulting lines
         * \param directional_line_groups Array of 3 times a polylines. Used to keep track of line segments that are all pointing the same direction for line segment combining
         */
        void generateSubdivisionLines(const coord_t z, Polygons(&directional_line_groups)[3]);

        struct CubeProperties
        {
            coord_t side_length; //!< side length of cubes
            coord_t height; //!< height of cubes based. This is the distance from one point of a cube to its 3d opposite.
            coord_t square_height; //!< square cut across lengths. This is the diagonal distance across a face of the cube.
            coord_t max_draw_z_diff; //!< maximum draw z differences. This is the maximum difference in z at which lines need to be drawn.
            coord_t max_line_offset; //!< maximum line offsets. This is the maximum distance at which subdivision lines should be drawn from the 2d cube center.
        };

        /*!
         * Rotates a point 120 degrees about the origin.
         * \param target the point to rotate.
         */
        static void rotatePoint120(Point& target);

        /*!
         * Rotates a point to align it with the orientation of the infill.
         * \param target the point to rotate.
         */
        static void rotatePointInitial(Point& target);

        /*!
         * Determines if a described theoretical cube should be subdivided based on if a sphere that encloses the cube touches the infill mesh.
         * \param mesh contains infill layer data and settings
         * \param center the center of the described cube
         * \param radius the radius of the enclosing sphere
         * \return the described cube should be subdivided
         */
        static bool isValidSubdivision(SliceMeshStorage& mesh, Point3& center, coord_t radius);

        /*!
         * Finds the distance to the infill border at the specified layer from the specified point.
         * \param mesh contains infill layer data and settings
         * \param layer_nr the number of the specified layer
         * \param location the location of the specified point
         * \param[out] distance2 the squared distance to the infill border
         * \return Code 0: outside, 1: inside, 2: boundary does not exist at specified layer
         */
        static coord_t distanceFromPointToMesh(SliceMeshStorage& mesh, const LayerIndex layer_nr, Point& location, coord_t* distance2);

        /*!
         * Adds the defined line to the specified polygons. It assumes that the specified polygons are all parallel lines. Combines line segments with touching ends closer than epsilon.
         * \param[out] group the polygons to add the line to
         * \param from the first endpoint of the line
         * \param to the second endpoint of the line
         */
        void addLineAndCombine(Polygons& group, Point from, Point to);

        size_t depth; //!< the recursion depth of the cube (0 is most recursed)
        Point3 center; //!< center location of the cube in absolute coordinates
        SubDivCube* children[8] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; //!< pointers to this cube's eight octree children
        static std::vector<CubeProperties> cube_properties_per_recursion_step; //!< precomputed array of basic properties of cubes based on recursion depth.
        static Ratio radius_multiplier; //!< multiplier for the bounding radius when determining if a cube should be subdivided
        static Point3Matrix rotation_matrix; //!< The rotation matrix to get from axis aligned cubes to cubes standing on a corner point aligned with the infill_angle
        static PointMatrix infill_rotation_matrix; //!< Horizontal rotation applied to infill
        static coord_t radius_addition; //!< addition to the bounding radius when determining if a cube should be subdivided
    };

}
#endif //INFILL_SUBDIVCUBE_H
