#pragma once
#include <string>
#include <sstream>
#include <iomanip>
#include <variant>
#include "area.h"
#include "cgen_attribute.h"
#include "cgen_shapes.h"
#include "cgen_arrowhead.h"
#include "stringparse.h"


struct SimpleTransform {
    enum Type {
        SHIFT,
        ROTATE, //x = degree
        SCALE,  //x,y = scale factor
        FLIP_H, //flip left-right (across a vertical mirror axis)
    };
    Type type;
    XY xy;
    static SimpleTransform Shift(const XY& _xy) noexcept { return {SHIFT, _xy}; }
    static SimpleTransform Rotate(double degree) noexcept { return {ROTATE, {degree, 0}}; }
    static SimpleTransform Scale(double scale) noexcept { return {SCALE, {scale, scale}}; }
    static SimpleTransform Scale(const XY &scale) noexcept { return {SCALE, scale}; }
    static SimpleTransform FlipH() noexcept { return {FLIP_H, {0, 0}}; }
    operator TRMatrix() const noexcept {
        switch (type) {
        case SimpleTransform::SHIFT:  return TRMatrix::CreateShift(xy);
        case SimpleTransform::ROTATE: return TRMatrix::CreateRotate(xy.x);
        case SimpleTransform::SCALE:  return TRMatrix::CreateScale(xy);
        case SimpleTransform::FLIP_H: return TRMatrix::CreateFlipYAxis();
        default: return TRMatrix::CreateIdentity();
        }
    }
};

enum class ETextAlign {Min, Mid, Max};

//can be used as a No-Op shape
struct GenericShape {
    static constexpr int EMU_per_inch = 914400;
    static constexpr int point_per_inch = 72;
    struct int_xy { int x, y; };
    struct int_xywh { int x, y; unsigned w, h; };

    static constexpr int emu(double px) noexcept { return int(px * EMU_per_inch / point_per_inch); }
    static constexpr int_xy emu(const XY& xy) noexcept { return {emu(xy.x), emu(xy.y)}; }
    static constexpr int_xywh emu(const Block& rect) noexcept { return {emu(rect.x.from), emu(rect.y.from), (unsigned)emu(std::max(0., rect.x.Spans())), (unsigned)emu(std::max(0., rect.y.Spans()))}; }

    virtual ~GenericShape() = default;
    virtual operator std::string() const { return "GenericShape{}"; };
    virtual bool empty() const noexcept { return true; }
    virtual void transform(const SimpleTransform&) noexcept {};
    virtual bool clip(const Block&) { return true; } //true when we need to be dropped

    static int indent(EIdentType t) noexcept { return t==MSC_IDENT_LEFT ? -1 : t==MSC_IDENT_RIGHT ? +1 : 0; }
    static int indent(EVIdentType t) noexcept { return t==MSC_VIDENT_TOP ? -1 : t==MSC_VIDENT_BOTTOM ? +1 : 0; }
};

struct GSBox : GenericShape {
    Block rect{false}; //In chart space (pixels), initialize to invalid
    LineAttr line;
    FillAttr fill;
    ShadowAttr shadow;
    Label label;
    ETextAlign valign;
    double scale_font = 1;  //negative values mean we need to flip: write backwards
    double degree = 0;      //rotate the box this much counterclocwise (in degrees)
    GSBox() noexcept = default;
    //Block is the 'outer edge' of the line (matters with thick lines)
    explicit GSBox(const Block& b, const LineAttr& l = {}, 
                   const FillAttr& f = FillAttr::None(), const ShadowAttr& s = ShadowAttr::None(), 
                   Label t = {}, ETextAlign va = ETextAlign::Mid) noexcept
        : rect(b), line(l), fill(f), shadow(s), label(std::move(t)), valign(va) {}
    operator std::string() const override { return "Box{"+rect.Dump(false) + ":\""+std::string(label)+"\"}"; };
    bool empty() const noexcept override { 
        return rect.IsInvalid() 
            || (line.IsFullyTransparent() 
                && (fill.IsFullyTransparent() || rect.x.Spans()<=0 || rect.y.Spans()<=0)
                && shadow.IsNone()
                && label.IsEmpty()
               ); }
    void transform(const SimpleTransform& m) noexcept override {
        switch (m.type) {
        case SimpleTransform::SHIFT:
            rect.Shift(m.xy);
            break;
        case SimpleTransform::SCALE:
            rect.Scale(m.xy);
            line.width.value() *= sqrt(m.xy.x*m.xy.y);
            shadow.offset.value() *= sqrt(m.xy.x*m.xy.y);
            shadow.blur.value() *= sqrt(m.xy.x*m.xy.y);
            scale_font *= sqrt(m.xy.x*m.xy.y);
            break;
        case SimpleTransform::FLIP_H:
            rect.x = -rect.x;
            scale_font *= -1;
            break;
        case SimpleTransform::ROTATE:
            const double radian = m.xy.x*M_PI/180;
            const XY new_centroid = rect.Centroid().Rotate(cos(radian), sin(radian));
            rect.Shift(new_centroid - rect.Centroid());
            degree += m.xy.x;
            break;
        }
    }
    bool clip(const Block &c) override { 
        if (c.IsInvalid()) return false;
        if (rect.IsInvalid()) return true;
        if (degree==0)
            rect = c*rect;
        else if (degree==90 || degree==-90)
            rect = (c * rect.Rotate90AroundCentroid()).Rotate90AroundCentroid();
        //else random rotation, do not clip 
        return rect.x.Spans()<=0 || rect.y.Spans()<=0;
    }
    auto& font() const { _ASSERT(label.size()); return label.front().GetStartFormat().face; }
    auto& text_color() const { _ASSERT(label.size()); return label.front().GetStartFormat().color; }
};

