/*
    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 cgen_shapes.h The declaration of classes for shapes.
 * @ingroup libcgencommon_files */


#ifndef SHAPES_H
#define SHAPES_H

#include "contour.h"
#include "canvas.h"
#include "optional"


/** Describes a Shape component.
 * This is either one vertex of the shape, or a section delimiter, or a
 * hint or text box. */
struct ShapeElement
{
    /** Enumerates shape commands.*/
    enum Type
    {
        LINE_TO = 0,
        MOVE_TO = 1,
        CURVE_TO = 2,
        CLOSE_PATH = 3,
        HINT_AREA = 4,
        TEXT_AREA = 5,
        SECTION_BG = 6,
        SECTION_FG_FILL = 7,  //SECTION_BG + 1
        SECTION_FG_LINE = 8,  //SECTION_BG + 2 
        SECTION_FG_BG = 9,    //SECTION_BG + 3  
        PORT = 10,
        _MAX = 10
    };

    Type action;
    static constexpr const char act_code[] = "LMCEHTSSSSP";
    static_assert(size_t(_MAX)+2 == sizeof(act_code));
    double x, y;
    double x1, y1; //x1 is also used for compass points 
    double x2, y2;
    string str;
    ShapeElement(Type aa, double a = 0, double b = 0, double c = 0, double d = 0, double e = 0, double f = 0)
        : action(aa), x(a), y(b), x1(c), y1(d), x2(e), y2(f)
    { if (aa>=SECTION_BG && a==int(a) && a>=0 && a<=2) aa = Type(SECTION_BG + unsigned(a)); }
    ShapeElement(Type aa, std::string_view s)
        : action(aa), str(s) {}
    ShapeElement(double a, double b, std::string_view port, double c=-1)
        :action(PORT), x(a), y(b), x1(c), str(port) {}
    static constexpr int GetNumArgs(Type t) { return t==LINE_TO||t==MOVE_TO ? 2 : t==CURVE_TO ? 6 : t==HINT_AREA || t==TEXT_AREA ? 4 : t==CLOSE_PATH ? 0 : t==PORT ? 3 : 1; }
    static string ErrorMsg(Type t, int numargs);
    string Write() const;
};

/**Describes a Shape*/
class Shape
{
public:
    struct Port
    {
        XY     xy;
        double dir; ///<Compass point: degree from noon clockwise. -1: no dir, -2: perpendicular to closest point on contour
    };
    using Ports = std::map<std::string, Port>;
private:
    Block max;       ///<Bounding box of the shape in its own coordinate system.
    Block label_pos; ///<Bounding box of where the label shall be placed inside the shape. Can be invalid if the label shall be placed outside.
    Block hint_pos;  ///<The bounding box of a part of the shape in its own coordinates - this part shall be drawn in small in hint boxes besides the name of the shape. If invalid all of it will be drawn.
    Ports ports;
    Path bg;         ///<The background path (section 0) (will be filled with fill.color)
    Path fg_fill;    ///<The foreground path (section 1) (will be filled with line.color)
    Path fg_line;    ///<The foreground path (section 2) (will be stroked with line.color and line.type)
    mutable bool cover_fresh; ///<Is the calculated 'cover' member up-to-date?
    mutable Contour cover;    ///<The area covered by all sections combined in the coordinate space of the shape.
    Path  &GetSection(unsigned section) { return section==0 ? bg : section==1 ? fg_fill : fg_line; } ///<fg_line is also returned for section == 3
    unsigned current_section=0;///<During parsing, it stores which section are we actually gathering.
    Path current_section_data; ///<During parsing, it stores the current section so far.
    XY current_section_point;  ///<During parsing, it stores the cursor.
    void ProcessSubPath(bool close);
public:
    const Path &GetSection(unsigned section) const { return section==0 ? bg : section==1 ? fg_fill : fg_line; }
    Path GetSection(unsigned section, const Block &o) const;
    const std::string name;           ///<Name of the shape
    const FileLineCol definition_pos; ///<Where was this shape defined?
    const std::string url;            ///<URL of the shape or shape file (given at definition)
    const std::string info;           ///<Some human readable info of the shape or shape file (given at definition)
    Shape() noexcept : cover_fresh(true), current_section(1), name(), definition_pos(), url(), info() { max.MakeInvalid(); label_pos.MakeInvalid(); hint_pos.MakeInvalid(); }
    Shape(std::string_view n, const FileLineCol &l, std::string_view u, std::string_view i);
    Shape(std::string_view n, const FileLineCol &l, std::string_view u, std::string_view i, Shape &&s);
    bool IsEmpty() const noexcept { return bg.IsEmpty() && fg_fill.IsEmpty() && fg_line.IsEmpty(); }
    const Contour &GetCover() const;
    void Add(ShapeElement &&e);
    const Block &GetLabelPos() const noexcept { return label_pos; }
    Block GetLabelPos(const Block &o) const noexcept;
    Block GetHintPos() const noexcept { return hint_pos; }
    Block GetMax() const noexcept { return max; }
    const Ports GetPorts() const noexcept { return ports; }
    std::optional<Port> GetPort(const std::string &port, const Block &o) const;
    std::optional<Port> GetPort(size_t port, const Block &o) const;
    const string &GetURL() const noexcept { return url; }
    const string &GetInfo() const noexcept { return info; }
    void CairoPath(cairo_t *cr, unsigned section, const Block &o) const;
    void Draw(Canvas &canvas, const Block &o, const LineAttr &line, 
              const FillAttr &fill, const ShadowAttr &shadow=ShadowAttr()) const;
};

