///|/ Copyright (c) Prusa Research 2020 - 2021 Lukáš Matěna @lukasmatena, Tomáš Mészáros @tamasmeszaros
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef MARCHINGSQUARES_HPP
#define MARCHINGSQUARES_HPP

#include <type_traits>
#include <cstdint>
#include <vector>
#include <algorithm>
#include <cassert>

namespace marchsq {

// Marks a square in the grid
struct Coord {
    long r = 0, c = 0;
    
    Coord() = default;
    explicit Coord(long s) : r(s), c(s) {}
    Coord(long _r, long _c): r(_r), c(_c) {}
    
    size_t seq(const Coord &res) const { return r * res.c + c; }
    Coord& operator+=(const Coord& b) { r += b.r; c += b.c; return *this; }
    Coord operator+(const Coord& b) const { Coord a = *this; a += b; return a; }
};

// Closed ring of cell coordinates
using Ring = std::vector<Coord>;

// Specialize this struct to register a raster type for the Marching squares alg
template<class T, class Enable = void> struct _RasterTraits {
    
    // The type of pixel cell in the raster
    using ValueType = typename T::ValueType;
    
    // Value at a given position
    static ValueType get(const T &raster, size_t row, size_t col);
    
    // Number of rows and cols of the raster
    static size_t rows(const T &raster);
    static size_t cols(const T &raster);
};

// Specialize this to use parellel loops within the algorithm
template<class ExecutionPolicy, class Enable = void> struct _Loop {
    template<class It, class Fn> static void for_each(It from, It to, Fn &&fn)
    {
        for (auto it = from; it < to; ++it) fn(*it, size_t(it - from));
    }
};

namespace __impl {

template<class T> using RasterTraits = _RasterTraits<std::decay_t<T>>;
template<class T> using TRasterValue = typename RasterTraits<T>::ValueType;

template<class T> size_t rows(const T &raster)
{
    return RasterTraits<T>::rows(raster);
}

template<class T> size_t cols(const T &raster)
{
    return RasterTraits<T>::cols(raster);
}

template<class T> TRasterValue<T> isoval(const T &rst, const Coord &crd)
{
    return RasterTraits<T>::get(rst, crd.r, crd.c);
}

template<class ExecutionPolicy, class It, class Fn>
void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn)
{
    _Loop<ExecutionPolicy>::for_each(from, to, fn);
}

// Type of squares (tiles) depending on which vertices are inside an ROI
// The vertices would be marked a, b, c, d in counter clockwise order from the 
// bottom left vertex of a square.
// d --- c
// |     |
// |     |
// a --- b
enum class SquareTag : uint8_t {
//     0, 1, 2,  3, 4,  5,  6,   7, 8,  9, 10,  11, 12,  13,  14,  15
    none, a, b, ab, c, ac, bc, abc, d, ad, bd, abd, cd, acd, bcd, full
};

template<class E> constexpr std::underlying_type_t<E> _t(E e) noexcept
{
    return static_cast<std::underlying_type_t<E>>(e);
}

enum class Dir: uint8_t { left, down, right, up, none};

static const constexpr Dir NEXT_CCW[] = {
    /* 00 */ Dir::none,      // SquareTag::none (empty square, nowhere to go)
    /* 01 */ Dir::left,      // SquareTag::a
    /* 02 */ Dir::down,      // SquareTag::b
    /* 03 */ Dir::left,      // SquareTag::ab
    /* 04 */ Dir::right,     // SquareTag::c
    /* 05 */ Dir::none,      // SquareTag::ac   (ambiguous case)
    /* 06 */ Dir::down,      // SquareTag::bc
    /* 07 */ Dir::left,      // SquareTag::abc
    /* 08 */ Dir::up,        // SquareTag::d
    /* 09 */ Dir::up,        // SquareTag::ad
    /* 10 */ Dir::none,      // SquareTag::bd   (ambiguous case)
    /* 11 */ Dir::up,        // SquareTag::abd
    /* 12 */ Dir::right,     // SquareTag::cd
    /* 13 */ Dir::right,     // SquareTag::acd
    /* 14 */ Dir::down,      // SquareTag::bcd
    /* 15 */ Dir::none       // SquareTag::full (full covered, nowhere to go)
};

static const constexpr uint8_t PREV_CCW[] = {
    /* 00 */ 1 << _t(Dir::none),
    /* 01 */ 1 << _t(Dir::up),      
    /* 02 */ 1 << _t(Dir::left),
    /* 03 */ 1 << _t(Dir::left),     
    /* 04 */ 1 << _t(Dir::down),     
    /* 05 */ 1 << _t(Dir::up) | 1 << _t(Dir::down),      
    /* 06 */ 1 << _t(Dir::down),
    /* 07 */ 1 << _t(Dir::down),
    /* 08 */ 1 << _t(Dir::right),
    /* 09 */ 1 << _t(Dir::up),
    /* 10 */ 1 << _t(Dir::left) | 1 << _t(Dir::right), 
    /* 11 */ 1 << _t(Dir::left),   
    /* 12 */ 1 << _t(Dir::right),
    /* 13 */ 1 << _t(Dir::up),
    /* 14 */ 1 << _t(Dir::right), 
    /* 15 */ 1 << _t(Dir::none)  
};

const constexpr uint8_t DIRMASKS[] = {
    /*left: */ 0x01, /*down*/ 0x12, /*right */0x21, /*up*/ 0x10, /*none*/ 0x00
};

inline Coord step(const Coord &crd, Dir d)
{
    uint8_t dd = DIRMASKS[uint8_t(d)];
    return {crd.r - 1 + (dd & 0x0f), crd.c - 1 + (dd >> 4)};
}

template<class Rst> class Grid {
    const Rst *            m_rst = nullptr;
    Coord                  m_cellsize, m_res_1, m_window, m_gridsize, m_grid_1;
    std::vector<uint8_t>   m_tags;     // Assign tags to each square

    Coord rastercoord(const Coord &crd) const
    {
        return {(crd.r - 1) * m_window.r, (crd.c - 1) * m_window.c};
    }

    Coord bl(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, 0}; }
    Coord br(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, m_res_1.c}; }
    Coord tr(const Coord &crd) const { return tl(crd) + Coord{0, m_res_1.c}; }
    Coord tl(const Coord &crd) const { return rastercoord(crd); }
    
    bool is_within(const Coord &crd)
    {
        long R = rows(*m_rst), C = cols(*m_rst);
        return crd.r >= 0 && crd.r < R && crd.c >= 0 && crd.c < C;
    };

    // Calculate the tag for a cell (or square). The cell coordinates mark the
    // top left vertex of a square in the raster. v is the isovalue
    uint8_t get_tag_for_cell(const Coord &cell, TRasterValue<Rst> v)
    {        
        Coord sqr[] = {bl(cell), br(cell), tr(cell), tl(cell)};
        
        uint8_t t = ((is_within(sqr[0]) && isoval(*m_rst, sqr[0]) >= v)) +
                    ((is_within(sqr[1]) && isoval(*m_rst, sqr[1]) >= v) << 1) +
                    ((is_within(sqr[2]) && isoval(*m_rst, sqr[2]) >= v) << 2) +
                    ((is_within(sqr[3]) && isoval(*m_rst, sqr[3]) >= v) << 3);
        
        assert(t < 16);
        return t;
    }
    
    // Get a cell coordinate from a sequential index
    Coord coord(size_t i) const
    {
        return {long(i) / m_gridsize.c, long(i) % m_gridsize.c};
    }

    size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); }
    
    bool is_visited(size_t idx, Dir d = Dir::none) const
    {
        SquareTag t = get_tag(idx);
        uint8_t ref = d == Dir::none ? PREV_CCW[_t(t)] : uint8_t(1 << _t(d));
        return t == SquareTag::full || t == SquareTag::none ||
               ((m_tags[idx] & 0xf0) >> 4) == ref;
    }
    
    void set_visited(size_t idx, Dir d = Dir::none)
    {
        m_tags[idx] |= (1 << (_t(d)) << 4);
    }
    
    bool is_ambiguous(size_t idx) const
    {
        SquareTag t = get_tag(idx);
        return t == SquareTag::ac || t == SquareTag::bd;
    }

    // Search for a new starting square
    size_t search_start_cell(size_t i = 0) const
    {
        // Skip ambiguous tags as starting tags due to unknown previous
        // direction.
        while ((i < m_tags.size()) && (is_visited(i) || is_ambiguous(i))) ++i;
        
        return i;
    }
    
    SquareTag get_tag(size_t idx) const { return SquareTag(m_tags[idx] & 0x0f); }
        
    Dir next_dir(Dir prev, SquareTag tag) const
    {
        // Treat ambiguous cases as two separate regions in one square.
        switch (tag) {
        case SquareTag::ac:
            switch (prev) {
            case Dir::down: return Dir::right;
            case Dir::up:   return Dir::left;
            default:        assert(false); return Dir::none;
            }
        case SquareTag::bd:
            switch (prev) {
            case Dir::right: return Dir::up;
            case Dir::left:  return Dir::down;
            default:         assert(false); return Dir::none;
            }
        default:
            return NEXT_CCW[uint8_t(tag)];
        }
        
        return Dir::none;
    }
    
    struct CellIt {
        Coord crd; Dir dir= Dir::none; const Rst *grid = nullptr;
        
        TRasterValue<Rst> operator*() const { return isoval(*grid, crd); }
        CellIt& operator++() { crd = step(crd, dir); return *this; }
        CellIt operator++(int) { CellIt it = *this; ++(*this); return it; }
        bool operator!=(const CellIt &it) { return crd.r != it.crd.r || crd.c != it.crd.c; }
        
        using value_type        = TRasterValue<Rst>;
        using pointer           = TRasterValue<Rst> *;
        using reference         = TRasterValue<Rst> &;
        using difference_type   = long;
        using iterator_category = std::forward_iterator_tag;
    };
    
    // Two cell iterators representing an edge of a square. This is then
    // used for binary search for the first active pixel on the edge.
    struct Edge { CellIt from, to; };
    
    Edge _edge(const Coord &ringvertex) const
    {
        size_t idx = ringvertex.r;
        Coord cell = coord(idx);
        uint8_t tg = m_tags[ringvertex.r];
        SquareTag t = SquareTag(tg & 0x0f);
        
        switch (t) {
        case SquareTag::a:
        case SquareTag::ab:
        case SquareTag::abc:
            return {{tl(cell), Dir::down,  m_rst}, {bl(cell)}};
        case SquareTag::b:
        case SquareTag::bc:
        case SquareTag::bcd:
            return {{bl(cell), Dir::right, m_rst}, {br(cell)}};
        case SquareTag::c:
            return {{br(cell), Dir::up,    m_rst}, {tr(cell)}};
        case SquareTag::ac:
            switch (Dir(ringvertex.c)) {
            case Dir::left:  return {{tl(cell), Dir::down, m_rst}, {bl(cell)}};
            case Dir::right: return {{br(cell), Dir::up,   m_rst}, {tr(cell)}};
            default: assert(false);
            }
        case SquareTag::d:
        case SquareTag::ad:
        case SquareTag::abd:
            return {{tr(cell), Dir::left, m_rst}, {tl(cell)}};
        case SquareTag::bd:
            switch (Dir(ringvertex.c)) {
            case Dir::down: return {{bl(cell), Dir::right, m_rst}, {br(cell)}};
            case Dir::up:   return {{tr(cell), Dir::left,  m_rst}, {tl(cell)}};
            default: assert(false);
            }
        case SquareTag::cd:
        case SquareTag::acd:
            return {{br(cell), Dir::up, m_rst}, {tr(cell)}};
        case SquareTag::full:
        case SquareTag::none: {
            Coord crd{tl(cell) + Coord{m_cellsize.r / 2, m_cellsize.c / 2}};
            return {{crd, Dir::none, m_rst}, {crd}};
        }
        }
        
        return {}; 
    }
    
    Edge edge(const Coord &ringvertex) const
    {
        const long R = rows(*m_rst), C = cols(*m_rst);
        const long R_1 = R - 1, C_1 = C - 1;
        
        Edge e = _edge(ringvertex);
        e.to.dir = e.from.dir;
        ++e.to;
        
        e.from.crd.r = std::min(e.from.crd.r, R_1);
        e.from.crd.r = std::max(e.from.crd.r, 0l);
        e.from.crd.c = std::min(e.from.crd.c, C_1);
        e.from.crd.c = std::max(e.from.crd.c, 0l);
        
        e.to.crd.r = std::min(e.to.crd.r, R);
        e.to.crd.r = std::max(e.to.crd.r, 0l);
        e.to.crd.c = std::min(e.to.crd.c, C);
        e.to.crd.c = std::max(e.to.crd.c, 0l);
        
        return e;
    }
    
