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

// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "mixviewer.h"
#include "mainfrm.h"
#include "wwstring.h"
#include "duplicatecombiner.h"
#include "wwfile.h"
#include "ffactory.h"
#include "mixfile.h"
#include "avassetsuck.h"
#include "mixpatchmaker.h"
#include "ffactory.h"
#include "mixfile.h"
#include "mixviewerdoc.h"
#include "makemixfiledialog.h"


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

/////////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
	//{{AFX_MSG_MAP(CMainFrame)
	ON_WM_CREATE()
	ON_COMMAND(IDM_COMBINE_DUPLICATES, OnCombineDuplicates)
	ON_COMMAND(IDM_CREATE_MIX_FILE, OnCreateMixFile)
	ON_COMMAND(IDM_EXPORT_FILES, OnExportFiles)
	ON_COMMAND(IDM_REMOVE_AV_ASSETS, OnRemoveAVAssets)
	ON_COMMAND(IDM_MAKE_MIX_PATCH, OnMakeMixPatch)
	ON_WM_DROPFILES()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

static UINT indicators[] =
{
	ID_SEPARATOR,           // status line indicator
	ID_INDICATOR_CAPS,
	ID_INDICATOR_NUM,
	ID_INDICATOR_SCRL,
};

/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
	// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
		| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
		!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
	{
		TRACE0("Failed to create toolbar\n");
		return -1;      // fail to create
	}

	if (!m_wndStatusBar.Create(this) ||
		!m_wndStatusBar.SetIndicators(indicators,
		  sizeof(indicators)/sizeof(UINT)))
	{
		TRACE0("Failed to create status bar\n");
		return -1;      // fail to create
	}

	// TODO: Delete these three lines if you don't want the toolbar to
	//  be dockable
	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
	EnableDocking(CBRS_ALIGN_ANY);
	DockControlBar(&m_wndToolBar);

	return 0;
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CFrameWnd::PreCreateWindow(cs) )
		return FALSE;
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
	CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
	CFrameWnd::Dump(dc);
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers



////////////////////////////////////////////////////////////////////////////
//
//  Get_Filename_From_Path
//
////////////////////////////////////////////////////////////////////////////
CString
Get_Filename_From_Path (LPCTSTR path)
{
	// Find the last occurance of the directory deliminator
	LPCTSTR filename = ::strrchr (path, '\\');
	if (filename != NULL) {
		// Increment past the directory deliminator
		filename ++;
	} else {
		filename = path;
	}

	// Return the filename part of the path
	return CString (filename);
}


////////////////////////////////////////////////////////////////////////////
//
//  Strip_Filename_From_Path
//
////////////////////////////////////////////////////////////////////////////
CString
Strip_Filename_From_Path (LPCTSTR path)
{
	// Copy the path to a buffer we can modify
	TCHAR temp_path[MAX_PATH];
	::lstrcpy (temp_path, path);

	// Find the last occurance of the directory deliminator
	LPTSTR filename = ::strrchr (temp_path, '\\');
	if (filename != NULL) {

		// Strip off the filename
		filename[0] = 0;
	} else if ((temp_path[1] != ':') && (temp_path[1] != '\\')) {
		temp_path[0] = 0;
	}

	// Return the path only
	return StringClass (temp_path);
}


