/*
	File                 : Symbol.cpp
	Project              : LabPlot
	Description          : Symbol
	--------------------------------------------------------------------
	SPDX-FileCopyrightText: 2015-2023 Alexander Semke <alexander.semke@web.de>
	SPDX-FileCopyrightText: 2021 Stefan Gerlach <stefan.gerlach@uni.kn>
	SPDX-License-Identifier: GPL-2.0-or-later
*/

/*!
  \class Symbol
  \brief

  \ingroup worksheet
*/

#include "Symbol.h"
#include "SymbolPrivate.h"
#include "backend/lib/XmlStreamReader.h"
#include "backend/lib/commandtemplates.h"
#include "backend/worksheet/Worksheet.h"

#include <KConfigGroup>
#include <KLocalizedString>

#include <QFont>
#include <QPainter>

#include <gsl/gsl_math.h>

// order of styles in UI comboboxes (defined in Symbol.h, order can be changed without breaking projects)
static QVector<Symbol::Style> StyleOrder = {Symbol::Style::NoSymbols,
											Symbol::Style::Circle,
											Symbol::Style::Square,
											Symbol::Style::EquilateralTriangle,
											Symbol::Style::Line,
											Symbol::Style::Cross,
											Symbol::Style::Tri,
											Symbol::Style::X,
											Symbol::Style::Asterisk,
											Symbol::Style::XPlus,
											Symbol::Style::TallPlus,
											Symbol::Style::LatinCross,
											Symbol::Style::DotPlus,
											Symbol::Style::Pin,
											Symbol::Style::Hash,
											Symbol::Style::SquareX,
											Symbol::Style::SquarePlus,
											Symbol::Style::SquareHalf,
											Symbol::Style::SquareDot,
											Symbol::Style::SquareDiag,
											Symbol::Style::SquareTriangle,
											Symbol::Style::CircleHalf,
											Symbol::Style::CircleDot,
											Symbol::Style::CircleX,
											Symbol::Style::CircleTri,
											Symbol::Style::Peace,
											Symbol::Style::TriangleDot,
											Symbol::Style::TriangleLine,
											Symbol::Style::TriangleHalf,
											Symbol::Style::RightTriangle,
											Symbol::Style::Bar,
											Symbol::Style::PeakedBar,
											Symbol::Style::SkewedBar,
											Symbol::Style::Diamond,
											Symbol::Style::Lozenge,
											Symbol::Style::Tie,
											Symbol::Style::TinyTie,
											Symbol::Style::Boomerang,
											Symbol::Style::SmallBoomerang,
											Symbol::Style::Star,
											Symbol::Style::Star3,
											Symbol::Style::Star4,
											Symbol::Style::Star5,
											Symbol::Style::Star6,
											Symbol::Style::Plus,
											Symbol::Style::Latin,
											Symbol::Style::David,
											Symbol::Style::Home,
											Symbol::Style::Pentagon,
											Symbol::Style::Hexagon,
											Symbol::Style::Female,
											Symbol::Style::Male,
											Symbol::Style::Flower,
											Symbol::Style::Flower2,
											Symbol::Style::Flower3,
											Symbol::Style::Flower5,
											Symbol::Style::Flower6,
											Symbol::Style::Heart,
											Symbol::Style::Spade,
											Symbol::Style::Club,
											Symbol::Style::Lightning};

Symbol::Symbol(const QString& name)
	: AbstractAspect(name, AspectType::AbstractAspect)
	, d_ptr(new SymbolPrivate(this)) {
}

Symbol::~Symbol() {
	delete d_ptr;
}

void Symbol::init(const KConfigGroup& group) {
	Q_D(Symbol);

	Symbol::Style defaultStyle = Symbol::Style::NoSymbols;
	double defaultSize = Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point);
	QColor defaultBorderColor(Qt::black);
	double defaultBorderWidth = Worksheet::convertToSceneUnits(0.0, Worksheet::Unit::Point);

	const auto type = parentAspect()->type();
	if (type == AspectType::CustomPoint || type == AspectType::LollipopPlot)
		defaultStyle = Symbol::Style::Circle;
	else if (type == AspectType::DatapickerImage || type == AspectType::DatapickerCurve) {
		defaultStyle = Symbol::Style::Cross;
		defaultSize = Worksheet::convertToSceneUnits(7, Worksheet::Unit::Point);
		defaultBorderColor = Qt::red;
		defaultBorderWidth = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point);
	}

	d->style = (Symbol::Style)group.readEntry("SymbolStyle", (int)defaultStyle);
	d->size = group.readEntry("SymbolSize", defaultSize);
	d->rotationAngle = group.readEntry("SymbolRotation", 0.0);
	d->opacity = group.readEntry("SymbolOpacity", 1.0);
	d->brush.setStyle((Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern));
	d->brush.setColor(group.readEntry("SymbolFillingColor", QColor(Qt::red)));
	d->pen.setStyle((Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine));
	d->pen.setColor(group.readEntry("SymbolBorderColor", defaultBorderColor));
	d->pen.setWidthF(group.readEntry("SymbolBorderWidth", defaultBorderWidth));
}

