/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, 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/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "hsTypes.h"
#include "hsWindows.h"
#include "plResTreeView.h"

#include "../plResMgr/plResManager.h"
#include "../plResMgr/plRegistryHelpers.h"
#include "../plResMgr/plRegistryNode.h"
#include "../plResMgr/plPageInfo.h"
#include "../pnKeyedObject/plUoid.h"
#include "../pnKeyedObject/plKey.h"
#include "../pnKeyedObject/plKeyImp.h"
#include "../pnFactory/plFactory.h"

#include <windows.h>
#include <commctrl.h>
#include <shlwapi.h>
#include "res\resource.h"


extern HINSTANCE		gInstance;
HWND	plResTreeView::fInfoDlg = nil;
bool	plResTreeView::fFilter = false;

static char			gSearchString[ 512 ];
static HTREEITEM	fFoundItem = nil;

extern void	ViewPatchDetails( plKey &patchKey );


BOOL CALLBACK	FindDialogProc( HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch( msg )
	{
		case WM_INITDIALOG:
			return true;

		case WM_COMMAND:
			if( LOWORD( wParam ) == IDOK )
			{
				GetDlgItemText( dlg, IDC_SEARCHSTRING, gSearchString, sizeof( gSearchString ) );
				fFoundItem = nil;
				EndDialog( dlg, IDOK );
			}
			else if( LOWORD( wParam ) == IDCANCEL )
				EndDialog( dlg, IDCANCEL );

			return -1;
	}

	return 0;
}


struct plKeyInfo
{
	plKey				fKey;
	plRegistryPageNode	*fPage;

	plKeyInfo( plKey k, plRegistryPageNode *p ) : fKey( k ), fPage( p ) {}
};

// How's this for functionality?
class plResDlgLoader : public plRegistryPageIterator, public plRegistryKeyIterator
{
	protected:

		HWND		fTree;
		HTREEITEM	fCurrItem, fCurrTypeItem;
		UInt16		fCurrType;
		bool		fFilter;

		plRegistryPageNode	*fCurrPage;


		HTREEITEM AddLeaf(HWND hTree, HTREEITEM hParent, const char *text, plKeyInfo *info )
		{
			TVITEM tvi = {0};
			tvi.mask       = TVIF_TEXT | TVIF_PARAM;
			tvi.pszText    = text ? (char*)text : "<NULL>";
			tvi.cchTextMax = text ? strlen(text) : 7;
			tvi.lParam     = (LPARAM)info;

			TVINSERTSTRUCT tvins = {0};
			tvins.item         = tvi;
			tvins.hParent      = hParent;
			tvins.hInsertAfter = TVI_SORT;

			return TreeView_InsertItem(hTree, &tvins);
		}

	public:

		plResDlgLoader( HWND hTree, bool filter )
		{
			fFilter = filter;
			fTree = hTree;
			((plResManager *)hsgResMgr::ResMgr())->IterateAllPages( this );
		}

		virtual hsBool	EatPage( plRegistryPageNode *page )
		{
			char	str[ 512 ];


			fCurrPage = page;
			const plPageInfo &info = page->GetPageInfo();
			sprintf( str, "%s->%s->%s", info.GetAge(), info.GetPage() );
			fCurrItem = AddLeaf( fTree, NULL, str, new plKeyInfo( nil, fCurrPage ) );

			fCurrType = (UInt16)-1;
			page->LoadKeys();
			page->IterateKeys( this );
			return true;
		}

		virtual hsBool	EatKey( const plKey& key )
		{
			if( fCurrType != key->GetUoid().GetClassType() )
			{
				fCurrType = key->GetUoid().GetClassType();
				const char *className = plFactory::GetNameOfClass( fCurrType );
				fCurrTypeItem = AddLeaf( fTree, fCurrItem, className != nil ? className : "<unknown>", nil );
			}

			if( !fFilter )
				AddLeaf( fTree, fCurrTypeItem, key->GetUoid().GetObjectName(), new plKeyInfo( key, fCurrPage ) );
			return true;
		}
};

void	plResTreeView::FillTreeViewFromRegistry( HWND hWnd )
{
	plResDlgLoader loader( hWnd, fFilter );
}

