/*
 * Copyright 2016 - 2021 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <QButtonGroup>
#include <QStringList>
#include <QStyledItemDelegate>
#include <QUndoStack>
#include <QVariant>

#include <nostalgia/common/bounds.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/studio/studio.hpp>

namespace nostalgia::core {

// Command IDs to use with QUndoCommand::id()
enum class CommandId {
	UpdatePixel = 1,
	ModPixel = 2,
	UpdateDimension = 3,
	InsertTile = 4,
	ClipboardPaste = 4,
};

enum class TileSheetTool: int {
	Select,
	Draw,
	Fill,
};

[[nodiscard]] constexpr auto toString(TileSheetTool t) noexcept {
	switch (t) {
		case TileSheetTool::Select:
			return "Select";
		case TileSheetTool::Draw:
			return "Draw";
		case TileSheetTool::Fill:
			return "Fill";
	}
	return "";
}

struct PixelChunk {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.PixelChunk";
	static constexpr auto Fields = 2;
	static constexpr auto TypeVersion = 1;
	common::Point pt;
	int size = 0;
};

template<typename T>
ox::Error model(T *io, PixelChunk *c) {
	io->template setTypeInfo<PixelChunk>();
	oxReturnError(io->field("pt", &c->pt));
	oxReturnError(io->field("size", &c->size));
	return OxError(0);
}

struct TileSheetClipboard {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
	static constexpr auto Fields = 3;
	static constexpr auto TypeVersion = 1;

	template<typename T>
	friend ox::Error model(T*, TileSheetClipboard*);

	protected:
		std::vector<int> m_pixels;
		common::Point m_p1;
		common::Point m_p2;

	public:
		void addPixel(int color);

		void clear();

		[[nodiscard]] bool empty() const;

		void pastePixels(common::Point pt, QVector<int> *tgt, int tgtColumns) const;

		void setPoints(common::Point p1, common::Point p2);

		[[nodiscard]] common::Point point1() const;

		[[nodiscard]] common::Point point2() const;

};

template<typename T>
ox::Error model(T *io, TileSheetClipboard *b) {
	io->template setTypeInfo<TileSheetClipboard>();
	oxReturnError(io->field("pixels", &b->m_pixels));
	oxReturnError(io->field("p1", &b->m_p1));
	oxReturnError(io->field("p2", &b->m_p2));
	return OxError(0);
}

struct TileSheetEditorColorTableDelegate: public QStyledItemDelegate {

	void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &idx) const;

};

class SheetData: public QObject {
	Q_OBJECT
	Q_PROPERTY(int columns READ columns WRITE setColumns NOTIFY columnsChanged)
	Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
	Q_PROPERTY(QVector<int> pixelSelected READ pixelSelected NOTIFY pixelSelectedChanged)
	Q_PROPERTY(QVector<int> pixels READ pixels NOTIFY pixelsChanged)
	Q_PROPERTY(QStringList palette READ palette NOTIFY paletteChanged)
	Q_PROPERTY(QString activeTool READ activeToolString)

	private:
		class QQuickItem *m_prevPixelOperand = nullptr;
		QString m_tilesheetPath;
		QString m_currentPalettePath;
		uint64_t m_cmdIdx = 0;
		QUndoStack *m_cmdStack;
		QStringList m_palette;
		QVector<int> m_pixelSelected;
		QVector<int> m_pixels;
		common::Point m_selectionStart = {-1, -1};
		common::Point m_selectionEnd = {-1, -1};
		int m_columns = 1;
		int m_rows = 1;
		int m_selectedColor = 0;
		TileSheetTool m_activeTool = TileSheetTool::Draw;
		TileSheetClipboard m_clipboard;

	public:
		explicit SheetData(QUndoStack*);

		Q_INVOKABLE void fillPixel(QVariant pixel);

		Q_INVOKABLE void selectPixel(QVariant pixel);

		Q_INVOKABLE void updatePixel(QVariant pixel);

		Q_INVOKABLE void beginCmd();

		Q_INVOKABLE void endCmd();

		Q_INVOKABLE void insertTileCmd(int tileIdx);

		Q_INVOKABLE void deleteTileCmd(int tileIdx);

		[[nodiscard]] int columns() const;

		[[nodiscard]] int rows() const;

		[[nodiscard]] const QVector<int> &pixelSelected() const;

		[[nodiscard]] const QVector<int> &pixels() const;

		[[nodiscard]] QStringList palette() const;

		[[nodiscard]] QString palettePath() const;

		void getFillPixels(bool *pixels, common::Point pt, int oldColor) const;

		void load(const studio::Context *ctx, QString ngPath, QString palPath = "");

		void reload(const studio::Context *ctx);

		void save(const studio::Context *ctx, QString ngPath) const;

		void setPalette(const NostalgiaPalette *pal);

		void setPalette(const studio::Context *ctx, QString palPath);

		void insertTile(int tileIdx, QVector<int> tileData = {});

		QVector<int> deleteTile(int tileIdx);

		void setSelectedColor(int index);

		void modGteCmd(int val, int mod);

		void modPixels(const QHash<int, int> &pixels, int inversion);

		void setPixel(int index, int color);

		void notifyUpdate();

		[[nodiscard]] std::unique_ptr<NostalgiaGraphic> toNostalgiaGraphic() const;

		[[nodiscard]] int activeTool() const;

		[[nodiscard]] bool clipboardEmpty() const;

		void cutToClipboard();

		void cutToClipboard(TileSheetClipboard *cb);

		void copyToClipboard();

		void copyToClipboard(TileSheetClipboard *cb);

		void pasteClipboard();

		void applyClipboard(const TileSheetClipboard &cb);

		void markSelection(common::Point selectionEnd);

	public slots:
		void setColumns(int columns);

		void setRows(int rows);

		/**
		 * Sets columns through a QUndoCommand.
		 */
		void updateColumns(int columns);

		/**
		 * Sets rows through a QUndoCommand.
		 */
		void updateRows(int rows);

		void setActiveTool(int t);

	private:
		void updatePixels(const NostalgiaGraphic *ng);

		const char *activeToolString() const;

	signals:
		void changeOccurred();

		void columnsChanged(int);

		void rowsChanged(int);

		void pixelSelectedChanged(int pixelsSelected);

		void pixelsChanged();

		void paletteChanged();

};


