#include "ddpalette_impl.h"
#include "ddraw_impl.h"
#include "dummysurface_impl.h"
#include "framebuffer_impl.h"
#include "miniwin.h"

#include <assert.h>

FrameBufferImpl::FrameBufferImpl(DWORD virtualWidth, DWORD virtualHeight)
	: m_virtualWidth(virtualWidth), m_virtualHeight(virtualHeight)
{
#ifdef __DJGPP__
	m_transferBuffer = new DirectDrawSurfaceImpl(m_virtualWidth, m_virtualHeight, SDL_PIXELFORMAT_INDEX8);
#else
	m_transferBuffer = new DirectDrawSurfaceImpl(m_virtualWidth, m_virtualHeight, SDL_PIXELFORMAT_RGBA32);
#endif
}

FrameBufferImpl::~FrameBufferImpl()
{
	m_transferBuffer->Release();
	if (m_palette) {
		m_palette->Release();
	}
}

// IUnknown interface
HRESULT FrameBufferImpl::QueryInterface(const GUID& riid, void** ppvObject)
{
	MINIWIN_NOT_IMPLEMENTED();
	return E_NOINTERFACE;
}

// IDirectDrawSurface interface
HRESULT FrameBufferImpl::AddAttachedSurface(LPDIRECTDRAWSURFACE lpDDSAttachedSurface)
{
	if (dynamic_cast<DummySurfaceImpl*>(lpDDSAttachedSurface)) {
		return DD_OK;
	}
	MINIWIN_NOT_IMPLEMENTED();
	return DDERR_GENERIC;
}

HRESULT FrameBufferImpl::Blt(
	LPRECT lpDestRect,
	LPDIRECTDRAWSURFACE lpDDSrcSurface,
	LPRECT lpSrcRect,
	DDBltFlags dwFlags,
	LPDDBLTFX lpDDBltFx
)
{
	if (!DDRenderer) {
		return DDERR_GENERIC;
	}

	if (dynamic_cast<FrameBufferImpl*>(lpDDSrcSurface)) {
		return Flip(nullptr, DDFLIP_WAIT);
	}

	if ((dwFlags & DDBLT_COLORFILL) == DDBLT_COLORFILL) {
		Uint8 a = (lpDDBltFx->dwFillColor >> 24) & 0xFF;
		Uint8 r = (lpDDBltFx->dwFillColor >> 16) & 0xFF;
		Uint8 g = (lpDDBltFx->dwFillColor >> 8) & 0xFF;
		Uint8 b = lpDDBltFx->dwFillColor & 0xFF;

		float fa = a / 255.0f;
		float fr = r / 255.0f;
		float fg = g / 255.0f;
		float fb = b / 255.0f;

		if (lpDestRect) {
			SDL_Rect dstRect = ConvertRect(lpDestRect);
			DDRenderer->Draw2DImage(NO_TEXTURE_ID, SDL_Rect{}, dstRect, {fr, fg, fb, fa});
		}
		else {
			DDRenderer->Clear(fr, fg, fb);
		}
		return DD_OK;
	}

	auto surface = static_cast<DirectDrawSurfaceImpl*>(lpDDSrcSurface);
	if (!surface) {
		return DDERR_GENERIC;
	}
	SDL_Rect srcRect =
		lpSrcRect ? ConvertRect(lpSrcRect) : SDL_Rect{0, 0, surface->m_surface->w, surface->m_surface->h};
	SDL_Rect dstRect =
		lpDestRect ? ConvertRect(lpDestRect) : SDL_Rect{0, 0, (int) m_virtualWidth, (int) m_virtualHeight};
	float scaleX = (float) dstRect.w / (float) srcRect.w;
	float scaleY = (float) dstRect.h / (float) srcRect.h;
	Uint32 textureId = DDRenderer->GetTextureId(surface->ToTexture(), true, scaleX, scaleY);
	DDRenderer->Draw2DImage(textureId, srcRect, dstRect, {1.0f, 1.0f, 1.0f, 1.0f});

	return DD_OK;
}

HRESULT FrameBufferImpl::BltFast(
	DWORD dwX,
	DWORD dwY,
	LPDIRECTDRAWSURFACE lpDDSrcSurface,
	LPRECT lpSrcRect,
	DDBltFastFlags dwTrans
)
{
	auto surface = static_cast<DirectDrawSurfaceImpl*>(lpDDSrcSurface);
	int width = lpSrcRect ? (lpSrcRect->right - lpSrcRect->left) : surface->m_surface->w;
	int height = lpSrcRect ? (lpSrcRect->bottom - lpSrcRect->top) : surface->m_surface->h;
	RECT destRect = {(int) dwX, (int) dwY, (int) (dwX + width), (int) (dwY + height)};

	DDBltFlags flags = DDBLT_NONE;
	if ((dwTrans & DDBLTFAST_SRCCOLORKEY) == DDBLTFAST_SRCCOLORKEY) {
		flags = flags | DDBLT_KEYSRC;
	}
	return Blt(&destRect, lpDDSrcSurface, lpSrcRect, flags, nullptr);
}

HRESULT FrameBufferImpl::Flip(LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride, DDFlipFlags dwFlags)
{
	if (!DDRenderer) {
		return DDERR_GENERIC;
	}
	DDRenderer->Flip();
	return DD_OK;
}

