/* GMM (C) 2018 Stephane Charette <stephanecharette@gmail.com>
 * $Id: LoadWnd.cpp 2682 2018-12-05 08:30:42Z stephane $
 */

#include <regex>
#include "LoadWnd.hpp"
#include "src-img.hpp"

#ifdef WIN32
#include <Windows.h>
#endif

LoadWnd::LoadWnd(void) : ThreadWithProgressWindow( "GMM", true, false ),
	finished_loading(false),
	total_number_of_items_to_load(0),
	progress_level(0)
{
	// move the progress window far enough down the screen so it appears beneath (not behind) the splash screen logo
	juce::Rectangle<int> r = getAlertWindow()->getBounds();
	r.setY( r.getY() + GormanSplashLogo().getHeight() + 25 );
	getAlertWindow()->setBounds( r );

	return;
}


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


void LoadWnd::update_progress_bar(const double progress, const std::string msg)
{
	if (msg.empty() == false)
	{
		LOG_MSG("loading: " << msg);
		setStatusMessage(msg);
	}

	if (total_number_of_items_to_load < 1.0)
	{
		total_number_of_items_to_load = gmm().cfg->getIntValue("number_of_items_to_load", 100 );

		// guard against things like divide-by-zero
		if (total_number_of_items_to_load < 1.0)
		{
			total_number_of_items_to_load = 100;
		}
	}

	progress_level += progress;
	if (progress_level < 1.0)
	{
		progress_level = 1.0;
	}

	const double tmp = progress_level / total_number_of_items_to_load;
	LOG_MSG("update progress bar: " << progress_level << "/" << total_number_of_items_to_load << " = " << Lox::Numbers::format(tmp * 100.0, 1) << "%");
	setProgress( tmp );

	return;
}


std::string LoadWnd::get_status_message(void)
{
	std::string msg = "Starting...";

	Random r;
	r.setSeedRandomly();
	if (r.nextFloat() >= 0.9)	// 10% chance of getting a random string
	{
		const SStr s =
		{
			"Beam me up..."									,
			"Calling Jenny at 867-5309..."					,
			"Chasing an untamed ornithoid without cause..."	,
			"Chatting with other computers..."				,
			"Comparing Picard and Kirk..."					,
			"Don't worry, be happy!"						,
			"E.T. phone home..."						,
			"Evaluating artificial intelligence..."			,
			"Fixing ozone layer..."							,
			"Going fishing..."								,
			"Looking for evidence of life..."				,
			"Looking for trees..."							,
			"Making him an offer he can't refuse..."		,
			"May the force be with you..."					,
			"Opening the pod bay doors..."					,
			"Party at your place next weekend..."			,
			"Re-booting the internet..."					,
			"Saying the alphabet backwards..."				,
			"Searching for doughnuts..."					,
			"Shall we play a game?"							,
			"Sipping coffee..."								,
			"Starting HAL 9000..."							,
			"Studying Shakespeare..."						,
			"There's no place like home..."					,
			"Thinking BBQ for dinner..."					,
			"Tightening lug nuts..."						,
			"Waiting for summer..."							,
			"We're going to need a bigger boat..."			,
			"You talking to me?"
		};

		// pick a random string to return
		const size_t idx = r.nextInt(s.size());
		SStr::const_iterator iter = s.begin();
		std::advance(iter, idx);
		msg = *iter;
	}

	return msg;
}


void LoadWnd::run(void)
{
	// this is on a secondary thread -- see call to runThread() in GMMApplication::initialise()

	try
	{
		// give more weight to some of the initial tasks to get the status bar started
		update_progress_bar(5.0, "Starting..." );

		auto &errors = gmm().errors;

		if (errors.empty()) check_for_new_version();
		if (errors.empty()) check_for_motd();
		if (errors.empty()) check_build_timestamp();
		if (errors.empty()) find_all_devices();
		if (errors.empty()) fix_all_devices();
		if (errors.empty())
		{
			// make sure the progress bar gets to 100% by inserting a tiny amount of sleep at the end of loading process
			update_progress_bar(1.0, get_status_message());
			for (size_t count=0; count < 10; count++)
			{
				update_progress_bar(1.0);
				// 10 x 100 milliseconds == 1 full second
				std::this_thread::sleep_for(std::chrono::milliseconds(100));
			}

			if (progress_level != total_number_of_items_to_load)
			{
				cfg().setValue( "number_of_items_to_load", String(progress_level) );
			}

			finished_loading = true;
		}
	}
	CATCH_LOG_SHOW_AND_RETHROW

	return;
}


