/*
**	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/>.
*/

// VisWindowDialog.cpp : implementation file
//

#include "stdafx.h"
#include "leveledit.h"
#include "viswindowdialog.h"
#include "visrasterizer.h"
#include "visrendercontext.h"
#include "pscene.h"
#include "phys.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


/////////////////////////////////////////////////////////////////////////////
//
// VisWindowDialogClass
//
/////////////////////////////////////////////////////////////////////////////
VisWindowDialogClass::VisWindowDialogClass(CWnd* pParent /*=NULL*/)	:
	BitmapBits (NULL),
	Bitmap (NULL),
	Width (0),
	Height (0),
	MemDC (NULL),
	CurToolTipVisId(-1),
	CDialog(VisWindowDialogClass::IDD, pParent)
{
	//{{AFX_DATA_INIT(VisWindowDialogClass)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT

	//
	//	Create a screen compatible DC
	//
	MemDC = ::CreateCompatibleDC (NULL);
	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
// ~VisWindowDialogClass
//
/////////////////////////////////////////////////////////////////////////////
VisWindowDialogClass::~VisWindowDialogClass (void)
{
	Free_DIB_Section ();

	::DeleteDC (MemDC);
	MemDC = NULL;
	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
// DoDataExchange
//
/////////////////////////////////////////////////////////////////////////////
void
VisWindowDialogClass::DoDataExchange (CDataExchange *pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(VisWindowDialogClass)
		// NOTE: the ClassWizard will add DDX and DDV calls here
	//}}AFX_DATA_MAP
	return ;
}


BEGIN_MESSAGE_MAP(VisWindowDialogClass, CDialog)
	//{{AFX_MSG_MAP(VisWindowDialogClass)
	ON_WM_PAINT()
	ON_WM_MOUSEMOVE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
//
// Create
//
/////////////////////////////////////////////////////////////////////////////
void
VisWindowDialogClass::Create (void)
{
	CDialog::Create (VisWindowDialogClass::IDD, NULL);
	return ;
}

/////////////////////////////////////////////////////////////////////////////
//
// OnInitDialog
//
/////////////////////////////////////////////////////////////////////////////
BOOL VisWindowDialogClass::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	// create the tool-tip
	if (ToolTip.Create(this, TTS_ALWAYSTIP) && ToolTip.AddTool(this))
	{
		ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
		ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_AUTOPOP, SHRT_MAX);
		ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_INITIAL, 200);
		ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_RESHOW, 200);
	}
	else
	{
		TRACE("Error in creating ToolTip");
	}
 	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}


