/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	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, either version 3 of the License, or
**	(at your option) any later version.
**
**	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 for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "binkmovie.h"
#include "dx8wrapper.h"
#include "formconv.h"
#include "render2d.h"
#include "Bink.h"
#include "rect.h"
#include "subtitlemanager.h"
#include "dx8caps.h"

class BINKMovieClass
{
	private:
		StringClass Filename;
		HBINK Bink;
		bool FrameChanged;
		unsigned TextureCount;
		unsigned long TicksPerFrame;

		struct TextureInfoStruct {
			TextureClass* Texture;
			int TextureWidth;
			int TextureHeight;
			int TextureLocX;
			int TextureLocY;
			RectClass UV;
			RectClass Rect;
		};

		TextureInfoStruct* TextureInfos;
		unsigned char* TempBuffer;
		Render2DClass Renderer;
		SubTitleManagerClass* SubTitleManager;

	public:
		BINKMovieClass(const char* filename,const char* subtitlename,FontCharsClass* font);
		~BINKMovieClass();

		void Update();
		void Render();
		bool Is_Complete();
};


static BINKMovieClass* CurrentMovie;


void BINKMovie::Play(const char* filename,const char* subtitlename, FontCharsClass* font)
{
	if (CurrentMovie) {
		delete CurrentMovie;
		CurrentMovie = NULL;
	}

	CurrentMovie = new BINKMovieClass(filename,subtitlename,font);
}


void BINKMovie::Stop()
{
	if (CurrentMovie) {
		delete CurrentMovie;
		CurrentMovie = NULL;
	}
}


void BINKMovie::Update()
{
	if (CurrentMovie) {
		CurrentMovie->Update();
	}
}


void BINKMovie::Render()
{
	if (CurrentMovie) {
		CurrentMovie->Render();
	}
}


void BINKMovie::Init()
{
	BinkSoundUseDirectSound(0);
}


void BINKMovie::Shutdown()
{
	Stop();
}


bool BINKMovie::Is_Complete()
{
	if (CurrentMovie) {
		return CurrentMovie->Is_Complete();
	}

	return true;
}


// ----------------------------------------------------------------------------

BINKMovieClass::BINKMovieClass(const char* filename, const char* subtitlename, FontCharsClass* font)
	:
	Filename(filename),
	Bink(0),
	FrameChanged(true),
	TicksPerFrame(0),
	SubTitleManager(NULL)
{
	Bink = BinkOpen(Filename, 0);

	if (Bink == NULL) {
		return;
	}

	TempBuffer = new unsigned char[Bink->Width * Bink->Height*2];

	const D3DCAPS8& dx8caps = DX8Wrapper::Get_Current_Caps()->Get_DX8_Caps();
	unsigned poweroftwowidth = 1;

	while (poweroftwowidth < Bink->Width) {
		poweroftwowidth <<= 1;
	}

	unsigned poweroftwoheight = 1;
	
	while (poweroftwoheight < Bink->Height) {
		poweroftwoheight <<= 1;
	}

	if (poweroftwowidth > dx8caps.MaxTextureWidth) {
		poweroftwowidth = dx8caps.MaxTextureWidth;
	}
	
	if (poweroftwoheight > dx8caps.MaxTextureHeight) {
		poweroftwoheight = dx8caps.MaxTextureHeight;
	}

	TextureCount = 0;
	unsigned max_width = poweroftwowidth;
	unsigned max_height = poweroftwoheight;
	unsigned x, y;

	for (y = 0; y < Bink->Height; y += max_height-2) {		// Two pixels are lost due to duplicated edges to prevent bilinear artifacts
		for (x = 0; x < Bink->Width; x += max_width-2) {
			++TextureCount;
		}
	}

	TextureInfos = new TextureInfoStruct[TextureCount];
	unsigned cnt = 0;
	
	for (y = 0; y < Bink->Height; y += max_height-1) {
		for (x = 0; x < Bink->Width; x += max_width-1) {
			TextureInfos[cnt].Texture = new TextureClass(
				max_width, max_height, D3DFormat_To_WW3DFormat(D3DFMT_R5G6B5),
				TextureClass::MIP_LEVELS_1, TextureClass::POOL_MANAGED, false);

			TextureInfos[cnt].TextureLocX = x;
			TextureInfos[cnt].TextureLocY = y;
			TextureInfos[cnt].TextureWidth = max_width;
			TextureInfos[cnt].UV.Right = float(max_width) / float(max_width);

			if ((TextureInfos[cnt].TextureWidth + x) > Bink->Width) {
				TextureInfos[cnt].TextureWidth = Bink->Width - x;
				TextureInfos[cnt].UV.Right = float(TextureInfos[cnt].TextureWidth - 1) / float(max_width);
			}

			TextureInfos[cnt].TextureHeight = max_height;
			TextureInfos[cnt].UV.Bottom = float(max_height) / float(max_height);

			if ((TextureInfos[cnt].TextureHeight + y) > Bink->Height) {
				TextureInfos[cnt].TextureHeight = Bink->Height - y;
				TextureInfos[cnt].UV.Bottom = float(TextureInfos[cnt].TextureHeight + 1) / float(max_height);
			}

			TextureInfos[cnt].UV.Left = 1.0f / float(max_width);
			TextureInfos[cnt].UV.Top = 1.0f / float(max_height);

			TextureInfos[cnt].Rect.Left = float(TextureInfos[cnt].TextureLocX) / float(Bink->Width);
			TextureInfos[cnt].Rect.Top = float(TextureInfos[cnt].TextureLocY) / float(Bink->Height);
			TextureInfos[cnt].Rect.Right = float(TextureInfos[cnt].TextureLocX + TextureInfos[cnt].TextureWidth) / float(Bink->Width);
			TextureInfos[cnt].Rect.Bottom = float(TextureInfos[cnt].TextureLocY + TextureInfos[cnt].TextureHeight) / float(Bink->Height);

			++cnt;
		}
	}

	Renderer.Reset();

	// Calculate the time per frame of video
	unsigned int rate = (Bink->FrameRate / Bink->FrameRateDiv);
	TicksPerFrame = (60 / rate);

	if (subtitlename && font) {
		SubTitleManager = SubTitleManagerClass::Create(filename, subtitlename, font);
	}
}