// ##############################################################################
// ##########################  getter methods  ##################################
// ##############################################################################
BASIC_SHARED_D_READER_IMPL(Symbol, Symbol::Style, style, style)
BASIC_SHARED_D_READER_IMPL(Symbol, qreal, opacity, opacity)
BASIC_SHARED_D_READER_IMPL(Symbol, qreal, rotationAngle, rotationAngle)
BASIC_SHARED_D_READER_IMPL(Symbol, qreal, size, size)
BASIC_SHARED_D_READER_IMPL(Symbol, QBrush, brush, brush)
BASIC_SHARED_D_READER_IMPL(Symbol, QPen, pen, pen)

// ##############################################################################
// #################  setter methods and undo commands ##########################
// ##############################################################################
STD_SETTER_CMD_IMPL_F_S(Symbol, SetStyle, Symbol::Style, style, updateSymbols)
void Symbol::setStyle(Symbol::Style style) {
	Q_D(Symbol);
	if (style != d->style)
		exec(new SymbolSetStyleCmd(d, style, ki18n("%1: set symbol style")));
}

STD_SETTER_CMD_IMPL_F_S(Symbol, SetSize, qreal, size, updateSymbols)
void Symbol::setSize(qreal size) {
	Q_D(Symbol);
	if (!qFuzzyCompare(1 + size, 1 + d->size))
		exec(new SymbolSetSizeCmd(d, size, ki18n("%1: set symbol size")));
}

STD_SETTER_CMD_IMPL_F_S(Symbol, SetColor, QColor, color, update)
void Symbol::setColor(const QColor& color) {
	Q_D(Symbol);
	if (color != d->color)
		exec(new SymbolSetColorCmd(d, color, ki18n("%1: set symbol color")));
}

STD_SETTER_CMD_IMPL_F_S(Symbol, SetRotationAngle, qreal, rotationAngle, updateSymbols)
void Symbol::setRotationAngle(qreal angle) {
	Q_D(Symbol);
	if (!qFuzzyCompare(1 + angle, 1 + d->rotationAngle))
		exec(new SymbolSetRotationAngleCmd(d, angle, ki18n("%1: rotate symbols")));
}

STD_SETTER_CMD_IMPL_F_S(Symbol, SetBrush, QBrush, brush, updatePixmap)
void Symbol::setBrush(const QBrush& brush) {
	Q_D(Symbol);
	if (brush != d->brush)
		exec(new SymbolSetBrushCmd(d, brush, ki18n("%1: set symbol filling")));
}

STD_SETTER_CMD_IMPL_F_S(Symbol, SetPen, QPen, pen, updateSymbols)
void Symbol::setPen(const QPen& pen) {
	Q_D(Symbol);
	if (pen != d->pen)
		exec(new SymbolSetPenCmd(d, pen, ki18n("%1: set symbol outline style")));
}

STD_SETTER_CMD_IMPL_F_S(Symbol, SetOpacity, qreal, opacity, updatePixmap)
void Symbol::setOpacity(qreal opacity) {
	Q_D(Symbol);
	if (opacity != d->opacity)
		exec(new SymbolSetOpacityCmd(d, opacity, ki18n("%1: set symbols opacity")));
}

// ##############################################################################
// ####################### Private implementation ###############################
// ##############################################################################
SymbolPrivate::SymbolPrivate(Symbol* owner)
	: q(owner) {
}

QString SymbolPrivate::name() const {
	return q->parentAspect()->name();
}

void SymbolPrivate::update() {
	pen.setColor(color);
	brush.setColor(color);
	Q_EMIT q->updateRequested();
}

void SymbolPrivate::updateSymbols() {
	Q_EMIT q->updateRequested();
}

void SymbolPrivate::updatePixmap() {
	Q_EMIT q->updatePixmapRequested();
}

