/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/

/** @file contour_simple.h Declares class SimpleContour and class DoubleMap.
 * @ingroup contour_files
 */

#if !defined(CONTOUR_SIMPLE_H)
#define CONTOUR_SIMPLE_H

#include <map>
#include "contour_path.h"

namespace contour {

/** A template that allows storing of state along a 1 dimensional axis.
 * @ingroup contour_internal
 *
 * The state can change at any real (double) value.
 * The template parameter contains the type of state.
 * We have constructs to set the state at a walue and onwards or between two points only.
 * We also have members to query the state at any point and ask where will the next change is.
 * Finally, if the element supports the += operator we can add changes to the state.
 */

template <class element>
class DoubleMap : public std::map<double, element>
{
public:
    using std::map<double, element>::operator[];   //indicate to two-stage (dependent) name lookup
    using std::map<double, element>::upper_bound;  //that we want these names (not dependent on "element")
    using std::map<double, element>::lower_bound;  //looked up only at instantiation time (from base class)
    using std::map<double, element>::begin;        //and not during template compilation (searched in global scope).
    using std::map<double, element>::end;
    using std::map<double, element>::insert;
    using std::map<double, element>::erase;

    DoubleMap() {};
    DoubleMap(const element &e) {insert(typename std::map<double, element>::value_type(-CONTOUR_INFINITY, e));
                                 insert(typename std::map<double, element>::value_type(CONTOUR_INFINITY, e));}
    void clear(const element &e) {std::map<double, element>::clear();
                                  insert(typename std::map<double, element>::value_type(-CONTOUR_INFINITY, e));
                                  insert(typename std::map<double, element>::value_type(CONTOUR_INFINITY, e));}
    void clear() {if (this->size()>2) erase(++begin(), --end());} ///<Clear all of the state
    void Set(double pos, const element&e) {operator[](pos) = e;}  ///<Set state to `e` from `pos` till any existing state change after pos.
    void Set(const Range &r, const element &e);                   ///<Set state to `e` in the range `r`.
    void Add(double pos, const element&e);                        ///<Add the element `e` to the state from `pos` till any existing state change after pos. Assumes element has operator +=
    void Add(const Range &r, const element&e);                    ///<Add state to `e` in the range `r`. Assumes element has operator +=
    template <typename T> void Add(double pos, const element&e, T &combine);     ///<Add the element `e` to the state from `pos` till any existing state change after pos. Uses `combine(a, b)` to combine elements
    template <typename T> void Add(const Range &r, const element&e, T &combine); ///<Add state to `e` in the range `r`. Uses `combine(a, b)` to combine elements
    /** Return state at `pos` if any. In case 'pos' is an exact boundary point,
     * we return the state after(=above) 'pos'.*/
    const element* Get(double pos) const {auto i=upper_bound(pos); return i==begin()?nullptr:&std::prev(i)->second;}
    /** Return state at `pos` if any. In case 'pos' is an exact boundary point,
     * we return the state above from 'pos'.*/
    const element* GetBelowBoundary(double pos) const { auto i = lower_bound(pos); return i==begin() ? nullptr : &std::prev(i)->second; }
    double Till(double pos) const {auto i=upper_bound(pos); return i==end()?CONTOUR_INFINITY:i->first;}    ///<Returns the location of any state change strictly before `pos`. Returns `-CONTOUR_INFINITY` if none.
    double From(double pos) const {auto i=--lower_bound(pos); return i==end()?-CONTOUR_INFINITY:i->first;} ///<Returns the location of any state change strictly after `pos`. Returns `CONTOUR_INFINITY` if none.
    void Prune();      ///<Kill redundant elements (no-op state changes) to reduce size.
};

template <class element>
void DoubleMap<element>::Add(double pos, const element&e)
{
    auto i = --upper_bound(pos);
    if (i==begin()) Set(pos, e);
    else if (i->first == pos)
        i->second += e;
    else insert(i, typename std::map<double, element>::value_type(pos, i->second))->second += e;
}

template <class element>
template <typename T>
void DoubleMap<element>::Add(double pos, const element&e, T &combine)
{
    auto i = --upper_bound(pos);
    if (i==begin()) Set(pos, e);
    else if (i->first == pos)
        i->second = combine(i->second, e);
    else insert(i, typename std::map<double, element>::value_type(pos, combine(i->second, e)));
}

template <class element>
void DoubleMap<element>::Set(const Range &r, const element& e)
{
    if (r.till <= r.from) return;
    auto i = --upper_bound(r.till);
    if (i!=end()) {//avoid the case when whole range is before first element
        if (i->first == r.till) {
            erase(upper_bound(r.from), i);
        } else {
            operator[](r.till) = i->second;
            erase(upper_bound(r.from), ++i);
        }
    }
    operator[](r.from) = e;
}

template <class element>
void DoubleMap<element>::Add(const Range &r, const element& e)
{
    if (r.till <= r.from) return;
    auto i = --upper_bound(r.till);
    if (i==end())
        operator[](r.from) = e; //if the whole range is before the first element
    else {
        if (i->first != r.till) //i points to a place before r.till
            i = insert(i, typename std::map<double, element>::value_type(r.till, i->second));
        //now i points to the element at r.till
        auto h = --upper_bound(r.from);
        if (h==end())
            h = insert(begin(), typename std::map<double, element>::value_type(r.from, e))++;
        else if (h->first != r.from) //j points to an element before r.from
            h = insert(h, typename std::map<double, element>::value_type(r.from, h->second));
        //now h points to the first element to add e to
        for (; h!=i; h++)
            h->second += e;
    }
}

template <class element>
template <typename T>
void DoubleMap<element>::Add(const Range &r, const element& e, T &combine)
{
    if (r.till <= r.from) return;
    auto i = --upper_bound(r.till);
    if (i==end())
        operator[](r.from) = e; //if the whole range is before the first element
    else {
        if (i->first != r.till) //i points to a place before r.till
            i = insert(i, typename std::map<double, element>::value_type(r.till, i->second));
        //now i points to the element at r.till
        auto h = --upper_bound(r.from);
        if (h==end())
            h = insert(begin(), typename std::map<double, element>::value_type(r.from, e))++;
        else if (h->first != r.from) //j points to an element before r.from
            h = insert(h, typename std::map<double, element>::value_type(r.from, h->second));
        //now h points to the first element to add e to
        for (; h!=i; h++)
            h->second = combine(h->second, e);
    }
}
template <class element>
void DoubleMap<element>::Prune()
{
    auto i = begin();
    while(i!=end()) {
        auto j = i;
        j++;
        if (j==end()) return;
        if (i->second == j->second)
            erase(j);
        else
            i=j;
    }
}


/** Contains a single, contiguous non-degenerate 2D shape with no holes. Can be clockwise or counterclockwise.
 * @ingroup contour_internal
 *
 * We maintain the following invariants in this class.
 * - Edges are connected - the endpoint of an edge is always *exactly* the same as the start of the next one.
 * - Edges are circular - the last edge connects to the first.
 * - The surface enclosed is never flopped - edges never cross each-other (except joining at vertices)
 *       Edges may touch each other, but not cross.
 * - Never degenerate - The surface enclosed always has actual nonzero surface area,
 * - Always contagious - one single surface
 * - Keep a cache (pre-)calculated bounding box, clockwisedness and surface area.
 *       These are calculated when first needed, and re-used after.
 *
 * The constructors are all protected, since you are not supposed to create
 * an instance of this class. This is used by class Contour to store parts.
 */
class SimpleContour
{
    template <typename Edgeish> friend class EdgeVector;
    template <typename Edgeish> friend class EdgeList;
    friend class Contour;
    friend class HoledSimpleContour;
    friend class ContourList;
    friend class ContoursHelper; //For walk related members
    friend struct node;

public:
    /** A data structure to store additional data with an edge.
     * The additional data is about how this edge connects with the following one.
     * Used during SimpleContour::CreateExpand().
     * When created from points, it sets Edge::mark to true. */
    struct ExpandMetaData : public Edge
    {
        const int           original_edge; ///<The index of the edge in the SimpleContour under expansion this struct corresponds to.
        EExpandType         join_type;     ///<The join type to use between this edge and the next. Normally according to the wishes of the caller to Expand() and ExpandHelper(), but for concave edge joins it is set to BEVEL.
        Edge::EExpandCPType cross_type;    ///<The type of crossing between this edge and the following one (wrapped around)
        XY                  cross_point;   ///<The cp between this edge and the following one (wrapped around)
        XY                  prev_tangent;  ///<A point along the linear continuation of this edge's start. Used only for CP_PARALLEL and CP_EXTENDED
        XY                  next_tangent;  ///<A point along the linear continuation of this edge's end. Used only for CP_PARALLEL and CP_EXTENDED
        double              us_end_pos;    ///<In case of a real crosspoint, the position of it on us.
        double              next_start_pos;///<In case of a real crosspoint, the position of it on the following edge.

