// 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 tutorial menu dialog
		\author Tim Shead (tshead@k-3d.com)
*/

#include "button.h"
#include "k3ddialog.h"
#include "tutorial_menu.h"

#include <k3dsdk/application.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/result.h>
#include <k3dsdk/scripting.h>
#include <k3dsdk/string_cast.h>
#include <k3dsdk/user_interface.h>

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

#include <sdpgtk/sdpgtkevents.h>

/////////////////////////////////////////////////////////////////////////////
// CTutorialMenu

namespace
{

boost::filesystem::path g_tutorial_path;

const std::string control_tutorials = "tutorials";
const std::string control_play = "play";

/////////////////////////////////////////////////////////////////////////////
// tutorial_menu_implementation

/// Implements a menu of available tutorials
class tutorial_menu_implementation :
	public k3dDialog
{
	typedef k3dDialog base;

public:
	tutorial_menu_implementation(k3d::icommand_node& Parent, const boost::filesystem::path& TutorialsPath) :
		base(&Parent, "tutorial_menu", new k3d::options_window_geometry_store())
	{
		// We want to be notified if the application is closed
		k3d::application().close_signal().connect(SigC::slot(*this, &tutorial_menu_implementation::OnClose));

		return_if_fail(LoadGTKMLTemplate("tutorial_menu.gtkml"));

		if(get_button("close"))
			get_button("close")->signal_activate().connect(SigC::slot(*this, &tutorial_menu_implementation::OnClose));

		sdpGtkCList list = CList(control_tutorials);
		return_if_fail(list.Attached());

		// Iterate through the files in the tutorial directory, looking for an index file ...
		boost::filesystem::path index_file;
		const std::string default_index = "tutorialindex.k3d";
		const std::string preferred_index = "tutorialindex.k3d." + k3d::application().options().preferred_language();
		for(boost::filesystem::directory_iterator file(TutorialsPath); file != boost::filesystem::directory_iterator(); ++file)
			{
				if(file->leaf() == preferred_index)
					{
						index_file = *file;
						break;
					}
					
				if(file->leaf() == default_index)
					{
						index_file = *file;
					}
			}

		// See if we've got anything ...
		return_if_fail(!index_file.empty());

		// Load the index file as XML ...
		sdpxml::Document index("k3dml");
		boost::filesystem::ifstream stream(index_file);
		index.Load(stream, index_file.native_file_string());

		// We will be generating a collection of strings to insert into our list ...
		std::vector<std::string> tutorials;

		// Get the set of indexed tutorials ...
		unsigned long tutorialnumber = 0;

		sdpxml::ElementPointer xml_application = sdpxml::FindElement(index, sdpxml::SameName("application"));
		if(xml_application)
			{
				sdpxml::ElementPointer xml_tutorials = sdpxml::FindElement(*xml_application, sdpxml::SameName("tutorials"));
				if(xml_tutorials)
					{
						for(sdpxml::ElementCollection::const_iterator tutorial = xml_tutorials->Children().begin(); tutorial != xml_tutorials->Children().end(); ++tutorial)
							{
								const std::string xml_title = sdpxml::GetAttribute<std::string>(*tutorial, "title", "");
								const std::string xml_path = sdpxml::GetAttribute<std::string>(*tutorial, "path", "");

								if(xml_title.empty())
									{
										std::cerr << error << "Tutorial without title attribute will be ignored" << std::endl;
										continue;
									}

								if(xml_path.empty())
									{
										std::cerr << error << "Tutorial without path attribute will be ignored" << std::endl;
										continue;
									}
								
								const std::string tutorial_title = k3d::string_cast(++tutorialnumber) + ". " + xml_title;
								const boost::filesystem::path tutorial_path = TutorialsPath / boost::filesystem::path(xml_path, boost::filesystem::native);

								if(!boost::filesystem::exists(tutorial_path))
									{
										std::cerr << error << "Couldn't locate tutorial [" << tutorial_path.native_file_string() << "]" << std::endl;
										continue;
									}
									
									{
										tutorials.push_back(tutorial_title);
										m_title_map[tutorial_title] = tutorial_path;
									}
							}
					}
			}

		// Insert data into the list ...
		for(std::vector<std::string>::iterator tutorial = tutorials.begin(); tutorial != tutorials.end(); tutorial++)
			list.Append(tutorial->c_str());

		Show();
	}

private:
	void OnEvent(sdpGtkEvent* Event)
	{
		if(Event->Name() == control_play)
			on_play();
		else if(Event->Name() == control_tutorials)
			on_tutorials(Event);
		else
			base::OnEvent(Event);
	}
	
	void on_play()
	{
		// Get the current selection ...
		const sdpGtkCList::Rows selection = CList(control_tutorials).GetSelectedRows();
		return_if_fail(selection.size() == 1);

		// Look-up the path to the actual tutorial implementation ...
		const std::string tutorial(CList(control_tutorials).GetText(selection[0], 0));
		const boost::filesystem::path filepath = m_title_map[tutorial];

		// Make sure it exists!
		if(!boost::filesystem::exists(filepath))
			{
				k3d::error_message(
					"Could not find the tutorial implementation file.  This may be caused by a partial- or incorrect-installation.\n"
					"If you built the application from CVS source, make sure you re-run bootstrap and configure after every update.",
					"Play " + filepath.native_file_string() + ":");
				return;
			}

		delete this;

		bool recognized = false;
		bool executed = false;
		boost::filesystem::ifstream file(filepath);

		k3d::execute_script(file, filepath.native_file_string(), k3d::iscript_engine::context_t(), recognized, executed);

		if(!recognized)
			{
				k3d::error_message(
					"Could not determine scripting language.  K-3D supports multiple scripting languages, but the language for this tutorial was\n"
					"not recognized. Most K-3D script engines use some type of \"magic token\" at the beginning of a script to recognize it,\n"
					"e.g. \"//javascript\" in the first 12 characters of a script for K-3D's built-in JavaScript engine.  If you are writing a K-3D tutorial,\n"
					"check the documentation for the scripting language you're writing in to see how to make it recognizable.",
					"Play " + filepath.native_file_string() + ":");
			}
	}
	
	void on_tutorials(sdpGtkEvent* Event)
	{
		// Sanity checks ...
		assert(Event);

		sdpGtkEventWidgetButtonPressEvent* event = (sdpGtkEventWidgetButtonPressEvent*)Event;

		// If it isn't the LMB, forget it ...
		if(event->Event()->button != 1 || event->Event()->type != GDK_2BUTTON_PRESS)
			{
				event->SetResult(true);
				return;
			}

		// Turn it into a "play" event ...
		on_play();
	}

	/// Maps user-visible tutorial titles to filepaths
	typedef std::map<std::string, boost::filesystem::path> title_map;
	title_map m_title_map;
};

} // namespace

namespace k3d
{

void set_tutorial_path(const boost::filesystem::path& Path)
{
	g_tutorial_path = Path;
}

void create_tutorial_menu(k3d::icommand_node& Parent)
{
	// Sanity checks ...
	return_if_fail(boost::filesystem::exists(g_tutorial_path));

	new tutorial_menu_implementation(Parent, g_tutorial_path);
}

} // namespace k3d

