// Copyright (c) 2012- PPSSPP Project.

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

// 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 General Public License 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

#pragma once

#include <vector>
#include <string>

#include "GPU/GPU.h"
#include "GPU/GPUInterface.h"
#include "Core/MemMap.h"

struct GPUDebugOp {
	u32 pc;
	u8 cmd;
	u32 op;
	std::string desc;
};

enum GPUDebugBufferFormat {
	// These match GEBufferFormat.
	GPU_DBG_FORMAT_565 = 0,
	GPU_DBG_FORMAT_5551 = 1,
	GPU_DBG_FORMAT_4444 = 2,
	GPU_DBG_FORMAT_8888 = 3,
	GPU_DBG_FORMAT_INVALID = 0xFF,

	// These are reversed versions.
	GPU_DBG_FORMAT_REVERSE_FLAG = 4,
	GPU_DBG_FORMAT_565_REV = 4,
	GPU_DBG_FORMAT_5551_REV = 5,
	GPU_DBG_FORMAT_4444_REV = 6,

	// 565 is just reversed, the others have B and R swapped.
	GPU_DBG_FORMAT_565_BGRA = 0x04,
	GPU_DBG_FORMAT_BRSWAP_FLAG = 0x08,
	GPU_DBG_FORMAT_5551_BGRA = 0x09,
	GPU_DBG_FORMAT_4444_BGRA = 0x0A,
	GPU_DBG_FORMAT_8888_BGRA = 0x0B,

	// These don't, they're for depth/stencil buffers.
	GPU_DBG_FORMAT_FLOAT = 0x10,
	GPU_DBG_FORMAT_16BIT = 0x11,
	GPU_DBG_FORMAT_8BIT = 0x12,
	GPU_DBG_FORMAT_24BIT_8X = 0x13,
	GPU_DBG_FORMAT_24X_8BIT = 0x14,

	GPU_DBG_FORMAT_FLOAT_DIV_256 = 0x18,
	GPU_DBG_FORMAT_24BIT_8X_DIV_256 = 0x1B,

	// This is used for screenshots, mainly.
	GPU_DBG_FORMAT_888_RGB = 0x20,
};

enum GPUDebugFramebufferType {
	// The current render target.
	GPU_DBG_FRAMEBUF_RENDER,
	// The current display target (not the displayed screen, though.)
	GPU_DBG_FRAMEBUF_DISPLAY,
};

inline GPUDebugBufferFormat &operator |=(GPUDebugBufferFormat &lhs, const GPUDebugBufferFormat &rhs) {
	lhs = GPUDebugBufferFormat((int)lhs | (int)rhs);
	return lhs;
}

struct GPUDebugBuffer {
	GPUDebugBuffer() {
	}

	GPUDebugBuffer(void *data, u32 stride, u32 height, GEBufferFormat fmt, bool reversed = false)
		: alloc_(false), data_((u8 *)data), stride_(stride), height_(height), fmt_(GPUDebugBufferFormat(fmt)), flipped_(false) {
		if (reversed && fmt_ < GPU_DBG_FORMAT_8888) {
			fmt_ |= GPU_DBG_FORMAT_REVERSE_FLAG;
		}
	}

	GPUDebugBuffer(void *data, u32 stride, u32 height, GETextureFormat fmt, bool reversed = false)
		: alloc_(false), data_((u8 *)data), stride_(stride), height_(height), fmt_(GPUDebugBufferFormat(fmt)), flipped_(false) {
		if (reversed && fmt_ < GPU_DBG_FORMAT_8888) {
			fmt_ |= GPU_DBG_FORMAT_REVERSE_FLAG;
		}
	}

	GPUDebugBuffer(void *data, u32 stride, u32 height, GPUDebugBufferFormat fmt)
		: alloc_(false), data_((u8 *)data), stride_(stride), height_(height), fmt_(fmt), flipped_(false) {
	}

	GPUDebugBuffer(GPUDebugBuffer &&other) noexcept {
		alloc_ = other.alloc_;
		data_ = other.data_;
		height_ = other.height_;
		stride_ = other.stride_;
		flipped_ = other.flipped_;
		fmt_ = other.fmt_;
		other.alloc_ = false;
		other.data_ = nullptr;
	}

	~GPUDebugBuffer() {
		Free();
	}