HRESULT FrameBufferImpl::GetAttachedSurface(LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE* lplpDDAttachedSurface)
{
	if ((lpDDSCaps->dwCaps & DDSCAPS_BACKBUFFER) != DDSCAPS_BACKBUFFER) {
		return DDERR_INVALIDPARAMS;
	}
	AddRef();
	*lplpDDAttachedSurface = static_cast<IDirectDrawSurface*>(this);
	return DD_OK;
}

HRESULT FrameBufferImpl::GetDC(HDC* lphDC)
{
	MINIWIN_NOT_IMPLEMENTED();
	return DD_OK;
}

HRESULT FrameBufferImpl::GetPalette(LPDIRECTDRAWPALETTE* lplpDDPalette)
{
	if (!m_palette) {
		return DDERR_GENERIC;
	}
	m_palette->AddRef();
	*lplpDDPalette = m_palette;
	return DD_OK;
}

HRESULT FrameBufferImpl::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat)
{
	memset(lpDDPixelFormat, 0, sizeof(*lpDDPixelFormat));
	lpDDPixelFormat->dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS;
	const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_transferBuffer->m_surface->format);
	if (details->bits_per_pixel == 8) {
		lpDDPixelFormat->dwFlags |= DDPF_PALETTEINDEXED8;
	}
	lpDDPixelFormat->dwRGBBitCount = details->bits_per_pixel;
	lpDDPixelFormat->dwRBitMask = details->Rmask;
	lpDDPixelFormat->dwGBitMask = details->Gmask;
	lpDDPixelFormat->dwBBitMask = details->Bmask;
	lpDDPixelFormat->dwRGBAlphaBitMask = details->Amask;
	return DD_OK;
}

HRESULT FrameBufferImpl::GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc)
{
	lpDDSurfaceDesc->dwFlags = DDSD_PIXELFORMAT;
	GetPixelFormat(&lpDDSurfaceDesc->ddpfPixelFormat);
	lpDDSurfaceDesc->dwFlags |= DDSD_WIDTH | DDSD_HEIGHT;
	lpDDSurfaceDesc->dwWidth = m_transferBuffer->m_surface->w;
	lpDDSurfaceDesc->dwHeight = m_transferBuffer->m_surface->h;
	return DD_OK;
}

HRESULT FrameBufferImpl::Lock(LPRECT lpDestRect, DDSURFACEDESC* lpDDSurfaceDesc, DDLockFlags dwFlags, HANDLE hEvent)
{
	if (!DDRenderer) {
		return DDERR_GENERIC;
	}

	m_readOnlyLock = (dwFlags & DDLOCK_READONLY) == DDLOCK_READONLY;

	if ((dwFlags & DDLOCK_WRITEONLY) == DDLOCK_WRITEONLY) {
		const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_transferBuffer->m_surface->format);
		SDL_Palette* palette = m_palette ? static_cast<DirectDrawPaletteImpl*>(m_palette)->m_palette : nullptr;
		Uint32 color = SDL_MapRGBA(details, palette, 0, 0, 0, 0);
		SDL_FillSurfaceRect(m_transferBuffer->m_surface, nullptr, color);
	}
	else {
		DDRenderer->Download(m_transferBuffer->m_surface);
	}

	m_transferBuffer->Lock(lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent);

	return DD_OK;
}

HRESULT FrameBufferImpl::ReleaseDC(HDC hDC)
{
	MINIWIN_NOT_IMPLEMENTED();
	return DD_OK;
}

HRESULT FrameBufferImpl::Restore()
{
	MINIWIN_NOT_IMPLEMENTED();
	return DD_OK;
}

HRESULT FrameBufferImpl::SetColorKey(DDColorKeyFlags dwFlags, LPDDCOLORKEY lpDDColorKey)
{
	MINIWIN_NOT_IMPLEMENTED();
	return DD_OK;
}

HRESULT FrameBufferImpl::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette)
{
	// If the transfer buffer is not INDEX8 yet, recreate it — but only when
	// the renderer actually works with paletted surfaces (palette SW / DOS).
	// GL-based renderers use RGBA32 transfer buffers and convert on upload.
	if (m_transferBuffer->m_surface->format != SDL_PIXELFORMAT_INDEX8 && DDRenderer &&
		DDRenderer->UsesPalettedSurfaces()) {
		m_transferBuffer->Release();
		m_transferBuffer = new DirectDrawSurfaceImpl(m_virtualWidth, m_virtualHeight, SDL_PIXELFORMAT_INDEX8);
	}

	lpDDPalette->AddRef();

	if (m_palette) {
		m_palette->Release();
	}

	m_palette = lpDDPalette;
	SDL_SetSurfacePalette(m_transferBuffer->m_surface, ((DirectDrawPaletteImpl*) m_palette)->m_palette);

	if (DDRenderer) {
		DDRenderer->SetPalette(((DirectDrawPaletteImpl*) m_palette)->m_palette);
	}

	return DD_OK;
}

HRESULT FrameBufferImpl::Unlock(LPVOID lpSurfaceData)
{
	m_transferBuffer->Unlock(lpSurfaceData);
	if (!m_readOnlyLock) {
		BltFast(0, 0, m_transferBuffer, nullptr, DDBLTFAST_WAIT);
	}

	return DD_OK;
}
