// 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 Declares the k3dTextEditor, k3dShaderEditor, and k3dScriptEditor classes, which provide extensible user interfaces for working with text files
		\author Tim Shead (tshead@k-3d.com)
*/

#include "menu_item.h"
#include "text_editors.h"

#include <k3dsdk/result.h>
#include <k3dsdk/scripting.h>
#include <k3dsdk/user_interface.h>

#include <sdpgtk/sdpgtkevents.h>

#include <boost/filesystem/fstream.hpp>

/////////////////////////////////////////////////////////////////////////////
// k3dTextEditor

k3dTextEditor::k3dTextEditor(k3d::iunknown* ParentCommandNode, const std::string& CommandNodeName) :
	base(ParentCommandNode, CommandNodeName, new k3d::options_window_geometry_store()),
	m_changed(false)
{
}

k3dTextEditor::~k3dTextEditor()
{
}

bool k3dTextEditor::LoadGTKMLTemplate(const std::string& Template)
{
	// Sanity checks ...
	assert_warning(Template.size());

	// Load the dialog template ...
	return_val_if_fail(base::LoadGTKMLTemplate(Template), false);

	if(get_menu_item("file_new"))
		get_menu_item("file_new")->signal_activate().connect(SigC::slot(*this, &k3dTextEditor::on_file_new));
	if(get_menu_item("file_open"))
		get_menu_item("file_open")->signal_activate().connect(SigC::slot(*this, &k3dTextEditor::on_file_open));
	if(get_menu_item("file_save"))
		get_menu_item("file_save")->signal_activate().connect(SigC::slot(*this, &k3dTextEditor::on_file_save));
	if(get_menu_item("file_save_as"))
		get_menu_item("file_save_as")->signal_activate().connect(SigC::slot(*this, &k3dTextEditor::on_file_save_as));
	if(get_menu_item("file_revert"))
		get_menu_item("file_revert")->signal_activate().connect(SigC::slot(*this, &k3dTextEditor::on_file_revert));
	if(get_menu_item("file_close"))
		get_menu_item("file_close")->signal_activate().connect(SigC::slot(*this, &k3dTextEditor::on_file_close));

	// Attach scrollbars to the text widget ...
	GtkText* text = text_control();
	Scrollbar("vscrollbar").SetAdjustment(text->vadj);

	// Put something in the titlebar ...
	update_titlebar();

	// Template design pattern ...
	on_create_new_document();

	return true;
}

const boost::filesystem::path k3dTextEditor::filepath()
{
	return m_file;
}

const std::string k3dTextEditor::get_text()
{
	return text_control().GetText();
}

void k3dTextEditor::set_text(const std::string& Text)
{
	// Get rid of old text ...
	text_control().DeleteText(0, -1);

	gint position = 0;
	text_control().InsertText(Text.data(), Text.size(), &position);

	m_changed = false;
	update_titlebar();
}

sdpGtkText k3dTextEditor::text_control()
{
	return Text("text");
}

// We have an unfortunate class with X
#ifdef RootWindow
#undef RootWindow
#endif // RootWindow

const std::string k3dTextEditor::get_title()
{
	if(m_file.empty())
		return "Untitled File";

	return m_file.leaf();
}

const std::string k3dTextEditor::get_titlebar_text()
{
	std::string result = get_title();

	if(m_changed)
		result += " [changed]";

	return result;
}

void k3dTextEditor::update_titlebar()
{
	RootWindow().SetTitle(get_titlebar_text());
}

const std::string k3dTextEditor::get_path_category()
{
	return "generic";
}

void k3dTextEditor::load(const boost::filesystem::path& File)
{
	// Try opening the file ...
	boost::filesystem::ifstream file(File);
	if(!file.good())
		{
			k3d::error_message("Error opening file!", "Open File:");
			return;
		}

	m_file = File;
	load(file);
}

void k3dTextEditor::load(std::istream& Stream)
{
	// Get rid of old text ...
	text_control().DeleteText(0, -1);

	// Load the file into our text widget ...
	std::stringstream script;
	Stream.get(*script.rdbuf(), '\0');

	gint position = 0;
	text_control().InsertText(script.str().data(), strlen(script.str().data()), &position);

	m_changed = false;
	update_titlebar();
}

void k3dTextEditor::save(const boost::filesystem::path& File)
{
	// Try opening the file ...
	boost::filesystem::ofstream file(File);
	if(!file.good())
		{
			k3d::error_message("Error saving file!", "Save File:");
			return;
		}

	m_file = File;
	save(file);
}

void k3dTextEditor::save(std::ostream& Stream)
{
	// Sanity checks ...
	assert_warning(Stream.good());

	// Get the script text from our widget ...
	const std::string script = text_control().GetText();
	Stream.write(script.c_str(), script.size());

	m_changed = false;
	update_titlebar();
}

bool k3dTextEditor::safe_to_overwrite()
{
	// If we don't have any unsaved changes, go for it!
	if(!m_changed)
		return true;

	// Prompt the user to save changes ...
	std::vector<std::string> buttons;
	buttons.push_back("Save Changes");
	buttons.push_back("Discard Changes");
	buttons.push_back("Cancel");

	std::string message = "Save ";
	message += get_title();
	message += " before proceeding? Unsaved changes will be lost (No Undo)";

	const int choice = k3d::query_message(message, get_title(), 1, buttons);
	switch(choice)
		{
			case 0:
				return false;
			case 1:
				return file_save();
			case 2:
				return true;
			case 3:
				return false;
		}

	return false;
}