        constexpr ExpandMetaData(EExpandType et, const Edge &e, int o=-1, Edge::EExpandCPType a = Edge::CP_TRIVIAL, const XY &c = XY(), double ue=double(), double ns=double()) noexcept :
            Edge(e), original_edge(o), join_type(et), cross_type(a), cross_point(c), us_end_pos(ue), next_start_pos(ns) {} ///<Consturct from an edge.
        constexpr ExpandMetaData(EExpandType et, const XY&s, const XY &d, bool v = true, Edge::EExpandCPType a = Edge::CP_TRIVIAL, const XY &c = XY(), double ue = double(), double ns = double()) noexcept :
            Edge(s, d, v, true), original_edge(-1), join_type(et), cross_type(a), cross_point(c), us_end_pos(ue), next_start_pos(ns) {} ///<Construct from a straight line
        ExpandMetaData(EExpandType et, const XY&s, const XY &d, const XY&c1, const XY &c2, bool v = true, Edge::EExpandCPType a = Edge::CP_TRIVIAL, const XY &c = XY(), double ue = double(), double ns = double()) noexcept :
            Edge(s, d, c1, c2, v, true), original_edge(-1), join_type(et), cross_type(a),cross_point(c), us_end_pos(ue), next_start_pos(ns) {} ///<Construct from a bezier's four points
    };

