/*
    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 element.h The declaration of Element - the base for all chart elements.
 * @ingroup libcgencommon_files  */

#if !defined(TRACKABLE_H)
#define TRACKABLE_H

#include "error.h" //for FileLineCol
#include "area.h"  //for Area
#include "cgen_attribute.h"  //for NPtrList, UPtrList

/** Describes the type of a GUI Element control*/
enum class EGUIControlType {
    INVALID=0,       ///<The invalid value
    EXPAND,          ///<A control to expand a collapsed element
    COLLAPSE,        ///<A control to collapse an expanded group element (box or group entity, etc)
    ARROW,           ///<A control to collapse a box to a block arrow
    MAX              ///<The max number of control types.
};

class Chart;
class LineAttr;
struct FillAttr;
struct ShadowAttr;

/** The base object for elements on the chart (entity headings, arcs, boxes, commands, etc.)
 * This object contains a lot of members useful for the Windows GUI and not needed
 * for commandline operation. A potential refactoring would take these out. */
class Element {
public:
    static constexpr XY control_size = XY(25, 25);       ///<The size of a GUI control
    static constexpr XY indicator_size = XY(25, 10);     ///<The size of an indicator
protected:
    bool            linenum_final;      ///<True if `file_pos` member is the final value. Needed during parsing.
    mutable Area    area;               ///<The area covered by the element. This is used by tracking to recognize which element the pointer points to.
    mutable Contour area_draw;          ///<The area to draw when highlighting the element - if different from `area` (usually a frame, like for e.g., boxes with content)
    mutable bool    draw_is_different;  ///<True is `area_draw` is different from `area`.
    mutable bool    area_draw_is_frame; ///<True if `area_draw` is a frame. If so, we will not expand `area_draw` in PostPosProcess.
    mutable Contour area_to_note;       ///<If not empty the notes targeting this element will point towards this area.
    mutable Contour area_to_note2;      ///<If a note with "at" clause does not hit the 'area_to_note`, try this.

    mutable Contour area_important;     ///<Those parts of `area`, which must not be covered by notes.

    mutable std::vector<EGUIControlType>
                   controls;           ///<GUI controls of this element.
    mutable Block  control_location;   ///<The area the GUI controls occupy.
    static XY GetIndicatorSize(const LineAttr& line, const FillAttr&, const ShadowAttr& shadow) { const double a = *shadow.offset + line.LineWidth()*2; return indicator_size+XY(a, a); }

public:
    FileLineColRange file_pos;  ///<The location of the element in the source file. For inlined charts, lines and cols are counted starting at the place of inlining and not from the absolute beginning of the file. See GetRootFilePos()
    Element() noexcept;
    Element(const Element&o);
    Element(Element &&o) noexcept;
    virtual ~Element() = default;

    void SetLineEnd(FileLineColRange l, bool f = true);
    void ExpandLineEnd(FileLineColRange l, bool f = true);
    /** Add the attribute names we take to `csh`.*/
    static void AttributeNames(Csh &) {}
    /** Add a list of possible attribute value names to `csh` for attribute `attr`.*/
    static bool AttributeValues(std::string_view /*attr*/, Csh &/*csh*/) { return false; }
    virtual void ShiftBy(double y);
    virtual void ShiftBy(const XY &);
    /** An area over which if the mouse hoovers, we highlight the Element*/
    const Area &GetAreaToSearch() const
        {return area;};
    /** An area to highlight when the mouse moves over the Element*/
    const Contour &GetAreaToDraw() const
        {return draw_is_different ? area_draw : static_cast<const Contour&>(area);}
    /** An area which shall not get covered by notes, if possible. */
    const Contour &GetAreaImportant() const
        {return area_important;}
    /** An area to the edge of which notes made to this element will point to*/
    const Contour &GetAreaToNote() const
        {return area_to_note.IsEmpty() ? static_cast<const Contour&>(area) : area_to_note;}
    /** An second try for an area to the edge of which notes with an 'at' clause will point to*/
    const Contour &GetAreaToNote2() const {return area_to_note2;}
    /** Return the list of GUI controls associated with this Element*/
    const std::vector<EGUIControlType>& GetControls() const {return controls;}
    /** Register the labels of the element in chart->labelData (recursively called)*/
    virtual void RegisterLabels() {}
    /** Collect link information for ismap generation.*/
    virtual void CollectIsMapElements(Canvas &) {}
    /** Do things we need to do after our position is known.*/
    virtual void PostPosProcess(Canvas &, Chart *);


    /** Return the location of our GUI controls*/
    const Block &GetControlLocation() const {return control_location;}
    void DrawControls(cairo_t*, double size) const;
    static void DrawControl(cairo_t*, EGUIControlType type, double size);
    EGUIControlType WhichControl(const XY &xy) const noexcept;

    static Block GetIndicatorCover(const XY& pos, const LineAttr& line,
                                   const FillAttr& fill, const ShadowAttr& shadow);
    static void DrawIndicator(XY pos, Canvas* canvas, const LineAttr& line,
                              const FillAttr& fill, const ShadowAttr& shadow);

    struct InliningData
    {
        std::shared_ptr<Chart> chart;            ///<The compiled chart. Filled at parsing
        FileLineColRange       file_pos;         ///<The location of the inlined text (just excluding the INLINE_OPEN_STR and INLINE_CLOSE_STR tokens) in the parent's source text. 'file_pos.file' shall be equally valid in both the parent and inlined chart.
        TRMatrix               parent_to_inline; ///<The affine transformation from the parent chart's coordinate space to that of the inlined. Filled only in PostPosProcess().
    };
    virtual const InliningData* GetInlinedChartData() const { return nullptr; }
    /** This is called if the GUI state of an inlined chart changes */
    virtual void InlinedChartGUIStateChange() const {}
};

/** The result of Element lookup after compilation. 
 * Elements may be looked up to highlight or draw controls for them.
 * In case of no inlining, this is effectively a pointer to Element.
 * For Elements in inlined charts, it also contains the beginning of the 
 * inlining in the source text (absolute file position) and the 
 * TRMatrix that transforms points in the inlined chart's space to
 * that of the root chart. (Both source location and point transformation
 * works even with with nested inlining.)*/
struct ElementRef
{
    struct InlineData
    {
        TRMatrix inlined_to_parent; ///<Converts an XY point from the chart space of the inlined chart to that of the parent (and eventually root) chart
        Chart* chart = nullptr;     ///<The chart of the element                                                                      
        void AddParent(const Element::InliningData &d) { inlined_to_parent = d.parent_to_inline.Invert() * inlined_to_parent /* order is reverse! */; }
    };
    Element* element = nullptr;
    std::optional<InlineData> inline_data;
    constexpr explicit operator bool() const noexcept { return bool(element); }
    constexpr auto operator<=>(const ElementRef& o) const noexcept { return element <=> o.element; }
    constexpr auto operator<=>(const Element* o) const noexcept { return element <=> o; }
    const Contour& GetAreaToDraw(Contour& tmp_storage) const { return inline_data ? (tmp_storage = element->GetAreaToDraw().CreateTransformed(inline_data->inlined_to_parent)) : element->GetAreaToDraw(); }
};



#endif //TRACKABLE_H
