/*
	Aseba - an event-based framework for distributed robot control
	Created by Stéphane Magnenat <stephane at magnenat dot net> (http://stephane.magnenat.net)
	with contributions from the community.
	Copyright (C) 2007--2018 the authors, see authors.txt for details.

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published
	by the Free Software Foundation, version 3 of the License.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef VPL_CARD_H
#define VPL_CARD_H

#include <QObject>
#include <QGraphicsObject>
#include <QList>
#include <QDomElement>

class QMimeData;
class QSlider;
class QDomDocument;

namespace Aseba { namespace ThymioVPL
{
	/** \addtogroup studio */
	/*@{*/

	class GeometryShapeButton;

	/**
		An "event" or "action" block.

		These blocks have a type (event or action) and a name (prox, etc.)
		and may provide several values (set/get by setValue()/getValue()).
		These values are set by the user through buttons (typically
		GeometryShapeButton), sliders, or specific widgets.
	*/
	class Block : public QGraphicsObject
	{
		Q_OBJECT

	public:
		// TODO: move that somewhere else
		class ThymioBody : public QGraphicsItem
		{
		public:
			ThymioBody(QGraphicsItem *parent = 0, int yShift = 0) : QGraphicsItem(parent), bodyColor(Qt::white), yShift(yShift), up(true) { }

			void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);
			QRectF boundingRect() const { return QRectF(-128, -128+yShift, 256, 256); }
			void setUp(bool u) { up = u; }

			static void drawBody(QPainter * painter, int xShift, int yShift, bool up, const QColor& bodyColor);

			QColor bodyColor;

		private:
			const int yShift;
			bool up;
		};

		static Block* createBlock(const QString& name, bool advanced=false, QGraphicsItem *parent=0);

		Block(const QString& type, const QString& name, QGraphicsItem *parent=0);
		virtual ~Block();

		QString getType() const { return type; }
		QString getName() const { return name; }
		QString getTranslatedName() const;
		unsigned getNameAsUInt4() const;

		virtual unsigned valuesCount() const = 0;
		virtual int getValue(unsigned i) const = 0;
		virtual void setValue(unsigned i, int value) = 0;
		virtual QVector<uint16_t> getValuesCompressed() const = 0;
		virtual bool isAnyValueSet() const;
		void resetValues();

		virtual bool isAdvancedBlock() const { return false; }
		virtual bool isAnyAdvancedFeature() const { return isAdvancedBlock(); }
		virtual void setAdvanced(bool advanced) {}

		QMimeData* mimeData() const;
		QDomElement serialize(QDomDocument& document) const;
		static Block* deserialize(const QDomElement& element, bool advanced);
		static Block* deserialize(const QByteArray& data, bool advanced);
		static QString deserializeType(const QByteArray& data);
		static QString deserializeName(const QByteArray& data);

		virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);
		QRectF boundingRect() const { return QRectF(0, 0, 256, 256); }
		virtual QImage image(qreal factor=1);
		QImage translucidImage(qreal factor=1);

		void render(QPainter& painter);

	protected slots:
		void clearChangedFlag();
		void setChangedFlag();
		void emitUndoCheckpointAndClearIfChanged();

	signals:
		void contentChanged();
		void undoCheckpoint();

	public:
		const QString type;
		const QString name;
		bool beingDragged;
		bool keepAfterDrop;

	protected:
		void renderChildItems(QPainter& painter, QGraphicsItem* item, QStyleOptionGraphicsItem& opt);
		virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
		virtual void mousePressEvent(QGraphicsSceneMouseEvent * event);

		bool changed;
	};

	class BlockWithNoValues: public Block
	{
	public:
		BlockWithNoValues(const QString& type, const QString& name, QGraphicsItem *parent);

		virtual unsigned valuesCount() const { return 0; }
		virtual int getValue(unsigned i) const { return -1; }
		virtual void setValue(unsigned i, int value) {}
		virtual QVector<uint16_t> getValuesCompressed() const { return QVector<uint16_t>(); }
	};

	class BlockWithBody: public Block
	{
	public:
		BlockWithBody(const QString& type, const QString& name, bool up, QGraphicsItem *parent);

		virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);

	public:
		const bool up;

	protected:
		QColor bodyColor;
	};

	class BlockWithButtons: public BlockWithBody
	{
	public:
		BlockWithButtons(const QString& type, const QString& name, bool up, QGraphicsItem *parent);

		virtual unsigned valuesCount() const;
		virtual int getValue(unsigned i) const;
		virtual void setValue(unsigned i, int value);
		virtual QVector<uint16_t> getValuesCompressed() const;

	protected:
		QList<GeometryShapeButton*> buttons;
	};

	class BlockWithButtonsAndRange: public BlockWithButtons
	{
		Q_OBJECT

	public:
		enum PixelToValModel
		{
			PIXEL_TO_VAL_LINEAR,
			PIXEL_TO_VAL_SQUARE
		};

	public:
		BlockWithButtonsAndRange(const QString& type, const QString& name, bool up, const PixelToValModel pixelToValModel, int lowerBound, int upperBound, int defaultLow, int defaultHigh, const QColor& lowColor, const QColor& highColor, bool advanced, QGraphicsItem *parent);

		virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);

		virtual unsigned valuesCount() const;
		virtual int getValue(unsigned i) const;
		virtual void setValue(unsigned i, int value);
		virtual QVector<uint16_t> getValuesCompressed() const;
		virtual bool isAnyValueSet() const;

		virtual bool isAnyAdvancedFeature() const;
		virtual void setAdvanced(bool advanced);

	public:
		const PixelToValModel pixelToValModel; //< whether we have a linear or square mapping
		const int lowerBound;
		const int upperBound;
		const int range;
		const int defaultLow;
		const int defaultHigh;
		const QColor lowColor;
		const QColor highColor;

	protected:
		virtual void mousePressEvent( QGraphicsSceneMouseEvent * event);
		virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event);
		virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);

		QRectF rangeRect() const;
		float pixelToVal(float pixel) const;
		float valToPixel(float val) const;

		QGraphicsItem* createIndicationLED(int x, int y);

	protected slots:
		void updateIndicationLEDsOpacity(void);

	protected:
		int low; //< low activation threshold (at right)
		int high; //< high activation threshold (at left)
		int buttonsCountSimple; //< only show that number of buttons in basic mode, default is -1, no limitation
		bool lastPressedIn; //< whether last mouse press event was in
		bool showRangeControl; //< whether we are in advanced mode

		QList<QGraphicsItem*> indicationLEDs; //< indication LEDs on the robot's body
	};

	class StateFilterBlock: public BlockWithButtons
	{
	public:
		StateFilterBlock(const QString& type, const QString& name, QGraphicsItem *parent=0);

		virtual bool isAdvancedBlock() const { return true; }
	};

	/*@}*/
} } // namespace ThymioVPL / namespace Aseba

#endif
