/*
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/>.
*/


#include "mscelement.h"
#include "msc.h"

using namespace msc;

/** Textual representation of drawing passes*/
template<> const char EnumEncapsulator<EMscDrawPass>::names[][ENUM_STRING_LEN] =
{"invalid", "before_background", "before_entity_lines", "after_entity_lines", "default", "after_default",
"note", "after_note", ""};

/** Textual descripton of drawing passes*/
template<> const char * const EnumEncapsulator<EMscDrawPass>::descriptions[] =
{"", "Draw it first, before drawing the background. (Maybe useful for transparent or partial background.)",
"Draw it just after drawing the background, even before drawing the entity line.",
"Draw it just after the entity lines, but before chart elements of default drawing time.",
"This is the default drawing time for chart elements.",
"Draw them after chart elements of default drawing time, but before notes.",
"Default drawing time for notes (pretty much the last things to draw).",
"Draw it last, even after the notes.", ""};

MscElement::MscElement(MscChart *m) :
    chart(m), yPos(0),
    comment_height(0),
    indicator_style(m->MyCurrentContext().styles["indicator"]),
    draw_pass(EMscDrawPass::DEFAULT)
{
    area.arc = this;
    control_location.MakeInvalid();
}

MscElement::~MscElement()
{
    if (chart)
        chart->InvalidateNotesToThisTarget(this);
}


MscElement::MscElement(const MscElement&o) :
    Element(o), chart(o.chart), yPos(o.yPos),
    comment_height(0),//we do not copy comments!!!
    indicator_style(o.indicator_style),
    draw_pass(o.draw_pass)
{
}

/** Applies an Attribute to us.
* We only accept `draw_time` attribute.
* @param [in] a The attribute to add.
* @return true if we recognized the attribute as ours.*/
bool MscElement::AddAttribute(const Attribute &a)
{
    if (a.Is("draw_time")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (a.type == EAttrType::STRING && Convert(a.value, draw_pass)) return true;
        a.InvalidValueError(CandidatesFor(draw_pass), chart->Error);
        return true;
    }
    return false;
}


/** Add the attribute names we take to `csh`.*/
void MscElement::AttributeNames(Csh &csh)
{
    Element::AttributeNames(csh);
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "draw_time",
        "Specify Z-ordering with this attribute to control which visual element is drawn on top of which other.",
        EHintType::ATTR_NAME));
}

/** Add a list of possible attribute value names to `csh` for attribute `attr`.*/
bool MscElement::AttributeValues(std::string_view attr, Csh &csh)
{
    if (CaseInsensitiveEqual(attr, "draw_time")) {
        csh.AddToHints(EnumEncapsulator<EMscDrawPass>::names,
            EnumEncapsulator<EMscDrawPass>::descriptions,
            csh.HintPrefix(COLOR_ATTRVALUE),
            EHintType::ATTR_VALUE);
        return true;
    }
    return Element::AttributeValues(attr, csh);
}

/** Shift an elememt up or down
* Shifts all `area_*` members, `yPos`, `control_location` and
* all our comments;
*/
void MscElement::ShiftBy(double y)
{
    if (y==0) return;
    Element::ShiftBy(y);
    yPos += y;
    for (auto c : comments)
        c->ShiftCommentBy(y);
}



/** Attach a comment to us.
* The caller will remain responsible do delete `cn`, when the time comes.*/
void MscElement::AttachComment(Note *cn)
{
    _ASSERT(cn);
    _ASSERT(!cn->is_float);
    comments.Append(cn);
}

/** Move the comments from `te` to us.
* Comments are moved in order after our comments (if any).*/
void MscElement::CombineComments(MscElement *te)
{
    _ASSERT(te);
    if (te)
        comments.splice(comments.end(), te->comments);
}

/** A helper placing comments.
* @param canvas The canvas used to determine comment geometry
* @param cover We add the cover of the placed comments to this, if not nullptr.
* @param l At call says where we shall start laying out left comments from, at return the height of he comments on the left.
* @param r At call says where we shall start laying out right comments from, at return the height of he comments on the right.
*/
void MscElement::LayoutCommentsHelper(Canvas &canvas, AreaList *cover, double &l, double &r)
{
    for (auto c : comments)
        c->PlaceSideTo(canvas, cover, c->GetStyle().read().side == ESide::LEFT ? l : r);
    comment_height = std::max(l, r);
}

/** Register our cover (if any) if pass equals to our draw_pass.
*  See documentation for libmscgen for more info. */
void MscElement::RegisterCover(EMscDrawPass pass) const
{
    if (pass == draw_pass && !area.IsEmpty())
        chart->AllCovers += area;
}
