/*
    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 mscentity.h The declaration of entities and related classes.
 * @ingroup libmscgen_files */
#if !defined(ENTITY_H)
#define ENTITY_H

#include "cgen_shapes.h"
#include "mscelement.h"
#include "style.h"

namespace msc {


/** Describes the status of an entity, like shown, hidden or active.
 * This essentially encapsulates an enum and add helpers.*/
class EEntityStatus {
public:
    /** Enumerates all possible states of an entity line.*/
    enum E {
        SHOW_OFF,       ///<The entity is not active and is turned off.
        SHOW_ON,        ///<The entity is not active but is turned on.
        SHOW_ACTIVE_ON, ///<The entity is active but is turned off.
        SHOW_ACTIVE_OFF ///<The entity is noth active and is on.
    };
private:
    E status; ///<The actual value.
public:
    EEntityStatus(E e) : status(e) {}
    EEntityStatus() : status(SHOW_OFF) {}
    /** True if the entity is shown.*/
    bool IsOn() const {return status==SHOW_ON||status==SHOW_ACTIVE_ON;}
    /** True if the entity is active */
    bool IsActive() const {return status==SHOW_ACTIVE_OFF||status==SHOW_ACTIVE_ON;}
    /** Shows/hides the entity with keeping its activation state.*/
    EEntityStatus& Show(bool on) {status = on ? (IsOn() ? status : status==SHOW_OFF ? SHOW_ON : SHOW_ACTIVE_ON) : (!IsOn() ? status : status==SHOW_ON ? SHOW_OFF : SHOW_ACTIVE_OFF); return *this;}
    /** Activates/deactivates the entity with keeping its show/hide state.*/
    EEntityStatus& Activate(bool on) {status = on ? (IsActive() ? status : status==SHOW_OFF ? SHOW_ACTIVE_OFF : SHOW_ACTIVE_ON) : (!IsActive() ? status : status==SHOW_ACTIVE_OFF ? SHOW_OFF : SHOW_ON); return *this;}
    bool operator ==(EEntityStatus o) const {return status == o.status;}
    bool operator ==(E o) const {return status == o;}
    EEntityStatus& operator =(E o) {status=o; return *this;}
};

/** A map showing the status and style of an entity along a floating point axis (the y axis).*/
class EntityStatusMap
{
public:

protected:
    contour::DoubleMap<MscStyleCoW>   styleStatus; ///<Style of the entity line, fill, text, etc. at and beyond a position
    contour::DoubleMap<EEntityStatus> showStatus;  ///<Status of the entity at and beyond a position
public:
    /** Creates a map with the entity being inactive and off everywhere and having style `def`*/
    explicit EntityStatusMap(const MscStyleCoW &def) : styleStatus(def), showStatus(EEntityStatus::SHOW_OFF) {}
    /** Applies a style at `pos` (merging styles) and onwards up till the next existing change.*/
    void ApplyStyle(double pos, const MscStyleCoW &style) {styleStatus.Add(pos, style);}
    /** Applies a style within range `pos` (merging styles).*/
    void ApplyStyle(Range pos, const MscStyleCoW &style) {styleStatus.Add(pos, style);}
    /** Set the entity status at `pos` and onwards up till the next existing change.*/
    void SetStatus(double pos, EEntityStatus status) {showStatus.Set(pos, status);}
    /** Get the current style at `pos`*/
    const MscStyleCoW &GetStyle(double pos) const {return *styleStatus.Get(pos);}
    /** Get the current status at `pos`*/
    EEntityStatus GetStatus(double pos) const {return *showStatus.Get(pos);}
    /** Get the width of the entity line */
    double GetWidth(double pos, double activeEntitySize) const;
    /** Return the next change of status strictly after `pos`. */
    double ShowTill(double pos) const {return showStatus.Till(pos);}
    /** Return the next change of style strictly after `pos`. */
    double StyleTill(double pos) const {return styleStatus.Till(pos);}
    /** Return the previous change of status strictly before `pos`. */
    double ShowFrom(double pos) const {return showStatus.From(pos);}
    /** Return the previous change of style strictly before `pos`. */
    double StyleFrom(double pos) const {return styleStatus.From(pos);}
    /** Clear all content, making the entity having the default style and being off/inactive everywhere.*/
    void Reset() {styleStatus.clear(); showStatus.clear();}
};

class MscChart;
class EntityApp;
/** A list of EntityApp objects by pointer*/
typedef UPtrList<EntityApp> EntityAppList;

/** A class describing an entity.
 * Each entity is allocated exactly one such object.*/
class Entity
{
public:
    const string      name;      ///<Name of entity as referenced in src file
    const string      label;     ///<Label of the entity (==name if none)
    const string      orig_label;///<The text the user specified for label (for error msgs)
    const FileLineCol file_pos;  ///<The location of the definition of this entity in the input file (the first character of its name)
    const int         shape;     ///<The shape we shall use when drawing the entity. -1 if none.
    const EArrowSize  shape_size;///<It size
    double            pos;       ///<The position of the entity with hscale=1. E.g., 0 for the 1st, 1 for 2nd, etc. 1.5 for one in-between
    const double      pos_exp;   ///<The position of the entity if all group entities were expanded (for a->b->c sanity checking)
    unsigned          index;     ///<The index of the entity, counting entities left to right
    EntityStatusMap   status;    ///<Contains vertical line status & type & color and on/off active/inactive status.
    MscStyleCoW       parse_running_style;///<Used during Parse process for some attributes (atext, aline and arrow) required by arrows already during parsing
    MscStyleCoW       running_style;     ///<Used during PostParse process to make EntityApp::style's fully specified. Also used in AutoPaginate to gather the style for automatically inserted headers.
    EEntityStatus     running_shown;     ///<Used during PostParse process to see if it is shown/hidden active/inactive.
    EMscDrawPass      running_draw_pass; ///<The running Z-order position of this arc during PostParse process
    bool              running_defined;   ///<Used during PostParseProcess() to see if this entity was already defined. If so, we include it into solo prefix commands, such as "show;" or "hide;"
    double            maxwidth;          ///<Used during PostParse process to collect the maximum width of the entity

