// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
		\brief Implements the K-3D main() entry point
		\author Tim Shead (tshead@k-3d.com)
*/

// Standard K-3D interface implementations for embedding
#include <k3dembed/application.h>
#include <k3dembed/plugin_factory_collection.h>
#include <k3dembed/render_farm.h>
#include <k3dembed/shader_collection.h>
#include <k3dembed/user_options.h>

// Standard K-3D user interface components
#include <k3dui/application_window.h>
#include <k3dui/black_box_recorder.h>
#include <k3dui/gtkml.h>
#include <k3dui/splash_box.h>
#include <k3dui/tutorial_menu.h>
#include <k3dui/tutorial_recorder.h>
#include <k3dui/user_interface.h>

// Standard K-3D interfaces and helpers
#include <k3dsdk/algebra.h>
#include <k3dsdk/auto_ptr.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/log.h>
#include <k3dsdk/logbufs.h>
#include <k3dsdk/objects.h>
#include <k3dsdk/paths.h>
#include <k3dsdk/register_application.h>
#include <k3dsdk/scripting.h>
#include <k3dsdk/system_functions.h>

#include <sdpgtk/sdpgtkutility.h>
#include <sdpgl/sdpgl.h>

#include <boost/filesystem/convenience.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>

#include <iomanip>
#include <iterator>

namespace
{

typedef std::vector<std::string> string_array;

/////////////////////////////////////////////////////////////////////////////
// User options specified on the command line

typedef boost::shared_ptr<std::streambuf> logbuf;
std::vector<logbuf> g_logging;

bool g_show_timestamps = false;
bool g_show_process = false;
bool g_syslog = false;
bool g_color_level = false;
k3d::log_level_t g_minimum_log_level = k3d::WARNING;

bool g_batch_mode = false;
bool g_show_splash = true;
bool g_show_tutorials = false;
bool g_show_tutorial_recorder = false;
bool g_show_gui = true;
bool g_gtk_standard = true;

std::string g_lib_paths;
std::string g_shader_source_paths;

boost::filesystem::path g_dialog_template_path;
boost::filesystem::path g_options_path;
boost::filesystem::path g_shader_cache_path;
boost::filesystem::path g_share_path;
boost::filesystem::path g_tutorials_path;

/////////////////////////////////////////////////////////////////////////////
// handle_error

void handle_error(const std::string& Message, bool& Quit, bool& Error)
{
	std::cerr << error << Message << std::endl;

	Quit = true;
	Error = true;
}

/////////////////////////////////////////////////////////////////////////////
// startup_message_handler

/// Called during application startup to display progress to the user
void startup_message_handler(const std::string Message)
{
	std::cerr << info << Message << std::endl;
}

/////////////////////////////////////////////////////////////////////////////
// exit_handler

/// Called to shutdown the application
bool exit_handler()
{
	// Kick us out of the main UI event loop ...
	if(gtk_main_level())
		gtk_main_quit();
	
	return true;
}

/////////////////////////////////////////////////////////////////////////////
// parse_logging_options

/// Looks for command-line arguments that apply to logging (so we can set them up right away)
string_array parse_command_line_logging_options(const std::string& ProcessName, const string_array Options, bool& Quit, bool& Error)
{
	// Keep track of "unused" options ...
	string_array unused;

	for(string_array::const_iterator option = Options.begin(); option != Options.end(); ++option)
		{
			if((*option) == "--show-timestamps")
				{
					g_show_timestamps = true;
				}
			else if((*option) == "--show-process")
				{
					g_show_process = true;
				}
			else if((*option) == "--syslog")
				{
					g_syslog = true;
				}
			else if((*option) == "--color")
				{
					g_color_level = true;
				}
			else if((*option) == "--log-level")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a minimum log level with the --log-level option!", Quit, Error);
							return string_array();
						}
						
					if((*option) == "warning")
						g_minimum_log_level = k3d::WARNING;
					else if((*option) == "information")
						g_minimum_log_level = k3d::INFO;
					else if((*option) == "debug")
						g_minimum_log_level = k3d::DEBUG;
					else
						{
							handle_error("Valid values for the --log-level option are \"warning\", \"information\", or \"debug\"", Quit, Error);
							return string_array();
						}
				}
			else
				{
					unused.push_back(*option);
				}
		}
		
	g_logging.push_back(logbuf(new k3d::reset_level_buf(std::cerr)));

	if(g_show_timestamps)
		g_logging.push_back(logbuf(new k3d::timestamp_buf(std::cerr)));
	
	if(g_show_process)
		g_logging.push_back(logbuf(new k3d::tag_buf("[" + boost::filesystem::path(ProcessName, boost::filesystem::native).leaf() + "]", std::cerr)));
	
	if(g_color_level)
		g_logging.push_back(logbuf(new k3d::color_level_buf(std::cerr)));

	g_logging.push_back(logbuf(new k3d::show_level_buf(std::cerr)));
	