HTREEITEM	IGetNextTreeItem( HWND tree, HTREEITEM item )
{
	// First try child items of this one
	HTREEITEM next = TreeView_GetChild( tree, item );
	if( next == nil )
		// If no child items, try next sibling
		next = TreeView_GetNextSibling( tree, item );
	if( next == nil )
	{
		// If no siblings, go up to the parent and keep searching until we find a parent with a sibling
		next = item;
		while( true )
		{
			next = TreeView_GetParent( tree, next );
			if( next == nil )
			{
				// No parent; not found, so stop
				break;
			}
			else if( TreeView_GetNextSibling( tree, next ) != nil )
			{
				next = TreeView_GetNextSibling( tree, next );
				break;
			}
		}
	}

	return next;
}

void	plResTreeView::FindNextObject( HWND tree )
{
	if( fFoundItem == nil )
		FindObject( tree );
	else
	{
		fFoundItem = IGetNextTreeItem( tree, fFoundItem );
		IFindNextObject( tree );
	}
}

void	plResTreeView::FindObject( HWND tree )
{
	if( DialogBox( gInstance, MAKEINTRESOURCE( IDD_FINDOBJ ), tree, FindDialogProc ) == IDOK )
	{
		fFoundItem = TreeView_GetRoot( tree );
		IFindNextObject( tree );
	}
}

void	plResTreeView::IFindNextObject( HWND tree )
{
	while( fFoundItem != nil )
	{
		// Get the item
		TVITEM	itemInfo;
		itemInfo.mask = TVIF_PARAM | TVIF_HANDLE;
		itemInfo.hItem = fFoundItem;
		TreeView_GetItem( tree, &itemInfo );
		plKeyInfo *keyInfo = (plKeyInfo *)itemInfo.lParam;
		if( keyInfo != nil && keyInfo->fKey != nil )
		{
			if( StrStrI( keyInfo->fKey->GetUoid().GetObjectName(), gSearchString ) != nil )
			{
				/// FOUND
				TreeView_SelectItem( tree, fFoundItem );
				return;
			}
		}

		// Keep searching. First try child items of this one
		fFoundItem = IGetNextTreeItem( tree, fFoundItem );
	}

	MessageBox( tree, "No objects found", "Find Object", MB_OK );
}

void	IDeleteRecurse( HWND tree, HTREEITEM item )
{
	while( item != nil )
	{
		HTREEITEM	child = TreeView_GetChild( tree, item );
		if( child != nil )
			IDeleteRecurse( tree, child );

		TVITEM	itemInfo;
		itemInfo.mask = TVIF_PARAM | TVIF_HANDLE;
		itemInfo.hItem = item;
		TreeView_GetItem( tree, &itemInfo );
		plKeyInfo *keyInfo = (plKeyInfo *)itemInfo.lParam;
		if( keyInfo != nil )
		{
			delete keyInfo;
			itemInfo.lParam = 0;
			TreeView_SetItem( tree, &itemInfo );
		}

		item = TreeView_GetNextSibling( tree, item );
	}
}

void	plResTreeView::ClearTreeView( HWND hWnd )
{
	HTREEITEM	root = TreeView_GetRoot( hWnd );
	if( root != nil )
		IDeleteRecurse( hWnd, root );

	TreeView_DeleteAllItems( hWnd );
}

void	plResTreeView::SelectionDblClicked( HWND treeCtrl )
{
	HTREEITEM	sel = TreeView_GetSelection( treeCtrl );
	if( sel != nil )
	{
		TVITEM	item;

		item.mask = TVIF_PARAM | TVIF_HANDLE;
		item.hItem = sel;
		if( !TreeView_GetItem( treeCtrl, &item ) )
			return;
	}
}

void	plResTreeView::FilterLoadables( bool filter, HWND treeCtrl )
{
	fFilter = filter;
	ClearTreeView( treeCtrl );
	FillTreeViewFromRegistry( treeCtrl );
}

BOOL CALLBACK	plResTreeView::InfoDlgProc( HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch( msg )
	{
		case WM_INITDIALOG:
			fInfoDlg = dlg;
			break;

		case WM_COMMAND:
			return SendMessage( GetParent( dlg ), msg, wParam, lParam );			
	}

	return 0;
}

