#include "DirectX2dPainter.h"

#include "DirectX2dInterface.h"

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

#include "Curve.h"
#include "Arc.h"
#include "QuadraticBezierCurve.h"
#include "CubicBezierCurve.h"

#include "FileLogger.h"

#include "SceneModel.h"

void DirectX2dPainter::startDrawing()
{
	mD2dInterface->getRenderTarget()->BeginDraw();
}

void DirectX2dPainter::finishDrawing()
{
	mD2dInterface->getRenderTarget()->EndDraw();
}

D2D1::ColorF DirectX2dPainter::toD2dColor(const Color& color)
{
	return D2D1::ColorF(static_cast<float>(color.getR() / 255.0), static_cast<float>(color.getG() / 255.0), 
		static_cast<float>(color.getB() / 255.0), static_cast<float>(color.getAlpha()));
}

void DirectX2dPainter::clearBackground(const Color& color)
{
	mD2dInterface->getRenderTarget()->Clear(toD2dColor(color));
}

void DirectX2dPainter::paint(SceneModel* model)
{
	auto rt = mD2dInterface->getRenderTarget();

	if (model->getGeometry()->getType() == AbstractGeometricItem::Type::RECTANGLE)
	{
		paintRect(model);
	}
	else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::CIRCLE)
	{
		paintCircle(model);
	}
	else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::PATH)
	{
		paintPath(model);
	}
	else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::LINE)
	{
		paintLine(model);
	}
	else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::POLYGON)
	{
		paintPolygon(model);
	}
}

void DirectX2dPainter::paintRect(SceneModel* model)
{
	auto rt = mD2dInterface->getRenderTarget();

	auto rect = dynamic_cast<ntk::Rectangle*>(model->getGeometry());

	const auto loc = model->getTransform().getLocation();
	const auto scale_x = model->getTransform().getScaleX();
	const auto scale_y = model->getTransform().getScaleY();

	const auto min_x = static_cast<float>(loc.getX());
	const auto max_x = static_cast<float>(loc.getX() + rect->getWidth() * scale_x);

	const auto min_y = static_cast<float>(loc.getY());
	const auto max_y = static_cast<float>(loc.getY() + rect->getHeight() * scale_y);

	D2D1_RECT_F d2d_rect{ min_x, max_y, max_x, min_y };

	const auto material = model->getSolidMaterial();
	if (rect->getRadius() == 0.0)
	{
		if (material.hasFillColor())
		{
			mSolidBrush->SetColor(toD2dColor(material.getFillColor()));
			rt->FillRectangle(d2d_rect, mSolidBrush.Get());
		}
		if (material.hasStrokeColor())
		{
			mSolidBrush->SetColor(toD2dColor(material.getStrokeColor()));
			rt->DrawRectangle(d2d_rect, mSolidBrush.Get(), 1.0f);
		}
	}
	else
	{
		D2D1_ROUNDED_RECT rounded_rect{ d2d_rect , static_cast<float>(rect->getRadius()), static_cast<float>(rect->getRadius()) };
		if (material.hasFillColor())
		{
			mSolidBrush->SetColor(toD2dColor(material.getFillColor()));
			rt->FillRoundedRectangle(rounded_rect, mSolidBrush.Get());
		}
		if (material.hasStrokeColor())
		{
			mSolidBrush->SetColor(toD2dColor(material.getStrokeColor()));
			rt->DrawRoundedRectangle(rounded_rect, mSolidBrush.Get(), 1.0f);
		}
	}
}

void DirectX2dPainter::paintCircle(SceneModel* model)
{
	const auto loc = model->getTransform().getLocation();
	const auto scale_x = model->getTransform().getScaleX();
	const auto scale_y = model->getTransform().getScaleY();

	auto circle = dynamic_cast<Circle*>(model->getGeometry());
	const auto radius = circle->getRadius() * scale_x;
	const auto radiusy = circle->getMinorRadius() * scale_y;

	D2D1_POINT_2F d2d_centre{ static_cast<float>(loc.getX()), static_cast<float>(loc.getY()) };
	D2D1_ELLIPSE ellipse{ d2d_centre, static_cast<float>(radius), static_cast<float>(radiusy) };

	const auto material = model->getSolidMaterial();
	auto rt = mD2dInterface->getRenderTarget();
	if (material.hasFillColor())
	{
		mSolidBrush->SetColor(toD2dColor(material.getFillColor()));
		rt->FillEllipse(ellipse, mSolidBrush.Get());
	}
	if (material.hasStrokeColor())
	{
		mSolidBrush->SetColor(toD2dColor(material.getStrokeColor()));
		rt->DrawEllipse(ellipse, mSolidBrush.Get(), 1.0f);
	}
}

