/*
    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 area.h The declaration of Area.
 * @ingroup libcgencommon_files */

#if !defined(CONTOUR_XAREA_H)
#define CONTOUR_XAREA_H

#include <list>
#include "contour.h"

class Element;

/** Defines coverage and mainline for chart elements.
 * Basically a Contour that includes a backpointer to an arc,
 * plus another mainline contour.
 * Basically redefines all Contour operations for the above content.*/
class Area : public contour::Contour
{
public:
    mutable Element *arc;        ///<The element we are the area for.
    contour::Contour mainline;   ///<A horizontal block spanning the entire width of the chart, not allowing any other element completely besides us to shift above us. For slanted arrows, this is of a more complicated shape than a horizontal block.

    /** Construct an empty area */
    explicit Area(Element *a=nullptr) : arc(a) {}
    /** Construct an area with a given shape and element.*/
    explicit Area(const Contour &cl, Element *a=nullptr) : Contour(cl), arc(a) {}
    /** Construct an area with a given shape and element.*/
    explicit Area(Contour &&cl, Element *a = nullptr) : Contour(std::move(cl)), arc(a) {}
    /** Construct an area with a given rectangular shape and element.*/
    explicit Area(const contour::Block &b, Element *a = nullptr) : Contour(b), arc(a) {}
    /** Copy constructor. */
    Area(const Area &a) = default;
    /** Move constructor. */
    Area(Area &&a) noexcept : Contour(std::move(static_cast<Contour&>(a))), arc(a.arc), mainline(std::move(a.mainline)) {}

    void clear() {Contour::clear(); mainline.clear();}
    void swap(Area &a);
    void assign(std::span<const contour::XY> v, contour::EForceClockwise force_clockwise = contour::EForceClockwise::DONT, bool winding=true)  {Contour::assign(v, force_clockwise, winding); mainline.clear();}
    template <typename Edgeish>
    void assign(std::span<const Edgeish> v, contour::EForceClockwise force_clockwise = contour::EForceClockwise::DONT, bool winding=true)  {Contour::assign(v, force_clockwise, winding); mainline.clear();}

    bool operator <(const Area &b) const;
    bool operator ==(const Area &b) const;
    Area &operator =(const contour::Block &a) {Contour::operator=(a); mainline.clear(); return *this;}
    Area &operator =(const Contour &a) {Contour::operator=(a); mainline.clear(); return *this;}
    Area &operator =(Contour &&a) {Contour::operator=(std::move(a)); mainline.clear(); return *this;}
    Area &operator =(const Area &a) = default;
    Area &operator =(Area &&a) noexcept { swap(a); return *this; };

    Area &operator += (const Area &b) {Contour::operator+=(b); mainline+=b.mainline; if (arc==nullptr) arc = b.arc; return *this;}
    Area &operator *= (const Area &b) {Contour::operator*=(b); mainline*=b.mainline; if (arc==nullptr) arc = b.arc; return *this;}
    Area &operator -= (const Area &b) {Contour::operator-=(b); mainline-=b.mainline; if (arc==nullptr) arc = b.arc; return *this;}
    Area &operator ^= (const Area &b) {Contour::operator^=(b);                       if (arc==nullptr) arc = b.arc; return *this;}
    Area &operator += (Area &&b) { mainline += std::move(b.mainline); Contour::operator+=(static_cast<Contour&&>(b)); if (arc==nullptr) arc = b.arc; return *this;}
    Area &operator *= (Area &&b) { mainline *= std::move(b.mainline); Contour::operator*=(static_cast<Contour&&>(b)); if (arc==nullptr) arc = b.arc; return *this;}
    Area &operator -= (Area &&b) { mainline -= std::move(b.mainline); Contour::operator-=(static_cast<Contour&&>(b)); if (arc==nullptr) arc = b.arc; return *this;}
    Area &operator ^= (Area &&b) {                                    Contour::operator^=(static_cast<Contour&&>(b)); if (arc==nullptr) arc = b.arc; return *this;}

    Area &operator += (const Contour &b) { Contour::operator+=(b); return *this; }
    Area &operator *= (const Contour &b) { Contour::operator*=(b); return *this; }
    Area &operator -= (const Contour &b) { Contour::operator-=(b); return *this; }
    Area &operator ^= (const Contour &b) { Contour::operator^=(b); return *this; }
    Area &operator += (Contour &&b) { Contour::operator+=(std::move(b)); return *this; }
    Area &operator *= (Contour &&b) { Contour::operator*=(std::move(b)); return *this; }
    Area &operator -= (Contour &&b) { Contour::operator-=(std::move(b)); return *this; }
    Area &operator ^= (Contour &&b) { Contour::operator^=(std::move(b)); return *this; }

