// Copyright (c) 2021 - 2022 kio@little-bat.de
// BSD-2-Clause license
// https://opensource.org/licenses/BSD-2-Clause

#pragma once
#include "kio/kio.h"
#include <memory>
#include <QWidget>
#include "enums.h"
#include "DrawingPipeline.h"
#include "NVPtr.h"

namespace gterm
{

template<typename T>
using u_ptr = std::unique_ptr<T>;

template<typename T>
using s_ptr = std::shared_ptr<T>;

class StaticImage;


/////////////////////////////////////////////////////////////
// Image: base class for an image drawn into the GTerm window
/////////////////////////////////////////////////////////////


class Image
{
	NO_COPY_MOVE(Image);

	std::mutex mutex;
protected:
	QSize _size;			// nominal size
public:
	QRgb background_color;	// for erase. alpha may be 0
	int id = -1;			// for use by client app

protected:
	Image (const QSize& sz, QRgb bkg) noexcept : _size(sz), background_color(bkg) {}

public:
	virtual void lock()		{ mutex.lock(); }
	virtual void unlock()	{ mutex.unlock(); }
	virtual bool try_lock() { return mutex.try_lock(); }

	virtual ~Image() = default;

	virtual bool is_static() const volatile noexcept = 0;
	virtual bool is_dynamic() const volatile noexcept = 0;

	QSize size() const volatile noexcept { return nv(this)->_size; }
	int width()  const volatile noexcept { return size().width(); }
	int height() const volatile noexcept { return size().height(); }
	virtual void resize(const QSize& sz) { _size = sz; }

	virtual void erase() = 0;
	virtual void setPen(QRgb,int lw) = 0;
	virtual void setBrush(QRgb) = 0;
	virtual void setFont(const QString& name, int height, FontStyle=NORMAL) = 0;
	virtual void setTransformation(const QTransform&) = 0;
	virtual void drawLine(const QPoint&, const QPoint&) = 0;
	virtual void drawRect(const QRect&) = 0;
	virtual void drawEllipse(const QRect&) = 0;
	virtual void drawText(const QPoint&, const QString&) = 0;

	virtual void drawImage(s_ptr<Image>, const QPoint& z) = 0;
	virtual void drawImage(s_ptr<Image>, const QRect& z) = 0;	// scaled
	virtual void drawImage(s_ptr<StaticImage>, const QPoint& z, const QRect& q) = 0; // partial, e.g. for sprite

	virtual void drawCommand(const DrawingCommand&) = 0;
};


////////////////////////////////////////////////////////////
// StaticImage: Buffering is done by a bitmap (QImage).
// The Image is drawn onto another image by copying the buffered image.
////////////////////////////////////////////////////////////


class StaticImage final : public Image
{
public:
	QImage image;
	u_ptr<QPainter> painter; // painter currently drawing onto this image

	explicit StaticImage(const QSize& sz, QRgb bkg);
	explicit StaticImage(const QString& filename, QRgb bkg=0);
	~StaticImage() override = default;

	virtual void lock() override;
	virtual void unlock() override;
	virtual bool try_lock() override;

	virtual bool is_static() const volatile noexcept override { return true; }
	virtual bool is_dynamic() const volatile noexcept override { return false; }

	virtual void resize(const QSize& sz) override;

	virtual void erase() override;
	virtual void setPen(QRgb,int lw) override;
	virtual void setBrush(QRgb) override;
	virtual void setFont(const QString& name, int height, FontStyle=NORMAL) override;
	virtual void setTransformation(const QTransform&) override;
	virtual void drawLine(const QPoint& , const QPoint&) override;
	virtual void drawRect(const QRect&) override;
	virtual void drawEllipse(const QRect&) override;
	virtual void drawText(const QPoint&, const QString&) override;

	virtual void drawImage(s_ptr<Image>, const QPoint& z) override;
	virtual void drawImage(s_ptr<Image>, const QRect& z) override;	// scaled
	virtual void drawImage(s_ptr<StaticImage>, const QPoint& z, const QRect& q) override; // partial

	virtual void drawCommand(const DrawingCommand& cmd) override { assert(painter); cmd(*painter); }
};


////////////////////////////////////////////////////////////
// DynamicImage: Buffering is done using a drawing pipeline.
// The Image is drawn onto another image by executing all drawing commands in the pipeline.
////////////////////////////////////////////////////////////


class DynamicImage final : public Image
{
	DrawingPipeline pipeline;

public:

	DynamicImage(const QSize& sz, QRgb bkg=0x00000000) : Image(sz,bkg) {}
	~DynamicImage() override = default;

	virtual bool is_static() const volatile noexcept override { return false; }
	virtual bool is_dynamic() const volatile noexcept override { return true; }

	virtual void erase() override;
	virtual void setPen(QRgb,int lw) override;
	virtual void setBrush(QRgb) override;
	virtual void setFont(const QString& name, int height, FontStyle=NORMAL) override;
	virtual void setTransformation(const QTransform&) override;
	virtual void drawLine(const QPoint&, const QPoint&) override;
	virtual void drawRect(const QRect&) override;
	virtual void drawEllipse(const QRect&) override;
	virtual void drawText(const QPoint&, const QString&) override;

	virtual void drawImage(s_ptr<Image>, const QPoint& z) override;
	virtual void drawImage(s_ptr<Image>, const QRect& z) override;	// scaled
	virtual void drawImage(s_ptr<StaticImage>, const QPoint& z, const QRect& q) override; // partial

	virtual void drawCommand(const DrawingCommand& cmd) override { pipeline.append(cmd); }

	void append(const DrawingCommand& cmd) { pipeline.append(cmd); }
	void append(DrawingCommand&& cmd) { pipeline.append(std::move(cmd)); }
	DynamicImage& operator<< (const DrawingCommand& cmd) { append(cmd); return *this; }
	DynamicImage& operator<< (DrawingCommand&& cmd) { append(std::move(cmd)); return *this; }

	void run (QPainter&p, bool with_background=true)
	{
		if (with_background && background_color != 0)
			p.fillRect(QRect(QPoint(),size()), QColor::fromRgba(background_color));

		pipeline.run(p);
	}
};


////////////////////////////////////////////////////////////


inline s_ptr<StaticImage> newStaticImage(const QSize& size, QRgb bkg)
{
	return std::make_shared<StaticImage>(size, bkg);
}

inline s_ptr<StaticImage> newStaticImage(const QString& filename, QRgb bkg=0x00000000)
{
	return std::make_shared<StaticImage>(filename, bkg);
}

inline s_ptr<DynamicImage> newDynamicImage(const QSize& size, QRgb bkg=0x00000000)
{
	return std::make_shared<DynamicImage>(size, bkg);
}


} // namespace