D2D_POINT_2F DirectX2dPainter::toD2dPoint(const Point& point)
{
	return D2D1::Point2F(static_cast<float>(point.getX()), static_cast<float>(point.getY()));
}

void DirectX2dPainter::paintLine(SceneModel* model)
{
	Microsoft::WRL::ComPtr<ID2D1PathGeometry> path_geom;
	mD2dInterface->getFactory()->CreatePathGeometry(&path_geom);

	Microsoft::WRL::ComPtr<ID2D1GeometrySink> path_sink;

	path_geom->Open(&path_sink);

	auto line = dynamic_cast<Line*>(model->getGeometry());

	path_sink->BeginFigure(toD2dPoint(line->getFirstPoint()), D2D1_FIGURE_BEGIN_FILLED);

	onLine(line, path_sink.Get());

	path_sink->EndFigure(D2D1_FIGURE_END_OPEN);
	path_sink->Close();

	auto rt = mD2dInterface->getRenderTarget();

	const auto loc = model->getTransform().getLocation();
	D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(static_cast<float>(loc.getX()), static_cast<float>(loc.getY()));
	rt->SetTransform(translation);

	const auto material = model->getSolidMaterial();
	if (material.hasFillColor())
	{
		mSolidBrush->SetColor(toD2dColor(material.getFillColor()));
		rt->FillGeometry(path_geom.Get(), mSolidBrush.Get());
	}
	if (material.hasStrokeColor())
	{
		mSolidBrush->SetColor(toD2dColor(material.getStrokeColor()));
		rt->DrawGeometry(path_geom.Get(), mSolidBrush.Get(), 1.0f);
	}

	rt->SetTransform(D2D1::Matrix3x2F::Identity());
}

void DirectX2dPainter::paintPolygon(SceneModel* model)
{
	Microsoft::WRL::ComPtr<ID2D1PathGeometry> path_geom;
	mD2dInterface->getFactory()->CreatePathGeometry(&path_geom);

	Microsoft::WRL::ComPtr<ID2D1GeometrySink> path_sink;

	path_geom->Open(&path_sink);

	auto polygon = dynamic_cast<ntk::Polygon*>(model->getGeometry());

	path_sink->BeginFigure(toD2dPoint(polygon->getLocation()), D2D1_FIGURE_BEGIN_FILLED);

	bool first{ true };
	for (const auto& point : polygon->getPoints().getPoints())
	{
		if (first)
		{
			first = false;
			continue;
		}
		path_sink->AddLine(toD2dPoint(point));
	}

	path_sink->EndFigure(D2D1_FIGURE_END_CLOSED);
	path_sink->Close();

	auto rt = mD2dInterface->getRenderTarget();

	const auto loc = model->getTransform().getLocation();
	D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(static_cast<float>(loc.getX()), static_cast<float>(loc.getY()));
	rt->SetTransform(translation);

	const auto material = model->getSolidMaterial();
	if (material.hasFillColor())
	{
		mSolidBrush->SetColor(toD2dColor(material.getFillColor()));
		rt->FillGeometry(path_geom.Get(), mSolidBrush.Get());
	}
	if (material.hasStrokeColor())
	{
		mSolidBrush->SetColor(toD2dColor(material.getStrokeColor()));
		rt->DrawGeometry(path_geom.Get(), mSolidBrush.Get(), 1.0f);
	}

	rt->SetTransform(D2D1::Matrix3x2F::Identity());
}