    Area operator + (const Area &p) const {return Area(*this)+=p;}
    Area operator * (const Area &p) const {return Area(*this)*=p;}
    Area operator - (const Area &p) const {return Area(*this)-=p;}
    Area operator ^ (const Area &p) const {return Area(*this)^=p;}
    Area operator + (Area &&p) const {return Area(*this)+=std::move(p);}
    Area operator * (Area &&p) const {return Area(*this)*=std::move(p);}
    Area operator - (Area &&p) const {return Area(*this)-=std::move(p);}
    Area operator ^ (Area &&p) const {return Area(*this)^=std::move(p);}

    Area operator + (const Contour &p) const { return Area(*this) += p; }
    Area operator * (const Contour &p) const { return Area(*this) *= p; }
    Area operator - (const Contour &p) const { return Area(*this) -= p; }
    Area operator ^ (const Contour &p) const { return Area(*this) ^= p; }
    Area operator + (Contour &&p) const { return Area(*this) += std::move(p); }
    Area operator * (Contour &&p) const { return Area(*this) *= std::move(p); }
    Area operator - (Contour &&p) const { return Area(*this) -= std::move(p); }
    Area operator ^ (Contour &&p) const { return Area(*this) ^= std::move(p); }

    /** Add 'xy' to all points of the area, effectively moving it.*/
    Area& Shift(contour::XY xy) { Contour::Shift(xy); mainline.Shift(xy); return *this; }
    /** Create a shifted version of the area by adding 'xy' to all points of it.*/
    Area CreateShifted(const contour::XY & xy) const {Area a(*this); a.Shift(xy); return a;}
    /** Rotate 'degrees' degree around the origin. Leaves 'mainline' unchanged.*/
    Area& Rotate(double degrees) {Contour::Rotate(degrees); return *this;}
    /** Rotate 'degrees' degree around 'c'.  Leaves 'mainline' unchanged.*/
    Area& RotateAround(const contour::XY&c, double degrees) {Contour::RotateAround(c, degrees); return *this;}
    /** Swap x and y coordinates. Clears 'mainline'.*/
    Area& SwapXY() {Contour::SwapXY(); mainline.clear(); return *this;}

    /** Create an expanded version of the area. Both the area, and mainline.*/
    Area CreateExpand(double gap, contour::EExpandType et4pos=contour::EXPAND_MITER_ROUND, contour::EExpandType et4neg=contour::EXPAND_MITER_ROUND,
                      double miter_limit_positive=CONTOUR_INFINITY, double miter_limit_negative=CONTOUR_INFINITY) const;
    /** Expand both the area and the mainline.*/
    Area &Expand(double gap, contour::EExpandType et4pos=contour::EXPAND_MITER_ROUND, contour::EExpandType et4neg=contour::EXPAND_MITER_ROUND,
                 double miter_limit_positive=CONTOUR_INFINITY, double miter_limit_negative=CONTOUR_INFINITY)
         {Contour::Expand(gap, et4pos, et4neg, miter_limit_positive, miter_limit_negative); mainline.Expand(gap); return *this;}
    /** Remove holes from the area. Leaves 'mainline' unchanged. */
	void ClearHoles() {Contour::ClearHoles();}

    /** Determine the relative vertical distance between two shapes. See Contour::OffsetBelow for more. Ignores 'mainline'.*/
    double OffsetBelow(const contour::Contour &below, double &touchpoint, double offset = CONTOUR_INFINITY) const;
    /** Determine the relative vertical distance between two shapes. See Contour::OffsetBelow for more.
     * If bMainline is true, we also take mainlines into account not allowing them to overlap either.*/
    double OffsetBelow(const Area &below, double &touchpoint, double offset = CONTOUR_INFINITY, bool bMainline = true) const;
};

/** A list of Area objects.
 * No effort is made to avoid overlap, thus addition
 * operations are indeed just appends to the list.
 * We maintain a total boundingBox and mainline and
 * these are actually combined to a single block and
 * contour, respectively.
 * Ordering is important in the list for InWhich*
 * functions.*/
