/*
    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 contour_debug.h Declares classes that help debugging contour functionality.
 * @ingroup contour_files
 */

#if !defined(CONTOUR_DEBUG_H)
#define CONTOUR_DEBUG_H

#include <string>
#include <sstream>
#include <iomanip>
#include <memory>
#include "canvas.h"

class ShapeCollection;

namespace contour {
namespace debug {

enum class EC {
    LINEWIDTH,
    MARKERWIDTH,
    START,
    END,
    STRINGFORMAT,
    TITLE
};

class DebugCanvas
{
    template <typename T>
    std::string to_string(const T a_value, const int n)
    {
        std::ostringstream out;
        std::fixed(out);
        out << std::setprecision(n) << a_value;
        return std::move(out).str();
    }

public:
    std::unique_ptr<Canvas> canvas;
    LineAttr line;
    StringFormat sf;
    double marker_size = 2;
    std::string start;
    std::string end;
    EC command = EC::LINEWIDTH;
    std::string title;
    DebugCanvas(std::string_view fn, const Block &place, const char *text = nullptr);
    void DrawX(const XY & xy);
    void DrawLabel(std::string e, const XY &xy, unsigned n);
    void Draw(const Edge &e, bool cont1, bool cont2, std::string s,  std::string en, unsigned n);
    void Draw(const SimpleContour::ExpandMetaData &e, bool cont1, bool cont2, std::string s, std::string en, unsigned n);

    template <typename First, typename...Rest>
    void DrawElement(const First &f, Rest&&... r) { DrawElement(f); DrawElement(std::forward<Rest>(r)...); }
};


template <>
inline void DebugCanvas::DrawElement(const Edge &e) { Draw(e, false, false, start, end, 0); }

template <>
inline void DebugCanvas::DrawElement(const SimpleContour::MetaDataList &l)
{
    if (l.size()==0) return;
    bool cont = l.front().GetStart().test_equal(l.back().GetEnd());
    unsigned u = 0;
    for (auto i = l.begin(); i!=l.end(); i++, u++) {
        auto next = std::next(i);
        if (next==l.end()) next = l.begin();
        bool cont2 = next->GetStart().test_equal(i->GetEnd());
        Draw(*i, cont, cont2, start, end, u);
    }
}

template <>
inline void DebugCanvas::DrawElement(const std::list<Edge> &l)
{
    if (l.size()==0) return;
    bool cont = l.front().GetStart().test_equal(l.back().GetEnd());
    unsigned u = 0;
    for (auto i = l.begin(); i!=l.end(); i++, u++) {
        auto next = std::next(i); if (i==l.end()) i = l.begin();
        bool cont2 = next->GetStart().test_equal(i->GetEnd());
        Draw(*i, cont, cont2, start, end, u);
    }
}


template <>
inline void DebugCanvas::DrawElement(const Path &p)
{
    if (p.size()==0) return;
    bool cont = p[0].GetStart().test_equal(p[p.size()-1].GetEnd());
    for (unsigned u = 0; u<p.size(); u++) {
        bool cont2 = p.at_next(u).GetStart().test_equal(p[u].GetEnd());
        Draw(p[u], cont, cont2, start, end, u);
    }
}

template <>
inline void DebugCanvas::DrawElement(const SimpleContour &c)
{
    for (unsigned u = 0; u<c.size(); u++)
        Draw(c[u], true, true, start, end, u);
}

template <>
inline void DebugCanvas::DrawElement(const Contour& c) {
    canvas->Fill(c, FillAttr::Solid(line.color.value().MoreTransparent(0.8)));
    Path p = c;
    for (unsigned u = 0; u<p.size(); u++)
        Draw(p[u], true, true, start, end, u);
}


template <>
inline void DebugCanvas::DrawElement(const XY &xy) { Draw(Edge(xy, xy), false, true, start, "", 0); }

template <>
inline void DebugCanvas::DrawElement(const ColorType &c) { line.color = c;  sf.SetColor(c); } //color

template <>
inline void DebugCanvas::DrawElement(const std::string_view &s)
{
    switch (command) {
    default:
        break;
    case EC::START: start = s; break;
    case EC::END: end = s; break;
    case EC::STRINGFORMAT: sf.Apply(s); break;
    case EC::TITLE: title = s; break;
    }
}

template <>
inline void DebugCanvas::DrawElement(const std::string& s) { DrawElement<std::string_view>(s); }

template <>
inline void DebugCanvas::DrawElement(const char * const &s)
{
    switch (command) {
    default:
        break;
    case EC::START: start = s; break;
    case EC::END: end = s; break;
    case EC::STRINGFORMAT: sf.Apply(s); break;
    case EC::TITLE: title = s; break;
    }
}

template <>
inline void DebugCanvas::DrawElement(const double &d)
{
    switch (command) {
    default:
        break;
    case EC::LINEWIDTH: line.width = d; break;
    case EC::MARKERWIDTH: marker_size = d; break;
    }
}

template <>
inline void DebugCanvas::DrawElement(const EC &c) { command = c; }


template <typename First, typename...Rest>
inline Block GetBB(const First &f, Rest&&... r) { return GetBB(std::forward<const First &>(f)) + GetBB(std::forward<Rest>(r)...); }

template <>
inline Block GetBB(const Edge &e) { return e.CreateBoundingBox(); }

template <>
inline Block GetBB(const std::list<Edge> &l)
{
    Block b(true);
    for (auto &e : l)
        b += e.CreateBoundingBox();
    return b;
}

template <>
inline Block GetBB(const SimpleContour::MetaDataList &l)
{
    Block b(false);
    for (auto &e : l)
        b += e.CreateBoundingBox();
    return b;
}

template <>
inline Block GetBB(const Path &p) { return p.CalculateBoundingBox(); }

template <>
inline Block GetBB(const SimpleContour &c) { return c.GetBoundingBox(); }

template <>
inline Block GetBB(const Contour& c) { return c.GetBoundingBox(); }

template <>
inline Block GetBB(const XY &xy) { return Block(xy, xy); }

template <>
inline Block GetBB(const ColorType&) { return Block(false); } //color

template <>
inline Block GetBB(const std::string_view&) { return Block(false); } //vertex or edge text

template <>
inline Block GetBB(const std::string&) { return Block(false); } //vertex or edge text

template <>
inline Block GetBB(const char * const&) { return Block(false); } //vertex or edge text

template <>
inline Block GetBB(const double&) { return Block(false); } //line/marker size width

template <>
inline Block GetBB(const EC&) { return Block(false); } //command

constexpr bool HasTitle(const EC& e) { return e == EC::TITLE; }
constexpr bool HasTitle(const char*) { return false; }

template <typename First>
constexpr bool HasTitle(const First&) { return false; }

template <typename First, typename...Rest>
constexpr bool HasTitle(const First& f, Rest&&... r) { return HasTitle(std::forward<const First&>(f)) || HasTitle(std::forward<Rest>(r)...); }

unsigned get_canvas_seq() noexcept;
template <typename... Args>
inline void Snapshot(Args... args)
{
    char buff[512];
    snprintf(buff, sizeof(buff), "debug%03u.pdf", get_canvas_seq());
    const bool has_title = HasTitle(args...);
    Block bb = GetBB(args...);
    if (has_title) bb.y.from -= 20;
    DebugCanvas c(buff, bb);
    c.DrawElement(args...);
    c.sf.Default();
    c.sf.ident = EIdentType::MSC_IDENT_LEFT;
    if (has_title) c.DrawLabel(c.title, bb.UpperLeft(), 0);
}

} //ns debug
} //ns contour

using contour::debug::EC;

#endif