public:
    explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap)
        : m_rst{&rst}
        , m_cellsize{cellsz}
        , m_res_1{m_cellsize.r - 1, m_cellsize.c - 1}
        , m_window{overlap.r < cellsz.r ? cellsz.r - overlap.r : cellsz.r,
                   overlap.c < cellsz.c ? cellsz.c - overlap.c : cellsz.c}
        , m_gridsize{2 + (long(rows(rst)) - overlap.r) / m_window.r,
                     2 + (long(cols(rst)) - overlap.c) / m_window.c}
        , m_tags(m_gridsize.r * m_gridsize.c, 0)
    {}
    
    // Go through the cells and mark them with the appropriate tag.
    template<class ExecutionPolicy>
    void tag_grid(ExecutionPolicy &&policy, TRasterValue<Rst> isoval)
    {        
        // parallel for r
        for_each (std::forward<ExecutionPolicy>(policy),
                 m_tags.begin(), m_tags.end(),
                 [this, isoval](uint8_t& tag, size_t idx) {
            tag = get_tag_for_cell(coord(idx), isoval);
        });
    }
    
    // Scan for the rings on the tagged grid. Each ring vertex stores the
    // sequential index of the cell and the next direction (Dir).
    // This info can be used later to calculate the exact raster coordinate.
    std::vector<Ring> scan_rings()
    {
        std::vector<Ring> rings;
        size_t startidx = 0;
        while ((startidx = search_start_cell(startidx)) < m_tags.size()) {
            Ring ring;
            
            size_t idx = startidx;
            Dir prev = Dir::none, next = next_dir(prev, get_tag(idx));
            
            while (next != Dir::none && !is_visited(idx, prev)) {
                Coord ringvertex{long(idx), long(next)};
                ring.emplace_back(ringvertex);
                set_visited(idx, prev);
                
                idx  = seq(step(coord(idx), next));
                prev = next;
                next = next_dir(next, get_tag(idx));
            }
            
            // To prevent infinite loops in case of degenerate input
            if (next == Dir::none) m_tags[startidx] = _t(SquareTag::none);
            
            if (ring.size() > 1) {
                ring.pop_back();
                rings.emplace_back(ring);
            }
        }
        
        return rings;
    }
    
    // Calculate the exact raster position from the cells which store the
    // sequantial index of the square and the next direction
    template<class ExecutionPolicy>
    void interpolate_rings(ExecutionPolicy && policy,
                           std::vector<Ring> &rings,
                           TRasterValue<Rst>  isov)
    {
        for_each(std::forward<ExecutionPolicy>(policy),
                 rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t)
        {
            for (Coord &ringvertex : ring) {
                Edge e = edge(ringvertex);
                
                CellIt found = std::lower_bound(e.from, e.to, isov);
                ringvertex = found.crd;
            }
        });
    }
};