class AreaList
{
    std::list<Area>  cover;       ///<The list of Areas.
    contour::Block   boundingBox; ///<The combined bounding box.
public:
    contour::Contour mainline;    ///<A combined total mainline.
    /** Creates an empty area list. */
    AreaList() {boundingBox.MakeInvalid();}
    /** Creates an area list containing a single area. */
    AreaList(const Area &area) {boundingBox.MakeInvalid(); operator+=(area);}
    /** Creates an area list containing a single area. */
    AreaList(Area &&area) {boundingBox.MakeInvalid(); operator+=(std::move(area));}
    /** Copy constructor. */
    AreaList(const AreaList &al) : cover(al.cover), boundingBox(al.boundingBox), mainline(al.mainline) {}
    /** Move constructor. */
    AreaList(AreaList &&al) noexcept : cover(std::move(al.cover)), boundingBox(al.boundingBox), mainline(std::move(al.mainline)) {}
    AreaList& operator =(const AreaList& al) { if (&al!=this) { cover = al.cover; mainline = al.mainline; boundingBox = al.boundingBox; } return *this; }
    /** Return the actual areas contained in the list.*/
    const std::list<Area> &GetCover() const {return cover;}
    /** Empty the list.*/
    void clear() {boundingBox.MakeInvalid(); mainline.clear(); cover.clear();}
    /** Number of areas in the list.*/
    size_t size() const {return cover.size();}
    std::list<Area>::const_iterator begin() const {return cover.begin();}
    std::list<Area>::const_iterator end() const {return cover.end();}
    void swap(AreaList &o) {cover.swap(o.cover); std::swap(boundingBox, o.boundingBox); std::swap(mainline, o.mainline);}
    /** Set the 'arc' member of all areas to the same value.*/
    void SetArc(Element *a) const {for(auto i=cover.begin(); i!=cover.end(); i++) i->arc = a;}
    AreaList &operator+=(const Area &b) {cover.push_back(b); boundingBox+=b.GetBoundingBox(); mainline+=b.mainline; return *this;}
    AreaList &operator+=(Area &&b) {boundingBox+=b.GetBoundingBox(); mainline+=b.mainline; cover.push_back(std::move(b)); return *this;}
    AreaList &operator+=(const AreaList &g) {cover.insert(cover.end(), g.cover.begin(), g.cover.end()); boundingBox+=g.boundingBox; mainline+=g.mainline; return *this;}
    AreaList &operator+=(AreaList &&g) {cover.splice(cover.end(), g.cover); boundingBox+=g.boundingBox; mainline+=std::move(g.mainline); return *this;}
    /** True if no areas contained in the list.*/
    bool IsEmpty() const {return cover.size()==0;}
    /** Shift all areas. */
    AreaList &Shift(contour::XY xy) {for (auto &a : cover) a.Shift(xy); mainline.Shift(xy); boundingBox.Shift(xy); return *this;}
    /** Scale all areas. */
    AreaList &Scale(double sc) {for (auto &a : cover) a.Scale(sc); mainline.Scale(sc); boundingBox.Scale(sc); return *this;}
    /** Returns the bounding box containing all areas in the list.*/
    const contour::Block& GetBoundingBox() const {return boundingBox;}

    /** Create a copy of the list with all areas expanded. */
    AreaList CreateExpand(double gap, contour::EExpandType et4pos=contour::EXPAND_MITER_ROUND, contour::EExpandType et4neg=contour::EXPAND_MITER_ROUND,
                          double miter_limit_positive=CONTOUR_INFINITY, double miter_limit_negative=CONTOUR_INFINITY) const;

    /** Determine the relative vertical distance between two shapes. See Contour::OffsetBelow for more. 'mainline' is ignored.*/
    double OffsetBelow(const contour::Contour &below, double &touchpoint, double offset = CONTOUR_INFINITY) const;
    /** Determine the relative vertical distance between two shapes. See Contour::OffsetBelow for more.
    * If bMainline is true, we also take mainlines into account not allowing them to overlap either.*/
    double OffsetBelow(const Area &below, double &touchpoint, double offset = CONTOUR_INFINITY, bool bMainline = true) const;
    /** Determine the relative vertical distance between two shapes. See Contour::OffsetBelow for more.
    * If bMainline is true, we also take mainlines into account not allowing them to overlap either.*/
    double OffsetBelow(const AreaList &below, double &touchpoint, double offset=CONTOUR_INFINITY, bool bMainline=true) const;

    /** Sort the content of the list using a comparator functor. */
    template <typename T> void sort(T t) {cover.sort(t);}

    /** Determines which area `p` falls in. Searches from the beginning of the list and returns the first hit.*/
    const Area *InWhich(const contour::XY &p) const {for (auto &a : cover) if (inside(a.IsWithin(p))) return &a; return nullptr;}
    /** Determines which area `p` falls in. Searches from the end of the list and returns the first hit.*/
    const Area *InWhichFromBack(const contour::XY &p) const {for (auto i=cover.rbegin(); !(i==cover.rend()); i++) if (inside(i->IsWithin(p))) return &*i; return nullptr;}
    /** Clear all mainline members. */
    void InvalidateMainLine() {mainline.clear(); for (auto &a : cover) a.mainline.clear();}
    std::list<Area> EmptyToList() { std::list<Area> c; c.swap(cover); boundingBox.MakeInvalid(); return c; } ///<Empties the list and moves its content to the return value
};

inline bool Area::operator <(const Area &b) const
{
    if (arc!=b.arc) return arc<b.arc;
    if (mainline!=b.mainline) return mainline<b.mainline;
    return Contour::operator<(b);
}

inline bool Area::operator ==(const Area &b) const
{
    if (arc!=b.arc) return false;
    if (mainline!=b.mainline) return false;
    return Contour::operator==(b);
}


#endif //CONTOUR_XAREA_H
