/* === S Y N F I G ========================================================= */
/*!	\file synfigapp/pluginmanager.cpp
**	\brief  Plugin Manager responsible for loading plugins
**
**	$Id$
**
**	\legal
**	Copyright (c) 2012-2013 Konstantin Dmitriev
**
**	This package 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 package 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.
**	\endlegal
*/
/* ========================================================================= */

/* === H E A D E R S ======================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "pluginmanager.h"

#include <iostream>

#include <libxml++/libxml++.h>

#include <dirent.h>
#include <sys/stat.h>

#include <synfig/general.h>
#include <synfig/savecanvas.h>
#include <synfig/filesystemnative.h>
#include <synfigapp/main.h>

#include <synfigapp/localization.h>

#endif

/* === U S I N G =========================================================== */

using namespace std;
using namespace etl;
using namespace synfig;
using namespace synfigapp;

/* === M A C R O S ========================================================= */


/* === G L O B A L S ======================================================= */

/* === M E T H O D S ======================================================= */

PluginLauncher::PluginLauncher(synfig::Canvas::Handle canvas)
{
	// Save the original filename
	filename_original = canvas->get_file_name();

	String filename_base;
	if (is_absolute_path(filename_original))
	{
		filename_base = filename_original;
	} else {
		filename_base = synfigapp::Main::get_user_app_directory()+ETL_DIRECTORY_SEPARATOR+"tmp"+ETL_DIRECTORY_SEPARATOR+filename_original;
	}

	// Make random filename and ensure there's no file with such name exist
	struct stat buf;

	// Filename to save the file for processing
	do {
		synfig::GUID guid;
		filename_processed = filename_base+"."+guid.get_string().substr(0,8)+".sif"; // without .sif suffix it won't be read back
	} while (stat(filename_processed.c_str(), &buf) != -1);

	/* The plugin could die with nonzero exit code
	 * synfig could crash loading the modified file (should not happen)
	 * having a backup file should protect against both cases
	 */
	do {
		synfig::GUID guid;
		filename_backup = filename_base+"."+guid.get_string().substr(0,8)+".sif";
	} while (stat(filename_backup.c_str(), &buf) != -1);

	save_canvas(FileSystemNative::instance()->get_identifier(filename_processed),canvas);
	// copy file would be faster ..
	save_canvas(FileSystemNative::instance()->get_identifier(filename_backup),canvas);

	//canvas=0;
	exitcode=-1;
	output="";
}

bool
PluginLauncher::check_python_version(String path)
{
	String command;
	String result;
	command = path + " --version 2>&1";
	FILE* pipe = popen(command.c_str(), "r");
	if (!pipe) {
		return false;
	}
	char buffer[128];
	while(!feof(pipe)) {
		if(fgets(buffer, 128, pipe) != NULL)
				result += buffer;
	}
	pclose(pipe);
	// Output is like: "Python 3.3.0"
	if (result.substr(7,1) != "3"){
		return false;
	}
	return true;
}

bool
#ifdef _WIN32
PluginLauncher::execute( std::string script_path, const std::string& synfig_root )
#else
PluginLauncher::execute( std::string script_path, const std::string& /* synfig_root */ )
#endif
{
	String command = "";
	
	// Path to python binary can be overriden
	// with SYNFIG_PYTHON_BINARY env variable:
	char* custom_python_binary=getenv("SYNFIG_PYTHON_BINARY");
	if(custom_python_binary) {
		command=custom_python_binary;
		if (!check_python_version(command)) {
			output="Error: You need to have Python 3 installed.";
			return false;
		}
	} else {
	// Set path to python binary depending on the os type.
	// For Windows case Python binary is expected
	// at INSTALL_PREFIX/python/python.exe
		std::list< String > binary_choices;
		binary_choices.push_back("python");
		binary_choices.push_back("python3");
		std::list< String >::iterator iter;
		for(iter=binary_choices.begin();iter!=binary_choices.end();iter++)
		{
			String python_path;
#ifdef _WIN32
			python_path = "\"" + synfig_root+ETL_DIRECTORY_SEPARATOR+"python"+ETL_DIRECTORY_SEPARATOR+*iter+".exe" + "\"";
#else
			python_path = *iter;
#endif
			if (check_python_version(python_path))
			{
				command = python_path;
				break;
			}
			
		}
		if (command == "")
		{
			output=_("Error: No Python 3 binary found.\n\nHint: You can set SYNFIG_PYTHON_BINARY environment variable pointing at your custom python installation.");
			return false;
		}
	}
	synfig::info("Python 3 binary found: "+command);
	
	
	// Construct the full command:
	command = command+" \""+script_path+"\" \""+filename_processed+"\" 2>&1";
#ifdef _WIN32
	// This covers the dumb cmd.exe behavior.
	// See: http://eli.thegreenplace.net/2011/01/28/on-spaces-in-the-paths-of-programs-and-files-on-windows/
	command = "\"" + command + "\"";
#endif
	
	FILE* pipe = popen(command.c_str(), "r");
	if (!pipe) {
		output = "ERROR: pipe failed!";
		return false;
	}
	char buffer[128];
	while(!feof(pipe)) {
		if(fgets(buffer, 128, pipe) != NULL)
				output += buffer;
	}
	
	if (output != "" ){
		synfig::info(output);
	}
	
	exitcode=pclose(pipe);

	if (0==exitcode){
		return true;
	} else {
		return false;
	}
}