void	plResTreeView::VerifyCurrentPage( HWND treeCtrl )
{
	HTREEITEM	sel = TreeView_GetSelection( treeCtrl );
	if( sel != nil )
	{
		TVITEM	item;

		item.mask = TVIF_PARAM | TVIF_HANDLE;
		item.hItem = sel;
		if( !TreeView_GetItem( treeCtrl, &item ) )
			return;

		plKeyInfo *info = (plKeyInfo *)item.lParam;
		if( info != nil )
		{
			if( info->fPage != nil )
			{
				// TODO: FIXME
				/*
				/// HACK. Live with it
				class plHackResManager : public plResManager
				{
					public:
						plRegistry *GetRegistry( void ) { return IGetRegistry(); }
				};

				plRegistry *registry = ((plHackResManager *)hsgResMgr::ResMgr())->GetRegistry();

				plRegistry::plPageCond result = registry->VerifyOnePage( info->fPage );

				char msg[ 512 ];
				if( result == plRegistry::kOK || result == plRegistry::kTooNew )
					strcpy( msg, "Page verifies OK" );
				else if( result == plRegistry::kChecksumInvalid )
					strcpy( msg, "Checksums for page are invalid" );
				else if( result == plRegistry::kOutOfDate )
					strcpy( msg, "Page is older than the current data version" );

				hsMessageBox( msg, "Verification Results", hsMessageBoxNormal );
				*/
			}
		}
	}
}

void	plResTreeView::UpdateInfoDlg( HWND treeCtrl )
{
	hsBool showAsHex = (hsBool)IsDlgButtonChecked( fInfoDlg, IDC_SHOWASHEX );

	SetDlgItemText( fInfoDlg, IDC_NAME, "" );
	SetDlgItemText( fInfoDlg, IDC_CLASS, "" );
	SetDlgItemText( fInfoDlg, IDC_LENGTH, "" );
	SetDlgItemText( fInfoDlg, IDC_STARTPOS, "" );
	EnableWindow( GetDlgItem( fInfoDlg, ID_FILE_VERIFYPAGE ), FALSE );

	HTREEITEM	sel = TreeView_GetSelection( treeCtrl );
	if( sel != nil )
	{
		TVITEM	item;

		item.mask = TVIF_PARAM | TVIF_HANDLE;
		item.hItem = sel;
		if( !TreeView_GetItem( treeCtrl, &item ) )
			return;

		plKeyInfo *info = (plKeyInfo *)item.lParam;
		if( info != nil )
		{
			if( info->fPage != nil )
			{
				const plPageInfo	&pageInfo = info->fPage->GetPageInfo();
				char				tempStr[ 32 ];

				SetDlgItemText( fInfoDlg, IDC_AGE, pageInfo.GetAge() );
				SetDlgItemText( fInfoDlg, IDC_PAGE, pageInfo.GetPage() );

				SetDlgItemText( fInfoDlg, IDC_LOCATION, pageInfo.GetLocation().StringIze( tempStr ) );

				CheckDlgButton(fInfoDlg, IDC_RESERVED,   (pageInfo.GetLocation().GetFlags() & plLocation::kReserved) ? BST_CHECKED : BST_UNCHECKED);
				CheckDlgButton(fInfoDlg, IDC_BUILTIN,    (pageInfo.GetLocation().GetFlags() & plLocation::kBuiltIn) ? BST_CHECKED : BST_UNCHECKED);
				CheckDlgButton(fInfoDlg, IDC_VOLATILE,	 (pageInfo.GetLocation().GetFlags() & plLocation::kVolatile) ? BST_CHECKED : BST_UNCHECKED);
				CheckDlgButton(fInfoDlg, IDC_LOCAL_ONLY, (pageInfo.GetLocation().GetFlags() & plLocation::kLocalOnly) ? BST_CHECKED : BST_UNCHECKED);

				sprintf( tempStr, "%d", pageInfo.GetMajorVersion());
				SetDlgItemText( fInfoDlg, IDC_DATAVERSION, tempStr );

				SetDlgItemText( fInfoDlg, IDC_CHECKSUMTYPE, "Basic (file size)" );
				EnableWindow( GetDlgItem( fInfoDlg, ID_FILE_VERIFYPAGE ), TRUE );
			}

			if( info->fKey != nil )
			{
				char	str[ 128 ];


				SetDlgItemText( fInfoDlg, IDC_NAME, info->fKey->GetUoid().GetObjectName() );

				const char *name = plFactory::GetNameOfClass( info->fKey->GetUoid().GetClassType() );
				sprintf( str, "%s (%d)", name != nil ? name : "<unknown>", info->fKey->GetUoid().GetClassType() );
				SetDlgItemText( fInfoDlg, IDC_CLASS, str );

				plKeyImp *imp = (plKeyImp *)info->fKey;
				EnableWindow( GetDlgItem( fInfoDlg, IDC_STARTPOS_LABEL ), true );
				EnableWindow( GetDlgItem( fInfoDlg, IDC_SIZE_LABEL ), true );

				if( showAsHex )
					sprintf( str, "0x%X", imp->GetStartPos() );
				else
					sprintf( str, "%d", imp->GetStartPos() );
				SetDlgItemText( fInfoDlg, IDC_STARTPOS, str );

				if( imp->GetDataLen() < 1024 )
					sprintf( str, "%d bytes", imp->GetDataLen() );
				else if( imp->GetDataLen() < 1024 * 1024 )
					sprintf( str, "%4.2f kB", imp->GetDataLen() / 1024.f );
				else
					sprintf( str, "%4.2f MB", imp->GetDataLen() / 1024.f / 1024.f );

				SetDlgItemText( fInfoDlg, IDC_LENGTH, str );
			}
		}
	}
}