    /** Holds a lis to metadata, with circular prev() and next() functions. */
    using MetaDataList = EdgeList<ExpandMetaData>;
    static constexpr double EXPAND_TOLERANCE = 1e-3;

    SimpleContour() noexcept : boundingBox(false), area(0) {}  ///<Create an empty shape with no edges.
    SimpleContour(SimpleContour&& p) noexcept = default;  ///<Move content of another shape. The other shape gets undefined.
    SimpleContour(const SimpleContour& p) = default; ///<Create a copy of another shape
    SimpleContour(double sx, double dx, double sy, double dy) {operator = (Block(sx,dx,sy,dy));} ///<Create a rectangle
    SimpleContour(const Block &b) {operator =(b);} ///<Create a rectangle
    SimpleContour(XY a, XY b, XY c); ///<Create an (eventually clockwise) triangle. Empty if degenerate.
    SimpleContour(double ax, double ay, double bx, double by, double cx, double cy) : SimpleContour(XY(ax,ay), XY(bx,by), XY(cx,cy)) {} ///<Create an (eventually clockwise) triangle. Empty if degenerate.
    SimpleContour(const XY &c, double radius_x, double radius_y=0, double tilt_deg=0, double s_deg=0, double d_deg=360);
    SimpleContour &operator =(const Block &b);
    SimpleContour& operator =(SimpleContour&& p) noexcept = default;
    SimpleContour& operator =(const SimpleContour& p) = default;
    std::strong_ordering operator <=>(const SimpleContour &b) const noexcept;
    bool operator ==(const SimpleContour& b) const noexcept { return operator<=>(b)==0; }
    const Edge &operator[](size_t edge) const noexcept { return edges[edge]; } ///<Returns const reference to edge at index `i`.
    const Edge *operator[](const XY &p) const noexcept {size_t edge; const EPointRelationType r = IsWithin(p, &edge); return r==WI_ON_EDGE||r==WI_ON_VERTEX ? &edges[edge] : nullptr;} ///<Returns const pointer to edge `p` is at, nullptr if `p` is not on the contour.
    size_t      next(size_t vertex) const noexcept { return edges.next(vertex); }         ///<Returns index of subsequent edge, warps around to 0 at last one.
    size_t      prev(size_t vertex) const noexcept { return edges.prev(vertex); }  ///<Returns index of preceeding edge, warps around at 0 to last edge.
    const Edge &at(size_t i) const noexcept { return edges[i]; }      ///<Returns reference to edge at index `i`.
    const Edge &at_next(size_t i) const noexcept { return edges.at_next(i); } ///<Returns reference to edge after index `i`. Wraps around at last edge.
    const Edge &at_prev(size_t i) const noexcept { return edges.at_prev(i); } ///<Returns reference to edge before index `i`. Wraps around at first edge.