BINKMovieClass::~BINKMovieClass()
{
	if (Bink == NULL) {
		return;
	}

	if (Bink) {
		BinkClose(Bink);
	}

	delete[] TempBuffer;

	if (TextureInfos) {
		for (unsigned t = 0; t < TextureCount; ++t) {
			REF_PTR_RELEASE(TextureInfos[t].Texture);
		}

		delete[] TextureInfos;
	}

	if (SubTitleManager) {
		delete SubTitleManager;
	}
}


void BINKMovieClass::Update()
{
	if (!Bink) {
		return;
	}

	FrameChanged |= !BinkWait(Bink);
}


static unsigned char* Get_Tex_Address(unsigned char* buffer, int x, int y, int w, int h)
{
	if (x < 0) {
		x = 0;
	} else if (x >= w) {
		x = w - 1;
	}

	if (y < 0) {
		y = 0;
	} else if (y >= h) {
		y = h - 1;
	}

	return buffer + x * 2 + y * 2 * w;
}


void BINKMovieClass::Render()
{
	if (!Bink) {
		return;
	}

	// decompress a frame
	if (FrameChanged) {
		BinkDoFrame(Bink);
		FrameChanged = false;

		BinkCopyToBuffer(Bink, TempBuffer, Bink->Width * 2, Bink->Height, 0, 0, BINKSURFACE565|BINKCOPYNOSCALING);

		for (unsigned t = 0; t < TextureCount; ++t) {
			IDirect3DTexture8* d3d_texture = TextureInfos[t].Texture->Peek_DX8_Texture();

			if (d3d_texture) {
				unsigned char* cur_tex_ptr = Get_Tex_Address(TempBuffer, TextureInfos[t].TextureLocX,
					TextureInfos[t].TextureLocY, Bink->Width, Bink->Height);

				unsigned w = TextureInfos[t].TextureWidth;
				unsigned h = TextureInfos[t].TextureHeight;

				if (w > Bink->Width-TextureInfos[t].TextureLocX) {
					w = Bink->Width-TextureInfos[t].TextureLocX;
				}

				if (h > Bink->Height-TextureInfos[t].TextureLocY) {
					h = Bink->Height-TextureInfos[t].TextureLocY;
				}

				D3DSURFACE_DESC d3d_surf_desc;
				D3DLOCKED_RECT locked_rect;

				DX8_ErrorCode(d3d_texture->GetLevelDesc(0, &d3d_surf_desc));

				RECT rect;
				rect.left = 0;
				rect.top = 0;
				rect.right = w;
				rect.bottom = h;
				DX8_ErrorCode(d3d_texture->LockRect(0,&locked_rect,&rect,0));

				for (unsigned y = 0; y < h; ++y) {
					unsigned char* dest = (unsigned char*)locked_rect.pBits + y * locked_rect.Pitch;
					memcpy(dest, cur_tex_ptr, w * 2);
					cur_tex_ptr += Bink->Width * 2;
				}

				DX8_ErrorCode(d3d_texture->UnlockRect(0));
			}
		}

		if (Bink->FrameNum < Bink->Frames) // goto the next if not on the last
			BinkNextFrame(Bink);
	}

	for (unsigned t = 0; t < TextureCount; ++t) {
		Renderer.Reset();
		Renderer.Set_Texture(TextureInfos[t].Texture);
		Renderer.Set_Coordinate_Range(RectClass(0.0f, 0.0f, 1.0f, 1.0f));//Bink->Width,Bink->Height));

		RectClass rect(TextureInfos[t].TextureLocX, TextureInfos[t].TextureLocY, TextureInfos[t].TextureWidth, TextureInfos[t].TextureHeight);
		Renderer.Add_Quad(TextureInfos[t].Rect, TextureInfos[t].UV, 0xffffffff);
		Renderer.Render();
	}

	if (SubTitleManager) {
		unsigned long movieTime = (Bink->FrameNum * TicksPerFrame);
		SubTitleManager->Process(movieTime);
		SubTitleManager->Render();
	}
}


bool BINKMovieClass::Is_Complete()
{
	if (!Bink) return true;
	return (Bink->FrameNum>=Bink->Frames);
}