#ifndef	WIN32	
	if(g_syslog)
		g_logging.push_back(logbuf(new k3d::syslog_buf(std::cerr)));
#endif	//WIN32
		
	g_logging.push_back(logbuf(new k3d::filter_by_level_buf(g_minimum_log_level, std::cerr)));

	return unused;
}

/////////////////////////////////////////////////////////////////////////////
// parse_command_line_1

/// Looks for command-line arguments that cause immediate shutdown
string_array parse_command_line_1(const string_array Options, bool& Quit, bool& Error)
{
	// Currently, all arguments that cause immediate shutdown are handled by the startup-script, so nothing to do here!
	return Options;
}

/////////////////////////////////////////////////////////////////////////////
// parse_command_line_2

/// Handles "normal" command-line arguments that specify initial application state
string_array parse_command_line_2(const string_array Options, bool& Quit, bool& Error)
{
	// We return any "unused" options ...
	string_array unused;

	// For each command-line argument ...
	for(string_array::const_iterator option = Options.begin(); option != Options.end(); ++option)
		{
			if((*option) == "--batch")
				{
					g_batch_mode = true;
				}
			else if((*option) == "--nosplash")
				{
					g_show_splash = false;
				}
			else if((*option) == "--gui")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a valid GUI type with the --gui option!", Quit, Error);
							return string_array();
						}

					if(*option == "none")
						{
							g_show_gui = false;
							g_gtk_standard = false;
						}
					else if(*option == "gtk")
						{
							g_show_gui = true;
							g_gtk_standard = true;
						}
					else
						{
							handle_error("Valid values for the --gui option are \"none\" or \"gtk\"", Quit, Error);
							return string_array();
						}
				}
			else if((*option) == "--dialogpath")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a directory path with the --dialogpath option!", Quit, Error);
							return string_array();
						}

					g_dialog_template_path = boost::filesystem::path(*option, boost::filesystem::native);
				}
			else if((*option) == "--libpaths")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a delimited set of directories with the --libpaths option!", Quit, Error);
							return string_array();
						}

					g_lib_paths = (*option);
				}
			else if((*option) == "--shadersourcepaths")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a delimited set of directories with the --shadersourcepaths option!", Quit, Error);
							return string_array();
						}

					g_shader_source_paths = (*option);
				}
			else if((*option) == "--shadercachepath")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a directory path with the --shadercachepath option!", Quit, Error);
							return string_array();
						}

					g_shader_cache_path = boost::filesystem::path(*option, boost::filesystem::native);
				}
			else if((*option) == "--sharepath")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a directory path with the --sharepath option!", Quit, Error);
							return string_array();
						}

					g_share_path = boost::filesystem::path(*option, boost::filesystem::native);
				}
			else if((*option) == "--tutorialspath")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a directory path with the --tutorialspath option!", Quit, Error);
							return string_array();
						}

					g_tutorials_path = boost::filesystem::path(*option, boost::filesystem::native);
				}
			else if((*option) == "--optionsfile")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a file path with the --optionsfile option!", Quit, Error);
							return string_array();
						}

					g_options_path = boost::filesystem::path(*option, boost::filesystem::native);
				}
			else
				{
					unused.push_back(*option);
				}
		}

	return unused;
}

