#include "SvgPainter.h"

#include "SvgDocument.h"
#include "Scene.h"

#include "SceneItem.h"
#include "SceneModel.h"
#include "SceneText.h"

#include "AbstractGeometricItem.h"
#include "AbstractMesh.h"
#include "TriMesh.h"
#include "AbstractFace.h"

#include "Circle.h"
#include "Rectangle.h"
#include "Path.h"
#include "Line.h"
#include "LineSegment.h"
#include "Polygon.h"

#include "SvgShapeElements.h"
#include "SvgTextElement.h"
#include "XmlAttribute.h"

std::unique_ptr<SvgDocument> SvgPainter::paint(Scene* scene, double width, double height) const
{
    auto doc = std::make_unique<SvgDocument>();
    doc->setViewBox(0.0, 0.0, width, height);

    scene->update();

    for (auto item : scene->getItems())
    {
        if (item->getType() == SceneItem::Type::MODEL)
        {
            auto model = dynamic_cast<SceneModel*>(item);
            if (model->getGeometry())
            {
                paintPrimitive(doc.get(), model);
            }
            else
            {
                paintMesh(doc.get(), model, scene->shouldShowMeshOutline());
            }
        }
        else
        {
            auto text = dynamic_cast<SceneText*>(item);
            paintText(doc.get(), text);
        }
    }

    return doc;
}

void SvgPainter::paintMesh(SvgDocument* document, SceneModel* model, bool showOutline) const
{
    auto mesh = model->getMesh();
    auto transform = model->getTransform();

    if (mesh->getType() == AbstractMesh::MeshType::TRI)
    {
        for (const auto& face : dynamic_cast<TriMesh*>(mesh)->getFaces())
        {
            auto svg_tri = std::make_unique<SvgPolygon>();

            std::vector<Point> points(3);
            unsigned count = 0;

            auto face_locs = face->getNodeLocations();
            for (const auto& loc : face_locs)
            {
                const auto x = loc.getX() * transform.getScale().mX + transform.getLocation().getX();
                const auto y = loc.getY() * transform.getScale().mY + transform.getLocation().getY();
                points[count] = { x, y };
                count++;
            }
            svg_tri->setPoints(points);
            svg_tri->setFill(model->getSolidMaterial().getFillColor());
            document->getRoot()->addChild(std::move(svg_tri));

            if (showOutline)
            {
                auto mesh_outline = std::make_unique<SvgPolyline>();
                points.push_back(points[0]);
                mesh_outline->setPoints(points);
                mesh_outline->setStrokeColor({ 0, 0, 0 });
                mesh_outline->setStrokeWidth(0.1);
                document->getRoot()->addChild(std::move(mesh_outline));
            }
        }
    }
}

void SvgPainter::setStyle(SceneModel* model, SvgShapeElement* element) const
{
    const auto transform = model->getTransform();
    const auto material = model->getSolidMaterial();

    if (material.hasFillColor())
    {
        element->setFill(material.getFillColor());
        auto opacity = static_cast<float>(material.getFillColor().getAlpha());
        if (opacity != 1.0)
        {
            element->setFillOpacity(opacity);
        }
    }
    else
    {
        element->setNoFill();
    }

    if (material.hasStrokeColor())
    {
        element->setStrokeColor(material.getStrokeColor());
        element->setStrokeWidth(material.getStrokeThickness());
    }
    else
    {
        element->setNoStroke();
    }
 
    if (!transform.isIdentityTransform())
    {
        element->addAttribute(toTransform(transform));
    }
}

void SvgPainter::paintPrimitive(SvgDocument* document, SceneModel* model) const
{
    if (model->getGeometry()->getType() == AbstractGeometricItem::Type::RECTANGLE)
    {
        paintRect(document, model);
    }
    else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::CIRCLE)
    {
        paintCircle(document, model);
    }
    else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::PATH)
    {
        paintPath(document, model);
    }
    else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::LINE)
    {
        paintLine(document, model);
    }
    else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::LINE_SEGMENT)
    {
        paintLineSegment(document, model);
    }
    else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::POLYGON)
    {
        paintPolygon(document, model);
    }
}

