/*
	Copyright 2022 flyinghead

	This file is part of Flycast.

    Flycast 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, either version 2 of the License, or
    (at your option) any later version.

    Flycast 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 for more details.

    You should have received a copy of the GNU General Public License
    along with Flycast.  If not, see <https://www.gnu.org/licenses/>.
 */
#pragma once
#include "types.h"
#include "rend/texconv.h"
#include "hw/pvr/ta_structs.h"

extern const u32 VQMipPoint[11];
extern const u32 OtherMipPoint[11];

enum PvrDataFormat {
	PvrSquareTwiddled           = 0x01,
	PvrSquareTwiddledMipmaps    = 0x02,
    PvrVQ                       = 0x03,
    PvrVQMipmaps                = 0x04,
    PvrPal4                     = 0x05,
    PvrPal4Mipmaps              = 0x06,
    PvrPal8                     = 0x07,
    PvrPal8Mipmaps              = 0x08,
    PvrRectangle                = 0x09,
    PvrStride                   = 0x0B,
    PvrRectangleTwiddled        = 0x0D,
    PvrSmallVQ                  = 0x10,
    PvrSmallVQMipmaps           = 0x11,
    PvrSquareTwiddledMipmapsAlt = 0x12,
};

static inline bool pvrParse(const u8 *data, u32 len, u32& width, u32& height, std::vector<u8>& out)
{
	if (len < 16)
		return false;
	const u8 *p = data;
	const u8 *end = data + len;
	if (!memcmp("GBIX", p, 4))
	{
		p += 4;
		u32 idxSize = *(u32 *)p;
		p += 4 + idxSize;
	}
	if (memcmp("PVRT", p, 4))
	{
		WARN_LOG(COMMON, "Invalid PVR file: header not found");
		return false;
	}
	if (end - p < 16) {
		WARN_LOG(COMMON, "Invalid PVR file: too small");
		return false;
	}
	p += 4;
	u32 size = *(u32 *)p;
	p += 4;
	PixelFormat pixelFormat = (PixelFormat)*p++;
	PvrDataFormat imgType = (PvrDataFormat)*p++;
	p += 2;
	width = *(u16 *)p;
	p += 2;
	height = *(u16 *)p;
	p += 2;
	if (width > 1024 || height > 1024) {
		WARN_LOG(COMMON, "Invalid PVR file: wrong texture dimensions: %d x %d", width, height);
		return false;
	}

	::vq_codebook = p;
	TexConvFP32 texConv;
	switch (pixelFormat)
	{
	case Pixel1555:
		if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps
				|| imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt)
			texConv = opengl::tex1555_TW32;
		else if (imgType == PvrVQ || imgType == PvrVQMipmaps)
			texConv = opengl::tex1555_VQ32;
		else if (imgType == PvrRectangle || imgType == PvrStride)
			texConv = opengl::tex1555_PL32;
		else
		{
			WARN_LOG(COMMON, "Unsupported 1555 image type: %d", imgType);
			return false;
		}
		break;

	case Pixel565:
		if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps
				|| imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt)
			texConv = opengl::tex565_TW32;
		else if (imgType == PvrVQ || imgType == PvrVQMipmaps)
			texConv = opengl::tex565_VQ32;
		else if (imgType == PvrRectangle || imgType == PvrStride)
			texConv = opengl::tex565_PL32;
		else
		{
			WARN_LOG(COMMON, "Unsupported 565 image type: %d", imgType);
			return false;
		}
		break;

	case Pixel4444:
		if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps
				|| imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt)
			texConv = opengl::tex4444_TW32;
		else if (imgType == PvrVQ || imgType == PvrVQMipmaps)
			texConv = opengl::tex4444_VQ32;
		else if (imgType == PvrRectangle || imgType == PvrStride)
			texConv = opengl::tex4444_PL32;
		else
		{
			WARN_LOG(COMMON, "Unsupported 4444 image type: %d", imgType);
			return false;
		}
		break;

	default:
		WARN_LOG(COMMON, "Unsupported PVR pixel type: %d", pixelFormat);
		return false;
	}
	DEBUG_LOG(COMMON, "PVR file: size %d pixelFmt %d imgType %d w %d h %d", size, pixelFormat, imgType, width, height);
	u32 texU = 3;
	while (1u << texU < width)
		texU++;
	if (imgType == PvrSquareTwiddledMipmapsAlt)
		// Hardcoding pixel size. Not correct for palette texs
		p += OtherMipPoint[texU] * 2;
	else if (imgType == PvrSquareTwiddledMipmaps)
		// Hardcoding pixel size. Not correct for palette texs
		p += (OtherMipPoint[texU] - 2) * 2;
	else if (imgType == PvrVQMipmaps)
		p += VQMipPoint[texU];
	else if (imgType == PvrVQ)
		p += VQ_CODEBOOK_SIZE;

	u32 expectedSize = width * height;
	if (imgType == PvrVQ || imgType == PvrVQMipmaps)
		expectedSize /= 4;	// 4 pixels per byte
	else
		expectedSize *= 2;	// 2 bytes per pixel
	if (end - p < expectedSize)
	{
		WARN_LOG(COMMON, "Invalid texture: expected %d bytes, %d remaining", expectedSize, (int)(end - p));
		return false;
	}

	PixelBuffer<u32> pb;
	pb.init(width, height);
	texConv(&pb, p, width, height);
	out.resize(width * height * 4);
	memcpy(out.data(), pb.data(), out.size());

	return true;
}