// ##############################################################################
// ##################  Serialization/Deserialization  ###########################
// ##############################################################################
//! Save as XML
void Symbol::save(QXmlStreamWriter* writer) const {
	Q_D(const Symbol);

	if (parentAspect()->type() == AspectType::CustomPoint || parentAspect()->type() == AspectType::LollipopPlot)
		writer->writeStartElement(QStringLiteral("symbol"));
	else if (parentAspect()->type() == AspectType::BoxPlot)
		writer->writeStartElement(name()); // BoxPlot has multiple symbols, differentiated by their names
	else
		writer->writeStartElement(QStringLiteral("symbols")); // keep the backward compatibility for "symbols" used in XYCurve and Histogram

	writer->writeAttribute(QStringLiteral("symbolsStyle"), QString::number(static_cast<int>(d->style)));
	writer->writeAttribute(QStringLiteral("opacity"), QString::number(d->opacity));
	writer->writeAttribute(QStringLiteral("rotation"), QString::number(d->rotationAngle));
	writer->writeAttribute(QStringLiteral("size"), QString::number(d->size));
	WRITE_QBRUSH(d->brush);
	WRITE_QPEN(d->pen);
	writer->writeEndElement(); // close "Symbol" section
}

//! Load from XML
bool Symbol::load(XmlStreamReader* reader, bool preview) {
	if (preview)
		return true;

	Q_D(Symbol);
	QString str;
	const auto& attribs = reader->attributes();
	READ_INT_VALUE("symbolsStyle", style, Symbol::Style);
	READ_DOUBLE_VALUE("opacity", opacity);
	READ_DOUBLE_VALUE("rotation", rotationAngle);
	READ_DOUBLE_VALUE("size", size);
	READ_QBRUSH(d->brush);
	READ_QPEN(d->pen);

	return true;
}

// ##############################################################################
// #########################  Theme management ##################################
// ##############################################################################
void Symbol::loadThemeConfig(const KConfigGroup& group, const QColor& themeColor) {
	setOpacity(group.readEntry("SymbolOpacity", 1.0));
	QBrush brush;
	brush.setStyle((Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern));
	brush.setColor(themeColor);
	setBrush(brush);

	QPen p;
	p.setStyle((Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine));
	p.setColor(themeColor);
	p.setWidthF(group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Unit::Point)));
	setPen(p);
}

void Symbol::saveThemeConfig(const KConfigGroup& /*group*/) const {
	// TODO:
	// 	group.writeEntry("SymbolOpacity", opacity());
}

//*************************************************************
//********************* static functions **********************
//*************************************************************

int Symbol::stylesCount() {
	return ENUM_COUNT(Symbol, Style);
}

QString Symbol::styleName(Symbol::Style style) {
	switch (style) {
	case Style::NoSymbols:
		return i18n("none");
	case Style::Circle:
		return i18n("circle");
	case Style::Square:
		return i18n("square");
	case Style::EquilateralTriangle:
		return i18n("equilateral triangle");
	case Style::RightTriangle:
		return i18n("right triangle");
	case Style::Bar:
		return i18n("bar");
	case Style::PeakedBar:
		return i18n("peaked bar");
	case Style::SkewedBar:
		return i18n("skewed bar");
	case Style::Diamond:
		return i18n("diamond");
	case Style::Lozenge:
		return i18n("lozenge");
	case Style::Tie:
		return i18n("tie");
	case Style::TinyTie:
		return i18n("tiny tie");
	case Style::Plus:
		return i18n("plus");
	case Style::Boomerang:
		return i18n("boomerang");
	case Style::SmallBoomerang:
		return i18n("small boomerang");
	case Style::Star4:
		return i18n("star4");
	case Style::Star5:
		return i18n("star5");
	case Style::Line:
		return i18n("line");
	case Style::Cross:
		return i18n("cross");
	case Style::Heart:
		return i18n("heart");
	case Style::Lightning:
		return i18n("lightning");
	case Style::X:
		return i18n("character 'X'");
	case Style::Asterisk:
		return i18n("asterisk");
	case Style::Tri:
		return i18n("tri");
	case Style::XPlus:
		return i18n("x plus");
	case Style::TallPlus:
		return i18n("tall plus");
	case Style::LatinCross:
		return i18n("latin cross");
	case Style::DotPlus:
		return i18n("dot plus");
	case Style::Hash:
		return i18n("hash");
	case Style::SquareX:
		return i18n("square x");
	case Style::SquarePlus:
		return i18n("square plus");
	case Style::SquareHalf:
		return i18n("half square");
	case Style::SquareDot:
		return i18n("square dot");
	case Style::SquareDiag:
		return i18n("diag square");
	case Style::SquareTriangle:
		return i18n("square triangle");
	case Style::CircleHalf:
		return i18n("circle half");
	case Style::CircleDot:
		return i18n("circle dot");
	case Style::CircleX:
		return i18n("circle x");
	case Style::CircleTri:
		return i18n("circle tri");
	case Style::Peace:
		return i18n("peace");
	case Style::TriangleDot:
		return i18n("triangle dot");
	case Style::TriangleLine:
		return i18n("triangle line");
	case Style::TriangleHalf:
		return i18n("half triangle");
	case Style::Flower:
		return i18n("flower");
	case Style::Flower2:
		return i18n("flower2");
	case Style::Flower3:
		return i18n("flower3");
	case Style::Flower5:
		return i18n("flower5");
	case Style::Flower6:
		return i18n("flower6");
	case Style::Star:
		return i18n("star");
	case Style::Star3:
		return i18n("star3");
	case Style::Star6:
		return i18n("star6");
	case Style::Pentagon:
		return i18n("pentagon");
	case Style::Hexagon:
		return i18n("hexagon");
	case Style::Latin:
		return i18n("latin");
	case Style::David:
		return i18n("david");
	case Style::Home:
		return i18n("home");
	case Style::Pin:
		return i18n("pin");
	case Style::Female:
		return i18n("female");
	case Style::Male:
		return i18n("male");
	case Style::Spade:
		return i18n("spade");
	case Style::Club:
		return i18n("club");
	}

	return {};
}

