#include "PathPostScriptConverter.h"

#include "Path.h"

#include "StringUtils.h"
#include "FileLogger.h"

#include "Line.h"
#include "LineSegment.h"
#include "Arc.h"
#include "QuadraticBezierCurve.h"
#include "CubicBezierCurve.h"

#include "PointParser.h"

void PathPostScriptConverter::fromPostScript(GeometryPath* targetPath, const std::string& postScriptPath)
{
	mCurrentPoint = Point();

	for (auto c : postScriptPath)
	{
		if (c == 'M' || c == 'm')
		{
			mLineState = LineState::IN_FIRST_POINT;
			mPositionState = getPositionState(c);

			mWorkingFeature = std::make_unique<GeometryPathFeature>();
		}
		else if (c == 'H' || c == 'h')
		{
			onNewElement(c);
			mLineState = LineState::IN_HORIZONTAL;
		}
		else if (c == 'V' || c == 'v')
		{
			onNewElement(c);
			mLineState = LineState::IN_VERTICAL;
		}
		else if (c == 'L' || c == 'l')
		{
			onNewElement(c);
			mLineState = LineState::IN_LINE;
		}
		else if (c == 'C' || c == 'c')
		{
			onNewElement(c);
			mLineState = LineState::IN_CUBIC_BEZIER;
		}
		else if (c == 'Q' || c == 'q')
		{
			onNewElement(c);
			mLineState = LineState::IN_QUADRATIC_BEZIER;
		}
		else if (c == 'A' || c == 'a')
		{
			onNewElement(c);
			mLineState = LineState::IN_ARC;
		}
		else if (c == 'Z' || c == 'z')
		{
			onNewElement(c);

			mLineState = LineState::START;
			targetPath->addFeature(std::move(mWorkingFeature));
			mWorkingFeature = nullptr;
		}
		else if (std::isblank(c))
		{
			onNonNumeric();
		}
		else if (std::isalpha(c))
		{
			onNewElement(c);
			mLineState = LineState::IN_UNSUPPORTED;
		}
		else
		{
			mBuffer.push_back(c);
		}
	}
}

void PathPostScriptConverter::onNewElement(char c)
{
	onNonNumeric();

	onElementEnd();

	mPositionState = getPositionState(c);
}

void PathPostScriptConverter::onNonNumeric()
{
	if (!mBuffer.empty())
	{
		mPointBuffer.push_back(std::stod(mBuffer));
		mBuffer.clear();
	}
}

PathPostScriptConverter::PositionState PathPostScriptConverter::getPositionState(char c) const
{
	return std::isupper(c) ? PositionState::ABSOLUTE : PositionState::RELATIVE;
}

void PathPostScriptConverter::onElementEnd()
{
	if (mPointBuffer.empty())
	{
		return;
	}

	PathElementPtr element;
	if (mLineState == LineState::IN_HORIZONTAL)
	{
		element = onHorizontalLineTo();
	}
	else if (mLineState == LineState::IN_VERTICAL)
	{
		element = onVerticalLineTo();
	}
	else if (mLineState == LineState::IN_LINE)
	{
		element = onLineTo();
	}
	else if (mLineState == LineState::IN_ARC)
	{
		element = onArc();
	}
	else if (mLineState == LineState::IN_QUADRATIC_BEZIER)
	{
		element = onQuadraticBezier();
	}
	else if (mLineState == LineState::IN_CUBIC_BEZIER)
	{
		element = onCubicBezier();
	}
	else if (mLineState == LineState::IN_FIRST_POINT)
	{
		onMoveTo();
	}

	if (element)
	{
		const auto positioning = (mPositionState == PositionState::RELATIVE) ? PathElement::PostscriptPositioning::RELATIVE_TO : PathElement::PostscriptPositioning::ABSOLUTE_TO;
		element->setPostscriptPositioning(positioning);
		mCurrentPoint = element->getEndPoint();
		mWorkingFeature->addElement(std::move(element));
	}

	mPointBuffer.clear();
}

void PathPostScriptConverter::onMoveTo()
{
	if (mPointBuffer.size() != 2)
	{
		return;
	}
	if (mPositionState == PositionState::RELATIVE)
	{
		mCurrentPoint = Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY() + mPointBuffer[1]);
	}
	else
	{
		mCurrentPoint = Point(mPointBuffer[0], mPointBuffer[1]);
	}
}

PathElementPtr PathPostScriptConverter::onHorizontalLineTo()
{
	PathElementPtr element;
	if (mPointBuffer.size() == 1)
	{
		if (mPositionState == PositionState::RELATIVE)
		{
			element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY()));
		}
		else
		{
			element = std::make_unique<LineSegment>(mCurrentPoint, Point(mPointBuffer[0], mCurrentPoint.getY()));
		}
	}
	else
	{
		Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::HORIZONTAL_REL : Line::InputBufferType::HORIZONTAL_ABS;
		element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
	}
	return element;
}