void LoadWnd::check_for_new_version(void)
{
	if (cfg().get_bool("get_version_number") == false)
	{
		return;
	}

	update_progress_bar(10.0, "Verifying client version number...");

	// sort by modification time, descending order
	const std::string url = "http://www.ccoderun.ca/gmm/download/?C=M;O=D";
	LOG_MSG( "getting download information from " << url );
	const std::string text = get_url( url );

	// if this works, we end up with lines like this:
	//
	//	[...] <td><a href="gmm-1.0.0-2265-win64.exe">gmm-1.0.0-2265-win64.exe</a></td> [...]
	//	[...] <td><a href="gmm-1.0.0-2265-win32.exe">gmm-1.0.0-2265-win32.exe</a></td> [...]
	//
	// from which we'll try to extract the first version number (since they're sorted in descending order)
	const std::regex rx( "href=\"gmm-([0-9-.]+)-win" );
	std::smatch match;
	if ( std::regex_search( text, match, rx ) )
	{
		const std::string new_version_number = match.str(1);

		LOG_MSG( "current version=" << GMM_VERSION << ", most recent version=" << new_version_number );
		
		if (new_version_number > GMM_VERSION)
		{
			AlertWindow::showOkCancelBox(
				AlertWindow::InfoIcon,
				"This version is out of date!",
				"You are using version " + std::string(GMM_VERSION) + " but the newer version " + new_version_number + " is available for download.\n"
				"\n"
				"Open the download page?",
				String::empty,
				String::empty,
				nullptr,
				ModalCallbackFunction::create(
					[url](int result_code)
					{
						if (result_code)
						{
							LOG_MSG( "opening download page " << url );
							URL::createWithoutParsing(url).launchInDefaultBrowser();
							LOG_MSG( "exiting since the download page has been opened" );
							gmm().quit();
						}
						else
						{
							LOG_MSG( "do not open the download page " << url );
						}
					} ) );
		}
	}

	return;
}


void LoadWnd::check_for_motd(void)
{
	if (cfg().get_bool("get_motd") == false)
	{
		return;
	}

	update_progress_bar(10.0, "Getting ready...");

	String motd = Lox::trim( get_url("http://www.ccoderun.ca/gmm/motd.txt") );
	if (motd.isEmpty())
	{
		update_progress_bar(10.0);

		motd = Lox::trim( get_url("http://www.ccoderun.ca/gmm/download/motd.txt") );
	}

	if (motd.isNotEmpty())
	{
		LOG_MSG( "MOTD: " << motd );

		AlertWindow::showMessageBoxAsync(AlertWindow::InfoIcon, "Gorman Moisture Meter", motd );
	}

	return;
}


void LoadWnd::check_build_timestamp(void)
{
	#if 1
	update_progress_bar(1.0);

	const std::time_t tt1 = Lox::timeFromString(__DATE__ " " __TIME__);
	const std::time_t tt2 = std::time(nullptr);
	const std::time_t seconds_in_one_day	= 86400;
	const std::time_t seconds_in_one_week	= 7 * seconds_in_one_day;

	if (tt2 - tt1 > seconds_in_one_week)
	{
		AlertWindow::showMessageBoxAsync(
			AlertWindow::WarningIcon,
			"Gorman Moisture Meter",
			"This build is from " + Lox::approximateTime(tt1) + ". Please consider upgrading to a newer version of GMM.\n"
			"\n"
			"Visit \"https://www.ccoderun.ca/gmm/download/\" for the latest build.");
	}
	#endif

	return;
}