std::string
PluginLauncher::get_result_path()
{
	if (0==exitcode){
		return filename_processed;
	} else {
		return filename_backup;
	}
}

PluginLauncher::~PluginLauncher()
{
	remove( filename_processed.c_str() );
	remove( filename_backup.c_str() );
}

PluginManager::PluginManager():
	list_()
{
} // END of synfigapp::PluginManager::PluginManager()

void
PluginManager::load_dir( const std::string &pluginsprefix )
{
	
	synfig::info("Loading plugins from %s", pluginsprefix.c_str());
	
	DIR *dir;
	struct dirent *entry;
	
	dir = opendir(pluginsprefix.c_str());
	if(dir) {
		while ( (entry = readdir(dir)) != NULL) {
			if ( std::string(entry->d_name) != std::string(".") && std::string(entry->d_name) != std::string("..") ) {
				std::string pluginpath;
				pluginpath = pluginsprefix+ETL_DIRECTORY_SEPARATOR+entry->d_name;
				struct stat sb;
				stat(pluginpath.c_str(), &sb);
				// error handling if stat failed
				if (S_ISDIR(sb.st_mode)) {
					// checking if directory contains a plugin...
					DIR *plugindir;
					struct dirent *plugindirentry;
					
					plugindir = opendir(pluginpath.c_str());
					if(!plugindir) {
						synfig::warning("Can't read plugin directory!");
						return;
					}
					
					while ( (plugindirentry = readdir(plugindir)) != NULL) {
						if ( std::string(plugindirentry->d_name) == std::string("plugin.xml") ){
							std::string pluginfilepath;
							pluginfilepath = pluginpath+ETL_DIRECTORY_SEPARATOR+plugindirentry->d_name;
							
							load_plugin(pluginfilepath);
						}
					}
					
				}
			}

		};
		
		closedir(dir);
	}
} // END of synfigapp::PluginManager::load_dir()

void
PluginManager::load_plugin( const std::string &path )
{
	// Get locale
	std::string current_locale = setlocale(LC_ALL, NULL);
	
	synfig::info("   Loading plugin: %s", basename(dirname(path)).c_str());
							
	PluginManager::plugin p;
	std::string plugindir = dirname(path);
	p.id=plugindir;
	
	// parse xml file
	try
	{
		xmlpp::DomParser parser;
		//parser.set_validate();
		parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
		parser.parse_file(path);
		if(parser)
		{
			//Walk the tree:
			const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
			if ( std::string(pNode->get_name()) == std::string("plugin") ){
				//Recurse through child nodes:
				xmlpp::Node::NodeList list = pNode->get_children();
				
				unsigned int name_relevance = 0;
				
				for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
				{
					const xmlpp::Node* node = *iter;
					if ( std::string(node->get_name()) == std::string("name") ) {

						const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
						
						xmlpp::Node::NodeList l = nodeElement->get_children();
						xmlpp::Node::NodeList::iterator i = l.begin();
						xmlpp::Node* n = *i;
						
						const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(n);
						
						if(nodeText)
						{
							// Get the language attribute
							const xmlpp::Attribute* langAttribute = nodeElement->get_attribute("lang", "xml");

							if (langAttribute) {
								// Element have language attribute,
								std::string lang = langAttribute->get_value();
								// let's compare it with current locale
								 if (!current_locale.compare(0, lang.size(), lang)) {
									 if (lang.size() > name_relevance){
										 p.name=nodeText->get_content();
									 }
								 }
							} else {
								// Element have no language attribute - use as fallback
								if (name_relevance == 0){
									p.name=nodeText->get_content();
								}
							}
						}
						
					} else if ( std::string(node->get_name()) == std::string("exec") ) {
						
						xmlpp::Node::NodeList l = node->get_children();
						xmlpp::Node::NodeList::iterator i = l.begin();
						xmlpp::Node* n = *i;
						
						const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(n);
						
						if(nodeText)
						{
							p.path=plugindir+ETL_DIRECTORY_SEPARATOR+nodeText->get_content();
						}
					}
				}
			} else {
				synfig::info("Invalid plugin.xml file.");
			}
		}
	}
	catch(const std::exception& ex)
	{
		std::cout << "Exception caught: " << ex.what() << std::endl;
	}
	
	if ( p.id != "" && p.name != "" && p.path != ""){
		list_.push_back(p);
	} else {
		synfig::warning("Invalid plugin.xml file!");
	}
}

PluginManager::~PluginManager()
{
}