void SvgPainter::paintPolygon(SvgDocument* document, SceneModel* model) const
{
    auto model_polygon = dynamic_cast<ntk::Polygon*>(model->getGeometry());

    auto svg_polygon = std::make_unique<SvgPolygon>();
    svg_polygon->setPoints(model_polygon->getPoints().getPoints());

    setStyle(model, svg_polygon.get());
    document->getRoot()->addChild(std::move(svg_polygon));
}

void SvgPainter::paintRect(SvgDocument* document, SceneModel* model) const
{
    auto model_rect = dynamic_cast<ntk::Rectangle*>(model->getGeometry());

    auto rect = std::make_unique<SvgRectangle>();
    rect->setWidth(model_rect->getWidth());
    rect->setHeight(model_rect->getHeight());

    if (model_rect->getRadius() > 0.0)
    {
        rect->setRadius(model_rect->getRadius());
    }

    setStyle(model, rect.get());
    document->getRoot()->addChild(std::move(rect));
}

void SvgPainter::paintCircle(SvgDocument* document, SceneModel* model) const
{
    auto model_circle = dynamic_cast<Circle*>(model->getGeometry());

    auto circle = std::make_unique<SvgCircle>(model_circle->isEllipse() ? SvgCircle::Type::ELLIPSE : SvgCircle::Type::REGULAR);
    circle->setRadius(model_circle->getRadius());
    if (model_circle->isEllipse())
    {
        circle->setMinorRadius(model_circle->getMinorRadius());
    }

    setStyle(model, circle.get());
    document->getRoot()->addChild(std::move(circle));
}

void SvgPainter::paintLine(SvgDocument* document, SceneModel* model) const
{
    auto model_line = dynamic_cast<Line*>(model->getGeometry());
    auto svg_line = std::make_unique<SvgPolyline>();
    svg_line->setPoints(model_line->getFirstPoint(), model_line->getPoints().getPoints());

    setStyle(model, svg_line.get());
    document->getRoot()->addChild(std::move(svg_line));
}

void SvgPainter::paintLineSegment(SvgDocument* document, SceneModel* model) const
{
    auto model_line = dynamic_cast<LineSegment*>(model->getGeometry());
    auto svg_line = std::make_unique<SvgLine>(model_line->getFirstPoint(), model_line->getEndPoint());

    setStyle(model, svg_line.get());
    document->getRoot()->addChild(std::move(svg_line));
}

void SvgPainter::paintPath(SvgDocument* document, SceneModel* model) const
{
    auto model_path = dynamic_cast<GeometryPath*>(model->getGeometry());

    auto path_string = model_path->getAsPostScript();

    auto svg_path = std::make_unique<SvgPath>();
    svg_path->setPath(path_string);

    setStyle(model, svg_path.get());
    document->getRoot()->addChild(std::move(svg_path));
}

void SvgPainter::paintText(SvgDocument* document, SceneText* text) const
{
    auto svg_text = std::make_unique<SvgTextElement>();
    svg_text->setContent(text->getTextData().mContent);
    
    Point centre(text->getTextWidth() / 2.0, text->getTextHeight() / 2.0);
    svg_text->setLocation(centre);
    svg_text->setFontFamily(text->getTextData().mFont.getFaceName());

    svg_text->setFill(text->getSolidMaterial().getFillColor());
    auto opacity = static_cast<float>(text->getSolidMaterial().getFillColor().getAlpha());
    if (opacity != 1.0)
    {
        svg_text->setFillOpacity(opacity);
    }
    svg_text->setFontSize(text->getTextData().mFont.getSize());

    const auto transform = text->getTransform();
    if (!transform.isIdentityTransform())
    {
        svg_text->addAttribute(toTransform(transform));
    }

    document->getRoot()->addChild(std::move(svg_text));
}

std::unique_ptr<XmlAttribute> SvgPainter::toTransform(const Transform& transform) const
{
    auto svg_transform = std::make_unique<XmlAttribute>("transform");

    std::string ops;
    if (!transform.isIdentityTransform())
    {
        ops += "translate(" + std::to_string(transform.getLocation().getX()) + " " + std::to_string(transform.getLocation().getY()) + ") ";
    }
    if (!transform.getScale().isIdentity())
    {
        ops += "scale(" + std::to_string(transform.getScale().mX) + " " + std::to_string(transform.getScale().mY) + ") ";
    }    
    svg_transform->setValue(ops);

    return svg_transform;
}