    void swap(SimpleContour &b) noexcept;
    void clear() noexcept { boundingBox.MakeInvalid(); area = 0.; edges.clear();  } ///<Empty the shape by deleting all edges.
    operator Path() const { return Path(edges); } ///<Convert our content to a Path

    bool AddAnEdge(const Edge &edge);
    SimpleContour &Invert() noexcept;
    SimpleContour CreateInvert() const { return SimpleContour(*this).Invert(); } ///<Creates an inverted version of us
    /** Remove edges shorter than 'tolerance'. We also combine subsequent edges
     * that can be expressed as a single edge.
     * @returns true if a change was made.*/
    bool Simplify(double tolerance = SMALL_NUM) { if (!edges.Simplify(tolerance)) return false; /*assume bb and area remained the same*/ return true; }

    void Shift(const XY &xy) noexcept { if (!is_bb_dirty()) boundingBox.Shift(xy); edges.Shift(xy); } ///<Translates the shape.
    void Scale(double sc) noexcept { if (!is_bb_dirty()) boundingBox.Scale(sc); edges.Scale(sc); area *= sc*sc; } ///<Scale the shape.
    void Scale(const XY &sc) noexcept { if (!is_bb_dirty()) boundingBox.Scale(sc); edges.Scale(sc); } ///<Scale the shape.
    void SwapXY() noexcept { _ASSERT(IsSane()); if (!is_bb_dirty()) boundingBox.SwapXY(); edges.SwapXY(); Invert(); area *= -1; } ///<Transpose the shape: swap XY coordinates (but keep clockwisedness).
    void Rotate(double cos, double sin) noexcept { edges.Rotate(cos, sin); dirty_bb(); } ///<Rotate the shape by `radian`. `sin` and `cos` are pre-computed values.
    void RotateAround(const XY&c, double cos, double sin) noexcept { edges.RotateAround(c, cos, sin); dirty_bb(); } ///<Rotate the shape by `radian` around `c`. `sin` and `cos` are pre-computed values.
    SimpleContour& NonInvertingTransform(const TRMatrix& M) noexcept { _ASSERT(!M.IsFlipping()); edges.Transform(M); dirty_bb(); dirty_area(); return *this; } ///<Apply a non-inverting transformation. We retain our clockwisedness status
    SimpleContour CreateNonInvertingTransformed(const TRMatrix &M) const { SimpleContour ret(*this); return ret.NonInvertingTransform(M); } ///<Apply a non-inverting a transformation. We retain our clockwisedness status
    SimpleContour &InvertingTransform(const TRMatrix &M) noexcept { _ASSERT(M.IsFlipping()); edges.Transform(M); Invert(); dirty_bb(); if (area != infa && area != 0) area = area < 0 ? maxa : lowa; return *this; } ///<Apply an inverting transformation. We retain our clockwisedness status
    SimpleContour CreateInvertingTransformed(const TRMatrix &M) const { SimpleContour ret(*this); return ret.InvertingTransform(M); }  ///<Apply an inverting a transformation. We retain our clockwisedness status

    EContourRelationType RelationTo(const SimpleContour& c) const;
    MetaDataList ExpandHelper(EExpandType type, double gap, double miter_limit,
                              double tolerance = SMALL_NUM, bool always_simplify_result = true) const;
    bool IsConcaveJoin(size_t u) const;

    EContourRelationType CheckContainment(const SimpleContour &b) const;
    EPointRelationType IsWithin(XY p, size_t *edge = nullptr, double *pos = nullptr, bool strict = true) const noexcept;
    /** Take tangent of `edge` at `pos` and return a point on the line of a tangent towards the start of curve/edge. */
    XY PrevTangentPoint(PathPos pos) const noexcept { return edges.PrevTangentPoint(pos, true); }
    /** Take tangent of `edge` at `pos` and return a point on the line of a tangent towards the end of curve/edge. */
    XY NextTangentPoint(PathPos pos) const noexcept { return edges.NextTangentPoint(pos, true); }