    string            parent_name;    ///<Empty if we are not part of an entity group, otherwise the name of the parent entity.
    std::set<string>  children_names; ///<If we are an entity group, tells who are within us
    const bool        collapsed;      ///<True if we are group, but show collapsed
    const bool        large;          ///<If we are a large group entity or not.

    Entity(const string &n, const string &l, const string &ol, double p, double pe,
           const MscStyleCoW &entity_style, const FileLineCol &fp, bool coll, bool larg);
    void AddChildrenList(const EntityAppList *children, MscChart *chart);
    double GetRunningWidth(double activeEntitySize) const;
    string Print(int indent = 0) const;
};

/** A reference to an entity that you cannot delete or increment
 * But can dereference & read/write. Now it is a pointer - exists for historic reasons.*/
typedef Entity* EntityRef;
/** A reference to an entity that you cannot delete or increment
 * But can dereference & read only. Now it is a pointer - exists for historic reasons.*/
typedef const Entity* CEntityRef;

/** A list of Entity object pointers ownning their content.
 * Has special search functions */
class EntityList : public std::list<Entity>
{
public:
    using std::list<Entity>::end;
    using std::list<Entity>::begin;
    using std::list<Entity>::front;
    using std::list<Entity>::back;
    using std::list<Entity>::emplace_back;
    /** Find an entity by name.
      * If not found, returns nullptr*/
    EntityRef Find_by_Name(std::string_view name)
    {auto i = std::find_if(begin(), end(), [name](auto &p) {return p.name==name; }); return i==end() ? nullptr : &*i;}
    /** Find an entity by name.
    * If not found, returns nullptr*/
    CEntityRef Find_by_Name(std::string_view name) const
    { auto i = std::find_if(begin(), end(), [name](auto &p) {return p.name==name; }); return i==end() ? nullptr : &*i; }
    /** Find an entity by pointer.
      * If not found, returns end()*/
    iterator Find_by_Ptr(Entity *p)
    { return std::find_if(begin(), end(), [p](auto &pp) {return &pp==p; }); }
    /** Find an entity by pointer.
    * If not found, returns end()*/
    const_iterator Find_by_Ptr(const Entity *p) const
    { return std::find_if(begin(), end(), [p](auto &pp) {return &pp==p; }); }
    /** Find an entity by index.
    * If not found, returns end()*/
    iterator Find_by_Index(unsigned index)
    { return std::find_if(begin(), end(), [index](auto &pp) {return pp.index==index; }); }
    /** Find an entity by index.
    * If not found, returns end()*/
    const_iterator Find_by_Index(unsigned index) const
    { return std::find_if(begin(), end(), [index](auto &pp) {return pp.index==index; }); }
};

/** A list of Entity object pointers owning their content.
* Has special search functions */
class NEntityList : public NPtrList<Entity>
{
public:
    /** Find an entity by name.
    * If not found, returns nullptr*/
    EntityRef Find_by_Name(const string &name) const
    { auto i = std::find_if(begin(), end(), [&name](auto &p) {return p->name==name; }); return i==end() ? nullptr : *i; }
    /** Find an entity by pointer.
    * If not found, returns end()*/
    iterator Find_by_Ptr(Entity *p)
    { return std::find_if(begin(), end(), [p](auto &pp) {return pp==p; }); }
    /** Find an entity by pointer.
    * If not found, returns end()*/
    const_iterator Find_by_Ptr(const Entity *p)
    { return std::find_if(begin(), end(), [p](auto &pp) {return pp==p; }); }
    /** Find an entity by index.
    * If not found, returns end()*/
    iterator Find_by_Index(unsigned index)
    { return std::find_if(begin(), end(), [index](auto &pp) {return pp->index==index; }); }
};


/** A collection of bool values for each entity by name to see which is collapsed and which is not.*/
class EntityCollapseCatalog : public std::map<std::string, bool>
{
public:
    bool Deserialize(std::string_view &s);
    std::string Serialize() const;
};

class ArcBase;
/** A list of arcs (ArcBase descendants) by pointer*/
typedef UPtrList<ArcBase> ArcList;

/** Store a note temporarily */
struct StoredNote {
    std::unique_ptr<Note> note;  //The note we have stored
    std::string target;          //The entity it targets (empty if none)
};
using StoredNotes = std::vector<StoredNote>;

/** A list of EntityApp objects with potential associated CommandNotes.
 * This is used during parsing to build up EntityCommand objects. */
struct EntityAppHelper
{
    EntityAppList entities; ///<A list of entity definitions.
    StoredNotes   notes;    ///<A set of notes associated with one of the entities in `entities` member.
    std::string   target;   ///<A name of an entity in `entity` to be the target for a subsequent note, if that has no 'at' clause.
    /** Prepend a set of entities, and merge the list of notes.*/
    EntityAppHelper* Prepend(EntityAppHelper* edh);
};

class DirArrow;

/** Describes information for each appearance of an entity in the input.
 * Each time we mention an entity (not as part of an arc, but to change its attributes)
 * one EntityApp is created.
 * There is a special EntityApp for each Entity though, the first one, the one which
 * actually *defines* the Entity. Parsing always generates EntityApp objects and
 * if we see that the Entity does not exist yet, we create one Entity object.
 * So this class have to be able contain all things and entity definition needs,
 * like label, grouping and so on, not just the things that may be changed later on.
 * It is a descendant of of MscElement because these motions can be
 * highlighted individually in the chart.
 *
 * The creation of this object goes as follows.
 * (Note that entities can be listed separated by commas, each may have attributes
 * they can also have child entities behind them in curly braces, plus they can be
 * preceded by one of `show`, `hide`, `activate` and `deactivate` keyword, which
 * is syntactic sugar to set the corresponding attribute in all entitydefs in the
 * comma-separated list.)
 * - first we parse it learning its name (`entity_string` rule in language.yy)
 * - then we parse its associated attributes (rule `full_arcattrlist_with_label`)
 *   and a potential list of arcs (`braced_arclist`). All this is in the rule
 *   `entity`.
 * - In rule `entity` we create the EntityApp object (the corresponding Entity
 *   may not exist if we define the entity here.
 * - We call EntityApp::AddAttributeList() with the parsed attribute list and
 *   arclist (if any). This function will check if the associated arclist
 *   contains only EntityCommand or Note arcs, fixes the notes
 *   applies the attribute list and creates the Entity object if needed.*/
class EntityApp : public MscElement
{
public:
    const string        name;                ///<The name of the Entity. Empty if we refer to multiple already, defined entities.
    const bool          only_shown_if_multi; ///<If name is empty, we refer to only visible entities (true) or non-visible ones (false).
    OptAttrLine<string> label;               ///<The label specified at this mention of the entity (if any). `file_pos` contains the position of the attribute (name).
    FileLineCol         linenum_label_value; ///<Location of label text (attribute value) in the input file (if any). Only a minor difference to `label.file_pos`.
    string              url;                 ///<The value of the url attribute (if any).
    FileLineCol         linenum_url_attr;    ///<Location of url attribute (if any).
    OptAttrLine<double> pos;                 ///<The `pos=` attribute if specified by the user. `file_pos` contains the location of the attribute (name) in the input file.
    OptAttrLine<string> rel;                 ///<The `rel=` attribute if specified by the user. `file_pos` contains the location of the attribute (name) in the input file.
    OptAttrLine<bool>   large;               ///<The `large=` attribute if specified by the user. `file_pos` contains the location of the attribute (name) in the input file.
    OptAttrLine<bool>   collapsed;           ///<The `collapsed=` attribute if specified by the user. `file_pos` contains the location of the attribute (name) in the input file.
    OptAttr<bool>       show;                ///<The `show=` attribute if specified by the user.
    OptAttrLine<bool>   active;              ///<The `active=` attribute if specified by the user. `file_pos` contains the location of the attribute (name) in the input file.
    bool                show_is_explicit;    ///<True if a show attribute was specified by the user. In this case a "show/hide" command/prefix will not override the `show` member.
    bool                active_is_explicit;  ///<True if an active attribute was specified by the user. In this case an "activate/deactivate" command/prefix will not override the `active` member.
    bool                centerlined;         ///<true if entity changes shall be aligned to the centerline target (the arrow above the entity, defined below).
    const DirArrow*     centerline_target;   ///<If we need to activate at the centerline of an arrow, this is the one. Else nullptr.