/** Describes a collection of shapes */
class ShapeCollection
{
    std::vector<Shape> shapes; ///<The list of shapes in the collection
public:
    operator bool() const noexcept { return shapes.size()>0; }
    const Shape &operator [](size_t sh) const { return shapes[sh]; }
    ShapeCollection& operator = (const ShapeCollection&); //default is not OK, as Shape is not copy assignable
    ShapeCollection& operator = (ShapeCollection&& o) noexcept = default;
    void swap(ShapeCollection &s) noexcept { shapes.swap(s.shapes); }
    void clear() { shapes.clear(); }
    bool Add(std::string_view shape_text, MscError &error);
    bool Add(std::string_view name, const FileLineCol &pos,
             std::string_view u, std::string_view i, Shape &&s, MscError &error);
    /** Returns the size of the collection, how many shapes.*/
    size_t ShapeNum() const noexcept { return shapes.size(); }
    /** What is the number of the shape with this name? -1 if not found.*/
    int GetShapeNo(std::string_view sh_name) const;
    /** Add the shape names to hints (with small drawings)*/
    void AttributeValues(Csh &csh) const;
    std::vector<const string*> ShapeNames() const;
    std::vector<string> ShapeNames(std::string_view partial) const;
    std::pair<std::string, std::string> ErrorOnShapeName(std::string_view name) const;
    /** Draws a shape to a canvas.
     * @param canvas The canvas to draw onto
     * @param sh The number of the shape to draw.
     * @param o The outer bounding box to fit the shape into.
     * @param line Use this style for sections 1 and 2.
     * @param fill Use this style for section 0.
     * @param shadow Use this style for shadow.*/
    void Draw(Canvas& canvas, size_t sh, const Block& o, const LineAttr& line,
              const FillAttr& fill, const ShadowAttr& shadow = ShadowAttr()) const
        { if (sh>=shapes.size()) return; shapes[sh].Draw(canvas, o, line, fill, shadow); }
    Contour Cover(unsigned sh, const Block &o) const;
    /** Return the shape of a given number.*/
    const Shape* GetShape(size_t shape) const noexcept { if (shape>=shapes.size()) return nullptr; return &shapes[shape]; }
    /** True if this shape exists and has a valid Label position.*/
    bool HasLabelPos(unsigned shape) const noexcept { return shape<shapes.size() && !shapes[shape].GetLabelPos(Block(0,1,0,1)).IsInvalid(); }
};

/** Callback for drawing a port position in hintboxes.
 * 'p' shall be the number of the shape multiplied by ShapePortCshHintGraphicCallbackMultiplier,
 * then the number of the port added. (Both start at zero.)
 * @ingroup hintpopup_callbacks*/
bool CshHintGraphicCallbackForPorts(Canvas *canvas, CshHintGraphicParam p, CshHintStore &);

/** Constant used by CshHintGraphicCallbackForPorts().*/
constexpr size_t ShapePortCshHintGraphicCallbackMultiplier = 10000;

/** Callback for drawing a symbol before shape attribute values in the hints popup list box.
* @ingroup hintpopup_callbacks*/
inline bool CshHintGraphicCallbackForEShapes(Canvas *canvas, CshHintGraphicParam p, CshHintStore&hs)
{
    //Call port drawer with large port number - then it will be omitted.
    return CshHintGraphicCallbackForPorts(canvas, 
                                          CshHintGraphicParam((size_t(p)+1)*ShapePortCshHintGraphicCallbackMultiplier -1), 
                                          hs);
}




#endif