////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia 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 2 of the License, or
// (at your option) any later version.
//
// Nestopia 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 Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
////////////////////////////////////////////////////////////////////////////////////////

#include "NstIoScreen.hpp"
#include "NstIoLog.hpp"
#include "NstResourceString.hpp"
#include "NstWindowUser.hpp"
#include "NstWindowParam.hpp"
#include "NstWindowDropFiles.hpp"
#include "NstManagerPaths.hpp"
#include "NstManagerPreferences.hpp"
#include "NstManagerMovie.hpp"
#include "NstManagerTapeRecorder.hpp"
#include "NstManagerSaveStates.hpp"
#include "NstManagerCheats.hpp"
#include "NstManagerFiles.hpp"
#include "NstWindowMain.hpp"
#include "../core/api/NstApiMachine.hpp"
#include "../core/api/NstApiCartridge.hpp"

namespace Nestopia
{
	namespace Managers
	{
		Files::Files
		(
			Emulator& e,
			Window::Menu& m,
			const Paths& p,
			const Preferences& r,
			Movie& o,
			const TapeRecorder& t,
			const Cheats& c,
			const SaveStates& s,
			Window::Main& w
		)
		:
		Manager      ( e, m, this, &Files::OnEmuEvent ),
		paths        ( p ),
		preferences  ( r ),
		movie        ( o ),
		tapeRecorder ( t ),
		cheats       ( c ),
		saveStates   ( s ),
		window       ( w )
		{
			static const Window::MsgHandler::Entry<Files> messages[] =
			{
				{ WM_DROPFILES,                         &Files::OnMsgDropFiles },
				{ WM_COPYDATA,                          &Files::OnMsgCopyData  },
				{ Application::Instance::WM_NST_LAUNCH, &Files::OnMsgLaunch    }
			};

			window.Get().Messages().Add( this, messages );

			static const Window::Menu::CmdHandler::Entry<Files> commands[] =
			{
				{ IDM_FILE_OPEN,  &Files::OnCmdOpen  },
				{ IDM_FILE_CLOSE, &Files::OnCmdClose }
			};

			menu.Commands().Add( this, commands );

			UpdateMenu();
		}

		Files::~Files()
		{
			window.Get().Messages().Remove( this );
		}

		void Files::UpdateMenu() const
		{
			const bool available = (emulator.NetPlayers() == 0 && emulator.IsGame());

			menu[IDM_FILE_CLOSE].Enable( emulator.IsImage() );
			menu[IDM_FILE_SAVE_NST].Enable( available );
		}

		void Files::Open(wcstring const name,uint types) const
		{
			Application::Instance::Events::Signal( Application::Instance::EVENT_SYSTEM_BUSY );

			if (!window.Get().Activate())
				return;

			if (!types)
			{
				types =
				(
					Paths::File::IMAGE |
					Paths::File::STATE |
					Paths::File::MOVIE |
					Paths::File::PATCH |
					Paths::File::BATTERY |
					Paths::File::ARCHIVE
				);
			}

			Path path;

			if (name)
			{
				path = name;

				if (path.File().Empty())
					path = paths.BrowseLoad( types, name );
			}
			else
			{
				path = paths.BrowseLoad( types );
			}

			if (path.Empty())
				return;

			if (path == emulator.GetStartPath())
			{
				AutoStart();
				return;
			}

			Paths::File file;
			Paths::File patch;
			Emulator::Context context;

			switch (paths.Load( file, types, path ))
			{
				case Paths::File::NONE:
					return;

				case Paths::File::STATE:

					if (emulator.IsGame())
					{
						saveStates.Load( file.data, file.name );
						return;
					}

					types = Paths::File::GAME|Paths::File::ARCHIVE;
					context.state = file.name;
					break;

				case Paths::File::MOVIE:

					if (emulator.IsGame())
					{
						if (movie.Load( file.name, Movie::NOISY ) && preferences[Preferences::AUTOSTART_EMULATION])
						{
							AutoStart();
							window.Get().PostCommand( IDM_FILE_MOVIE_PLAY );
						}

						return;
					}

					types = Paths::File::GAME|Paths::File::ARCHIVE;
					context.movie = file.name;
					break;

				case Paths::File::BATTERY:

					if (file.name.FileInArchive().Length())
					{
						Window::User::Fail( IDS_FILE_ERR_CANT_USE_IN_ARCHIVE );
						return;
					}

					if (emulator.IsCart() && Nes::Cartridge(emulator).GetProfile()->board.HasBattery())
					{
						if (Window::User::Confirm( IDS_LOAD_APPLY_CURRENT_GAME ))
							context.image = emulator.GetImagePath();
					}

					types = Paths::File::CARTRIDGE|Paths::File::ARCHIVE;
					context.save = file.name;
					break;

				case Paths::File::IPS:
				case Paths::File::UPS:

					if (emulator.IsCart())
					{
						if (Window::User::Confirm( IDS_LOAD_APPLY_CURRENT_GAME ))
							context.image = emulator.GetImagePath();
					}

					types = Paths::File::CARTRIDGE|Paths::File::ARCHIVE;
					patch.name = file.name;
					patch.data.Import( file.data );
					break;

				default:

					types = Paths::File::NONE;
					context.image = file.name;
					break;
			}

			if (types)
			{
				if (context.image.Empty())
				{
					context.image = file.name;

					if (!paths.LocateFile( context.image, types ))
					{
						if (Window::User::Confirm( IDS_LOAD_SPECIFY_FILE ))
							context.image = paths.BrowseLoad( types );
						else
							context.image.Clear();
					}
				}

				if (context.image.Empty())
					return;

				path = file.name;

				if (!paths.Load( file, types, context.image ))
					return;

				file.name = path;
			}

			NST_ASSERT( file.type & Paths::File::IMAGE );

			if (context.save.Empty())
				context.save = paths.GetSavePath( context.image, file.type );

			if (patch.data.Empty())
			{
				const Path path(paths.GetPatchPath( context.image, file.type ));

				if (path.FileExists() && !paths.Load( patch, Paths::File::PATCH|Paths::File::ARCHIVE, path, Paths::QUIETLY ))
					Window::User::Warn( Resource::String( IDS_EMU_WARN_PATCH_LOAD_ERR ).Invoke( path ).Ptr() );
			}

			if (context.tape.Empty())
				context.tape = tapeRecorder.GetFile( context.save );

			context.samples = paths.GetSamplesPath();

			if (!emulator.Load( file.data, file.name, patch.data, paths.BypassPatchValidation(), context, preferences.GetFavoredSystem(), preferences.GetAlwaysAskProfile(), !preferences[Preferences::SUPPRESS_WARNINGS] ))
				return;

			if (context.mode == Emulator::Context::UNKNOWN && menu[IDM_MACHINE_SYSTEM_AUTO].Checked())
				Nes::Machine(emulator).SetMode( Nes::Machine(emulator).GetDesiredMode() );

			if (context.state.Length())
			{
				if (paths.Load( file, Paths::File::STATE|Paths::File::ARCHIVE, context.state ))
					saveStates.Load( file.data, file.name );
			}

			if (context.movie.Length())
				movie.Load( context.movie, Movie::QUIET );

			AutoStart();
		}

