/* GMM (C) 2018 Stephane Charette <stephanecharette@gmail.com>
 * $Id: GMMApp.cpp 2636 2018-10-16 00:02:15Z stephane $
 */

#include "GMM.hpp"


void sort_config_file(const std::string & config_filename)
{
	// this is called on shutdown when certain things like logging may no longer exist,
	// so beware what calls you make from within this function

	/* 1) we read the XML config file
	 * 2) we find the "PROPERTIES" section
	 * 3) we sort every line within the "PROPERTIES" section
	 * 4) if there is a difference after sorting then we write out a new config file to a temporary location
	 * 5) if we write out a new config file, we delete the original and rename the temporary file
	 */

	try
	{
		// step #1
		const VStr input = Lox::readTextFile(config_filename);
		VStr output = input;

		// now we need to find the <PROPERTIES> and </PROPERTIES> lines, and sort everything in between
		auto iter1 = output.begin();
		auto iter2 = output.end();

		// step #2
		for (auto i = iter1; i != iter2; i++)
		{
			VStr v = Lox::splitOnWords(*i);
			if (v.empty() == false)
			{
				if (v[0] == "<PROPERTIES>")
				{
					iter1 = ++i;
					continue;
				}
				if (v[0] == "</PROPERTIES>")
				{
					iter2 = i;
					break;
				}
			}
		}

		if (iter1 != output.begin() && iter2 != output.end())
		{
			// step #3

			// we need to sort the text strings between these two positions
			std::sort(iter1, iter2,
				[](const std::string & lhs, const std::string & rhs)
				{
					// perform a case-insensitive sort
					return Lox::lowercase(lhs) < Lox::lowercase(rhs);
				} );

			if (input != output)
			{
				// step #4
				File config_file(config_filename);
				File tmp_config_file = config_file.withFileExtension("tmp");
				tmp_config_file.deleteFile();
				for (auto str : output)
				{
					tmp_config_file.appendText(str + "\n", false, false, "\r\n");
				}

				// step #5
				config_file.deleteFile();
				tmp_config_file.moveFileTo(config_file);
			}
		}
	}
	catch(...)
	{
		// do nothing, just ignore the error since we're shutting down anyway
	}

	return;
}


void GMM_Juce_Crash_Handler(void *ptr)
{
#ifndef WIN32
	const long signal_number = (long)ptr;
	LOG_MSG( "crash handler invoked for signal #" << signal_number << " (" << strsignal(signal_number) << ")" );
#else
	LOG_MSG( "crash handler invoked" );
#endif

	const Lox::Exception e( LOX_WHERE, "crash handler invoked" );
	LOG_MSG( e.to_string() );

	exit(1);
}


void GMM_CPlusPlus_Terminate_Handler(void)
{
	LOG_MSG( "terminate handler invoked" );

	const Lox::Exception e( LOX_WHERE, "terminate handler invoked" );
	LOG_MSG( e.to_string() );

	exit(2);
}


void GMM_CPlusPlus_Unexpected_Handler(void)
{
	LOG_MSG( "unexpected handler invoked" );

	const Lox::Exception e( LOX_WHERE, "unexpected handler invoked" );
	LOG_MSG( e.to_string() );

	exit(3);
}


GMMApplication::GMMApplication(void)
{
	return;
}


GMMApplication::~GMMApplication(void)
{
	return;
}


void GMMApplication::initialise(const String& commandLine)
{
	// This method is where you should put your application's initialisation code.

	std::set_terminate(GMM_CPlusPlus_Terminate_Handler);
	std::set_unexpected(GMM_CPlusPlus_Unexpected_Handler);
	SystemStats::setApplicationCrashHandler(GMM_Juce_Crash_Handler);

	LOG_MSG( "starting Gorman Moisture Measurement" );

	std::srand(Time::getMillisecondCounter());

	run( EJob::kInitializeAndLoad );

	return;
}


void GMMApplication::shutdown()
{
	// shutdown the application
	LOG_MSG( "shutting down Gorman Moisture Measurement" );

	std::string config_filename;
	if (cfg)
	{
		config_filename = cfg->getFile().getFullPathName().toStdString();
	}

	// strictly speaking, we don't have to reset the unique ptr since all the
	// objects will be deleted when the unique pointers go out of scope, but
	// this way by being explicit we can control the exact order in which
	// they're deleted
	wnd.reset(nullptr);
	cfg.reset(nullptr);

	reset_gmm_look_and_feel();
	laf.reset(nullptr);

	if (config_filename.empty() == false)
	{
		sort_config_file(config_filename);
	}

	return;
}