PathElementPtr PathPostScriptConverter::onVerticalLineTo()
{
	PathElementPtr element;
	if (mPointBuffer.size() == 1)
	{
		if (mPositionState == PositionState::RELATIVE)
		{
			element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX(), mCurrentPoint.getY() + mPointBuffer[0]));
		}
		else
		{
			element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX(), mPointBuffer[0]));
		}
	}
	else
	{
		Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::VERTICAL_REL : Line::InputBufferType::VERTICAL_ABS;
		element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
	}
	return element;
}

PathElementPtr PathPostScriptConverter::onLineTo()
{
	PathElementPtr element;
	if (mPointBuffer.size() == 2)
	{
		if (mPositionState == PositionState::RELATIVE)
		{
			const auto next_point = Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY() + mPointBuffer[1]);
			element = std::make_unique<LineSegment>(mCurrentPoint, next_point);
		}
		else
		{
			const auto next_point = Point(mPointBuffer[0], mPointBuffer[1]);
			element = std::make_unique<LineSegment>(mCurrentPoint, next_point);
		}
	}
	else
	{
		Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::XY_REL : Line::InputBufferType::XY_ABS;
		element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
	}
	return element;
}

PathElementPtr PathPostScriptConverter::onArc()
{
	PathElementPtr element;
	if (mPointBuffer.size() == 7)
	{
		double rx = mPointBuffer[0];
		double ry = mPointBuffer[1];
		double rotation = mPointBuffer[2];
		bool large_arc = bool(mPointBuffer[3]);
		bool sweep = bool(mPointBuffer[4]);

		if (mPositionState == PositionState::RELATIVE)
		{
			const auto end_point = Point(mCurrentPoint.getX() + mPointBuffer[5], mCurrentPoint.getY() + mPointBuffer[6]);
			element = std::make_unique<ntk::Arc>(mCurrentPoint, end_point, rx, ry, rotation, large_arc, sweep);
		}
		else
		{
			const auto end_point = Point(mPointBuffer[5], mPointBuffer[6]);
			element = std::make_unique<ntk::Arc>(mCurrentPoint, end_point, rx, ry, rotation, large_arc, sweep);
		}
	}
	return element;
}

PathElementPtr PathPostScriptConverter::onQuadraticBezier()
{
	PathElementPtr element;
	if (mPointBuffer.size() == 4)
	{
		double control_x = mPointBuffer[0];
		double control_y = mPointBuffer[1];
		double end_x = mPointBuffer[2];
		double end_y = mPointBuffer[3];
		if (mPositionState == PositionState::RELATIVE)
		{
			const auto control_point = Point(mCurrentPoint.getX() + control_x, mCurrentPoint.getY() + control_y);
			const auto end_point = Point(mCurrentPoint.getX() + end_x, mCurrentPoint.getY() + end_y);
			element = std::make_unique<QuadraticBezierCurve>(mCurrentPoint, end_point, control_point);
		}
		else
		{
			const auto control_point = Point(control_x, control_y);
			const auto end_point = Point(end_x, end_y);
			element = std::make_unique<QuadraticBezierCurve>(mCurrentPoint, end_point, control_point);
		}
	}
	return element;
}

PathElementPtr PathPostScriptConverter::onCubicBezier()
{
	PathElementPtr element;
	if (mPointBuffer.size() == 6)
	{
		double control0_x = mPointBuffer[0];
		double control0_y = mPointBuffer[1];
		double control1_x = mPointBuffer[2];
		double control1_y = mPointBuffer[3];
		double end_x = mPointBuffer[4];
		double end_y = mPointBuffer[5];
		if (mPositionState == PositionState::RELATIVE)
		{
			const auto control_point0 = Point(mCurrentPoint.getX() + control0_x, mCurrentPoint.getY() + control0_y);
			const auto control_point1 = Point(mCurrentPoint.getX() + control1_x, mCurrentPoint.getY() + control1_y);
			const auto end_point = Point(mCurrentPoint.getX() + end_x, mCurrentPoint.getY() + end_y);
			element = std::make_unique<CubicBezierCurve>(mCurrentPoint, end_point, control_point0, control_point1);
		}
		else
		{
			const auto control_point0 = Point(control0_x, control0_y);
			const auto control_point1 = Point(control1_x, control1_y);
			const auto end_point = Point(end_x, end_y);
			element = std::make_unique<CubicBezierCurve>(mCurrentPoint, end_point, control_point0, control_point1);
		}
	}
	return element;
}

std::string PathPostScriptConverter::toPostScript(const GeometryPath* targetPath)
{
	std::string path;
	for (const auto& feature : targetPath->getFeatures())
	{
		auto start_loc = feature->getLocation();
		path += "M" + PointParser::toString(start_loc, 2, " ", mPrecision);

		for (const auto& path_element : feature->getElements())
		{
			path += path_element->toPostScriptString(mPrecision);
		}
		path += "Z ";
	}
	return path;
}