/////////////////////////////////////////////////////////////////////////////
//
//	OnCombineDuplicates
//
/////////////////////////////////////////////////////////////////////////////
void
CMainFrame::OnCombineDuplicates (void)
{
	CFileDialog dialog (	TRUE,
								".dat",
								"always.dat",
								OFN_HIDEREADONLY | OFN_EXPLORER,
								"Mix File (*.dat, *.mix)|*.dat;*.mix||",
								this);

	dialog.m_ofn.lpstrTitle = "Pick Shared Mix File";
	if (dialog.DoModal () == IDOK) {
		DuplicateRemoverClass combiner;

		//
		//	Determine what directory to search
		//
		StringClass full_path	= dialog.GetPathName ();
		StringClass directory	= Strip_Filename_From_Path (full_path);
		combiner.Set_Destination_File (full_path);

		WIN32_FIND_DATA find_info	= { 0 };
		BOOL keep_going				= TRUE;

		CString search_mask = directory + "\\*.mix";

		//
		// Loop over all the mix files in the search directory
		//
		int count = 0;
		for (HANDLE find_handle = ::FindFirstFile (search_mask, &find_info);
			  (find_handle != INVALID_HANDLE_VALUE) && keep_going;
			  keep_going = ::FindNextFile (find_handle, &find_info))
		{
			//
			// Check to make sure this isn't a directory
			//
			if (!(find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
				StringClass full_path	= directory;
				full_path					+= "\\";
				full_path					+= find_info.cFileName;
				combiner.Add_Mix_File (full_path);
				count ++;
			}
		}

		//
		// Close the handle we needed for the file find routines
		//
		if (find_handle != INVALID_HANDLE_VALUE) {
			::FindClose (find_handle);
		}

		//
		//	Combine the files (if necessary)
		//
		if (count > 0) {
			combiner.Process ();
		}
	}

	return ;
}

/////////////////////////////////////////////////////////////////////////////
//	
//	OnCreateMixFile
//
/////////////////////////////////////////////////////////////////////////////
void
CMainFrame::OnCreateMixFile (void)
{
	MakeMixFileDialogClass dialog( this );
	dialog.DoModal();
	return ;
}

/////////////////////////////////////////////////////////////////////////////
//	
//	OnExportFiles
//
/////////////////////////////////////////////////////////////////////////////
void
CMainFrame::OnExportFiles (void)
{
	StringClass	current_filename;

	CDocument * doc = GetActiveDocument();
	if ( doc != NULL ) {
		current_filename = doc->GetPathName();
	}

	if ( !current_filename.Is_Empty() ) {

		DynamicVectorClass<StringClass>	filenames;
		MixFileFactoryClass mix_factory (current_filename, _TheFileFactory);
		if ( mix_factory.Build_Filename_List( filenames ) ) {

			// Make the destination folder
			StringClass export_path = current_filename;
			export_path += " Files";
			int result = (int)::CreateDirectory( export_path, NULL );
			int error = ::GetLastError();
			if ( result != 0 || error == ERROR_ALREADY_EXISTS ) {

				bool	read_error = false;
				bool	write_error = false;

				for (int index = 0; index < filenames.Count (); index ++) {
					StringClass source_name = filenames[index];
					StringClass dest_name;
					dest_name = export_path;
					dest_name += "\\";
					dest_name += filenames[index];

					// If the filename contains a /, create the directory
					if ( ::strchr( filenames[index], '\\' ) != NULL ) {
						StringClass dest_folder = dest_name;
						int length = ::strrchr( dest_folder, '\\' ) - dest_folder;
						dest_folder.Erase( length, dest_folder.Get_Length() - length );
						int result = (int)::CreateDirectory( dest_folder, NULL );
						int error = ::GetLastError();
						if ( result == 0 && error != ERROR_ALREADY_EXISTS ) {
							StringClass message;
							message.Format ("Failed to create folder %s.", dest_folder );
							MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK);
							return;
						}
					}

					FileClass * source_file = mix_factory.Get_File( source_name );
					FileClass * dest_file = _TheFileFactory->Get_File( dest_name );

					if ( dest_file ) {
						if ( dest_file->Open( FileClass::WRITE ) == 0 ) {
							StringClass message;
							message.Format ("Failed to open %s for writing.", dest_name );
							MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK);
							return;
						}
					}
					if ( source_file ) {
						if ( source_file->Open() == 0 ) {
							StringClass message;
							message.Format ("Failed to open %s for reading.", source_name );
							MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK);
							return;
						}
					}


					if ( source_file && source_file->Is_Available() && 
							dest_file && dest_file->Is_Available() ) {

						int length = source_file->Size();
						unsigned char file_buffer[4096];

						while ( length > 0 ) {
							int amount = min ( (int)length, (int)sizeof( file_buffer ) );
							if ( source_file->Read( &(file_buffer[0]), amount ) != amount ) {
								read_error = true;
								length = 0;
							}
							if ( dest_file->Write( &file_buffer[0], amount ) != amount ) {
								write_error = true;
								length = 0;
							}
							length -= amount;
						}

					}

					if ( source_file != NULL ) {
						mix_factory.Return_File( source_file );
					}
					if ( dest_file != NULL ) {
						_TheFileFactory->Return_File( dest_file );
					}

					if ( read_error ) {
						StringClass message;
						message.Format ("Read Error on %s.", source_name );
						MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK);
						return;
					}
					if ( write_error ) {
						StringClass message;
						message.Format ("Write Error on %s.", dest_name );
						MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK);
						return;
					}

//					filenames[index];
				}

			} else {
				StringClass message;
				message.Format ("Failed to create folder %s.", export_path);
				MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK);
			}

		} else {
			StringClass message;
			message.Format ("Error reading the filename list from %s.", current_filename);
			MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK);
		}		
	} else {
		StringClass message;
		message.Format ("No Current Mix File.");
		MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK);
	}		

	return ;
}






