// 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

#include "user_options.h"

#include <k3dsdk/result.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/system_functions.h>
#include <k3dsdk/xml_utility.h>
using k3d::xml::safe_element;

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

namespace
{

sdpxml::Element& helpers(sdpxml::Element& Storage)
{
	return safe_element(safe_element(Storage, "application"), "helpers");
}

sdpxml::Element& user_interface_variables(sdpxml::Element& Storage)
{
	return safe_element(safe_element(safe_element(Storage, "application"), "user_interface"), "variables");
}

sdpxml::Element& paths(sdpxml::Element& Storage)
{
	return safe_element(safe_element(Storage, "application"), "paths");
}

sdpxml::Element& render_engines_element(sdpxml::Element& Storage)
{
	return safe_element(safe_element(Storage, "application"), sdpxml::Element("renderengines"), sdpxml::Element("renderengines", "", sdpxml::Attribute("defaulttype", "ri"), sdpxml::Attribute("defaultengine", "aqsis")));
}

sdpxml::Element& preferred_language_element(sdpxml::Element& Storage)
{
	return safe_element(user_interface_variables(Storage), sdpxml::Element("variable", "", sdpxml::Attribute("name", "preferredlanguage")), sdpxml::Element("variable", "", sdpxml::Attribute("name", "preferredlanguage"), sdpxml::Attribute("value", "en")));
}

sdpxml::Element& blackbox_recorder_element(sdpxml::Element& Storage)
{
	return safe_element(user_interface_variables(Storage), sdpxml::Element("variable", "", sdpxml::Attribute("name", "showblackboxrecorder")), sdpxml::Element("variable", "", sdpxml::Attribute("name", "showblackboxrecorder"), sdpxml::Attribute("value", "true")));
}

sdpxml::Element& tutorial_menu_element(sdpxml::Element& Storage)
{
	return safe_element(user_interface_variables(Storage), sdpxml::Element("variable", "", sdpxml::Attribute("name", "showtutorialmenu")), sdpxml::Element("variable", "", sdpxml::Attribute("name", "showtutorialmenu"), sdpxml::Attribute("value", "true")));
}

sdpxml::Element& tutorial_speed_element(sdpxml::Element& Storage)
{
	return safe_element(user_interface_variables(Storage), sdpxml::Element("variable", "", sdpxml::Attribute("name", "tutorialspeed")), sdpxml::Element("variable", "", sdpxml::Attribute("name", "tutorialspeed"), sdpxml::Attribute("value", "1")));
}

sdpxml::Element& window_geometry_element(sdpxml::Element& Storage)
{
	return safe_element(user_interface_variables(Storage), sdpxml::Element("variable", "", sdpxml::Attribute("name", "restorewindows")), sdpxml::Element("variable", "", sdpxml::Attribute("name", "restorewindows"), sdpxml::Attribute("value", "true")));
}

sdpxml::Element& html_viewer_element(sdpxml::Element& Storage)
{
#ifdef K3D_PLATFORM_IS_WIN32
	const std::string htmlviewercommand = "mozilla %p";
#else // K3D_PLATFORM_IS_WIN32
	std::string htmlviewercommand = k3d::system::get_env("BROWSER");
	if(htmlviewercommand.size())
		{
			htmlviewercommand += " %p";
		}
	else
		{
			htmlviewercommand = "mozilla %p";
		}
#endif // !K3D_PLATFORM_IS_WIN32

	return safe_element(helpers(Storage), sdpxml::Element("command", "", sdpxml::Attribute("type", "htmlviewer")), sdpxml::Element("command", htmlviewercommand, sdpxml::Attribute("type", "htmlviewer")));
}

sdpxml::Element& bitmap_viewer_element(sdpxml::Element& Storage)
{
#ifdef K3D_PLATFORM_IS_WIN32
	const std::string bitmapviewercommand = "mspaint %p";
#else // K3D_PLATFORM_IS_WIN32
	const std::string bitmapviewercommand = "display %p";
#endif // !K3D_PLATFORM_IS_WIN32

	return safe_element(helpers(Storage), sdpxml::Element("command", "", sdpxml::Attribute("type", "bitmapviewer")), sdpxml::Element("command", bitmapviewercommand, sdpxml::Attribute("type", "bitmapviewer")));
}

sdpxml::Element& sl_preprocessor_element(sdpxml::Element& Storage)
{
	const std::string slpreprocessorcommand = "gpp -C %p";

	return safe_element(helpers(Storage), sdpxml::Element("command", "", sdpxml::Attribute("type", "slpreprocessor")), sdpxml::Element("command", slpreprocessorcommand, sdpxml::Attribute("type", "slpreprocessor")));
}

sdpxml::Element& render_farm_element(sdpxml::Element& Storage)
{
	return safe_element(paths(Storage), sdpxml::Element("path", "", sdpxml::Attribute("type", "renderfarm")), sdpxml::Element("path", k3d::system::get_temp_directory().native_file_string(), sdpxml::Attribute("type", "renderfarm")));
}

sdpxml::Element& render_engine_element(sdpxml::Element& Storage, const std::string& Type, const std::string& Engine)
{
	return safe_element(render_engines_element(Storage), sdpxml::Element("renderengine", "", sdpxml::Attribute("type", Type), sdpxml::Attribute("engine", Engine)));
}

void create_render_engine(sdpxml::Element& Storage, const std::string& Type, const std::string& Engine, const std::string& Name, const std::string& Render, const std::string& ShaderBinary = "", const std::string& CompileShaderCommand = "")
{
	// Sanity checks ...
	assert_warning(Type.size());
	assert_warning(Engine.size());
	assert_warning(Name.size());

	sdpxml::Element& renderengine = render_engine_element(Storage, Type, Engine);
	sdpxml::SetAttribute(renderengine, sdpxml::Attribute("name", Name));

	if(Render.size())
		safe_element(renderengine, sdpxml::Element("render"), sdpxml::Element("render", Render));

	if(ShaderBinary.size() && CompileShaderCommand.size())
		safe_element(safe_element(renderengine, "shaders"), sdpxml::Element("shader"), sdpxml::Element("shader", CompileShaderCommand, sdpxml::Attribute("shaderbinary", ShaderBinary)));
}

} // namespace