    EntityRef           entity;      ///<Points to the entity set during PostParse.
    MscStyleCoW         style;       ///<The complete style of the entity at this point. Finalized during PostParse taking unset attributes from running_style.
    Label               parsed_label;///<The complete label with the right formatting. Finalized during PostParse.
    bool                defining;    ///<True if this is the first EntityApp object for this entity = this EntityApp created the entity (set in AddAttrList).
    bool                draw_heading;///<True if we have to draw heading of this entity at this point. Set in PostParse.
    bool                large_appears;///<True if we are for a non-collapsed, large group entirty, which was not viaible, but appears here.

    mutable EntityRef left_ent;          ///<For a grouped entity the leftmost of its active child entities, else same as `entity` member.
    mutable EntityRef right_ent;         ///<For a grouped entity the rightmost of its active child entities, else same as `entity` member.
    mutable double left_offset;          ///<For a grouped entity the offset from its left outer edge till the middle of its leftmost active child entity, else zero.
    mutable double right_offset;         ///<For a grouped entity the offset from its right outer edge till the middle of its rightmost active child entity, else zero.
    mutable Block  outer_edge;           ///<The outer edge of a heading we have to draw, set in Height().
    mutable double indicator_ypos_offset;///<The y position of the indicator of a collapsed group entity within the entity heading. -1 if no indicator to be shown.

    EntityApp(std::string_view s, MscChart* chart);
    EntityApp(MscChart* chart, bool shown_only, std::string_view prefix, const  FileLineCol &l);

    bool AddAttribute(const Attribute&) override;
    std::unique_ptr<EntityAppHelper> AddAttributeList(AttributeList *, ArcList *children, FileLineCol l);
    static void AttributeNames(Csh &csh);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    void Combine(EntityApp *ed);

    double Width() const;
    Range Height(Area &cover, const NPtrList<EntityApp> &edl);
    void AddAreaImportantWhenNotShowing();
    void ShiftBy(double y) override {MscElement::ShiftBy(y); outer_edge.Shift(XY(0,y));}
    void PostPosProcess(Canvas &, Chart *ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void Draw(Canvas &);
};

} //namespace


#endif //ENTITY_H