Symbol::Style Symbol::indexToStyle(const int index) {
	return StyleOrder.at(index);
}

QPainterPath Symbol::stylePath(Symbol::Style style) {
	QPainterPath path;
	QPolygonF polygon;

	switch (style) {
	case Style::NoSymbols:
		break;
	case Style::Circle:
		path.addEllipse(QPoint(0, 0), 0.5, 0.5);
		break;
	case Style::Square:
		path.addRect(QRectF(-0.5, -0.5, 1.0, 1.0));
		break;
	case Style::EquilateralTriangle:
		polygon << QPointF(-0.5, 0.5) << QPointF(0, -0.5) << QPointF(0.5, 0.5) << QPointF(-0.5, 0.5);
		path.addPolygon(polygon);
		break;
	case Style::RightTriangle:
		polygon << QPointF(-0.5, -0.5) << QPointF(0.5, 0.5) << QPointF(-0.5, 0.5) << QPointF(-0.5, -0.5);
		path.addPolygon(polygon);
		break;
	case Style::Bar:
		path.addRect(QRectF(-0.5, -0.2, 1.0, 0.4));
		break;
	case Style::PeakedBar:
		polygon << QPointF(-0.5, 0) << QPointF(-0.3, -0.2) << QPointF(0.3, -0.2) << QPointF(0.5, 0) << QPointF(0.3, 0.2) << QPointF(-0.3, 0.2)
				<< QPointF(-0.5, 0);
		path.addPolygon(polygon);
		break;
	case Style::SkewedBar:
		polygon << QPointF(-0.5, 0.2) << QPointF(-0.2, -0.2) << QPointF(0.5, -0.2) << QPointF(0.2, 0.2) << QPointF(-0.5, 0.2);
		path.addPolygon(polygon);
		break;
	case Style::Diamond:
		polygon << QPointF(-0.5, 0) << QPointF(0, -0.5) << QPointF(0.5, 0) << QPointF(0, 0.5) << QPointF(-0.5, 0);
		path.addPolygon(polygon);
		break;
	case Style::Lozenge:
		polygon << QPointF(-0.25, 0) << QPointF(0, -0.5) << QPointF(0.25, 0) << QPointF(0, 0.5) << QPointF(-0.25, 0);
		path.addPolygon(polygon);
		break;
	case Style::Tie:
		polygon << QPointF(-0.5, -0.5) << QPointF(0.5, -0.5) << QPointF(-0.5, 0.5) << QPointF(0.5, 0.5) << QPointF(-0.5, -0.5);
		path.addPolygon(polygon);
		break;
	case Style::TinyTie:
		polygon << QPointF(-0.2, -0.5) << QPointF(0.2, -0.5) << QPointF(-0.2, 0.5) << QPointF(0.2, 0.5) << QPointF(-0.2, -0.5);
		path.addPolygon(polygon);
		break;
	case Style::Plus:
		polygon << QPointF(-0.2, -0.5) << QPointF(0.2, -0.5) << QPointF(0.2, -0.2) << QPointF(0.5, -0.2) << QPointF(0.5, 0.2) << QPointF(0.2, 0.2)
				<< QPointF(0.2, 0.5) << QPointF(-0.2, 0.5) << QPointF(-0.2, 0.2) << QPointF(-0.5, 0.2) << QPointF(-0.5, -0.2) << QPointF(-0.2, -0.2)
				<< QPointF(-0.2, -0.5);
		path.addPolygon(polygon);
		break;
	case Style::Boomerang:
		polygon << QPointF(-0.5, 0.5) << QPointF(0, -0.5) << QPointF(0.5, 0.5) << QPointF(0, 0) << QPointF(-0.5, 0.5);
		path.addPolygon(polygon);
		break;
	case Style::SmallBoomerang:
		polygon << QPointF(-0.3, 0.5) << QPointF(0, -0.5) << QPointF(0.3, 0.5) << QPointF(0, 0) << QPointF(-0.3, 0.5);
		path.addPolygon(polygon);
		break;
	case Style::Star4:
		polygon << QPointF(-0.5, 0) << QPointF(-0.1, -0.1) << QPointF(0, -0.5) << QPointF(0.1, -0.1) << QPointF(0.5, 0) << QPointF(0.1, 0.1) << QPointF(0, 0.5)
				<< QPointF(-0.1, 0.1) << QPointF(-0.5, 0);
		path.addPolygon(polygon);
		break;
	case Style::Star5:
		polygon << QPointF(-0.5, 0) << QPointF(-0.1, -0.1) << QPointF(0, -0.5) << QPointF(0.1, -0.1) << QPointF(0.5, 0) << QPointF(0.1, 0.1)
				<< QPointF(0.5, 0.5) << QPointF(0, 0.2) << QPointF(-0.5, 0.5) << QPointF(-0.1, 0.1) << QPointF(-0.5, 0);
		path.addPolygon(polygon);
		break;
	case Style::Line:
		path = QPainterPath(QPointF(0, -0.5));
		path.lineTo(0, 0.5);
		break;
	case Style::Cross:
		path = QPainterPath(QPointF(0, -0.5));
		path.lineTo(0, 0.5);
		path.moveTo(-0.5, 0);
		path.lineTo(0.5, 0);
		break;
	case Style::Heart: {
		// https://mathworld.wolfram.com/HeartCurve.html with additional
		// normalization to fit into a 1.0x1.0 rectangular
		int steps = 100;
		double range = 2. * M_PI / (steps - 1);
		for (int i = 0; i < steps; ++i) {
			double t = i * range + M_PI / 2;
			double x = gsl_pow_3(sin(t));
			double y = -(13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)) / 17;
			polygon << QPointF(x / 2, y / 2);
		}
		double t = M_PI / 2.;
		double x = gsl_pow_3(sin(t));
		double y = -(13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)) / 17;
		polygon << QPointF(x / 2, y / 2);
		path.addPolygon(polygon);
		break;
	}
	case Style::Lightning:
		polygon << QPointF(0, 0.5) << QPointF(0.4, -0.03) << QPointF(0, -0.03) << QPointF(0.2, -0.5) << QPointF(-0.4, 0.1) << QPointF(0.06, 0.1)
				<< QPointF(0, 0.5);
		path.addPolygon(polygon);
		break;
	case Style::X:
		path = QPainterPath(QPointF(-0.4, -0.5));
		path.lineTo(0.4, 0.5);
		path.moveTo(0.4, -0.5);
		path.lineTo(-0.4, 0.5);
		break;
	case Style::Asterisk:
		path = QPainterPath(QPointF(0., .5));
		path.lineTo(0., -.5);
		path.moveTo(M_SQRT3 / 4., -.25);
		path.lineTo(-M_SQRT3 / 4., .25);
		path.moveTo(M_SQRT3 / 4., .25);
		path.lineTo(-M_SQRT3 / 4., -.25);
		break;
	case Style::Tri:
		path = QPainterPath(QPointF(0., 0.));
		path.lineTo(0., -1.);
		path.moveTo(0., 0.);
		path.lineTo(-M_SQRT3 / 2., 1. / 2.);
		path.moveTo(0., 0.);
		path.lineTo(M_SQRT3 / 2., 1. / 2.);
		break;
	case Style::XPlus:
		path = QPainterPath(QPointF(.5, 0.));
		path.lineTo(-.5, 0.);
		path.moveTo(0., .5);
		path.lineTo(0., -.5);
		path.moveTo(.5 / M_SQRT2, .5 / M_SQRT2);
		path.lineTo(-.5 / M_SQRT2, -.5 / M_SQRT2);
		path.moveTo(.5 / M_SQRT2, -.5 / M_SQRT2);
		path.lineTo(-.5 / M_SQRT2, .5 / M_SQRT2);
		break;
	case Style::TallPlus:
		path = QPainterPath(QPointF(.25, 0.));
		path.lineTo(-.25, 0.);
		path.moveTo(0., .5);
		path.lineTo(0., -.5);
		break;
	case Style::LatinCross:
		path = QPainterPath(QPointF(0., .5));
		path.lineTo(0., -.5);
		path.moveTo(-1. / 3., -1. / 6.);
		path.lineTo(1. / 3., -1. / 6.);
		break;
	case Style::DotPlus:
		path = QPainterPath(QPointF(0., .5));
		path.lineTo(0., .25);
		path.moveTo(0., -.5);
		path.lineTo(0., -.25);
		path.moveTo(.5, 0.);
		path.lineTo(.25, 0.);
		path.moveTo(-.5, 0.);
		path.lineTo(-.25, 0.);
		path.addEllipse(-.05, -.05, .1, .1);
		break;
	case Style::Hash:
		path = QPainterPath(QPointF(-.25, .5));
		path.lineTo(-.25, -.5);
		path.moveTo(.25, .5);
		path.lineTo(.25, -.5);
		path.moveTo(.5, .25);
		path.lineTo(-.5, .25);
		path.moveTo(.5, -.25);
		path.lineTo(-.5, -.25);
		break;
	case Style::SquareX:
		path = QPainterPath(QPointF(-.5, .5));
		path.lineTo(.5, -.5);
		path.lineTo(-.5, -.5);
		path.lineTo(.5, .5);
		path.lineTo(-.5, .5);
		path.moveTo(-.5, .5);
		path.lineTo(-.5, -.5);
		path.moveTo(.5, .5);
		path.lineTo(.5, -.5);
		break;
	case Style::SquarePlus:
		path = QPainterPath(QPointF(-.5, .5));
		path.lineTo(-.5, 0.);
		path.lineTo(.5, 0.);
		path.lineTo(.5, -.5);
		path.lineTo(0., -.5);
		path.lineTo(0., .5);
		path.lineTo(-.5, .5);
		path.moveTo(.5, .5);
		path.lineTo(0., .5);
		path.moveTo(.5, .5);
		path.lineTo(.5, 0);
		path.moveTo(-.5, -.5);
		path.lineTo(0., -.5);
		path.moveTo(-.5, -.5);
		path.lineTo(-.5, 0);
		break;
	case Style::SquareHalf:
		path = QPainterPath(QPointF(-.5, .5));
		path.lineTo(.5, .5);
		path.lineTo(.5, 0.);
		path.lineTo(-.5, 0.);
		path.lineTo(-.5, .5);
		path.moveTo(-.5, -.5);
		path.lineTo(-.5, 0);
		path.moveTo(-.5, -.5);
		path.lineTo(.5, -.5);
		path.moveTo(.5, -.5);
		path.lineTo(.5, 0.);
		break;
	case Style::SquareDot:
		path.addEllipse(-.1, -.1, .2, .2);
		path.addRect(QRectF(-0.5, -0.5, 1.0, 1.0));
		break;
	case Style::SquareDiag:
		path = QPainterPath(QPointF(-.5, .5));
		path.lineTo(.5, .5);
		path.lineTo(-.5, -.5);
		path.lineTo(-.5, .5);
		path.moveTo(.5, -.5);
		path.lineTo(.5, .5);
		path.moveTo(.5, -.5);
		path.lineTo(-.5, -.5);
		break;
	case Style::SquareTriangle:
		path = QPainterPath(QPointF(-.5, .5));
		path.lineTo(0, -.5);
		path.lineTo(.5, .5);
		path.lineTo(-.5, .5);
		path.moveTo(.5, -.5);
		path.lineTo(-.5, -.5);
		path.moveTo(.5, -.5);
		path.lineTo(.5, .5);
		path.moveTo(-.5, -.5);
		path.lineTo(-.5, .5);
		break;
	case Style::CircleHalf:
		path = QPainterPath(QPointF(0., .5));
		path.arcTo(-.5, -.5, 1., 1., -90., 180.);
		path.closeSubpath();
		path.addEllipse(QPoint(0, 0), 0.5, 0.5);
		break;
	case Style::CircleDot:
		path.addEllipse(-.1, -.1, .2, .2);
		path.addEllipse(QPoint(0, 0), 0.5, 0.5);
		break;
	case Style::CircleX:
		path = QPainterPath(QPointF(.5 / M_SQRT2, .5 / M_SQRT2));
		path.lineTo(-.5 / M_SQRT2, -.5 / M_SQRT2);
		path.arcTo(-.5, -.5, 1., 1., -45., 90.);
		path.lineTo(-.5 / M_SQRT2, .5 / M_SQRT2);
		path.arcTo(-.5, -.5, 1., 1., 225., -90.);
		path.closeSubpath();
		path.addEllipse(QPoint(0, 0), 0.5, 0.5);
		break;
	case Style::CircleTri:
		path.addEllipse(QPoint(0, 0), 0.5, 0.5);
		path.moveTo(0., 0.);
		path.lineTo(0., -.5);
		path.moveTo(0., 0.);
		path.lineTo(-M_SQRT3 / 4., 1. / 4.);
		path.moveTo(0., 0.);
		path.lineTo(M_SQRT3 / 4., 1. / 4.);
		break;
	case Style::Peace:
		path = QPainterPath(QPointF(0, .5));
		path.lineTo(0, -.5);
		path.moveTo(0., 0.);
		path.lineTo(-.5 / M_SQRT2, .5 / M_SQRT2);
		path.moveTo(0., 0.);
		path.lineTo(.5 / M_SQRT2, .5 / M_SQRT2);
		path.closeSubpath();
		path.addEllipse(QPoint(0, 0), 0.5, 0.5);
		break;
	case Style::TriangleDot:
		path.addEllipse(-.1, -.1, .2, .2);
		polygon << QPointF(-0.5, 0.5) << QPointF(0, -0.5) << QPointF(0.5, 0.5) << QPointF(-0.5, 0.5);
		path.addPolygon(polygon);
		break;
	case Style::TriangleHalf:
		path = QPainterPath(QPointF(-.25, 0));
		path.lineTo(0, -.5);
		path.lineTo(.25, 0);
		path.closeSubpath();
		path.moveTo(.5, .5);
		path.lineTo(.25, 0);
		path.moveTo(.5, .5);
		path.lineTo(-.5, .5);
		path.moveTo(-.5, .5);
		path.lineTo(-.25, 0);
		break;
	case Style::TriangleLine:
		path = QPainterPath(QPointF(-.5, .5));
		path.lineTo(0, -.5);
		path.lineTo(0, .5);
		path.closeSubpath();
		path.moveTo(.5, .5);
		path.lineTo(0, .5);
		path.moveTo(.5, .5);
		path.lineTo(0, -.5);
		break;
	case Style::Flower: {
		int steps = 100;
		double range = 2. * M_PI / (steps - 1);
		for (int i = 0; i < steps; ++i) {
			double t = i * range;
			double r = sin(2 * t) * sin(2 * t);
			double x = r * sin(t);
			double y = r * cos(t);
			polygon << QPointF(x / 2, y / 2);
		}
		path.addPolygon(polygon);
		break;
	}
	case Style::Flower2: {
		int steps = 100;
		double range = 2. * M_PI / (steps - 1);
		for (int i = 0; i < steps; ++i) {
			double t = i * range;
			double r = sin(t) * sin(t);
			double x = r * sin(t);
			double y = r * cos(t);
			polygon << QPointF(x / 2, y / 2);
		}
		path.addPolygon(polygon);
		break;
	}
	case Style::Flower3: {
		int steps = 100;
		double range = 2. * M_PI / (steps - 1);
		for (int i = 0; i < steps; ++i) {
			double t = i * range;
			double r = sin(3. * t / 2.) * sin(3. * t / 2.);
			double x = r * sin(t);
			double y = r * cos(t);
			polygon << QPointF(x / 2, y / 2);
		}
		path.addPolygon(polygon);
		break;
	}
	case Style::Flower5: {
		int steps = 100;
		double range = 2. * M_PI / (steps - 1);
		for (int i = 0; i < steps; ++i) {
			double t = i * range;
			double r = sin(5. * t / 2.) * sin(5. * t / 2.);
			double x = r * sin(t);
			double y = r * cos(t);
			polygon << QPointF(x / 2, y / 2);
		}
		path.addPolygon(polygon);
		break;
	}
	case Style::Flower6: {
		int steps = 100;
		double range = 2. * M_PI / (steps - 1);
		for (int i = 0; i < steps; ++i) {
			double t = i * range;
			double r = sin(3. * t) * sin(3. * t);
			double x = r * sin(t);
			double y = r * cos(t);
			polygon << QPointF(x / 2, y / 2);
		}
		path.addPolygon(polygon);
		break;
	}
	case Style::Star:
		for (int i = 0; i < 5; i++) {
			double angle = 2. * M_PI * i / 5. - M_PI / 10.;
			polygon << QPointF(.5 * cos(angle), .5 * sin(angle));
			polygon << QPointF(.2 * cos(angle + M_PI / 5.), .2 * sin(angle + M_PI / 5.));
		}
		path.addPolygon(polygon);
		path.closeSubpath();
		break;
	case Style::Star3:
		for (int i = 0; i < 3; i++) {
			double angle = 2. * M_PI * i / 3. + M_PI / 6.;
			polygon << QPointF(.5 * cos(angle), .5 * sin(angle));
			polygon << QPointF(.1 * cos(angle + M_PI / 3.), .1 * sin(angle + M_PI / 3.));
		}
		path.addPolygon(polygon);
		path.closeSubpath();
		break;
	case Style::Star6:
		for (int i = 0; i < 6; i++) {
			double angle = 2. * M_PI * i / 6.;
			polygon << QPointF(.5 * cos(angle), .5 * sin(angle));
			polygon << QPointF(.1 * cos(angle + M_PI / 6.), .1 * sin(angle + M_PI / 6.));
		}
		path.addPolygon(polygon);
		path.closeSubpath();
		break;
	case Style::Pentagon:
		for (int i = 0; i < 5; i++) {
			double angle = 2. * M_PI * i / 5. - M_PI / 10.;
			polygon << QPointF(.5 * cos(angle), .5 * sin(angle));
		}
		path.addPolygon(polygon);
		path.closeSubpath();
		break;
	case Style::Hexagon:
		for (int i = 0; i < 6; i++) {
			double angle = 2. * M_PI * i / 6.;
			polygon << QPointF(.5 * cos(angle), .5 * sin(angle));
		}
		path.addPolygon(polygon);
		path.closeSubpath();
		break;
	case Style::Latin:
		polygon << QPointF(-0.1, -0.5) << QPointF(0.1, -0.5) << QPointF(0.1, -0.3) << QPointF(0.5, -0.3) << QPointF(0.5, -0.1) << QPointF(0.1, -0.1)
				<< QPointF(0.1, 0.5) << QPointF(-0.1, 0.5) << QPointF(-0.1, -0.1) << QPointF(-0.5, -0.1) << QPointF(-0.5, -0.3) << QPointF(-0.1, -0.3)
				<< QPointF(-0.1, -0.5);
		path.addPolygon(polygon);
		break;
	case Style::David:
		for (int i = 0; i < 4; i++) {
			double angle = 2. * M_PI * i / 3. + M_PI / 6.;
			polygon << QPointF(.5 * cos(angle), .5 * sin(angle));
		}
		path.setFillRule(Qt::FillRule::WindingFill);
		path.addPolygon(polygon);
		polygon.clear();
		for (int i = 0; i < 4; i++) {
			double angle = 2. * M_PI * i / 3. - M_PI / 6.;
			polygon << QPointF(.5 * cos(angle), .5 * sin(angle));
		}

		path.addPolygon(polygon);
		break;
	case Style::Home:
		path = QPainterPath(QPointF(-.25, .25));
		path.lineTo(.25, .25);
		path.lineTo(.25, -.25);
		path.lineTo(0., -.5);
		path.lineTo(-.25, -.25);
		path.lineTo(-.25, .25);
		break;
	case Style::Pin:
		path = QPainterPath(QPointF(0., 0.));
		path.lineTo(0., -.3);
		path.closeSubpath();
		path.addEllipse(QPointF(0., -.4), 0.2, 0.2);
		break;
	case Style::Female:
		path.addEllipse(QPointF(0., 0.), .25, .25);
		path.closeSubpath();
		path.moveTo(0., .25);
		path.lineTo(0., .5);
		path.moveTo(-.15, .375);
		path.lineTo(.15, .375);
		break;
	case Style::Male:
		path.addEllipse(QPointF(0., 0.), .3, .3);
		path.closeSubpath();
		path.moveTo(.3 / M_SQRT2, -.3 / M_SQRT2);
		path.lineTo(.5, -.5);
		path.moveTo(.5, -.5);
		path.lineTo(.35, -.5);
		path.moveTo(.5, -.5);
		path.lineTo(.5, -.35);
		break;
	case Style::Spade: {
		QFont font(QStringLiteral("Times"), 1);
		path.addText(-.3, .3, font, UTF8_QSTRING("♠"));
		break;
	}
	case Style::Club:
		QFont font(QStringLiteral("Times"), 1);
		path.addText(-.3, .3, font, UTF8_QSTRING("♣"));
		break;
	}

	return path;
}