namespace k3d
{

/////////////////////////////////////////////////////////////////////////////
// user_options

user_options::user_options(const boost::filesystem::path& OptionsFile) :
	m_path(OptionsFile),
	m_storage("k3dml")
{
	// The options file MUST exist at this point ...
	assert(boost::filesystem::exists(OptionsFile));

	// Load it up!
	sdpxml::Document xml("empty");
	boost::filesystem::ifstream stream(OptionsFile);
	xml.Load(stream, OptionsFile.native_file_string());

	if(xml.Name() == "k3dml")
		m_storage = xml;

	set_defaults();
	commit_options();
}

void user_options::set_defaults()
{
	// Force some default options so renderjob and renderframe can function properly
	bitmap_viewer();
	html_viewer();
	render_engines();
}

const std::string user_options::preferred_language()
{
	return sdpxml::GetAttribute<std::string>(preferred_language_element(m_storage), "value", "");
}

void user_options::set_preferred_language(const std::string& Language)
{
	sdpxml::SetAttribute(preferred_language_element(m_storage), sdpxml::Attribute("value", Language));
}

const bool user_options::blackbox_recorder_at_startup()
{
	return sdpxml::GetAttribute<bool>(blackbox_recorder_element(m_storage), "value", true);
}

void user_options::set_blackbox_recorder_at_startup(const bool State)
{
	sdpxml::SetAttribute(blackbox_recorder_element(m_storage), sdpxml::Attribute("value", sdpToString(State)));
}

const bool user_options::tutorial_menu_at_startup()
{
	return sdpxml::GetAttribute<bool>(tutorial_menu_element(m_storage), "value", false);
}

void user_options::set_tutorial_menu_at_startup(const bool State)
{
	sdpxml::SetAttribute(tutorial_menu_element(m_storage), sdpxml::Attribute("value", sdpToString(State)));
}

const double user_options::tutorial_speed()
{
	return sdpxml::GetAttribute<double>(tutorial_speed_element(m_storage), "value", 1.0);
}

void user_options::set_tutorial_speed(const double Speed)
{
	sdpxml::SetAttribute(tutorial_speed_element(m_storage), sdpxml::Attribute("value", sdpToString(Speed)));
}

const bool user_options::restore_window_geometry()
{
	return sdpxml::GetAttribute(window_geometry_element(m_storage), "value", false);
}

void user_options::set_restore_window_geometry(const bool State)
{
	sdpxml::SetAttribute(window_geometry_element(m_storage), sdpxml::Attribute("value", sdpToString(State)));
}

const std::string user_options::html_viewer()
{
	return html_viewer_element(m_storage).Text();
}

void user_options::set_html_viewer(const std::string& CommandLine)
{
	html_viewer_element(m_storage).Text() = CommandLine;
}

const std::string user_options::bitmap_viewer()
{
	return bitmap_viewer_element(m_storage).Text();
}

void user_options::set_bitmap_viewer(const std::string& CommandLine)
{
	bitmap_viewer_element(m_storage).Text() = CommandLine;
}

const std::string user_options::sl_preprocessor()
{
	return sl_preprocessor_element(m_storage).Text();
}

void user_options::set_sl_preprocessor(const std::string& CommandLine)
{
	sl_preprocessor_element(m_storage).Text() = CommandLine;
}

const boost::filesystem::path user_options::render_farm_path()
{
	return boost::filesystem::path(render_farm_element(m_storage).Text(), boost::filesystem::native);
}

void user_options::set_render_farm_path(const boost::filesystem::path& Path)
{
	render_farm_element(m_storage).Text() = Path.native_file_string();
}

void user_options::default_render_engine(std::string& Type, std::string& Engine, std::string& Name)
{
	sdpxml::Element& element = render_engines_element(m_storage);

	sdpxml::ParseAttribute(element, "defaulttype", Type);
	sdpxml::ParseAttribute(element, "defaultengine", Engine);

	const render_engines_t engines = render_engines();
	for(render_engines_t::const_iterator engine = engines.begin(); engine != engines.end(); ++engine)
		{
			// Look for the default engine's name ...
			if((Type == engine->type) && (Engine == engine->engine))
				{
					Name = engine->name;
					return;
				}
		}
}

void user_options::set_default_render_engine(const std::string& Type, const std::string& Engine)
{
	sdpxml::SetAttribute(render_engines_element(m_storage), sdpxml::Attribute("defaulttype", Type));
	sdpxml::SetAttribute(render_engines_element(m_storage), sdpxml::Attribute("defaultengine", Engine));
}

const k3d::ioptions::render_engines_t user_options::render_engines()
{
	// Default RenderMan engine types ...
	create_render_engine(m_storage, "ri", "aqsis", "Aqsis", "aqsis -shaders=%shaders %p", "%p.slx", "aqsl -o %b %s");
	create_render_engine(m_storage, "ri", "pixie", "Pixie", "rndr %p", "%p.sdr", "sdrc %s");
	create_render_engine(m_storage, "ri", "bmrt", "BMRT", "rendrib %p", "%p.slc", "slc %s -o %b");
	create_render_engine(m_storage, "ri", "prman", "PRman", "prman -progress %p", "%p.slo", "slcomp %s");
	create_render_engine(m_storage, "ri", "netprman", "NETPRman", "netrender -progress -f %p", "%p.slo", "slcomp %s");
	create_render_engine(m_storage, "ri", "3delight", "3Delight", "renderdl %p", "%p.sdl", "shaderdl -d %c --dont-keep-cpp-file --dont-keep-c++-file %s");
	create_render_engine(m_storage, "ri", "rdc", "Render Dot C", "renderdc %p", "%p.so", "shaderdc %s");
	create_render_engine(m_storage, "ri", "air", "AIR", "air %p", "%p.slb", "shaded %s");
	create_render_engine(m_storage, "ri", "editrib", "Edit RIB File", "gvim %p");
	create_render_engine(m_storage, "ri", "saverib", "Save RIB File", "file=`Xdialog --stdout --title \"Save RIB File As:\" --fselect ~/world.rib 0 0`; if [ -n \"$file\" ]; then cp %p $file; fi");

	// Default YAFRAY engine type ...
	create_render_engine(m_storage, "yafray", "yafray", "YafRay", "yafray %p");

	// Default GraphViz engine types ...
	create_render_engine(m_storage, "graphviz", "dot", "Dot", "dot -Tps %p -o world.ps");
	create_render_engine(m_storage, "graphviz", "neato", "Neato", "neato -Tps %p -o world.ps");
	create_render_engine(m_storage, "graphviz", "twopi", "TwoPI", "twopi -Tps %p -o world.ps");

	// Default POV engine types ...
	create_render_engine(m_storage, "pov", "povray", "POV-Ray", "povray %p");
	create_render_engine(m_storage, "pov", "povman", "POVMan", "povman %p", "%p.slp", "povslc -o %b %s");

	// Return the set of all render engines ...
	render_engines_t results;
	sdpxml::Element& engines = render_engines_element(m_storage);
	for(sdpxml::ElementCollection::iterator engine = engines.Children().begin(); engine != engines.Children().end(); ++engine)
		{
			const std::string type = sdpxml::GetAttribute<std::string>(*engine, "type", "");
			if(!type.size())
				{
					std::cerr << __PRETTY_FUNCTION__ << ": empty engine type!" << std::endl;
					continue;
				}

			const std::string enginename = sdpxml::GetAttribute<std::string>(*engine, "engine", "");
			if(!enginename.size())
				{
					std::cerr << __PRETTY_FUNCTION__ << ": empty engine!" << std::endl;
					continue;
				}

			const std::string name = sdpxml::GetAttribute<std::string>(*engine, "name", "");
			if(!name.size())
				{
					std::cerr << __PRETTY_FUNCTION__ << ": empty engine name" << std::endl;
					continue;
				}

			const std::string render = safe_element(*engine, "render").Text();
			const sdpxml::Element& shader = safe_element(safe_element(*engine, "shaders"), "shader");
			const std::string shaderbinary = sdpxml::GetAttribute<std::string>(shader, "shaderbinary", "");
			const std::string compileshadercommand = shader.Text();

			results.push_back(render_engine(type, enginename, name, render, shaderbinary, compileshadercommand));
		}

	return results;
}

const boost::filesystem::path user_options::most_recent_path(const std::string& Type)
{
	return boost::filesystem::path(safe_element(paths(m_storage), sdpxml::Element("path", "", sdpxml::Attribute("type", Type)), sdpxml::Element("path", k3d::system::get_home_directory().native_file_string(), sdpxml::Attribute("type", Type))).Text(), boost::filesystem::native);
}

void user_options::set_most_recent_path(const std::string& Type, const boost::filesystem::path& Path)
{
	safe_element(paths(m_storage), sdpxml::Element("path", "", sdpxml::Attribute("type", Type)), sdpxml::Element("path", k3d::system::get_home_directory().native_file_string(), sdpxml::Attribute("type", Type))).Text() = Path.native_file_string();
}

const k3d::vector4 user_options::window_geometry(const std::string& WindowName)
{
	sdpxml::Element& window = safe_element(safe_element(safe_element(safe_element(m_storage, "application"), "user_interface"), "windows"), sdpxml::Element("window", "", sdpxml::Attribute("name", WindowName)));
	return sdpxml::GetAttribute<k3d::vector4>(window, "geometry", k3d::vector4());
}

void user_options::set_window_geometry(const std::string& WindowName, const k3d::vector4& Geometry)
{
	sdpxml::Element& window = safe_element(safe_element(safe_element(safe_element(m_storage, "application"), "user_interface"), "windows"), sdpxml::Element("window", "", sdpxml::Attribute("name", WindowName)));
	sdpxml::SetAttribute(window, sdpxml::Attribute("geometry", k3d::to_string(Geometry)));
}

void user_options::commit_options()
{
	boost::filesystem::ofstream stream(m_path);
	stream << m_storage << std::endl;
	stream.flush();
}

} // namespace k3d