/////////////////////////////////////////////////////////////////////////////
//
//	OnRemoveAVAssets
//
/////////////////////////////////////////////////////////////////////////////
void
CMainFrame::OnRemoveAVAssets (void)
{
	CFileDialog dialog(true, ".mix", NULL, OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this);
	dialog.m_ofn.lpstrTitle = "Pick Mix Files";

	char *file_name_buffer = new char [65536];
	*file_name_buffer = 0;

	dialog.m_ofn.lpstrFile = file_name_buffer;
	dialog.m_ofn.nMaxFile = 65535;

	if (dialog.DoModal () == IDOK) {

		//
		// Process each mix file and put the stripped mix file into a 'temp' subdirectory under the current one.
		//
		POSITION pos = dialog.GetStartPosition();

		while (pos != NULL) {
			StringClass file_name = dialog.GetNextPathName(pos);

			//
			// Get just the path portion.
			//
			char drive[_MAX_DRIVE];
			char dir[_MAX_DIR];
			char path[_MAX_PATH];
			char name[_MAX_PATH];
			char ext[_MAX_PATH];
			_splitpath(file_name, drive, dir, name, ext);

			//
			// Make sure the temp directory exists.
			//
			strcat(dir, "temp\\");
			_makepath(path, drive, dir, NULL, NULL);
			CreateDirectory(path, NULL);

			//
			// Make the output file name.
			//
			char output_file[_MAX_PATH];
			_makepath(output_file, drive, dir, name, ext);

			AVAssetSuckerClass sucker;
			sucker.Suck(file_name.Peek_Buffer(), output_file);
		}
	}
	delete [] file_name_buffer;
}