    void AppendToPath(Path &p) const { for (auto &e : edges) p.push_back(e); } ///<Append our edges to a path. We do not connect to what is there already, if our start differs from the last end.
    void MoveToPath(Path &p) { p = std::move(edges); } ///<Move our content to a Path (overwriting it, destroying us)
    Path CreatePathFromSelected(bool keep(const Edge&)) const; ///<Copy the edges matching a criteria to a path
    Path CreatePathFromVisible() const { return CreatePathFromSelected([](const Edge&e) {return e.IsVisible(); }); }
    void SetVisible(bool visible) const noexcept { for (auto &e: edges) e.SetVisible(visible); }
    void SetInternalMark(bool imark) const noexcept { for (auto& e: edges) e.SetInternalMark(imark); }
    void SetMark(unsigned mark) const noexcept { for (auto &e: edges) e.SetInternalMark(mark); }
    void Apply(Edge::Update u) const noexcept { for (auto &e: edges) e.Apply(u); }

    size_t size() const noexcept {return edges.size();}          ///<Returns the number of edges.
    const Edge& front() const noexcept { return edges.front(); } ///<Returns const reference to the first edge
    const Edge& back() const noexcept { return edges.back(); }   ///<Returns const reference to the last edge
    const Path &GetEdges() const & noexcept { return edges; }    ///<Returns const reference to the edges
    Path && GetEdges() && noexcept { return std::move(edges); }  ///<Moves the edges out
    const Block &GetBoundingBox() const noexcept { if (is_bb_dirty()) CalculateBoundingBox(); return boundingBox; } ///<Returns the bounding box.
    bool GetClockWise() const noexcept { if (area==infa) CalculateClockwise(); return 0<=area; }              ///<Returns if the shape is clockwise.
    bool IsEmpty() const noexcept {return edges.size()==0;}            ///<Returns if the shape is empty (no edges).
    bool IsRectangle() const noexcept; ///<Returns true if we are equivalent to our bounding box. False for empty.
    bool IsSane() const;
    double GetArea() const noexcept {return area==infa || area==lowa || area==maxa ? CalculateArea() : area;}  ///<Return surface area of shape.
    double GetCircumference(bool include_hidden=false) const noexcept;
    XY CentroidUpscaled() const noexcept;

    void VerticalCrossSection(double x, DoubleMap<bool> &section) const;
    double OffsetBelow(const SimpleContour &below, double &touchpoint, double offset=CONTOUR_INFINITY) const;
    Contour CreateExpand(EExpandType type, double gap, double miter_limit/*=MaxVal(miter_limit)*/) const;
    Contour CreateExpand2D(const XY &gap) const;

    void CairoPath(cairo_t *cr, bool show_hidden) const { edges.CairoPath(cr, show_hidden, true); }
    void CairoPath(cairo_t *cr, bool show_hidden, bool clockwiseonly) const {if (GetClockWise()==clockwiseonly) CairoPath(cr, show_hidden);} ///<Draw the shape to the path of a cairo context, but only if `clockwiseonly` equals to the clockwisedness of this shape
    void CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden) const
        { edges.CairoPathDashed(cr, pattern, show_hidden, true); }
    void CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden, bool clockwiseonly) const {if (GetClockWise()==clockwiseonly) CairoPathDashed(cr, pattern, show_hidden);} ///<Draw the shape in dashed lines to the path of a cairo context, but only if `clockwiseonly` equals to the clockwisedness of this shape.
    std::pair<cairo_pattern_t *, Contour>
        CairoMeshGradient(double gap, double r1, double g1, double b1, double a1, double r2, double g2, double b2, double a2,
                          EExpandType type = EXPAND_ROUND, double miter_limit = CONTOUR_INFINITY) const;
    void Distance(const SimpleContour &o, DistanceType &ret) const noexcept;
    double Distance(const XY &o, XY &ret) const noexcept;
    double DistanceWithTangents(const XY &o, XY &ret, XY &t1, XY &t2) const;
    Range Cut(const Edge &e) const;
    std::array<CPData, 2> CutEx(const Edge &e) const;
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    bool CrossPoints(std::vector<CPData> &ret, const Edge &e, CPTasks to_do, bool swap = false) const;
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    bool CrossPoints(std::vector<CPData> &ret, const Path &p, bool p_closed = true, const Block *bb_o = nullptr, CPTasks to_do = {}, bool swap = false) const
    {
        const size_t orig_ret_size = ret.size();
        const bool r = edges.CrossPoints(ret, true, bb_o ? &GetBoundingBox() : nullptr, p, p_closed, bb_o, to_do, swap);
        std::for_each(ret.begin()+orig_ret_size, ret.end(), [swap, this](CPData &d) {(swap ? d.contour_other : d.contour_me) = this; });
            return r;
    }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    template <typename iter>
    bool CrossPoints(std::vector<CPData> &ret, iter from, iter last, bool closed = true, const Block *bb_o = nullptr, CPTasks to_do = {}, bool swap = false) const
    {
        const size_t orig_ret_size = ret.size();
        const bool r = edges.CrossPoints(ret, true, bb_o ? &GetBoundingBox() : nullptr, from, last, closed, bb_o, to_do, swap);
        std::for_each(ret.begin()+orig_ret_size, ret.end(), [swap, this](CPData &d) {(swap ? d.contour_other : d.contour_me) = this; });
        return r;
    }
    std::vector<Path> SplitAt(std::vector<PathPos>, bool remove_empty = false) const;
    Range CutWithTangent(const Edge &e, std::pair<XY, XY> &from, std::pair<XY, XY> &till) const;
    void Cut(const XY &A, const XY &B, DoubleMap<bool> &map) const;
    bool TangentFrom(const XY &from, XY &clockwise, XY &cclockwise) const;
    bool TangentFrom(const SimpleContour &from, XY clockwise[2], XY cclockwise[2]) const;

    std::string Dump(bool precise) const;
    static SimpleContour UnsafeMake(Path&& v);