/////////////////////////////////////////////////////////////////////////////
// check_dependencies

/// Checks for any runtime resources required to continue program execution
void check_dependencies(bool& Quit, bool& Error)
{
	std::cerr << info << "K-3D version: " << k3d::version_string() << std::endl;
	std::cerr << info << "dialog template path: " << g_dialog_template_path.native_file_string() << std::endl;
	std::cerr << info << "lib path(s): " << g_lib_paths << std::endl;
	std::cerr << info << "shader source path(s): " << g_shader_source_paths << std::endl;
	std::cerr << info << "shader cache path: " << g_shader_cache_path.native_file_string() << std::endl;
	std::cerr << info << "share path: " << g_share_path.native_file_string() << std::endl;
	std::cerr << info << "tutorials path: " << g_tutorials_path.native_file_string() << std::endl;
	std::cerr << info << "options file: " << g_options_path.native_file_string() << std::endl;
	std::cerr << info << "home directory: " << k3d::system::get_home_directory().native_file_string() << std::endl;
	std::cerr << info << "temp directory: " << k3d::system::get_temp_directory().native_file_string() << std::endl;

	// If we're going to display a UI, the dialog path must exist ...
	if(g_show_gui && !boost::filesystem::exists(g_dialog_template_path))
		{
			handle_error("GUI enabled, but dialog template path [" + g_dialog_template_path.native_file_string() + "] does not exist.  Shutting down.", Quit, Error);
			return;
		}

	// Every lib path specified must exist ...
	k3d::system::paths_t lib_paths;
	k3d::system::decompose_path_list(g_lib_paths, lib_paths);
	for(k3d::system::paths_t::const_iterator lib_path = lib_paths.begin(); lib_path != lib_paths.end(); ++lib_path)
		{
			if(!boost::filesystem::exists(*lib_path))
				{
					handle_error("Lib path [" + lib_path->native_file_string() + "] does not exist.  Shutting down.", Quit, Error);
					return;
				}
		}

	// Every shader source path specified must exist ...
	k3d::system::paths_t shader_source_paths;
	k3d::system::decompose_path_list(g_shader_source_paths, shader_source_paths);
	for(k3d::system::paths_t::const_iterator shader_source_path = shader_source_paths.begin(); shader_source_path != shader_source_paths.end(); ++shader_source_path)
		{
			if(!boost::filesystem::exists(*shader_source_path))
				{
					handle_error("Shader source path [" + shader_source_path->native_file_string() + "] does not exist.  Shutting down.", Quit, Error);
					return;
				}
		}

	// The shader cache path must exist ...
	boost::filesystem::create_directories(g_shader_cache_path);
	if(!boost::filesystem::exists(g_shader_cache_path))
		{
			handle_error("Shader cache path [" + g_shader_cache_path.native_file_string() + "] does not exist and could not be created.  Shutting down.", Quit, Error);
			return;
		}

	// The share path must exist ...
	if(!boost::filesystem::exists(g_share_path))
		{
			handle_error("Share path [" + g_share_path.native_file_string() + "] does not exist.  Shutting down.", Quit, Error);
			return;
		}

	// If we're going to display a UI, the tutorials path must exist ...
	if(g_show_gui && !boost::filesystem::exists(g_tutorials_path))
		{
			handle_error("GUI enabled, but tutorials path [" + g_tutorials_path.native_file_string() + "] does not exist.  Shutting down.", Quit, Error);
			return;
		}

	// The options file must exist ...
	boost::filesystem::create_directories(g_options_path.branch_path());
	if(!boost::filesystem::exists(g_options_path))
		{
			boost::filesystem::ofstream stream(g_options_path);
			stream << "<k3dml/>";
		}
		
	if(!boost::filesystem::exists(g_options_path))
		{
			handle_error("Options path does not exist.  Shutting down.", Quit, Error);
			return;
		}

	// Test for an OpenGL implementation ...
	if(!sdpgl::exists(std::cerr))
		{
			handle_error("Could not connect to an OpenGL server.  Shutting down.", Quit, Error);
			return;
		}

	// Display details about the OpenGL implementation ...
#ifdef	SDPGL_POSIX
	sdpgl::implementation(std::cerr);
#endif	// SDPGL_POSIX
}