void DirectX2dPainter::paintPath(SceneModel* model)
{
	Microsoft::WRL::ComPtr<ID2D1PathGeometry> path_geom;
	mD2dInterface->getFactory()->CreatePathGeometry(&path_geom);

	Microsoft::WRL::ComPtr<ID2D1GeometrySink> path_sink;

	path_geom->Open(&path_sink);

	auto path_item = dynamic_cast<GeometryPath*>(model->getGeometry());
	for (const auto& feature : path_item->getFeatures())
	{
		const auto loc = feature->getLocation();
		path_sink->BeginFigure(toD2dPoint(loc), D2D1_FIGURE_BEGIN_FILLED);

		for (const auto& element : feature->getElements())
		{
			if (element->getType() == AbstractGeometricItem::Type::LINE)
			{
				onLine(element.get(), path_sink.Get());
			}
			else if (element->getType() == AbstractGeometricItem::Type::LINE_SEGMENT)
			{
				onLineSegment(element.get(), path_sink.Get());
			}
			else if (element->getType() == AbstractGeometricItem::Type::CURVE)
			{
				auto curve = dynamic_cast<Curve*>(element.get());
				if (curve->getCurveType() == Curve::CurveType::ARC)
				{
					onArc(curve, path_sink.Get());
				}
				else if (curve->getCurveType() == Curve::CurveType::CUBIC_BEZIER)
				{
					onCubicBezier(curve, path_sink.Get());
				}
				else if (curve->getCurveType() == Curve::CurveType::QUADRATIC_BEZIER)
				{
					onQuadraticBezier(curve, path_sink.Get());
				}
			}
		}
		path_sink->EndFigure(D2D1_FIGURE_END_CLOSED);
	}
	path_sink->Close();

	auto rt = mD2dInterface->getRenderTarget();

	const auto loc = model->getTransform().getLocation();
	D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(static_cast<float>(loc.getX()), static_cast<float>(loc.getY()));
	rt->SetTransform(translation);

	const auto material = model->getSolidMaterial();
	if (material.hasFillColor())
	{
		mSolidBrush->SetColor(toD2dColor(material.getFillColor()));
		rt->FillGeometry(path_geom.Get(), mSolidBrush.Get());
	}
	if (material.hasStrokeColor())
	{
		mSolidBrush->SetColor(toD2dColor(material.getStrokeColor()));
		rt->DrawGeometry(path_geom.Get(), mSolidBrush.Get(), 1.0f);
	}
	rt->SetTransform(D2D1::Matrix3x2F::Identity());
}

void DirectX2dPainter::onArc(Curve* element, ID2D1GeometrySink* sink)
{
	auto arc = dynamic_cast<ntk::Arc*>(element);

	const auto end = toD2dPoint(arc->getEndPoint());
	D2D1_SIZE_F size{static_cast<float>(arc->getRx()), static_cast<float>(arc->getRx()) };
	const auto angle = static_cast<float>(arc->getRotation());
	D2D1_SWEEP_DIRECTION direction = arc->getSweepParam() ? D2D1_SWEEP_DIRECTION_CLOCKWISE : D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
	D2D1_ARC_SIZE arc_size = arc->getUseLargeArc() ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL;

	D2D1_ARC_SEGMENT segment{ end , size , angle , direction , arc_size };
	sink->AddArc(segment);
}

void DirectX2dPainter::onQuadraticBezier(Curve* element, ID2D1GeometrySink* sink)
{
	auto bezier = dynamic_cast<QuadraticBezierCurve*>(element);
	D2D1_QUADRATIC_BEZIER_SEGMENT segment{ toD2dPoint(bezier->getControlPoint()) , toD2dPoint(bezier->getEndPoint()) };
	sink->AddQuadraticBezier(segment);
}

void DirectX2dPainter::onCubicBezier(Curve* element, ID2D1GeometrySink* sink)
{
	auto bezier = dynamic_cast<CubicBezierCurve*>(element);
	D2D1_BEZIER_SEGMENT segment{ toD2dPoint(bezier->getStartControlPoint()), toD2dPoint(bezier->getEndControlPoint()), toD2dPoint(bezier->getEndPoint()) };
	sink->AddBezier(segment);
}

void DirectX2dPainter::onLine(PathElement* element, ID2D1GeometrySink* sink)
{
	for (const auto& point : dynamic_cast<Line*>(element)->getPoints().getPoints())
	{
		sink->AddLine(toD2dPoint(point));
	}
}

void DirectX2dPainter::onLineSegment(PathElement* element, ID2D1GeometrySink* sink)
{
	const auto loc = element->getEndPoint();
	sink->AddLine(toD2dPoint(loc));
}

void DirectX2dPainter::setD2dInterface(DirectX2dInterface* d2dIterface)
{
	mD2dInterface = d2dIterface;
	auto hr = mD2dInterface->getRenderTarget()->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black, 1.0f), &mSolidBrush);
}