protected:
    Edge &operator[](size_t edge) { return at(edge); }  ///<Returns reference to edge at index `i`.
    Edge *operator[](const XY &p) { size_t edge; const EPointRelationType r = IsWithin(p, &edge); return r==WI_ON_EDGE||r==WI_ON_VERTEX ? &at(edge) : nullptr; }  ///<Returns pointer to edge `p` is at, nullptr if `p` is not on the contour.
    Edge       &at(size_t i) { return edges.at(i); }      ///<Returns reference to edge at index `i`.
    Edge       &at_next(size_t i) { return edges.at_next(i); } ///<Returns reference to edge after index `i`. Wraps around at last edge.
    Edge       &at_prev(size_t i) { return edges.at_prev(i); } ///<Returns reference to edge before index `i`. Wraps around at first edge.

    void dirty_area() noexcept { if (area != infa && area != 0) area = area < 0 ? lowa : maxa; } //we invalidate the area, but keep clockwisedness info
    void dirty_bb() noexcept { boundingBox.x.from = infa; }
    bool is_bb_dirty() const noexcept { return boundingBox.x.from == infa; }

    const Block &CalculateBoundingBox() const noexcept;
    double CalculateArea() const noexcept;
    void CalculateClockwise() const noexcept { if (area==infa) CalculateArea(); } ///<Calcualtes whether we are clockwise or not.
    bool Sanitize(double tolerance = SMALL_NUM);

    void assign_dont_check(std::span<const XY> v);       ///<Set shape content to `v`. Assume edges in `v` connect and do not cross.
    void assign_dont_check(std::initializer_list<XY> v); ///<Set shape content to `v`. Assume edges in `v` connect and do not cross.
    template <typename Edgeish>
    void assign_dont_check(std::span<const Edgeish> v) { area = infa; dirty_bb(); edges.assign(v.begin(), v.end()); Sanitize(); } ///<Set shape content to `v`. Assume edges in `v` connect and do not cross.
    void assign_dont_check(Path&& v) { area = infa; dirty_bb(); edges.swap(v); Sanitize(); }                                      ///<Set shape content to `v`. Assume edges in `v` connect and do not cross.

    EContourRelationType CheckContainmentHelper(const SimpleContour& b, size_t threshold=1, size_t allow=0) const;
    EContourRelationType CheckContainmentForOperation(const SimpleContour& b) const;
    double do_offsetbelow(const SimpleContour &below, double &touchpoint, double offset = CONTOUR_INFINITY) const;

    static void CreateRoundForExpand(XY center, const XY &start, const XY &end, bool clockwise,
                                     std::list<ExpandMetaData> &insert_to, std::list<ExpandMetaData>::iterator here,
                                     bool visible);
    void Expand2DHelper(const XY &gap, Path &a,
                        size_t original_last, size_t next, size_t my_index,
                        int last_type, int stype) const;