/////////////////////////////////////////////////////////////////////////////
//
// Update_Display
//
/////////////////////////////////////////////////////////////////////////////
void
VisWindowDialogClass::Update_Display (VisRasterizerClass &rasterizer)
{
	//
	// Don't do anything if the user has not enabled the vis window
	//
	if (m_hWnd == NULL || IsWindowVisible () == false) {
		return;
	}

	//
	//	Get the resolution of rasterizer
	//
	int width	= 0;
	int height	= 0;
	rasterizer.Get_Resolution (&width, &height);

	//
	//	Recreate DIB section
	//
	Create_DIB_Section (width, height);

	//
	//	Loop over all the rows in the bitmap
	//
	int index	= 0;
	int stride	= (((Width * 3) + 3) & ~3);
	for (int row = 0; row < Height; row ++) {

		//
		//	DIB sections are aligned on DWORD boundaries, so ensure
		// our starting index for this row is mapped appropriately.
		//
		index = row * stride;
		
		//
		//	Get a pointer to the raw vis-render data for this row of the bitmap
		//
		const uint32 *pixel_ptr = rasterizer.Get_Pixel_Row (row, 0, Width - 1);

		//
		//	Loop over all the columns of the bitmap
		//		
		for (int col = 0; col < Width; col ++) {

			unsigned int id			= pixel_ptr[col];// + 1;
			unsigned int pixel		= Id_To_Pixel(id);
			
			//
			//	Convert the pixel into red, green, and blue components
			//
			BYTE red		= BYTE(pixel & 0x000000FF);
			BYTE green	= BYTE((pixel & 0x0000FF00) >> 8);
			BYTE blue	= BYTE((pixel & 0x00FF0000) >> 16);			
			
			//
			//	Store the pixel in the bitmap
			//
			BitmapBits[index ++] = blue;
			BitmapBits[index ++] = green;
			BitmapBits[index ++] = red;
		}
	}

	//
	//	Update the window
	//
	Paint_Display ();
	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
// Free_DIB_Section
//
/////////////////////////////////////////////////////////////////////////////
void
VisWindowDialogClass::Free_DIB_Section (void)
{
	Width		= 0;
	Height	= 0;

	//
	//	Free the bitmap
	//
	if (Bitmap != NULL) {
		::DeleteObject (Bitmap);
		Bitmap = NULL;
	}

	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
// Create_DIB_Section
//
/////////////////////////////////////////////////////////////////////////////
void
VisWindowDialogClass::Create_DIB_Section (int width, int height)
{
	if (Width == width && Height == height) {
		return ;
	}

	//
	//	Calculate what size to make the window
	//
	RECT rect	= { 0, 0, width * 2, height * 2 };
	LONG style	= ::GetWindowLong (m_hWnd, GWL_STYLE);
	::AdjustWindowRect (&rect, style, FALSE);

	//
	//	Resize the window
	//
	SetWindowPos (NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE);

	//
	//	Release our old DIB section
	//
	Free_DIB_Section ();

	//
	//	Cache the new width and height
	//
	Width		= width;
	Height	= height;

	//
	// Set-up the fields of the BITMAPINFOHEADER
	//	Note: Top-down DIBs use negative height in Win32.
	//
	BITMAPINFOHEADER bitmap_info = { 0 };
	bitmap_info.biSize				= sizeof (BITMAPINFOHEADER);
	bitmap_info.biWidth				= Width;
	bitmap_info.biHeight				= -Height;
	bitmap_info.biPlanes				= 1;
	bitmap_info.biBitCount			= 24;
	bitmap_info.biCompression		= BI_RGB;
	bitmap_info.biSizeImage			= ((Width * Height) * 3);
	bitmap_info.biXPelsPerMeter	= 0;
	bitmap_info.biYPelsPerMeter	= 0;
	bitmap_info.biClrUsed			= 0;
	bitmap_info.biClrImportant		= 0;

	//
	// Create a bitmap that we can access the bits directly of
	//
	HDC screen_dc = ::GetDC (NULL);
	Bitmap = ::CreateDIBSection (	screen_dc,
											(const BITMAPINFO *)&bitmap_info,
											DIB_RGB_COLORS,
											(void **)&BitmapBits,
											NULL,
											0L);

	//
	// Release our temporary screen DC
	//
	::ReleaseDC (NULL, screen_dc);
	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
// Paint_Display
//
/////////////////////////////////////////////////////////////////////////////
void
VisWindowDialogClass::Paint_Display (void)
{
	if (m_hWnd == NULL || Bitmap == NULL) {
		return ;
	}

	//
	//	Setup the DCs for blitting
	//
	HDC wnd_dc			= ::GetDC (m_hWnd);
	HBITMAP old_bmp	= (HBITMAP)::SelectObject (MemDC, Bitmap);

	//
	//	Copy the contents of the bitmap to the screen
	//
	::StretchBlt (wnd_dc, 0, 0, Width * 2, Height * 2, MemDC, 0, 0, Width, Height, SRCCOPY);

	//
	//	Restore the DCs
	//
	::SelectObject (MemDC, old_bmp);
	::ReleaseDC (m_hWnd, wnd_dc);
	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
// OnPaint
//
/////////////////////////////////////////////////////////////////////////////
void
VisWindowDialogClass::OnPaint (void)
{
	CPaintDC dc (this);
	Paint_Display ();
	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
// Hit_Test - determines the vis-id under the cursor
//
/////////////////////////////////////////////////////////////////////////////
int 
VisWindowDialogClass::Hit_Test(CPoint point) const
{
	// 
	// If we don't have a bitmap, just return
	//
	if (BitmapBits == NULL) {
		return -1;
	}

	// 
	// Look up the pixel, we are StretchBlt'ing the bitmap to 2x the normal size
	// so we divide the coordinates by two
	//
	int x = point.x / 2;
	int y = point.y / 2;

	if ((x <= 0) || (x >= Width) || (y <= 0) || (y >= Height)) {
		return -1;
	}

	int stride	= (((Width * 3) + 3) & ~3);
	int pixel_address = x * 3 + stride * y;

	BYTE blue = BitmapBits[pixel_address ++];
	BYTE green = BitmapBits[pixel_address ++];
	BYTE red = BitmapBits[pixel_address ++];

	// 
	// Map the color back to a vis ID
	// 
	unsigned int color = red | (green<<8) | (blue<<16);
	unsigned int id = Pixel_To_Id(color);
	return id;
}

/////////////////////////////////////////////////////////////////////////////
//
// Id_To_Pixel
//
/////////////////////////////////////////////////////////////////////////////
unsigned int	
VisWindowDialogClass::Id_To_Pixel(unsigned int id) const		
{ 
	uint32 pixel = 0;
	pixel |= ((id & 0x0000000F) >>  0) << 20;		// LSN (Least Significant Nibble) goes into MSN of red
	pixel |= ((id & 0x000000F0) >>  4) << 12;		// next LSN goes into MSN of green
	pixel |= ((id & 0x00000F00) >>  8) << 4;		// next LSN goes into MSN of blue
	pixel |= ((id & 0x0000F000) >> 12) << 16;		// put into LSN of red
	pixel |= ((id & 0x000F0000) >> 16) << 8;		// put into LSN of green
	pixel |= ((id & 0x00F00000) >> 20) << 0;		// put into LSN of blue

	return pixel;
}

/////////////////////////////////////////////////////////////////////////////
//
// Pixel_To_Id
//
/////////////////////////////////////////////////////////////////////////////
unsigned int	
VisWindowDialogClass::Pixel_To_Id(unsigned int pixel) const
{ 
	uint32 id = 0;

	id |= ((pixel & 0x0000000F) >>  0) << 20;		
	id |= ((pixel & 0x000000F0) >>  4) << 8;		
	id |= ((pixel & 0x00000F00) >>  8) << 16;		
	id |= ((pixel & 0x0000F000) >> 12) << 4;
	id |= ((pixel & 0x000F0000) >> 16) << 12;
	id |= ((pixel & 0x00F00000) >> 20) << 0;

	return id;
}


/////////////////////////////////////////////////////////////////////////////
//
// PreTranslateMessage
//
/////////////////////////////////////////////////////////////////////////////
BOOL 
VisWindowDialogClass::PreTranslateMessage(MSG* pMsg) 
{
	if (::IsWindow(ToolTip.m_hWnd) && pMsg->hwnd == m_hWnd)
	{
		switch(pMsg->message)
		{
		case WM_LBUTTONDOWN:    
		case WM_MOUSEMOVE:
		case WM_LBUTTONUP:    
		case WM_RBUTTONDOWN:
		case WM_MBUTTONDOWN:    
		case WM_RBUTTONUP:
		case WM_MBUTTONUP:
			ToolTip.RelayEvent(pMsg);
			break;
		}
	}
	return CDialog::PreTranslateMessage(pMsg);
}

/////////////////////////////////////////////////////////////////////////////
//
// OnMouseMove
//
/////////////////////////////////////////////////////////////////////////////
void VisWindowDialogClass::OnMouseMove(UINT nFlags, CPoint point) 
{
	if (::IsWindow(ToolTip.m_hWnd))
	{
		unsigned int vis_id = Hit_Test(point);

		if ((vis_id == -1) || (vis_id != CurToolTipVisId)) {
			// Use Activate() to hide the tooltip.
			ToolTip.Activate(FALSE);        
		}

		if (vis_id != -1) {
			ToolTip.Activate(TRUE);

			if (vis_id == BACKFACE_VIS_ID) {

				CString tooltip_string;
				tooltip_string.Format("Vis Id: %d, Backface!",vis_id);
				ToolTip.UpdateTipText(tooltip_string,this);

			} else {

				PhysClass * obj = NULL;
				RefPhysListIterator	it = PhysicsSceneClass::Get_Instance()->Get_Static_Object_Iterator();
				while ((!it.Is_Done()) && (obj == NULL)) {
					if (it.Peek_Obj()->Get_Vis_Object_ID()==(int)vis_id) {
						obj = it.Peek_Obj();
					}
					it.Next();
				}

				if (obj != NULL) {
					CString tooltip_string;
					tooltip_string.Format("Vis Id: %d, Model Name: %s",vis_id,obj->Peek_Model()->Get_Name());
					ToolTip.UpdateTipText(tooltip_string,this);
				} else {
					CString tooltip_string;
					tooltip_string.Format("Vis Id: %d, Unknown Model",vis_id);
					ToolTip.UpdateTipText(tooltip_string,this);
				}
			}
			CurToolTipVisId = vis_id;
		}
	}
	CDialog::OnMouseMove(nFlags, point);
}

