//+----------------------------------------------------------------------------+
//| Description:  Magic Set Editor - Program to make Magic (tm) cards          |
//| Copyright:    (C) 2001 - 2010 Twan van Laarhoven and Sean Hunt             |
//| License:      GNU General Public License 2 or later (see file COPYING)     |
//+----------------------------------------------------------------------------+

// ----------------------------------------------------------------------------- : Includes

#include <util/prec.hpp>
#include <util/io/package_manager.hpp>
#include <util/spell_checker.hpp>
#include <data/game.hpp>
#include <data/set.hpp>
#include <data/settings.hpp>
#include <data/locale.hpp>
#include <data/installer.hpp>
#include <data/format/formats.hpp>
#include <cli/cli_main.hpp>
#include <cli/text_io_handler.hpp>
#include <gui/welcome_window.hpp>
#include <gui/update_checker.hpp>
#include <gui/packages_window.hpp>
#include <gui/set/window.hpp>
#include <gui/symbol/window.hpp>
#include <gui/thumbnail_thread.hpp>
#include <wx/fs_inet.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#include <wx/socket.h>

ScriptValueP export_set(SetP const& set, vector<CardP> const& cards, ExportTemplateP const& exp, String const& outname);

DECLARE_TYPEOF_COLLECTION(String);

// ----------------------------------------------------------------------------- : Main function/class

/// The application class for MSE.
/** This class is used by wxWidgets as a kind of 'main function'
 */
class MSE : public wxApp {
  public:
	/// Do nothing. The command line parsing, etc. is done in OnRun
	bool OnInit() { return true; }
	/// Main startup function of the program
	/** Use OnRun instead of OnInit, so we can determine whether or not we need a main loop
	 *  Also, OnExit is always run.
	 */
	int OnRun();
	/// Actually start the GUI mainloop
	int runGUI();
	/// On exit: write the settings to the config file
	int OnExit();
	/// On exception: display error message
	void HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEvent& event) const;
	/// Hack around some wxWidget idiocies
	int FilterEvent(wxEvent& ev);
	/// Fancier assert
	#if defined(_MSC_VER) && defined(_DEBUG) && defined(_CRT_WIDE)
		void OnAssert(const wxChar *file, int line, const wxChar *cond, const wxChar *msg);
	#endif
};

IMPLEMENT_APP(MSE)

// ----------------------------------------------------------------------------- : Checks

void nag_about_ascii_version() {
	#if !defined(UNICODE) && defined(__WXMSW__)
		// windows 2000/XP/Vista/... users shouldn't use the 9x version
		OSVERSIONINFO info;
		info.dwOSVersionInfoSize = sizeof(info);
		GetVersionEx(&info);
		if (info.dwMajorVersion >= 5) {
			queue_message(MESSAGE_WARNING,
				_("This build of Magic Set Editor is intended for Windows 95/98/ME systems.\n")
				_("It is recommended that you download the appropriate MSE version for your Windows version."));
		}
	#endif
}

// ----------------------------------------------------------------------------- : Initialization