void GMMApplication::anotherInstanceStarted(const String& commandLine)
{
	// When another instance of the app is launched while this one is running,
	// this method is invoked, and the commandLine parameter tells you what
	// the other instance's command-line arguments were.

	return;
}


void GMMApplication::unhandledException(const std::exception *e, const String &sourceFilename, int lineNumber)
{
	log_and_show_exception(e, sourceFilename.toStdString(), lineNumber );

	return;
}


void GMMApplication::run( const EJob job )
{
	LOG_MSG("running job #" << (int)job << ": \"" << to_string(job) << "\"");

	try
	{
		switch(job)
		{
			case EJob::kInvalid:
			{
				break;
			}
			case EJob::kInitializeAndLoad:
			{
				job_initialize_and_load();
				break;
			}
			case EJob::kShowFirstWindow:
			{
				// figure out which one of the possible windows we should be showing

				LOG_MSG("need to invoke the main window");
				run(EJob::kShowMainWindow);
				break;
			}
			case EJob::kShowMainWindow:
			{
				LOG_MSG("starting the main window");
				wnd.reset(new GMMWnd);
				break;
			}
			case EJob::kShowAboutWindow:
			{
				job_show_about_window();
				break;
			}
			case EJob::kShutdown:
			{
				if (wnd)
				{
					wnd->setVisible(false);
				}
				JUCEApplication::getInstance()->systemRequestedQuit();
				break;
			}
			case EJob::kNoOp:
			{
				break;
			}
		}
	}
	CATCH_AND_LOG

	return;
}


void GMMApplication::job_initialize_and_load(void)
{
	SplashScreen *splash = new SplashScreen( "Gorman Moisture Meter", GormanSplashLogo(), false);
	splash->deleteAfterDelay(RelativeTime::seconds(5), true);

	// Beware!  The order in which these are instantiated is important.
	laf	.reset(init_gmm_look_and_feel());
	cfg	.reset(new Cfg);

	LoadWnd load_wnd;
	load_wnd.runThread();	// wont return until LoadWnd::run() is done
	if (load_wnd.finished_loading == false)
	{
		errors.push_back("Load thread failed to complete.");
	}

	if (errors.size())
	{
		// we have 1 or more fatal error conditions

		for (const auto &msg : errors)
		{
			LOG_MSG("Error detected:" << std::endl << msg);
		}
		const std::string msg = errors[0];
		AlertWindow::showMessageBox(AlertWindow::WarningIcon, "Gorman Moisture Meter", msg, "quit");
		exit(1);
	}

	run(EJob::kShowFirstWindow);

	return;
}


void GMMApplication::job_show_about_window(void)
{
	std::stringstream ss;
	ss	<< "GMM v" << GMM_VERSION					<< std::endl
		<< ""										<< std::endl
		<< "stephanecharette@gmail.com"				<< std::endl
		<< "http://www.ccoderun.ca/"				<< std::endl
		<< ""										<< std::endl
		<< "Built: " << __DATE__ << " " << __TIME__	<< std::endl
		<< "Type: "
#if WIN32
		<< "Windows"
#else
		<< "Linux"
#endif
		<< (sizeof(void*) == 4 ? ", X86 build" : ", AMD64 build")							<< std::endl
		<< "User: "	<< SystemStats::getLogonName() << "@" << SystemStats::getComputerName()	<< std::endl
		<< "O/S: "	<< (SystemStats::isOperatingSystem64Bit() ? "64-bit " : "32-bit ")
					<< SystemStats::getOperatingSystemName()	<< ", "
					<< SystemStats::getUserLanguage()			<< ", "
					<< SystemStats::getUserRegion()											<< std::endl
		<< "Log: "	<< get_log_filename()													<< std::endl
		<< "Cfg: "	<< cfg->getFile().getFullPathName().toStdString()						<< std::endl
		<< ""																				<< std::endl
		<< "Juce: version "			<< JUCE_MAJOR_VERSION << "."
									<< JUCE_MINOR_VERSION << "."
									<< JUCE_BUILDNUMBER 									<< std::endl
		<< "Loxahatchee: version "	<< Lox::version();

	LOG_MSG("About window information:" << std::endl << ss.str());

	AlertWindow::showMessageBoxAsync(AlertWindow::AlertIconType::InfoIcon, "Gorman Moisture Meter", ss.str() );

	return;
}