/////////////////////////////////////////////////////////////////////////////
// parse_command_line_3

/// Handles any command-line options that simulate user input (e.g. running a script / tutorial)
string_array parse_command_line_3(const string_array Options, bool& Quit, bool& Error, k3d::iapplication& Application, k3d::application_window* const ApplicationWindow)
{
	// Keep track of "unused" options
	string_array unused;

	// For each command-line argument ...
	for(string_array::const_iterator option = Options.begin(); option != Options.end(); ++option)
		{
			if((*option) == "--new" || (*option) == "-n")
				{
					k3d::idocument* const document = Application.create_document();
					if(!document)
						{
							handle_error("Error opening document", Quit, Error);
							return string_array();
						}

					if(ApplicationWindow)
						{
							if(!ApplicationWindow->populate_document(*document))
								{
									handle_error("Error initializing document", Quit, Error);
									return string_array();
								}
						}
				}
			else if((*option) == "--open" || (*option) == "-o")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a K-3D document path with the --open option!", Quit, Error);
							return string_array();
						}

					Application.open_document(boost::filesystem::path(*option, boost::filesystem::native));
				}
			else if((*option) == "--script")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a K-3D script path with the --script option!", Quit, Error);
							return string_array();
						}

					bool recognized = false;
					bool executed = false;
					std::string scriptname(*option);

					if(scriptname == "-")
						{
							scriptname = "STDIN";
							k3d::execute_script(std::cin, scriptname, k3d::iscript_engine::context_t(), recognized, executed);
						}
					else
						{
							std::ifstream file(scriptname.c_str());
							k3d::execute_script(file, scriptname, k3d::iscript_engine::context_t(), recognized, executed);
						}

					if(!recognized)
						{
							std::cerr << error << "Couldn't recognize scripting language for script [ " << scriptname << " ]" << std::endl;
							Quit = true;
							Error = true;
							return string_array();
						}

					if(!executed)
						{
							std::cerr << error << "Error executing script [ " << scriptname << " ]" << std::endl;
							Quit = true;
							Error = true;
							return string_array();
						}
				}
			else if((*option) == "--exit")
				{
					Quit = true;
					return string_array();
				}
			else if((*option) == "--blackbox")
				{
					++option;
					if(option == Options.end())
						{
							handle_error("You must supply a log file path with the --blackbox option!", Quit, Error);
							return string_array();
						}

					new k3d::black_box_recorder(*dynamic_cast<k3d::icommand_node*>(&Application), boost::filesystem::path(*option, boost::filesystem::native));
				}
			else if((*option) == "--tutorials")
				{
					g_show_tutorials = true;
				}
			else if((*option) == "--record-tutorials")
				{
					g_show_tutorial_recorder = true;
				}
			else
				{
					unused.push_back(*option);
				}
		}

	return unused;
}

void check_unused_options(const string_array Options, bool& Quit, bool& Error)
{
	// If there aren't any leftover options, we're done ...
	if(Options.empty())
		return;

	std::cerr << error << "Unknown options: ";
	std::copy(Options.begin(), Options.end(), std::ostream_iterator<std::string>(std::cerr, " "));
	std::cerr << std::endl;

	Quit = true;
	Error = true;
}

} // namespace

