/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code 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.

Doom 3 Source Code 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "tools/edit_gui_common.h"


#include "../../sys/win32/rc/guied_resource.h"

#include "GEApp.h"

#define	GENAV_ITEMHEIGHT	22

rvGENavigator::rvGENavigator ( )
{
	mWnd = NULL;
	mWorkspace = NULL;
	mVisibleIcon = NULL;
	mScriptsIcon = NULL;
	mVisibleIconDisabled = NULL;
	mScriptsLightIcon = NULL;
}

/*
================
rvGENavigator::Create

Creates the navigator window
================
*/
bool rvGENavigator::Create ( HWND parent, bool visible )
{
	WNDCLASSEX wndClass;
	memset ( &wndClass, 0, sizeof(wndClass) );
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.lpszClassName = "GUIEDITOR_NAVIGATOR_CLASS";
	wndClass.lpfnWndProc = rvGENavigator::WndProc;
	wndClass.hbrBackground = (HBRUSH)GetStockObject( LTGRAY_BRUSH );;
	wndClass.hCursor       = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
	wndClass.lpszMenuName  = NULL;
	wndClass.hInstance     = win32.hInstance;
	RegisterClassEx ( &wndClass );

	mVisibleIcon = (HICON)LoadImage ( win32.hInstance, MAKEINTRESOURCE(IDI_GUIED_NAV_VISIBLE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR|LR_LOADMAP3DCOLORS );
	mScriptsIcon = (HICON)LoadImage ( win32.hInstance, MAKEINTRESOURCE(IDI_GUIED_NAV_SCRIPTS), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR );
	mScriptsLightIcon = (HICON)LoadImage ( win32.hInstance, MAKEINTRESOURCE(IDI_GUIED_NAV_SCRIPTSHI), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR );
	mVisibleIconDisabled = (HICON)LoadImage ( win32.hInstance, MAKEINTRESOURCE(IDI_GUIED_NAV_VISIBLEDISABLED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR|LR_LOADMAP3DCOLORS );
	mExpandIcon = (HICON)LoadImage ( win32.hInstance, MAKEINTRESOURCE(IDI_GUIED_NAV_EXPAND), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR|LR_LOADMAP3DCOLORS );
	mCollapseIcon = (HICON)LoadImage ( win32.hInstance, MAKEINTRESOURCE(IDI_GUIED_NAV_COLLAPSE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR|LR_LOADMAP3DCOLORS );

	mWnd = CreateWindowEx ( WS_EX_TOOLWINDOW,
							"GUIEDITOR_NAVIGATOR_CLASS",
							"Navigator",
							WS_SYSMENU|WS_THICKFRAME|WS_CAPTION|WS_POPUP|WS_OVERLAPPED|WS_BORDER|WS_CLIPSIBLINGS|WS_CHILD,
							0, 0, 200,300,
							parent,
							NULL,
							win32.hInstance,
							this );

	if ( !mWnd )
	{
		return false;
	}

	if ( !gApp.GetOptions().GetWindowPlacement ( "navigator", mWnd ) )
	{
		RECT rParent;
		RECT rNav;

		GetWindowRect ( parent, &rParent );
		GetWindowRect ( mWnd, &rNav );
		SetWindowPos ( mWnd, NULL,
					rParent.right - 10 - (rNav.right-rNav.left),
					rParent.bottom - 10 - (rNav.bottom-rNav.top),
					0,0,
					SWP_NOZORDER|SWP_NOSIZE );
	}

	Show ( visible );

	return true;
}

/*
================
Draw3dRect

Draws a 3d rectangle using the given brushes
================
*/
void Draw3dRect ( HDC hDC, RECT* rect, HBRUSH topLeft, HBRUSH bottomRight )
{
	RECT rOut;

	SetRect ( &rOut, rect->left, rect->top, rect->right - 1, rect->top + 1 );
	FillRect ( hDC,&rOut, topLeft );

	SetRect ( &rOut, rect->left, rect->top, rect->left + 1, rect->bottom );
	FillRect( hDC,&rOut, topLeft );

	SetRect ( &rOut, rect->right, rect->top, rect->right -1, rect->bottom  );
	FillRect( hDC,&rOut, bottomRight );

	SetRect ( &rOut, rect->left, rect->bottom, rect->right, rect->bottom - 1 );
	FillRect( hDC,&rOut, bottomRight );
}

/*
================
rvGENavigator::WndProc

Window Procedure
================
*/
LRESULT CALLBACK rvGENavigator::WndProc ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	rvGENavigator* nav = (rvGENavigator*) GetWindowLongPtr ( hWnd, GWLP_USERDATA );

	switch ( msg )
	{
		case WM_INITMENUPOPUP:
			return SendMessage ( gApp.GetMDIFrame ( ), msg, wParam, lParam );

		case WM_ACTIVATE:
			common->ActivateTool( LOWORD( wParam ) != WA_INACTIVE );
			break;

		case WM_ERASEBKGND:
			return TRUE;

		case WM_DESTROY:
			gApp.GetOptions().SetWindowPlacement ( "navigator", hWnd );
			break;

		case WM_CLOSE:
			gApp.GetOptions().SetNavigatorVisible ( false );
			nav->Show ( false );
			return 0;

		case WM_DRAWITEM:
		{
			DRAWITEMSTRUCT*	dis = (DRAWITEMSTRUCT*) lParam;
			idWindow*		window = (idWindow*)dis->itemData;

			if ( window )
			{
				rvGEWindowWrapper*	wrapper	= rvGEWindowWrapper::GetWrapper ( window );
				idStr				name    = window->GetName();
				RECT				rDraw;
				float				offset;
				bool				disabled;

				idWindow* parent = window;
				offset = 1;
				disabled = false;
				while ( parent = parent->GetParent ( ) )
				{
					if ( rvGEWindowWrapper::GetWrapper ( parent )->IsHidden ( ) )
					{
						disabled = true;
					}

					offset += 10;
				}

				CopyRect ( &rDraw, &dis->rcItem );
				rDraw.right = rDraw.left + GENAV_ITEMHEIGHT;
				rDraw.top ++;

				rDraw.right ++;
				FrameRect ( dis->hDC, &rDraw, (HBRUSH)GetStockObject ( BLACK_BRUSH ) );
				rDraw.right --;

				FillRect ( dis->hDC, &rDraw, GetSysColorBrush ( COLOR_3DFACE ) );

				Draw3dRect ( dis->hDC, &rDraw, GetSysColorBrush ( COLOR_3DHILIGHT ), GetSysColorBrush ( COLOR_3DSHADOW ) );

				InflateRect ( &rDraw, -3, -3 );
				Draw3dRect ( dis->hDC, &rDraw, GetSysColorBrush ( COLOR_3DSHADOW ), GetSysColorBrush ( COLOR_3DHILIGHT ) );

				if ( !wrapper->IsHidden ( ) )
				{
					DrawIconEx ( dis->hDC, rDraw.left, rDraw.top, disabled?nav->mVisibleIconDisabled:nav->mVisibleIcon, 16, 16,0, NULL, DI_NORMAL );
				}

				CopyRect ( &rDraw, &dis->rcItem );
				rDraw.left += GENAV_ITEMHEIGHT;
				rDraw.left += 1;

				if ( dis->itemState & ODS_SELECTED )
				{
					FillRect ( dis->hDC, &rDraw, GetSysColorBrush ( COLOR_HIGHLIGHT ) );
				}
				else
				{
					FillRect ( dis->hDC, &rDraw, GetSysColorBrush ( COLOR_WINDOW ) );
				}

				if ( wrapper->CanHaveChildren ( ) && window->GetChildCount ( ) )
				{
					if ( wrapper->IsExpanded ( ) )
					{
						DrawIconEx ( dis->hDC, rDraw.left + offset, rDraw.top + 3, nav->mCollapseIcon, 16, 16,0, NULL, DI_NORMAL );
					}
					else
					{
						DrawIconEx ( dis->hDC, rDraw.left + offset, rDraw.top + 3, nav->mExpandIcon, 16, 16,0, NULL, DI_NORMAL );
					}
				}

				HPEN pen = CreatePen ( PS_SOLID, 1, GetSysColor ( COLOR_3DSHADOW ) );
				HPEN oldpen = (HPEN)SelectObject ( dis->hDC, pen );
				MoveToEx ( dis->hDC, rDraw.left, dis->rcItem.top, NULL );
				LineTo ( dis->hDC, dis->rcItem.right, dis->rcItem.top );
				MoveToEx ( dis->hDC, rDraw.left, dis->rcItem.bottom, NULL );
				LineTo ( dis->hDC, dis->rcItem.right, dis->rcItem.bottom);
				SelectObject ( dis->hDC, oldpen );
				DeleteObject ( pen );

				rDraw.left += offset;
				rDraw.left += 20;

				int colorIndex = ( (dis->itemState & ODS_SELECTED ) ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT );
				SetTextColor ( dis->hDC, GetSysColor ( colorIndex ) );
				DrawText ( dis->hDC, name, name.Length(), &rDraw, DT_LEFT|DT_VCENTER|DT_SINGLELINE );

				if ( wrapper->GetVariableDict().GetNumKeyVals ( ) || wrapper->GetScriptDict().GetNumKeyVals ( ) )
				{
					DrawIconEx ( dis->hDC, dis->rcItem.right - 16, (dis->rcItem.bottom+dis->rcItem.top)/2-6, (dis->itemState & ODS_SELECTED)?nav->mScriptsLightIcon:nav->mScriptsIcon, 13, 13,0, NULL, DI_NORMAL );
				}
			}

			break;
		}

		case WM_MEASUREITEM:
		{
			MEASUREITEMSTRUCT* mis = (MEASUREITEMSTRUCT*) lParam;
			mis->itemHeight = 22;
			break;
		}

		case WM_CREATE:
		{
			LPCREATESTRUCT	cs;
			LVCOLUMN		col;

			// Attach the class to the window first
			cs = (LPCREATESTRUCT) lParam;
			nav = (rvGENavigator*) cs->lpCreateParams;
			SetWindowLongPtr( hWnd, GWLP_USERDATA, (LONG_PTR)nav );

			// Create the List view
			nav->mTree = CreateWindowEx ( 0, "SysListView32", "", WS_VSCROLL|WS_CHILD|WS_VISIBLE|LVS_REPORT|LVS_OWNERDRAWFIXED|LVS_NOCOLUMNHEADER|LVS_SHOWSELALWAYS, 0, 0, 0, 0, hWnd, (HMENU)IDC_GUIED_WINDOWTREE, win32.hInstance, 0 );
			ListView_SetExtendedListViewStyle ( nav->mTree, LVS_EX_FULLROWSELECT );
			ListView_SetBkColor ( nav->mTree, GetSysColor ( COLOR_3DFACE ) );
			ListView_SetTextBkColor ( nav->mTree, GetSysColor ( COLOR_3DFACE ) );
			nav->mListWndProc = (WNDPROC)GetWindowLongPtr ( nav->mTree, GWLP_WNDPROC );
			SetWindowLongPtr( nav->mTree, GWLP_USERDATA, (LONG_PTR)nav );
			SetWindowLongPtr( nav->mTree, GWLP_WNDPROC, (LONG_PTR)ListWndProc );

			// Insert the only column
			col.mask = 0;
			ListView_InsertColumn ( nav->mTree, 0, &col );

			break;
		}

		case WM_SIZE:
		{
			RECT rClient;
			MoveWindow ( nav->mTree, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE );
			GetClientRect ( nav->mTree, &rClient );
			ListView_SetColumnWidth ( nav->mTree, 0, rClient.right-rClient.left-1 );
			break;
		}

		case WM_NCACTIVATE:
			return gApp.ToolWindowActivate ( gApp.GetMDIFrame(), msg, wParam, lParam );

		case WM_NOTIFY:
		{
			LPNMHDR nh;

			nh = (LPNMHDR) lParam;

			switch ( nh->code )
			{
				case NM_CLICK:
				case NM_DBLCLK:
				{
					DWORD dwpos = GetMessagePos();
					LVHITTESTINFO info;
					info.pt.x = LOWORD(dwpos);
					info.pt.y = HIWORD(dwpos);
					MapWindowPoints(HWND_DESKTOP, nh->hwndFrom, &info.pt, 1);
					int index = ListView_HitTest ( nav->mTree, &info );
					if ( index != -1 )
					{
						RECT	rItem;
						int		offset;
						ListView_GetItemRect ( nav->mTree, index, &rItem, LVIR_BOUNDS );
						LVITEM item;
						item.mask = LVIF_PARAM;
						item.iItem = index;
						ListView_GetItem ( nav->mTree, &item );
						idWindow* window = (idWindow*)item.lParam;
						rvGEWindowWrapper* wrapper = rvGEWindowWrapper::GetWrapper(window);

						offset = wrapper->GetDepth ( ) * 10 + 1;

						if ( info.pt.x < GENAV_ITEMHEIGHT )
						{
							if ( !rvGEWindowWrapper::GetWrapper(window)->IsHidden ( ) )
							{
								nav->mWorkspace->HideWindow ( window );
							}
							else
							{
								nav->mWorkspace->UnhideWindow ( window );
							}
						}
						else if ( info.pt.x > GENAV_ITEMHEIGHT + offset && info.pt.x < GENAV_ITEMHEIGHT + offset + 16 )
						{
							if ( wrapper->CanHaveChildren ( ) && window->GetChildCount ( ) )
							{
								if ( wrapper->IsExpanded ( ) )
								{
									wrapper->Collapse ( );
									nav->Update ( );
								}
								else
								{
									wrapper->Expand ( );
									nav->Update ( );
								}
							}
						}
						else if ( nh->code == NM_DBLCLK )
						{
							SendMessage ( gApp.GetMDIFrame ( ), WM_COMMAND, MAKELONG(ID_GUIED_ITEM_PROPERTIES,0), 0 );
						}
					}

					break;
				}

				case NM_RCLICK:
				{
					DWORD dwpos = GetMessagePos();
					LVHITTESTINFO info;
					info.pt.x = LOWORD(dwpos);
					info.pt.y = HIWORD(dwpos);
					MapWindowPoints(HWND_DESKTOP, nh->hwndFrom, &info.pt, 1);
					int index = ListView_HitTest ( nav->mTree, &info );

					if ( index != -1 )
					{
						ClientToScreen ( hWnd, &info.pt );
						HMENU menu = GetSubMenu ( LoadMenu ( gApp.GetInstance(), MAKEINTRESOURCE(IDR_GUIED_ITEM_POPUP) ), 0 );
						TrackPopupMenu ( menu, TPM_RIGHTBUTTON|TPM_LEFTALIGN, info.pt.x, info.pt.y, 0, gApp.GetMDIFrame ( ), NULL );
						DestroyMenu ( menu );
					}

					break;
				}

				case LVN_ITEMCHANGED:
				{
					NMLISTVIEW* nml = (NMLISTVIEW*) nh;
					if ( (nml->uNewState & LVIS_SELECTED) != (nml->uOldState & LVIS_SELECTED) )
					{
						LVITEM item;
						item.iItem = nml->iItem;
						item.mask = LVIF_PARAM;
						ListView_GetItem ( nav->mTree, &item );

						if ( nml->uNewState & LVIS_SELECTED )
						{
							nav->mWorkspace->GetSelectionMgr().Add ( (idWindow*)item.lParam, false );
						}
						else
						{
							nav->mWorkspace->GetSelectionMgr().Remove ( (idWindow*)item.lParam );
						}
					}
					break;
				}
			}

			break;
		}
	}

	return DefWindowProc ( hWnd, msg, wParam, lParam );
}

/*
================
rvGENavigator::ListWndProc

Window Procedure for the embedded list control
================
*/
LRESULT CALLBACK rvGENavigator::ListWndProc ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	rvGENavigator* nav = (rvGENavigator*) GetWindowLongPtr ( hWnd, GWLP_USERDATA );
	assert ( nav );

	switch ( msg )
	{
		case WM_KEYDOWN:
		case WM_KEYUP:
		case WM_CHAR:
			if ( nav->mWorkspace )
			{
				return SendMessage ( nav->mWorkspace->GetWindow(), msg, wParam, lParam );
			}
			break;
	}

	return CallWindowProc ( nav->mListWndProc, hWnd, msg, wParam, lParam );
}

/*
================
rvGENavigator::AddWindow

Adds a new window to the navigator
================
*/
void rvGENavigator::AddWindow ( idWindow* window )
{
	int					index;
	LVITEM				item;
	rvGEWindowWrapper*	wrapper;

	wrapper = rvGEWindowWrapper::GetWrapper ( window );

	// Dont add deleted windows
	if ( !wrapper || wrapper->IsDeleted ( ) )
	{
		return;
	}

	// Insert the window into the tree
	ZeroMemory ( &item, sizeof(item) );
	item.mask = LVIF_PARAM|LVIF_STATE|LVIF_IMAGE;
	item.iItem = ListView_GetItemCount ( mTree );
	item.lParam = (LONG_PTR) window;
	item.iImage = 0;
	item.state = rvGEWindowWrapper::GetWrapper(window)->IsSelected ()? LVIS_SELECTED:0;
	item.stateMask = LVIS_SELECTED;
	ListView_InsertItem ( mTree, &item );

	if ( item.state & LVIS_SELECTED )
	{
		ListView_EnsureVisible ( mTree, item.iItem, false );
	}

	// Dont continue if not expanded.
	if ( !wrapper->IsExpanded ( ) )
	{
		return;
	}

	// Insert all the child windows into the tree
	for ( index = 0; index < wrapper->GetChildCount(); index ++ )
	{
		AddWindow ( wrapper->GetChild(index) );
	}
}

/*
================
rvGENavigator::SetWorkspace

Sets a new workspace for the navigator window
================
*/
void rvGENavigator::SetWorkspace ( rvGEWorkspace* workspace )
{
	mWorkspace = workspace;

	Update ( );
}

/*
================
rvGENavigator::Update

Updates the contents of the navigator window from the current workspace
================
*/
void rvGENavigator::Update ( void )
{
	// Clear the list first
	ListView_DeleteAllItems ( mTree );

	// Add starting with the desktop window
	if ( mWorkspace )
	{
		AddWindow ( mWorkspace->GetInterface ( )->GetDesktop ( ) );
	}

	// For some reason the horizontal scrollbar wants to show up initially after an update
	// so this forces it not to
	RECT rClient;
	GetClientRect ( mTree, &rClient );
	ListView_SetColumnWidth ( mTree, 0, rClient.right-rClient.left-1 );
}

/*
================
rvGENavigator::UpdateSelection

Updates the currently selected items
================
*/
void rvGENavigator::UpdateSelections ( void )
{
	int count = ListView_GetItemCount ( mTree );
	int i;

	for ( i = 0; i < count; i++  )
	{
		LVITEM				item;
		idWindow*			window;
		rvGEWindowWrapper*	wrapper;

		item.iItem = i;
		item.mask = LVIF_PARAM;
		ListView_GetItem ( mTree, &item );
		window = (idWindow*) item.lParam;
		wrapper = rvGEWindowWrapper::GetWrapper ( window );

		ListView_SetItemState ( mTree, i, wrapper->IsSelected ( )?LVIS_SELECTED:0, LVIS_SELECTED );

		if ( wrapper->IsSelected ( ) )
		{
			ListView_EnsureVisible ( mTree, i, false );
		}
	}
}

/*
================
rvGENavigator::Refresh

Repaints the navigator window
================
*/
void rvGENavigator::Refresh ( void )
{
	InvalidateRect ( mTree, NULL, FALSE );
//	UpdateWindow ( mTree );
}

/*
================
rvGENavigator::Show

Shows and hides the navigator window
================
*/
void rvGENavigator::Show ( bool visible )
{
	gApp.GetOptions().SetNavigatorVisible ( visible );
	ShowWindow ( mWnd, visible?SW_SHOW:SW_HIDE );
}