inline GSBox GSText(const Label &l, const ShapeCollection& shapes, double sx, double dx, double y, double cx = -CONTOUR_INFINITY) {
    return GSBox (l.Cover(shapes, sx, dx, y, cx).GetBoundingBox(),
                  LineAttr::None(), FillAttr::None(), ShadowAttr::None(), l);
}

struct GSShape : GSBox {
    //GSBox::rect will hold the label pos
    Contour shape; //In chart space (pixels)
    GSShape() noexcept = default;
    explicit GSShape(Contour c, const LineAttr& l = {}, 
                     const FillAttr& f = FillAttr::None(), const ShadowAttr& s = ShadowAttr::None(),
                     const Block text_pos = Block(false), const Label &lab = {}) noexcept
        : GSBox(text_pos, l, f, s, lab)
        ,  shape(std::move(c)) {}
    operator std::string() const override { return "Shape{"+shape.Dump(false) + "}"; };
    bool empty() const noexcept override { 
        return line.IsFullyTransparent()
                && (fill.IsFullyTransparent() || shape.IsEmpty())
                && shadow.IsNone()
                && label.empty();
    }
    void transform(const SimpleTransform& m) noexcept override {
        if (!shape.IsEmpty()) {
            if (m.type == SimpleTransform::ROTATE) {
                // actual rotation handled by GSBox::degree, we just handle the shift here
                const double radian = m.xy.x * M_PI / 180;
                const XY new_centroid = shape.GetBoundingBox().Centroid().Rotate(cos(radian), sin(radian));
                shape.Shift(new_centroid - shape.GetBoundingBox().Centroid());
            } else
                shape.Transform(m);  
        }
        GSBox::transform(m);
    }
    bool clip(const Block& c) override {
        if (c.IsInvalid()) return false;
        if (shape.IsEmpty()) return true;
        if (degree==0)
            shape = c*shape;
        else {
            const XY M = shape.Centroid();
            shape = (c*shape.RotateAround(M, degree)).RotateAround(M, -degree);
        }
        if (shape.IsEmpty()) return true;
        if (!rect.IsInvalid()) rect = c*rect;
        return false;
    }
};

struct GSPath : GenericShape {
    Path path;
    LineAttr line;
    const ArrowHead* aEnd, * aStart;
    double scale_arrows = 1;
    GSPath() noexcept = default;
    explicit GSPath(Path c, const LineAttr& l = {}, 
                    const ArrowHead* aE = nullptr, const ArrowHead* aS = nullptr) noexcept 
        : path(std::move(c)), line(l), aEnd(aE), aStart(aS) {}
    operator std::string() const override { return "Path{"+path.Dump(false)+'}'; };
    void swap_arrows() noexcept { std::swap(aEnd, aStart); }
    bool empty() const noexcept override { return path.IsEmpty() || (line.IsFullyTransparent() && (!aEnd || aEnd->IsNone()) && (!aStart || aStart->IsNone())); }
    void transform(const SimpleTransform& m) noexcept override {
        path.Transform(m);
        if (m.type==SimpleTransform::SCALE) {
            line.width.value() *= sqrt(m.xy.x*m.xy.y);
            scale_arrows *= sqrt(m.xy.x*m.xy.y);
        }
    }
    bool clip(const Block& c) {
        if (c.IsInvalid()) return false;
        if (path.IsEmpty()) return true;
        //The below is not precise as if the endpoint is exactly on the clip boundary,
        //but the line is outside, we should drop the arrowhead.
        if (!c.IsWithinBool(path.front().GetStart())) aStart = nullptr;
        if (!c.IsWithinBool(path.back().GetEnd())) aEnd = nullptr;
        path.ClipRemove(c);
        return path.IsEmpty();
    }
};

//TODO: Brace, Note, block arrow as special shapes...?

struct GS : std::variant<GenericShape, GSBox, GSShape, GSPath> {
    using std::variant<GenericShape, GSBox, GSShape, GSPath>::variant;
    const GenericShape & get() const & noexcept { return std::visit([](const GenericShape &s)->const GenericShape & { return           s;  }, static_cast<const std::variant<GenericShape, GSBox, GSShape, GSPath> &>(*this)); }
          GenericShape & get()       & noexcept { return std::visit([](      GenericShape &s)->      GenericShape & { return           s;  }, static_cast<      std::variant<GenericShape, GSBox, GSShape, GSPath> &>(*this)); }
          GenericShape&& get()      && noexcept { return std::visit([](      GenericShape&&s)->      GenericShape&& { return std::move(s); }, static_cast<      std::variant<GenericShape, GSBox, GSShape, GSPath>&&>(*this)); }
};

std::array<GS, 3>
GSMscShape(const ShapeCollection& shapes, int shape, const Block &outer_edge, 
           const LineAttr& l = {}, const FillAttr& f = FillAttr::None(), const ShadowAttr& s = ShadowAttr::None(),
           const Label &lab = {}, bool scale_font = false); //defined in canvas.cpp