void Symbol::draw(QPainter* painter, QPointF point) const {
	Q_D(const Symbol);
	if (d->style == Symbol::Style::NoSymbols)
		return;

	painter->setOpacity(d->opacity);
	painter->setPen(d->pen);
	painter->setBrush(d->brush);
	QTransform trafo;
	trafo.scale(d->size, d->size);
	QPainterPath path = Symbol::stylePath(d->style);
	if (d->rotationAngle != 0)
		trafo.rotate(-d->rotationAngle);

	path = trafo.map(path);

	trafo.reset();
	trafo.translate(point.x(), point.y());
	painter->drawPath(trafo.map(path));
}

void Symbol::draw(QPainter* painter, const QVector<QPointF>& points) const {
	Q_D(const Symbol);
	if (d->style == Symbol::Style::NoSymbols || points.isEmpty())
		return;

	painter->setOpacity(d->opacity);
	painter->setPen(d->pen);
	painter->setBrush(d->brush);
	QPainterPath path = Symbol::stylePath(d->style);
	QTransform trafo;
	trafo.scale(d->size, d->size);
	if (d->rotationAngle != 0)
		trafo.rotate(-d->rotationAngle);

	path = trafo.map(path);

	for (const auto& point : points) {
		trafo.reset();
		trafo.translate(point.x(), point.y());
		painter->drawPath(trafo.map(path));
	}
}