template<class Raster, class ExecutionPolicy>
std::vector<marchsq::Ring> execute_with_policy(ExecutionPolicy &&   policy,
                                               const Raster &       raster,
                                               TRasterValue<Raster> isoval,
                                               Coord windowsize = {})
{
    if (!rows(raster) || !cols(raster)) return {};
    
    size_t ratio = cols(raster) / rows(raster);
    
    if (!windowsize.r) windowsize.r = 2;
    if (!windowsize.c)
        windowsize.c = std::max(2l, long(windowsize.r * ratio));
    
    Coord overlap{1};
    
    Grid<Raster> grid{raster, windowsize, overlap};
    
    grid.tag_grid(std::forward<ExecutionPolicy>(policy), isoval);
    std::vector<marchsq::Ring> rings = grid.scan_rings();
    grid.interpolate_rings(std::forward<ExecutionPolicy>(policy), rings, isoval);
    
    return rings;
}

template<class Raster>
std::vector<marchsq::Ring> execute(const Raster &raster,
                                   TRasterValue<Raster> isoval,
                                   Coord                windowsize = {})
{
    return execute_with_policy(nullptr, raster, isoval, windowsize);
}

} // namespace __impl

using __impl::execute_with_policy;
using __impl::execute;

} // namespace marchsq

#endif // MARCHINGSQUARES_HPP
