// BaseContourGenerator class provides functionality common to multiple contouring algorithms.
// Uses the Curiously Recurring Template Pattern idiom whereby classes derived from this are
// declared as
//     class Derived : public BaseContourGenerator<Derived>
//
// It provides static polymorphism at compile-time rather than the more common dynamic polymorphism
// at run-time using virtual functions.  Hence it avoids the runtime overhead of calling virtual
// functions.

#ifndef CONTOURPY_BASE_H
#define CONTOURPY_BASE_H

#include "chunk_local.h"
#include "contour_generator.h"
#include "fill_type.h"
#include "line_type.h"
#include "outer_or_hole.h"
#include "z_interp.h"
#include <vector>

namespace contourpy {

template <typename Derived>
class BaseContourGenerator : public ContourGenerator
{
public:
    ~BaseContourGenerator();

    static FillType default_fill_type();
    static LineType default_line_type();

    py::tuple get_chunk_count() const;  // Return (y_chunk_count, x_chunk_count)
    py::tuple get_chunk_size() const;   // Return (y_chunk_size, x_chunk_size)

    bool get_corner_mask() const;

    FillType get_fill_type() const;
    LineType get_line_type() const;

    bool get_quad_as_tri() const;

    ZInterp get_z_interp() const;

    py::sequence filled(double lower_level, double upper_level);
    py::sequence lines(double level);

    static bool supports_fill_type(FillType fill_type);
    static bool supports_line_type(LineType line_type);

    void write_cache() const;  // For debug purposes only.

protected:
    BaseContourGenerator(
        const CoordinateArray& x, const CoordinateArray& y, const CoordinateArray& z,
        const MaskArray& mask, bool corner_mask, LineType line_type, FillType fill_type,
        bool quad_as_tri, ZInterp z_interp, index_t x_chunk_size, index_t y_chunk_size);

    typedef uint32_t CacheItem;
    typedef CacheItem ZLevel;

    // C++11 scoped enum for direction of movement from one quad to the next.
    enum class Direction
    {
        Left = 1,
        Straight = 0,
        Right = -1
    };

    struct Location
    {
        Location(index_t quad_, index_t forward_, index_t left_, bool is_upper_, bool on_boundary_)
            : quad(quad_), forward(forward_), left(left_), is_upper(is_upper_),
              on_boundary(on_boundary_)
        {}

        friend std::ostream &operator<<(std::ostream &os, const Location& location)
        {
            os << "quad=" << location.quad << " forward=" << location.forward << " left="
                << location.left << " is_upper=" << location.is_upper << " on_boundary="
                << location.on_boundary;
            return os;
        }

        index_t quad, forward, left;
        bool is_upper, on_boundary;
    };

    // Calculate and return z at middle of quad.
    double calc_middle_z(index_t quad) const;

    // Calculate, set and return z-level at middle of quad.
    ZLevel calc_and_set_middle_z_level(index_t quad);

    void closed_line(const Location& start_location, OuterOrHole outer_or_hole, ChunkLocal& local);

    void closed_line_wrapper(
        const Location& start_location, OuterOrHole outer_or_hole, ChunkLocal& local);

    // If point/line/hole counts not consistent, throw runtime error.
    void check_consistent_counts(const ChunkLocal& local) const;

    index_t find_look_S(index_t look_N_quad) const;

    // Return true if finished (i.e. back to start quad, direction and upper).
    bool follow_boundary(
        Location& location, const Location& start_location, ChunkLocal& local,
        count_t& point_count);

    // Return true if finished (i.e. back to start quad, direction and upper).
    bool follow_interior(
        Location& location, const Location& start_location, ChunkLocal& local,
        count_t& point_count);

    index_t get_boundary_start_point(const Location& location) const;

    // These are quad chunk limits, not point chunk limits.
    // chunk is index in range 0.._n_chunks-1.
    void get_chunk_limits(index_t chunk, ChunkLocal& local) const;

    index_t get_interior_start_left_point(
        const Location& location, bool& start_corner_diagonal) const;

    double get_interp_fraction(double z0, double z1, double level) const;

    double get_middle_x(index_t quad) const;
    double get_middle_y(index_t quad) const;

    index_t get_n_chunks() const;

    void get_point_xy(index_t point, double*& points) const;

    double get_point_x(index_t point) const;
    double get_point_y(index_t point) const;
    double get_point_z(index_t point) const;

    bool has_direct_line_offsets() const;
    bool has_direct_outer_offsets() const;
    bool has_direct_points() const;

    void init_cache_grid(const MaskArray& mask);

    // Either for a single chunk, or the whole domain (all chunks) if local == nullptr.
    void init_cache_levels_and_starts(const ChunkLocal* local = nullptr);

    // Increments local.points twice.
    void interp(index_t point0, index_t point1, bool is_upper, double*& points) const;

    // Increments local.points twice.
    void interp(
        index_t point0, double x1, double y1, double z1, bool is_upper, double*& points) const;

    bool is_filled() const;

    bool is_point_in_chunk(index_t point, const ChunkLocal& local) const;

    bool is_quad_in_bounds(
        index_t quad, index_t istart, index_t iend, index_t jstart, index_t jend) const;

    bool is_quad_in_chunk(index_t quad, const ChunkLocal& local) const;

    void line(const Location& start_location, ChunkLocal& local);

    void march_chunk(ChunkLocal& local, std::vector<py::list>& return_lists);

    py::sequence march_wrapper();

    void move_to_next_boundary_edge(index_t& quad, index_t& forward, index_t& left) const;

    void set_look_flags(index_t hole_start_quad);

    void write_cache_quad(index_t quad) const;

    ZLevel z_to_zlevel(double z_value) const;


private:
    const CoordinateArray _x, _y, _z;
    const double* _xptr;                   // For quick access to _x.data().
    const double* _yptr;
    const double* _zptr;
    const index_t _nx, _ny;                // Number of points in each direction.
    const index_t _n;                      // Total number of points (and quads).
    const index_t _x_chunk_size, _y_chunk_size;
    const index_t _nx_chunks, _ny_chunks;  // Number of chunks in each direction.
    const index_t _n_chunks;               // Total number of chunks.
    const bool _corner_mask;
    const LineType _line_type;
    const FillType _fill_type;
    const bool _quad_as_tri;
    const ZInterp _z_interp;

    CacheItem* _cache;

    // Current contouring operation.
    bool _filled;
    double _lower_level, _upper_level;

    // Current contouring operation, based on return type and filled or lines.
    bool _identify_holes;
    bool _output_chunked;             // Implies empty chunks will have py::none().
    bool _direct_points;              // Whether points array is written direct to Python.
    bool _direct_line_offsets;        // Whether line offsets array is written direct to Python.
    bool _direct_outer_offsets;       // Whether outer offsets array is written direct to Python.
    bool _outer_offsets_into_points;  // Otherwise into line offsets.  Only used if _identify_holes.
    unsigned int _return_list_count;
};

} // namespace contourpy

#endif // CONTOURPY_BASE_H