class TileSheetEditor: public studio::Editor {
	Q_OBJECT

	private:
		TileSheetEditorColorTableDelegate m_colorTableDelegate;
		QString m_itemPath;
		QString m_itemName;
		const studio::Context *m_ctx = nullptr;
		SheetData m_sheetData;
		QButtonGroup m_toolBtns;
		class QSplitter *m_splitter = nullptr;
		struct LabeledSpinner *m_tilesX = nullptr;
		struct LabeledSpinner *m_tilesY = nullptr;
		class QQuickWidget* m_canvas = nullptr;
		struct {
			QComboBox *palette = nullptr;
			class QTableWidget *colorTable = nullptr;
		} m_colorPicker;

	public:
		TileSheetEditor(QString path, const studio::Context *ctx, QWidget *parent);

		virtual ~TileSheetEditor();

		QString itemName() const override;

		void exportFile() override;

		void cut() override;

		void copy() override;

		void paste() override;

	protected:
		void saveItem() override;

		bool eventFilter(QObject *obj, QEvent *event) override;

	private:
		QWidget *setupColorPicker(QWidget *widget);

		void setPalette();

		void saveState();

		void restoreState();

		[[nodiscard]] QString paletteName(QString palettePath) const;

		[[nodiscard]] QString palettePath(QString palettePath) const;

		[[nodiscard]] QImage toQImage(NostalgiaGraphic *ng, NostalgiaPalette *npal) const;

	public slots:
		void colorSelected();

		void setColorTable();

	private slots:
		void updateAfterClicked();

};

}