/////////////////////////////////////////////////////////////////////////////
//
//	OnMakeMixPatch
//
/////////////////////////////////////////////////////////////////////////////
void
CMainFrame::OnMakeMixPatch(void)
{
	//
	// Ask for the name of the old source mix file.
	//
	CFileDialog dialog(true, ".mix", NULL, OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this);
	dialog.m_ofn.lpstrTitle = "Pick Old Source Mix File";

	char file_name_buffer[1024];
	file_name_buffer[0] = 0;

	dialog.m_ofn.lpstrFile = file_name_buffer;
	dialog.m_ofn.nMaxFile = 1024;

	if (dialog.DoModal () == IDOK) {

		//
		// Pull the name of the old source mix file.
		//
		POSITION pos = dialog.GetStartPosition();
		StringClass old_file_name = dialog.GetNextPathName(pos);

		//
		// Ask for the name of the new source mix file.
		//
		CFileDialog dialog2(true, ".mix", NULL, OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this);
		dialog2.m_ofn.lpstrTitle = "Pick New Source Mix File";

		file_name_buffer[0] = 0;

		dialog2.m_ofn.lpstrFile = file_name_buffer;
		dialog2.m_ofn.nMaxFile = 1024;

		if (dialog2.DoModal () == IDOK) {

			//
			// Pull the name of the new source mix file.
			//
			pos = dialog2.GetStartPosition();
			StringClass new_file_name = dialog2.GetNextPathName(pos);


			//
			// Ask for the name of the output mix file.
			//
			CFileDialog dialog3(true, ".mix", NULL, OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this);
			dialog3.m_ofn.lpstrTitle = "Pick Output Patch Mix File";

			file_name_buffer[0] = 0;

			dialog3.m_ofn.lpstrFile = file_name_buffer;
			dialog3.m_ofn.nMaxFile = 1024;

			if (dialog3.DoModal () == IDOK) {

				//
				// Pull the name of the new source mix file.
				//
				pos = dialog3.GetStartPosition();
				StringClass out_file_name = dialog3.GetNextPathName(pos);

				//
				// Ask for the directory containing the old source art.
				//
				char path[_MAX_PATH + 256];
				BROWSEINFO binfo;
				binfo.hwndOwner = NULL;
				binfo.pidlRoot = NULL;
				binfo.pszDisplayName = path;
    			binfo.lpszTitle = "Select Old Source Art Directory";
				binfo.ulFlags = 0;
				binfo.lpfn = NULL;
				binfo.lParam = 0;
				binfo.iImage = 0;

				LPITEMIDLIST itemptr = SHBrowseForFolder(&binfo);

				if (itemptr) {

					char old_art_dir[_MAX_PATH + 256];

					if (SHGetPathFromIDList(itemptr, old_art_dir)) {

						//
						// Ask for the directory containing the new source art.
						//
						char path[_MAX_PATH + 256];
						BROWSEINFO binfo;
						binfo.hwndOwner = NULL;
						binfo.pidlRoot = NULL;
						binfo.pszDisplayName = path;
    					binfo.lpszTitle = "Select New Source Art Directory";
						binfo.ulFlags = 0;
						binfo.lpfn = NULL;
						binfo.lParam = 0;
						binfo.iImage = 0;

						itemptr = SHBrowseForFolder(&binfo);

						if (itemptr) {
							char new_art_dir[_MAX_PATH + 256];
							if (SHGetPathFromIDList(itemptr, new_art_dir)) {
								MixPatchMakerClass patcher;
								patcher.Make_Patch(old_file_name.Peek_Buffer(), new_file_name.Peek_Buffer(), out_file_name.Peek_Buffer(), old_art_dir, new_art_dir);
							}
						}
					}
				}
			}
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
//
//	WindowProc
//
/////////////////////////////////////////////////////////////////////////////
LRESULT
CMainFrame::WindowProc
(
	UINT		message,
	WPARAM	wParam,
	LPARAM	lParam
) 
{
	return CFrameWnd::WindowProc(message, wParam, lParam);
}


/////////////////////////////////////////////////////////////////////////////
//
//	OnDropFiles
//
/////////////////////////////////////////////////////////////////////////////
void
CMainFrame::OnDropFiles (HDROP hDropInfo) 
{
	SetActiveWindow ();

	CWinApp *win_app = AfxGetApp ();
	ASSERT (win_app != NULL);

	//
	//	Get the count of files from the drop query
	//
	int file_count = (int)::DragQueryFile (hDropInfo, (UINT)-1, NULL, 0);
	if (file_count > 0) {

		CMixViewerDoc *doc = (CMixViewerDoc *)GetActiveDocument ();

		if (file_count == 1) {

			//
			//	Get the filename...
			//
			TCHAR filename[_MAX_PATH];
			::DragQueryFile (hDropInfo, 0, filename, _MAX_PATH);

			//
			//	Get the extension from the filename
			//
			char extension[_MAX_EXT] = { 0 };
			::_splitpath (filename, NULL, NULL, NULL, extension);

			//
			//	Is this a mix file, or should we be adding this file to the mix?
			//
			if (	::lstrcmpi (extension, ".mix") == 0 ||
					::lstrcmpi (extension, ".dat") == 0 ||
					::lstrcmpi (extension, ".dbs") == 0)
			{
				win_app->OpenDocumentFile (filename);
			} else {
				Add_To_Mix_File (doc->GetPathName (), filename);
				doc->Reload_Views ();
			}
		
		} else {

			DynamicVectorClass<StringClass> file_list;

			//
			//	Loop over each of the dropped files and add them to the current mix file
			//
			for (int index = 0; index < file_count; index ++) {
				
				//
				//	Add this file to a list so we can batch add all the files...
				//
				TCHAR filename[_MAX_PATH];
				::DragQueryFile (hDropInfo, index, filename, _MAX_PATH);				
				file_list.Add (filename);
			}
			::DragFinish(hDropInfo);

			//
			//	Add the mix files to the list
			//
			Add_To_Mix_File (doc->GetPathName (), file_list);
			doc->Reload_Views ();
		}
	}

	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
//	Add_To_Mix_File
//
/////////////////////////////////////////////////////////////////////////////
void
CMainFrame::Add_To_Mix_File (const char *mix_filename, const char *filename) 
{
	DynamicVectorClass<StringClass> file_list;	
	file_list.Add (filename);
	Add_To_Mix_File (mix_filename, file_list);
	return ;
}


/////////////////////////////////////////////////////////////////////////////
//
//	Add_To_Mix_File
//
/////////////////////////////////////////////////////////////////////////////
void
CMainFrame::Add_To_Mix_File (const char *mix_filename, DynamicVectorClass<StringClass> &new_file_list)
{
	//
	//	Set the current directory...
	//
	CString path = Strip_Filename_From_Path (mix_filename);
	if (path.GetLength () > 0) {	
		::SetCurrentDirectory (path);
	}

	//
	//	Get access to the mix file in qestion
	//
	MixFileFactoryClass *mix_factory = new MixFileFactoryClass (mix_filename, _TheFileFactory);
	if (mix_factory->Is_Valid () && mix_factory->Build_Internal_Filename_List ()) {

		//
		//	Now add the new files into the mix file...
		//
		for (int index = 0; index < new_file_list.Count (); index ++) {
			CString filename = ::Get_Filename_From_Path (new_file_list[index]);
			mix_factory->Delete_File (filename);
			mix_factory->Add_File (new_file_list[index], filename);
		}

		//
		//	Do it!
		//
		mix_factory->Flush_Changes ();
	}

	//
	//	Free the mix factory
	//
	delete mix_factory;
	mix_factory = NULL;
	return ;
}