int MSE::OnRun() {
	try {
		#ifdef __WXMSW__
			SetAppName(_("Magic Set Editor"));
		#else
			// Platform friendly appname
			SetAppName(_("magicseteditor"));
		#endif
		wxInitAllImageHandlers();
		wxFileSystem::AddHandler(new wxInternetFSHandler); // needed for update checker
		wxSocketBase::Initialize();
		init_script_variables();
		init_file_formats();
		cli.init();
		package_manager.init();
		settings.read();
		the_locale = Locale::byName(settings.locale);
		nag_about_ascii_version();
		
		// interpret command line
		{
			vector<String> args;
			for (int i = 1 ; i < argc ; ++i) {
				args.push_back(argv[i]);
				if (args.back() == _("--color")) args.pop_back(); // ingnore the --color argument, it is handled by cli.init()
			}
			if (!args.empty()) {
				// Find the extension
				wxFileName f(args[0].Mid(0,args[0].find_last_not_of(_("\\/"))+1));
				if (f.GetExt() == _("mse-symbol")) {
					// Show the symbol editor
					Window* wnd = new SymbolWindow(nullptr, args[0]);
					wnd->Show();
					return runGUI();
				} else if (f.GetExt() == _("mse-set") || f.GetExt() == _("mse") || f.GetExt() == _("set")) {
					// Show the set window
					Window* wnd = new SetWindow(nullptr, import_set(args[0]));
					wnd->Show();
					return runGUI();
				} else if (f.GetExt() == _("mse-installer")) {
					// Installer; install it
					InstallType type = settings.install_type;
					if (args.size() >= 2) {
						if (starts_with(args[1], _("--"))) {
							parse_enum(String(args[1]).substr(2), type);
						}
					}
					InstallerP installer = open_package<Installer>(args[0]);
					PackagesWindow wnd(nullptr, installer);
					wnd.ShowModal();
					return EXIT_SUCCESS;
				} else if (args[0] == _("--symbol-editor")) {
					Window* wnd = new SymbolWindow(nullptr);
					wnd->Show();
					return runGUI();
				} else if (args[0] == _("--create-installer")) {
					// create an installer
					Installer inst;
					FOR_EACH(arg, args) {
						if (!starts_with(arg,_("--"))) {
							inst.addPackage(arg);
						}
					}
					if (inst.prefered_filename.empty()) {
						throw Error(_("Specify packages to include in installer"));
					} else {
						inst.saveAs(inst.prefered_filename, false);
					}
					return EXIT_SUCCESS;
				} else if (args[0] == _("--help") || args[0] == _("-?") || args[0] == _("/?")) {
					// command line help
					cli << _("Magic Set Editor\n\n");
					cli << _("Usage: ") << BRIGHT << argv[0] << NORMAL << _(" [") << PARAM << _("OPTIONS") << NORMAL << _("]");
					cli << _("\n\n  no options");
					cli << _("\n         \tStart the MSE user interface showing the welcome window.");
					cli << _("\n\n  ") << BRIGHT << _("-?") << NORMAL << _(", ")
									   << BRIGHT << _("--help") << NORMAL;
					cli << _("\n         \tShows this help screen.");
					cli << _("\n\n  ") << BRIGHT << _("-v") << NORMAL << _(", ")
									   << BRIGHT << _("--version") << NORMAL;
					cli << _("\n         \tShow version information.");
					cli << _("\n\n  ") << PARAM << _("FILE") << FILE_EXT << _(".mse-set") << NORMAL << _(", ")
									   << PARAM << _("FILE") << FILE_EXT << _(".set") << NORMAL << _(", ")
									   << PARAM << _("FILE") << FILE_EXT << _(".mse") << NORMAL;
					cli << _("\n         \tLoad the set file in the MSE user interface.");
					cli << _("\n\n  ") << PARAM << _("FILE") << FILE_EXT << _(".mse-symbol") << NORMAL;
					cli << _("\n         \tLoad the symbol into the MSE symbol editor.");
					cli << _("\n\n  ") << PARAM << _("FILE") << FILE_EXT << _(".mse-installer")
									   << NORMAL << _(" [") << BRIGHT << _("--local") << NORMAL << _("]");
					cli << _("\n         \tInstall the packages from the installer.");
					cli << _("\n         \tIf the ") << BRIGHT << _("--local") << NORMAL << _(" flag is passed, install packages for this user only.");
					cli << _("\n\n  ") << BRIGHT << _("--symbol-editor") << NORMAL;
					cli << _("\n         \tShow the symbol editor instead of the welcome window.");
					cli << _("\n\n  ") << BRIGHT << _("--create-installer") << NORMAL << _(" [")
									   << PARAM << _("OUTFILE") << FILE_EXT << _(".mse-installer") << NORMAL << _("] [")
									   << PARAM << _("PACKAGE") << NORMAL << _(" [") << PARAM << _("PACKAGE") << NORMAL << _(" ...]]");
					cli << _("\n         \tCreate an instaler, containing the listed packages.");
					cli << _("\n         \tIf no output filename is specified, the name of the first package is used.");
					cli << _("\n\n  ") << BRIGHT << _("--export") << NORMAL << PARAM << _(" TEMPLATE SETFILE ") << NORMAL << _(" [") << PARAM << _("OUTFILE") << NORMAL << _("]");
					cli << _("\n         \tExport a set using an export template.");
					cli << _("\n         \tIf no output filename is specified, the result is written to stdout.");
					cli << _("\n\n  ") << BRIGHT << _("--export-images") << NORMAL << PARAM << _(" SETFILE") << NORMAL << _(" [") << PARAM << _("IMAGE") << NORMAL << _("]");
					cli << _("\n         \tExport the cards in a set to image files,");
					cli << _("\n         \tIMAGE is the same format as for 'export all card images'.");
					cli << _("\n\n  ") << BRIGHT << _("--cli") << NORMAL << _(" [")
									   << BRIGHT << _("--quiet") << NORMAL << _("] [")
									   << BRIGHT << _("--raw") << NORMAL << _("] [")
									   << BRIGHT << _("--script ") << NORMAL << PARAM << _("FILE") << NORMAL << _("] [")
									   << PARAM << _("SETFILE") << NORMAL << _("]");
					cli << _("\n         \tStart the command line interface for performing commands on the set file.");
					cli << _("\n         \tUse ") << BRIGHT << _("-q") << NORMAL << _(" or ") << BRIGHT << _("--quiet") << NORMAL << _(" to supress the startup banner and prompts.");
					cli << _("\n         \tUse ") << BRIGHT << _("--raw") << NORMAL << _(" for raw output mode.");
					cli << _("\n         \tUse ") << BRIGHT << _("--script") << NORMAL << _(" to execute a script file.");
					cli << _("\n\nRaw output mode is intended for use by other programs:");
					cli << _("\n    - The only output is only in response to commands.");
					cli << _("\n    - For each command a single 'record' is written to the standard output.");
					cli << _("\n    - The record consists of:");
					cli << _("\n        - A line with an integer status code, 0 for ok, 1 for warnings, 2 for errors");
					cli << _("\n        - A line containing an integer k, the number of lines to follow");
					cli << _("\n        - k lines, each containing UTF-8 encoded string data.");
					cli << ENDL;
					cli.flush();
					return EXIT_SUCCESS;
				} else if (args[0] == _("--version") || args[0] == _("-v") || args[0] == _("-V")) {
					// dump version
					cli << _("Magic Set Editor\n");
					cli << _("Version ") << app_version.toString() << version_suffix << ENDL;
					cli.flush();
					return EXIT_SUCCESS;
				} else if (args[0] == _("--cli")) {
					// command line interface
					SetP set;
					vector<String> scripts;
					bool quiet = false;
					for (size_t i = 1 ; i < args.size() ; ++i) {
						String const& arg = args[i];
						wxFileName f(arg);
						if (f.GetExt() == _("mse-set") || f.GetExt() == _("mse") || f.GetExt() == _("set")) {
							set = import_set(arg);
						} else if (arg == _("-q") || arg == _("--quiet") || arg == _("--silent")) {
							quiet = true;
						} else if (arg == _("-r") || arg == _("--raw")) {
							quiet = true;
							cli.enableRaw();
						} else if ((arg == _("-s") || arg == _("--script")) && i+1 < args.size()) {
							scripts.push_back(args[i+1]);
							++i;
						} else if (arg == _("--color")) {
							// ignore
						} else {
							throw Error(_("Invalid command line argument: ") + arg);
						}
					}
					CLISetInterface cli_interface(set,quiet);
					if (scripts.empty()) {
						cli_interface.run_interactive();
					} else {
						FOR_EACH(script, scripts) {
							cli_interface.run_script_file(script);
						}
					}
					return EXIT_SUCCESS;
				} else if (args[0] == _("--export-images")) {
					if (args.size() < 2) {
						throw Error(_("No input file specified for --export-images"));
					}
					SetP set = import_set(args[1]);
					// path
					String out = args.size() >= 3
							   ? args[2]
							   : settings.gameSettingsFor(*set->game).images_export_filename;
					String path = _(".");
					size_t pos = out.find_last_of(_("/\\"));
					if (pos != String::npos) {
						path = out.substr(0, pos);
						if (!wxDirExists(path)) wxMkdir(path);
						path += _("/x");
						out  = out.substr(pos + 1);
					}
					// export
					export_images(set, set->cards, path, out, CONFLICT_NUMBER_OVERWRITE);
					return EXIT_SUCCESS;
				} else if (args[0] == _("--export")) {
					if (args.size() < 2) {
						throw Error(_("No export template specified for --export"));
					} else if (args.size() < 3) {
						throw Error(_("No input set file specified for --export"));
					}
					String export_template = args[1];
					ExportTemplateP exp = ExportTemplate::byName(export_template);
					SetP set = import_set(args[2]);
					String out = args.size() >= 4 ? args[3] : _("");
					ScriptValueP result = export_set(set, set->cards, exp, out);
					if (out.empty()) {
						cli << result->toString();
					}
					return EXIT_SUCCESS;
				} else {
					throw Error(_("Invalid command line argument: ") + args[0]);
				}
			}
		}
		
		// no command line arguments, or error, show welcome window
		(new WelcomeWindow())->Show();
		return runGUI();
		
	} CATCH_ALL_ERRORS(true);
	cli.print_pending_errors();
	return EXIT_FAILURE;
}

