// 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 renderframe application, which renders frames scheduled as part of a job with the virtual render farm
		\author Tim Shead (tshead@k-3d.com)
*/

#include <k3dembed/user_options.h>
#include <k3dsdk/log.h>
#include <k3dsdk/logbufs.h>
#include <sdpxml/sdpxml.h>

#ifdef SDPWIN32
#define chdir SetCurrentDirectory
#endif // SDPWIN32

#ifdef SDPUNIX
#include <unistd.h>
#endif // SDPUNIX

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

#include <cassert>
#include <ctime>
#include <iostream>
#include <vector>

namespace
{

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

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

bool g_show_timestamps = true;
bool g_show_process = true;
bool g_syslog = false;
k3d::log_level_t g_minimum_log_level = k3d::DEBUG;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// formatted_replace

void formatted_replace(std::string& Text, const char Delimiter, const std::string& Specifier, const std::string& Replacement)
{
	// Sanity checks ...
	assert(Delimiter);
	assert(Specifier.size());

	const std::string specifier = Delimiter + Specifier;

	// For each copy of the specifier we find ...
	std::string::size_type index = Text.find(specifier);
	while(index != std::string::npos)
	{
		// Make sure the user didn't want a literal (%%Specifier) ...
		if(index)
			if(Text.at(index-1) == Delimiter)
			{
				index = Text.find(specifier, index + 1);
				continue;
			}

		// Make the replacement!
		Text.replace(index, specifier.size(), Replacement);
		index = Text.find(specifier, index + Replacement.size() + 1);
	}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// input_file

bool input_file(k3d::ioptions& UserOptions, sdpxml::Element& XMLOperation)
{
	// Sanity checks ...
	assert(XMLOperation.Name() == "inputfile");

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// output_file

bool output_file(k3d::ioptions& UserOptions, sdpxml::Element& XMLOperation)
{
	// Sanity checks ...
	assert(XMLOperation.Name() == "outputfile");

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// render_operation

bool render_operation(k3d::ioptions& UserOptions, sdpxml::Element& XMLOperation)
{
	// Sanity checks ...
	assert(XMLOperation.Name() == "renderoperation");

	// Load paths for use during rendering ...


	// Load render data ...
	const std::string type = sdpxml::GetAttribute<std::string>(XMLOperation, "type", "");
	const std::string engine = sdpxml::GetAttribute<std::string>(XMLOperation, "engine", "");
	const std::string sourcepath = sdpxml::GetAttribute<std::string>(XMLOperation, "sourcepath", "");
	const std::string shaderspath = sdpxml::GetAttribute<std::string>(XMLOperation, "shaderspath", "");
	const std::string sharepath = sdpxml::GetAttribute<std::string>(XMLOperation, "sharepath", "");

	// Poke through global options and look for a render engine that matches ...
	std::string command_line;
	const k3d::ioptions::render_engines_t render_engines = UserOptions.render_engines();
	for(k3d::ioptions::render_engines_t::const_iterator render_engine = render_engines.begin(); render_engine != render_engines.end(); ++render_engine)
		{
			if(type != render_engine->type)
				continue;

			if(engine != render_engine->engine)
				continue;

			command_line = render_engine->render;
			break;
		}

	if(command_line.empty())
		{
			std::cerr << error << "Could not find requested render engine [" << type << "] [" << engine << "]" << std::endl;
			return false;
		}

	// Log the render output ...
#ifndef SDPWIN32
	command_line += " 2>&1 | tee " + std::string("engine.log");
#else // !SDPWIN32
	command_line += " > " + std::string("engine.log");
#endif // SDPWIN32

	// Substitute the input source file, shaders, and share paths ...
	formatted_replace(command_line, '%', "p", sourcepath);
	formatted_replace(command_line, '%', "shaders", shaderspath);
	formatted_replace(command_line, '%', "share", sharepath);

	// Execute the command ...
	std::cerr << info << "Executing " << command_line << std::endl;
	system(command_line.c_str());

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// copy_operation

bool copy_operation(k3d::ioptions& UserOptions, sdpxml::Element& XMLOperation)
{
	// Sanity checks ...
	assert(XMLOperation.Name() == "copyoperation");

	// Extract copy data ...
	const std::string from = sdpxml::GetAttribute<std::string>(XMLOperation, "from", "");
	const std::string to = sdpxml::GetAttribute<std::string>(XMLOperation, "to", "");

	// Copy the file ...
#ifndef SDPWIN32
	std::string command_line = "cp " + from + " " + to;
#else // !SDPWIN32
	std::string command_line = "copy " + from + " " + to;
#endif // SDPWIN32

	// Execute the command ...
	std::cerr << info << "Executing " << command_line << std::endl;
	system(command_line.c_str());

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// view_operation

bool view_operation(k3d::ioptions& UserOptions, sdpxml::Element& XMLOperation)
{
	// Sanity checks ...
	assert(XMLOperation.Name() == "viewoperation");

	const std::string path = sdpxml::GetAttribute<std::string>(XMLOperation, "path", "");

	// View the image ...
	std::string command_line = UserOptions.bitmap_viewer();
	formatted_replace(command_line, '%', "p", path);
	command_line += " &";

	// Execute the command ...
	std::cerr << info << "Executing " << command_line << std::endl;
	system(command_line.c_str());

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// frame_operation

/// Handles a single operation during processing for the frame ...
bool frame_operation(k3d::ioptions& UserOptions, sdpxml::Element& XMLOperation)
{
	// Sanity checks ...
	const std::string operation(XMLOperation.Name());

	if(operation == "inputfile")
		return input_file(UserOptions, XMLOperation);
	else if(operation == "outputfile")
		return output_file(UserOptions, XMLOperation);
	else if(operation == "renderoperation")
		return render_operation(UserOptions, XMLOperation);
	else if(operation == "copyoperation")
		return copy_operation(UserOptions, XMLOperation);
	else if(operation == "viewoperation")
		return view_operation(UserOptions, XMLOperation);

	return false;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// render_frame

/// Handles all processing for the given frame
bool render_frame(k3d::ioptions& UserOptions, const boost::filesystem::path& FrameDirectory)
{
	// Sanity checks ...
	if(!boost::filesystem::exists(FrameDirectory))
		{
			std::cerr << error << "Frame directory " << FrameDirectory.native_file_string() << " does not exist" << std::endl;
			return false;
		}

	if(!boost::filesystem::is_directory(FrameDirectory))
		{
			std::cerr << error << "Frame directory " << FrameDirectory.native_file_string() << " is not a directory" << std::endl;
			return false;
		}

	// Skip the frame if it's complete ...
	if(boost::filesystem::exists(FrameDirectory / "complete"))
		return true;

	// Skip the frame if it errored out ...
	if(boost::filesystem::exists(FrameDirectory / "error"))
		return true;

	// Skip the frame if it's running ...
	if(boost::filesystem::exists(FrameDirectory / "running"))
		return true;

	// Make sure the frame is ready ...
	if(!boost::filesystem::exists(FrameDirectory / "ready"))
		{
			std::cerr << error << "Frame " << FrameDirectory.native_file_string() << " is not ready" << std::endl;
			return false;
		}

	// Standard logging ...
	std::cerr << info << "Starting Frame " << FrameDirectory.native_file_string() << std::endl;

	// Switch the frame status to running ...
	boost::filesystem::rename(FrameDirectory / "ready", FrameDirectory / "running");

	// Load the frame options file ...
	sdpxml::Document xml_frame_options("empty");
	const boost::filesystem::path control_file_path = FrameDirectory / "control.k3d";
	boost::filesystem::ifstream stream(control_file_path);
	if(!xml_frame_options.Load(stream, control_file_path.native_file_string()))
		{
			std::cerr << error << "Frame " << FrameDirectory.native_file_string() << " missing control file " << control_file_path.native_file_string() << std::endl;
			return false;
		}

	// Get the frame data ...
	sdpxml::ElementPointer xml_frame = sdpxml::FindElement(xml_frame_options, sdpxml::SameName("frame"));
	if(!xml_frame)
		{
			std::cerr << error << "Missing <frame> data in control file " << control_file_path.native_file_string() << std::endl;
			return false;
		}

	// Setup our execution environment ...
	chdir(FrameDirectory.native_file_string().c_str());

	for(sdpxml::ElementCollection::iterator xml_operation = xml_frame->Children().begin(); xml_operation != xml_frame->Children().end(); ++xml_operation)
		frame_operation(UserOptions, *xml_operation);

	// Switch the frame status to complete ...
	boost::filesystem::rename(FrameDirectory / "running", FrameDirectory / "complete");

	// Standard logging ...
	std::cerr << info << "Completed Frame " << FrameDirectory.native_file_string() << std::endl;

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// usage

/// Prints usage info
void usage(const std::string& Name, std::ostream& Stream)
{
	Stream << "usage: " << Name << " [options]" << std::endl;
	Stream << "       " << Name << " [optionspath] [directory ...]" << std::endl;
	Stream << std::endl;
	Stream << "  -h, --help               prints this help information and exits" << std::endl;
	Stream << "      --version            prints program version information and exits" << std::endl;
	Stream << std::endl;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// version

/// Prints version info
void print_version(std::ostream& Stream)
{
	Stream << "K-3D version " << VERSION << std::endl;
	Stream << "Copyright (c) 1995-2004, Timothy M. Shead.  See the AUTHORS file for contributors." << std::endl;
	Stream << "Licensed by the GNU General Public License.  See the COPYING file for details." << std::endl;
	Stream << "K-3D Home Page: http://www.k-3d.org" << std::endl;
	Stream << std::endl;
}

/////////////////////////////////////////////////////////////////////////////
// setup_logging

/// Sets-up options for logging our output
void setup_logging(const std::string& ProcessName)
{
	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("[" + ProcessName + "]", 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)));
}

} // namespace

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// main

/// Program main
int main(int argc, char* argv[])
{
	// Put our arguments in a more useable form ...
	string_array options(&argv[1], &argv[argc]);

	// Print a "help" message ...
	if(std::count(options.begin(), options.end(), "-h") || std::count(options.begin(), options.end(), "--help"))
		{
			usage(argv[0], std::cout);
			return 0;
		}

	// Print version data ...
	if(options.end() != std::find(options.begin(), options.end(), "--version"))
		{
			print_version(std::cout);
			return 0;
		}

	// Otherwise we should have a minimum of two arguments ...
	if(options.size() < 2)
		{
			usage(argv[0], std::cerr);
			return 1;
		}

	// Setup logging right away ...
	setup_logging(argv[0]);

	// Open the global options file ...
	const boost::filesystem::path options_path(options[0], boost::filesystem::native);
	if(!boost::filesystem::exists(options_path))
		{
			std::cerr << error << "User options file [" << options_path.native_file_string() << "] does not exist" << std::endl;
			return 1;
		}

/*
	boost::filesystem::ifstream options_stream(options_file);
	sdpxml::Document xml_options("empty");
	if(!xml_options.Load(options_stream, options_file.native_file_string()))
		{
			std::cerr << error << "Error opening options file " << options_file.native_file_string() << std::endl;
			return 1;
		}
*/

	k3d::user_options user_options(options_path);

	// Each remaining argument should be a frame path to render ...
	int result = 0;
	for(unsigned long j = 1; j < options.size(); j++)
		{
			if(!render_frame(user_options, boost::filesystem::path(options[j], boost::filesystem::native)))
				result = 1;
		}

	return result;
}