#include "hsStream.h"
#include <commdlg.h>

void plResTreeView::SaveSelectedObject(HWND treeCtrl)
{
	// TODO: FIXME
	/*
	plKey itemKey = nil;

	HTREEITEM sel = TreeView_GetSelection(treeCtrl);
	if (sel != nil)
	{
		TVITEM	item;
		item.mask = TVIF_PARAM | TVIF_HANDLE;
		item.hItem = sel;
		if (TreeView_GetItem(treeCtrl, &item))
		{
			plKeyInfo *info = (plKeyInfo*)item.lParam;
			if (info != nil)
				itemKey = info->fKey;
		}
	}

	if (!itemKey)
		return;

	char fileName[MAX_PATH];
	sprintf(fileName, "%s.bin", itemKey->GetName());

	OPENFILENAME openInfo;
	memset( &openInfo, 0, sizeof( OPENFILENAME ) );
//	openInfo.hInstance = gInstance;
//	openInfo.hwndOwner = hWnd;
	openInfo.lStructSize = sizeof( OPENFILENAME );
	openInfo.lpstrFile = fileName;
	openInfo.nMaxFile = sizeof( fileName );
	openInfo.lpstrFilter = "Binary Files\0*.bin\0All Files\0*.*\0";
//	openInfo.lpstrTitle = "Choose a pack index file to browse:";
//	openInfo.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;

	if (GetSaveFileName(&openInfo))
	{
		plKeyImp* keyImp = (plKeyImp*)itemKey;

		if (keyImp->GetDataLen() <= 0)
			return;

		plResManager* resMgr = (plResManager*)hsgResMgr::ResMgr();
		const plPageInfo& pageInfo = resMgr->FindPage(keyImp->GetUoid().GetLocation())->GetPageInfo();

		plRegistryDataStream *stream = registry->OpenPageDataStream( keyImp->GetUoid().GetLocation(), false );
		if( stream == nil )
			return;

		hsStream	*dataStream = stream->GetStream();
		UInt8		*buffer = TRACKED_NEW UInt8[ keyImp->GetDataLen() ];
		if( buffer != nil )
		{
			dataStream->SetPosition( keyImp->GetStartPos() );
			dataStream->Read( keyImp->GetDataLen(), buffer );
		}
		delete stream;

		if( buffer == nil )
			return;

		hsUNIXStream outStream;
		outStream.Open(fileName, "wb");
		outStream.Write(keyImp->GetDataLen(), buffer);
		outStream.Close();

		delete [] buffer;
	}
	*/
}