int MSE::runGUI() {
	check_updates();
	return wxApp::OnRun();
}

// ----------------------------------------------------------------------------- : Exit

int MSE::OnExit() {
	thumbnail_thread.abortAll();
	settings.write();
	package_manager.destroy();
	SpellChecker::destroyAll();
	return 0;
}

// ----------------------------------------------------------------------------- : Exception handling

void MSE::HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEvent& event) const {
	try {
		wxApp::HandleEvent(handler, func, event);
		return;
	} CATCH_ALL_ERRORS(true);
	if (wxNotifyEvent* nev = dynamic_cast<wxNotifyEvent*>(&event)) {
		// notifications about for instance list box selection will fail if an exception is thrown
		nev->Veto();
	}
}

#if defined(_MSC_VER) && defined(_DEBUG) && defined(_CRT_WIDE)
	// Print assert failures to debug output
	void MSE::OnAssert(const wxChar *file, int line, const wxChar *cond, const wxChar *msg) {
		#ifdef UNICODE
			msvc_assert(msg, cond, file, line);
		#else
			wchar_t file_[1024]; mbstowcs(file_,file,1023);
			wchar_t cond_[1024]; mbstowcs(cond_,cond,1023);
			wchar_t msg_ [1024]; mbstowcs(msg_, msg, 1023);
			msvc_assert(msg_, cond_, file_, line);
		#endif
	}
#endif

// ----------------------------------------------------------------------------- : Events

int MSE::FilterEvent(wxEvent& ev) {
	/*if (ev.GetEventType() == wxEVT_MOUSE_CAPTURE_LOST) {
		return 1;
	} else {
		return -1;
	}*/
	return -1;
}