private:
    /** Instead of extra bits, we use the value of the 'area' to determine clockwiseness. 
     * 0: The contour has no area. We treat these as clockwise by definition.
     * 0<: The contour has positive area, clockwise
     * <0: The contour has negative area, counterclockwise
     * max(): We do not know the area, but the contour is clockwise
     * lowest(): We do not know the area, but the contour is counterclockwise
     * infinity: We do not know the area nor the clockwiseness. */
    static constexpr double infa = std::numeric_limits<double>::infinity();
    static constexpr double lowa = std::numeric_limits<double>::lowest();
    static constexpr double maxa = std::numeric_limits<double>::max();
    static_assert(std::numeric_limits<double>::has_infinity, "We need infinity");

    mutable Block boundingBox;   ///<The bounding box for the shape. If 'dirty' (from.x==infa), we have not yet calculated it. If 'invalid' (either for x or y from=maxa&till=lowa), we have no edges.
    mutable double area = infa;  ///<The surface area of the shape. Negative for counterclockwise shapes. See the special values above.
    Path edges;                  ///<An ordered list of (directed, connected) edges.
};

template <typename Edgeish>
bool EdgeVector<Edgeish>::CrossPoints(std::vector<CPData> &ret, bool me_closed, const SimpleContour &o,
                                      CPTasks to_do, bool swap) const
{
    return CrossPoints(ret, me_closed, o.GetEdges(), true, to_do, swap);
}


/** Simple comparison operator for container ordering */
inline std::strong_ordering SimpleContour::operator <=>(const SimpleContour &b) const noexcept
{
    //Do not use bounding box, clockwiseness or area, which may possibly not
    //be computed, as we want this ordering to remain unchanged even if we compute a
    //formerly not computed value.
    if (size() != b.size()) return size() <=> b.size();
    for (size_t i=0; i<size(); i++)
        if (at(i) != b[i]) return at(i)<b[i] ? std::strong_ordering::less : std::strong_ordering::greater;
    return std::strong_ordering::equal;
}

inline Path SimpleContour::CreatePathFromSelected(bool keep(const Edge&)) const
{
    Path ret;
    if (IsEmpty()) return ret;
    //we may have the case where we have edges not to keep, but
    //the last and first are both to keep. We need to ensure
    //they are added to the path as continuations of one another.
    //First test if we have an edge not to keep - then start just after.
    size_t i;
    for (i = size(); i>0; i--)
        if (!keep(edges[i-1]))
            break;
    if (i==0)  //all edges visible
        ret.push_back(edges[0]);
    else
        i--; //to actually have the edge not visible
    for (size_t u = next(i); u!=i; u = next(u)) //we will skip 'i', but that is OK
        if (keep(edges[u]))
            ret.push_back(edges[u]);
    return ret;
}


/** Determine the relative vertical distance between two shapes.
 *
 * For detailed explanation and parameters, see
 * Contour::OffsetBelow(const Contour &below, double &touchpoint, double offset).
 */
inline double SimpleContour::OffsetBelow(const SimpleContour &below, double &touchpoint, double offset) const
{
    if (offset < below.GetBoundingBox().y.from - GetBoundingBox().y.till) return offset;
    if (!GetBoundingBox().x.Overlaps(below.GetBoundingBox().x)) return offset;
    return do_offsetbelow(below, touchpoint, offset);
}

/** Calculate & cache the surface area of the shape.
 *
 * This is based on <http://www.mathopenref.com/coordpolygonarea.html>
 * For each edge, we calculate the area above it (the area between the edge and Y axis,
 * but since y grows downward, the area is "above" the edge).
 * The value is signed. If the edge is from right to left it is positive, else negative.
 * Since (for straight edges) the area would contain "start.x*start.y-end.x*end.y",
 * which will cancel out. Plus it is easier to return twice the area.
 * Thus we request edge to return
 * "2*(area_above_edge - start.x*start.y + end.x*end.y)", this is what
 * Edge::GetAreaAboveAdjusted() returns.
 *
 * After this function the area cache will hold this value for instant access.
 *
 * @returns the calculated area. Negative if shape is counterclockwise.
 */
inline double SimpleContour::CalculateArea() const noexcept
{
    double ret = 0;
    for (const auto &e: edges)
        ret += e.GetAreaAboveAdjusted();    
    return area = ret/2;
}



} //namespace contour



#endif //CONTOUR_SIMPLE_H