		bool Files::Close() const
		{
			if
			(
				!emulator.IsOn() ||
				!preferences[Preferences::CONFIRM_RESET] ||
				Window::User::Confirm( IDS_ARE_YOU_SURE, IDS_MACHINE_POWER_OFF_TITLE )
			)
			{
				emulator.Unload();
				return true;
			}

			return false;
		}

		void Files::AutoStart() const
		{
			if
			(
				preferences[Preferences::AUTOSTART_EMULATION] &&
				emulator.IsImage() && !emulator.IsOn()
			)
				emulator.Power( true );
		}

		void Files::DisplayLoadMessage(const bool loaded) const
		{
			const uint length = window.GetMaxMessageLength();
			const uint threshold = (loaded ? 10 : 12);

			if (length > threshold)
				Io::Screen() << Resource::String( loaded ? IDS_SCREEN_LOADED : IDS_SCREEN_UNLOADED ).Invoke( Path::Compact( emulator.GetImagePath().Target(), length - (threshold - 1) ) );
		}

		ibool Files::OnMsgDropFiles(Window::Param& param)
		{
			Window::DropFiles dropFiles( param );

			if (dropFiles.Size() && menu[IDM_FILE_OPEN].Enabled())
				Open( dropFiles[0].Ptr() );

			return true;
		}

		ibool Files::OnMsgCopyData(Window::Param& param)
		{
			NST_VERIFY( param.lParam );

			if (menu[IDM_FILE_OPEN].Enabled() && param.lParam)
			{
				const COPYDATASTRUCT& copyData = *reinterpret_cast<COPYDATASTRUCT*>(param.lParam);
				NST_VERIFY( copyData.dwData == Application::Instance::COPYDATA_OPENFILE_ID );

				if (copyData.dwData == Application::Instance::COPYDATA_OPENFILE_ID)
				{
					NST_VERIFY( copyData.lpData && copyData.cbData >= sizeof(wchar_t) && static_cast<wcstring>(copyData.lpData)[copyData.cbData/sizeof(wchar_t)-1] == '\0' );

					if (copyData.lpData && copyData.cbData >= sizeof(wchar_t) && static_cast<wcstring>(copyData.lpData)[copyData.cbData/sizeof(wchar_t)-1] == '\0')
						Open( static_cast<wcstring>(copyData.lpData) );
				}
			}

			param.lResult = true;
			return true;
		}

		ibool Files::OnMsgLaunch(Window::Param& param)
		{
			NST_ASSERT( param.lParam );
			Open( reinterpret_cast<wcstring>(param.lParam), param.wParam );
			return true;
		}

		void Files::OnCmdOpen(uint)
		{
			Open();
		}

		void Files::OnCmdClose(uint)
		{
			Close();
		}

		void Files::OnEmuEvent(const Emulator::Event event,const Emulator::Data data)
		{
			switch (event)
			{
				case Emulator::EVENT_LOAD:
				case Emulator::EVENT_UNLOAD:

					if (!preferences.GetDisableStatusMsg())
						DisplayLoadMessage( event == Emulator::EVENT_LOAD );

					UpdateMenu();
					break;

				case Emulator::EVENT_NETPLAY_MODE:

					menu[IDM_FILE_OPEN].Enable( !data );
					menu[IDM_FILE_LOAD_NST].Enable( !data );
					break;
			}
		}
	}
}