int main(int argc, char* argv[])
{
	try
		{
			// Initialize GTK+ library ...
			gtk_init(&argc, &argv);

			// Put command-line options into an easier form for parsing ...
			string_array options(&argv[1], &argv[argc]);
			bool quit = false;
			bool error = false;

			// Setup our logging options before going any further ...
			options = parse_command_line_logging_options(argv[0], options, quit, error);
			if(quit)
				return error ? 1 : 0;
		
			// Look for command-line options that will cause us to quit immediately ...
			options = parse_command_line_1(options, quit, error);
			if(quit)
				return error ? 1 : 0;

			// Look for command-line options that will configure our subsequent behavior ...
			options = parse_command_line_2(options, quit, error);
			if(quit)
				return error ? 1 : 0;

			// Make sure we have all resources required to run ...
			check_dependencies(quit, error);
			if(quit)
				return error ? 1 : 0;

			// If the GUI is enabled, initialize it ...
			if(g_show_gui)
				k3d::set_dialog_template_path(g_dialog_template_path);

			// Create a splash screen as needed ...
			k3d::splash_box* const splash_box = g_show_splash && g_show_gui ? new k3d::splash_box() : 0;

			// Load user options ...
			k3d::user_options user_options(g_options_path);

			// Load plugins ...
			k3d::plugin_factory_collection plugins;

			plugins.message_signal().connect(SigC::slot(startup_message_handler));

			if(splash_box)
				plugins.message_signal().connect(SigC::slot(*splash_box, &k3d::splash_box::on_startup_message));

			plugins.load_modules(g_lib_paths, true);

			// Load shaders ...
			k3d::shader_collection shaders;
			shaders.message_signal().connect(SigC::slot(startup_message_handler));

			if(splash_box)
				shaders.message_signal().connect(SigC::slot(*splash_box, &k3d::splash_box::on_startup_message));

			shaders.load_shaders(user_options, g_shader_source_paths);

			// Setup a render farm ...
			k3d::render_farm render_farm(g_options_path);

			// Create the main application object ...
			k3d::application_implementation application(
				plugins,
				shaders,
				render_farm,
				user_options,
				g_show_gui ? k3d::create_user_interface(g_batch_mode) : 0,
				g_shader_cache_path,
				g_share_path);

			// Register it with the library as the global application object ...
			k3d::register_application(application.interface());

			// Setup defaults ...
			k3d::application().options().set_defaults();
			user_options.commit_options();

			// Register the tutorials path ...
			k3d::set_tutorial_path(g_tutorials_path);

			// Close the splash screen and open the main window ...
			delete splash_box;

			// Display the "standard" multi-window user interface
			std::auto_ptr<k3d::application_window> main_window;
			if(g_show_gui && g_gtk_standard)
				{
					main_window.reset(new k3d::application_window(dynamic_cast<k3d::icommand_node&>(application.interface())));
					sdpGtkHandlePendingEvents();
				}

			// We would *really* like users to run their blackbox recorder and submit error logs!
			if(g_show_gui && k3d::application().options().blackbox_recorder_at_startup())
				new k3d::black_box_recorder(dynamic_cast<k3d::icommand_node&>(application.interface()), g_options_path.branch_path() / "blackbox.log");

			// Check past user preferences for opening the tutorials menu ...
			g_show_tutorials = k3d::application().options().tutorial_menu_at_startup();

			// Parse command-line options that control program execution ...
			options = parse_command_line_3(options, quit, error, application.interface(), main_window.get());
			if(quit)
				return error ? 1 : 0;

			// Check for "unused" command-line options
			check_unused_options(options, quit, error);
			if(quit)
				return error ? 1 : 0;

			// Open-up the tutorials menu, if requested ...
			if(g_show_tutorials && g_show_gui)
				k3d::create_tutorial_menu(dynamic_cast<k3d::icommand_node&>(application.interface()));

			// Open-up the tutorial recorder, if requested ...
			if(g_show_tutorial_recorder && g_show_gui)
				new k3d::tutorial_recorder(dynamic_cast<k3d::icommand_node&>(application.interface()));

			// Handle shutdown requests so the application can close ...
			application.exit_signal().connect(SigC::slot(exit_handler));

			// Main UI event-loop
			if(g_show_gui)
				gtk_main();

			// Commit user options before we shut-down ...		
			user_options.commit_options();
		}
	catch(std::exception& e)
		{
			std::cerr << critical << "Caught exception: " << e.what() << std::endl;
			throw;
		}
	catch(...)
		{
			std::cerr << critical << "Caught unknown exception ... terminating" << std::endl;
			throw;
		}

	return 0;
}