	GPUDebugBuffer &operator = (GPUDebugBuffer &&other) noexcept {
		if (this != &other) {
			Free();
			alloc_ = other.alloc_;
			data_ = other.data_;
			height_ = other.height_;
			stride_ = other.stride_;
			flipped_ = other.flipped_;
			fmt_ = other.fmt_;
			other.alloc_ = false;
			other.data_ = nullptr;
		}

		return *this;
	}

	void Allocate(u32 stride, u32 height, GEBufferFormat fmt, bool flipped = false, bool reversed = false);
	void Allocate(u32 stride, u32 height, GPUDebugBufferFormat fmt, bool flipped = false);
	void Free();

	u8 *GetData() {
		return data_;
	}

	u32 GetRawPixel(int x, int y) const;

	const u8 *GetData() const {
		return data_;
	}

	u32 GetHeight() const {
		return height_;
	}

	u32 GetStride() const {
		return stride_;
	}

	bool GetFlipped() const {
		return flipped_;
	}

	GPUDebugBufferFormat GetFormat() const {
		return fmt_;
	}

private:
	u32 PixelSize(GPUDebugBufferFormat fmt) const;

	bool alloc_ = false;
	u8 *data_ = nullptr;
	u32 stride_ = 0;
	u32 height_ = 0;
	GPUDebugBufferFormat fmt_ = GPU_DBG_FORMAT_INVALID;
	bool flipped_ = false;
};

struct GPUDebugVertex {
	float u;
	float v;
	float x;
	float y;
	float z;
	u8 c[4];
	float nx;
	float ny;
	float nz;
};

class GPUDebugInterface {
public:
	virtual bool GetCurrentDisplayList(DisplayList &list) = 0;
	virtual std::vector<DisplayList> ActiveDisplayLists() = 0;
	virtual void ResetListPC(int listID, u32 pc) = 0;
	virtual void ResetListStall(int listID, u32 stall) = 0;
	virtual void ResetListState(int listID, DisplayListState state) = 0;

	GPUDebugOp DissassembleOp(u32 pc) {
		return DissassembleOp(pc, Memory::Read_U32(pc));
	}
	virtual GPUDebugOp DissassembleOp(u32 pc, u32 op) = 0;
	virtual std::vector<GPUDebugOp> DissassembleOpRange(u32 startpc, u32 endpc) = 0;

	// Enter/exit stepping mode.  Mainly for better debug stats on time taken.
	virtual void NotifySteppingEnter() = 0;
	virtual void NotifySteppingExit() = 0;

	virtual u32 GetRelativeAddress(u32 data) = 0;
	virtual u32 GetVertexAddress() = 0;
	virtual u32 GetIndexAddress() = 0;
	virtual GPUgstate GetGState() = 0;
	// Needs to be called from the GPU thread.
	// Calling from a separate thread (e.g. UI) may fail.
	virtual void SetCmdValue(u32 op) = 0;

	virtual bool GetCurrentSimpleVertices(int count, std::vector<GPUDebugVertex> &vertices, std::vector<u16> &indices) {
		return false;
	}

	// Needs to be called from the GPU thread, so on the same thread as a notification is fine.
	// Calling from a separate thread (e.g. UI) may fail.
	virtual bool GetCurrentFramebuffer(GPUDebugBuffer &buffer, GPUDebugFramebufferType type, int maxRes = -1) {
		// False means unsupported.
		return false;
	}

	// Similar to GetCurrentFramebuffer().
	virtual bool GetCurrentDepthbuffer(GPUDebugBuffer &buffer) {
		return false;
	}

	// Similar to GetCurrentFramebuffer().
	virtual bool GetCurrentStencilbuffer(GPUDebugBuffer &buffer) {
		return false;
	}

	// Similar to GetCurrentFramebuffer(), with texture level specification.
	virtual bool GetCurrentTexture(GPUDebugBuffer &buffer, int level) {
		return false;
	}

	virtual bool GetCurrentClut(GPUDebugBuffer &buffer) {
		return false;
	}

	virtual bool GetOutputFramebuffer(GPUDebugBuffer &buffer) {
		return false;
	}

	// TODO:
	// cached framebuffers / textures / vertices?
	// get content of specific framebuffer / texture?
	// vertex / texture decoding?
};