void LoadWnd::find_all_devices(void)
{
	// find all of the devices by looking for configuration keys that end in "_device"

	update_progress_bar(1.0, "Looking for devices...");

	auto & prefixes = gmm().prefixes;
	prefixes.clear();

	for (const auto k : cfg().getAllProperties().getAllKeys())
	{
		update_progress_bar(1.0);
		if (k.endsWith("_device"))
		{
			const std::string prefix = k.dropLastCharacters(7).toStdString();
			LOG_MSG("found device with key=" << k.toStdString() << ", prefix=" << prefix);
			prefixes.push_back(prefix);
		}
	}

	if (prefixes.empty())
	{
		// create a few sample entries in configuration if absolutely nothing was found
		cfg().set_str("a_device", "com4"		);
		cfg().set_str("b_device", "com5"		);
		cfg().set_str("c_device", "com6"		);
		cfg().set_str("d_device", "com7"		);
		cfg().set_str("a_name"	, "Planer #1"	);
		cfg().set_str("b_name"	, "Planer #2"	);
		cfg().set_str("c_name"	, "Moulder"		);
		cfg().set_str("d_name"	, "Spare"		);
		prefixes.push_back("a");
		prefixes.push_back("b");
		prefixes.push_back("c");
		prefixes.push_back("d");
	}

	return;
}


void LoadWnd::fix_all_devices(void)
{
	// also see Cfg::first_time_initialization()

	for (const auto prefix : gmm().prefixes)
	{
		update_progress_bar(1.0, "Verifying...");
		cfg().insert_if_not_exist(prefix + "_name"				, "Unknown"	);
		cfg().insert_if_not_exist(prefix + "_interval_ms"		, 250		);
		cfg().insert_if_not_exist(prefix + "_chart_redraw_ms"	, 1000		);
		cfg().insert_if_not_exist(prefix + "_run_simulation"	, "false"	);
		cfg().insert_if_not_exist(prefix + "_read_len"			, 11		);
		cfg().insert_if_not_exist(prefix + "_regex_pattern"		, "\\s*([\\d.]{4})\\s+([\\d.]{4})\\s*");
		cfg().insert_if_not_exist(prefix + "_export_image"		, "false"	);
		cfg().insert_if_not_exist(prefix + "_opc_kiln_register"	, "N75:3"	);
		cfg().insert_if_not_exist(prefix + "_opc_run_register"	, "N75:4"	);
		cfg().insert_if_not_exist(prefix + "_opc_pkg_register"	, "N75:5"	);
		cfg().insert_if_not_exist(prefix + "_opc_via_csv_filename", ""		);
		cfg().insert_if_not_exist(prefix + "_opc_via_csv_regex"	, "^\\s*\"(.+)\",([0-9]+)$");
		cfg().insert_if_not_exist(prefix + "_last_kiln"			, "1"		);
		cfg().insert_if_not_exist(prefix + "_last_run"			, "1"		);
		cfg().insert_if_not_exist(prefix + "_last_pkg"			, "1"		);

		// FLOW CONTROL: NONE:
		cfg().insert_if_not_exist(prefix + "_comm_str", "baud=9600 parity=N data=8 stop=1 to=on xon=off odsr=off octs=off dtr=on rts=on idsr=off");

		// FLOW CONTROL: HARDWARE:
//		cfg().insert_if_not_exist(prefix + "_comm_str", "baud=9600 parity=N data=8 stop=1 to=on xon=off odsr=off octs=on dtr=on rts=hs idsr=off");

		// FLOW CONTROL: XON/XOFF:
//		cfg().insert_if_not_exist(prefix + "_comm_str", "baud=9600 parity=N data=8 stop=1 to=on xon=on odsr=off octs=off dtr=on rts=on idsr=off");

		// these aren't used anymore -- see _comm_str instead -- so remove them from configuration
		cfg().removeValue(prefix + "_speed"			);
		cfg().removeValue(prefix + "_data_bits"		);
		cfg().removeValue(prefix + "_parity_bits"	);
		cfg().removeValue(prefix + "_stop_bits"		);
	}

	return;
}