void k3dTextEditor::OnEvent(sdpGtkEvent* Event)
{
	if(Event->Name() == "changed")
		on_changed();
	else if(Event->Name() == "delete")
		OnDelete(Event);
	else
		base::OnEvent(Event);
}

void k3dTextEditor::OnDelete(sdpGtkEvent* Event)
{
	// Sanity checks ...
	assert_warning(Event);

	// Don't allow the GTK+ tree to go away just yet ...
	((sdpGtkEventWidgetDeleteEvent*)Event)->SetResult(true);

	// Turn it into a "close", instead ...
	on_file_close();
}

void k3dTextEditor::on_create_new_document()
{
}

void k3dTextEditor::on_file_new()
{
	// Give the user a chance to save any existing changes ...
	if(!safe_to_overwrite())
		return;

	text_control().DeleteText(0, -1);
	m_file = boost::filesystem::path();

	update_titlebar();

	// Template design pattern ...
	on_create_new_document();
}

void k3dTextEditor::on_file_open()
{
	// Give the user a chance to save any existing changes ...
	if(!safe_to_overwrite())
		return;

	boost::filesystem::path filepath;
	if(!k3d::get_file_path(get_path_category(), "Open File:", false, m_file, filepath))
		return;

	load(filepath);
}

void k3dTextEditor::on_file_save()
{
	file_save();
}

void k3dTextEditor::on_file_save_as()
{
	file_save_as();
}

bool k3dTextEditor::file_save()
{
	// If we don't have a filepath, prompt the user ...
	if(m_file.empty())
		return file_save_as();
	
	save(m_file);
	return true;
}

bool k3dTextEditor::file_save_as()
{
	boost::filesystem::path filepath;
	if(!k3d::get_file_path(get_path_category(), "Save File As:", true, m_file, filepath))
		return false;

	save(filepath);

	return true;
}

void k3dTextEditor::on_file_revert()
{
	if(m_file.empty())
		return;

	if(!m_changed)
		return;

	std::vector<std::string> buttons;
	buttons.push_back("Yes");
	buttons.push_back("No");
	if(1 != k3d::query_message("The file has changed.  Revert to previously saved version?", get_title() + ":", 1, buttons))
		return;

	load(m_file);
}

void k3dTextEditor::on_file_close()
{
	// Give the user a chance to save any existing changes ...
	if(!safe_to_overwrite())
		return;

	delete this;
}

void k3dTextEditor::on_changed()
{
	if(!m_changed)
		{
			m_changed = true;
			update_titlebar();
		}
}


/////////////////////////////////////////////////////////////////////////////
// k3dScriptEditor

namespace
{

const std::string control_play = "play";

} // namespace

k3dScriptEditor::k3dScriptEditor(k3d::iunknown* ParentCommandNode, const std::string& CommandNodeName) :
	base(ParentCommandNode, CommandNodeName),
	m_running(false)
{
}

const std::string k3dScriptEditor::get_title()
{
	if(filepath().empty())
		return "Untitled Script";
		
	return filepath().leaf();
}

const std::string k3dScriptEditor::get_titlebar_text()
{
	return base::get_titlebar_text() + (m_running ? std::string(" [ running ]") : std::string(""));
}

const std::string k3dScriptEditor::get_path_category()
{
	return "script";
}

void k3dScriptEditor::start_running()
{
	m_running = true;
	Widget(control_play).SetSensitive(false);
	update_titlebar();
}

void k3dScriptEditor::stop_running()
{
	m_running = false;
	Widget(control_play).SetSensitive(true);
	update_titlebar();
}

void k3dScriptEditor::OnEvent(sdpGtkEvent* Event)
{
	if(Event->Name() == control_play)
		on_play();
	else
		base::OnEvent(Event);
}

void k3dScriptEditor::on_play()
{
	start_running();

	std::stringstream script;
	script << get_text();

	bool recognized = false;
	bool executed = false;

	k3d::execute_script(script, get_title(), 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 script 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 script,\n"
				"check the documentation for the scripting language you're writing in to see how to make it recognizable.",
				"Play " + get_title() + ":");
		}
	else if(!executed)
		{
			k3d::error_message("Error executing script", "Play " + get_title() + ":");
		}

	stop_running();
}

/////////////////////////////////////////////////////////////////////////////
// k3dShaderEditor

namespace
{

const std::string control_compile = "compile";

} // namespace

k3dShaderEditor::k3dShaderEditor(k3d::iunknown* ParentCommandNode, const std::string& CommandNodeName) :
	base(ParentCommandNode, CommandNodeName)
{
}

const std::string k3dShaderEditor::get_title()
{
	if(filepath().empty())
		return "Untitled Shader";
		
	return filepath().leaf();
}

const std::string k3dShaderEditor::get_path_category()
{
	return "shader";
}

void k3dShaderEditor::OnEvent(sdpGtkEvent* Event)
{
	if(Event->Name() == control_compile)
		on_compile();
	else
		base::OnEvent(Event);
}

void k3dShaderEditor::on_compile()
{
	// Not implemented!
	return_if_fail(0);
}


