/*
 *	subtitleeditor -- a tool to create or edit subtitle
 *
 *	https://kitone.github.io/subtitleeditor/
 *	https://github.com/kitone/subtitleeditor/
 *
 *	Copyright @ 2005-2009, kitone
 *
 *	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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "spellchecker.h"
#include <cfg.h>
#include <debug.h>
#include <enchant.h>

/*
 * So why do not using enchant++ ?
 * There are a lots of segfault and memory leaks at the exit 
 * of subtitleeditor caused by enchant++ 
 * (static Broker instance in the header ...)
 */
class SEEnchantDict 
{
public:
		class Exception : public std::exception {
		public:
			explicit Exception(const char *ex)
				: std::exception(), m_ex("")
			{
				if(ex)
					m_ex = ex;
			}

			virtual ~Exception() throw() {
			}

			virtual const char *what() throw() {
				return m_ex.c_str();
			}
		private:
			Exception();
			std::string m_ex;
	};

public:
	SEEnchantDict()
	: m_broker(enchant_broker_init()), m_dict(NULL)
	{
	}

	~SEEnchantDict()
	{
		free_dict();
		enchant_broker_free(m_broker);
	}

	bool request_dict(const std::string &lang)
	{
		free_dict();
		m_dict = enchant_broker_request_dict(m_broker, lang.c_str());
		if(!m_dict)
			throw Exception(enchant_broker_get_error(m_broker));
		m_active_lang = lang;
		return true;
	}


	void add_word_to_session(const std::string &utf8)
	{
		g_return_if_fail(m_dict);
		g_return_if_fail(!m_active_lang.empty());
		
		enchant_dict_add_to_session(m_dict, utf8.c_str(), utf8.size());
	}

	void add_word_to_personal(const std::string &utf8)
	{
		g_return_if_fail(m_dict);
		g_return_if_fail(!m_active_lang.empty());

		enchant_dict_add(m_dict, utf8.c_str(), utf8.size());
	}

	bool check(const std::string &utf8)
	{
		g_return_val_if_fail(m_dict, false);
		g_return_val_if_fail(!m_active_lang.empty(), false);

		int val = enchant_dict_check(m_dict, utf8.c_str(), utf8.size());
		if(val == 0)
			return true;
		else if(val > 0)
			return false;
		else
			throw Exception(enchant_dict_get_error(m_dict));
		return false; // never reached
	}

	void suggest(const std::string &utf8word, std::vector<std::string> &out_suggestions)
	{
		g_return_if_fail(m_dict);
		g_return_if_fail(!m_active_lang.empty());
		g_return_if_fail(!utf8word.empty());
	
		size_t n_suggs = 0;
		char **suggs = NULL;

		out_suggestions.clear();

		suggs = enchant_dict_suggest(m_dict, utf8word.c_str(), utf8word.size(), &n_suggs);
		if(suggs && n_suggs)
		{
			for(size_t i = 0; i< n_suggs; ++i)
				out_suggestions.push_back(suggs[i]);
			enchant_dict_free_string_list(m_dict, suggs);
		}
	}

	void store_replacement(const std::string &utf8bad, const std::string &utf8good)
	{
		g_return_if_fail(m_dict);
		g_return_if_fail(!m_active_lang.empty());

		enchant_dict_store_replacement(m_dict, 
				utf8bad.c_str(), utf8bad.size(), 
				utf8good.c_str(), utf8good.size());
	}

	void get_dictionaries(std::list<std::string> &list)
	{
		list.clear();

		g_return_if_fail(m_broker);

		enchant_broker_list_dicts(m_broker, callback_list_dicts, &list);
	}

	std::string get_lang()
	{
		return m_active_lang;
	}
protected:

	static void callback_list_dicts(
			const char *const lang_tag, 
			const char *const /*provider_name*/, 
			const char *const /*provider_desc*/, 
			const char *const /*provider_file*/, 
			void *user_data)
	{
		reinterpret_cast<std::list<std::string>*>(user_data)->push_back(lang_tag);
	}

	void free_dict()
	{
		if(m_dict != NULL)
		{
			enchant_broker_free_dict(m_broker, m_dict);
			m_dict = NULL;
			m_active_lang = std::string();
		}
	}
protected:
	EnchantBroker* m_broker;
	EnchantDict* m_dict;
	std::string m_active_lang;
};

/*
 */
bool spell_checker_is_digit(const Glib::ustring &text)
{
	for( Glib::ustring::const_iterator it = text.begin(); it != text.end(); ++it)
	{
		if(!g_unichar_isdigit(*it) && *it != '.' && *it != ',')
			return false;
	}
	return true;
}

/*
 * Constructor
 */
SpellChecker::SpellChecker()
:m_spellcheckerDict(new SEEnchantDict)
{
	se_debug(SE_DEBUG_SPELL_CHECKING);

	init_dictionary();
}

/*
 * Desctructor
 */
SpellChecker::~SpellChecker()
{
	se_debug(SE_DEBUG_SPELL_CHECKING);
}

/*
 * Return an instance of the SpellChecker.
 */
SpellChecker* SpellChecker::instance()
{
	static SpellChecker _instance;
	return &_instance;
}

/*
 * Setup the default dictionary.
 */
bool SpellChecker::init_dictionary()
{
	Glib::ustring lang;
	
	// Try with the last config
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "Try with the last config...");

	if(Config::getInstance().has_key("spell-checker", "lang"))
	{
		lang = Config::getInstance().get_value_string("spell-checker", "lang");
		if(set_dictionary(lang))
			return true;
	}
	// Second try to get a default language
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "Second try to get a default language...");

	lang = Glib::getenv("LANG");
	if(!lang.empty())
	{
		Glib::ustring::size_type p = lang.find(".");
		if(p != Glib::ustring::npos)
			lang = lang.substr(0, p);

		if(set_dictionary(lang))
			return true;
	}
	// Last try to get a first language
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "Last try to get a first language...");

	std::vector<Glib::ustring> dicts = get_dictionaries();
	if(!dicts.empty() && set_dictionary(dicts[0]))
			return true;

	se_debug_message(SE_DEBUG_SPELL_CHECKING, "cannot select a default language!");
	g_warning("Spell checker: cannot select a default language");
	return false;
}

/*
 * Add this word to the dictionary only the time of the session.
 */
void SpellChecker::add_word_to_session(const Glib::ustring &word)
{
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "add word '%s' to session", word.c_str());

	m_spellcheckerDict->add_word_to_session(word);
}

/*
 * Add this word to the personal dictionary.
 */
void SpellChecker::add_word_to_personal(const Glib::ustring &word)
{
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "add word '%s' to personal dictionary", word.c_str());

	m_spellcheckerDict->add_word_to_personal(word);
}

/*
 * Spell a word.
 */
bool SpellChecker::check(const Glib::ustring &word)
{
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "check the word '%s'", word.c_str());
	try
	{
		// Don't check number
		if(spell_checker_is_digit(word))
			return true;

		return m_spellcheckerDict->check(word);
	}
	catch(std::exception &ex)
	{
		se_debug_message(SE_DEBUG_SPELL_CHECKING, "exception '%s'", ex.what());
	}
	catch(...)
	{
		se_debug_message(SE_DEBUG_SPELL_CHECKING, "unknow exception");
	}
	return false;
}

/*
 * Returns a list of suggestions from the misspelled word.
 */
std::vector<Glib::ustring> SpellChecker::get_suggest(const Glib::ustring &word)
{
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "get suggestion from the word '%s'", word.c_str());

	std::vector<std::string> sugg;
	m_spellcheckerDict->suggest(word, sugg);
	return std::vector<Glib::ustring>(sugg.begin(), sugg.end());
}

/*
 * Set the current dictionary. ("en_US", "de", ...)
 */
bool SpellChecker::set_dictionary(const Glib::ustring &name)
{
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "try to set dictionary '%s' ...", name.c_str());

	if(name.empty())
		return false;

	try
	{
		m_spellcheckerDict->request_dict(name);

		Config::getInstance().set_value_string("spell-checker", "lang", name);
		m_signal_dictionary_changed.emit();
		return true;
	}
	catch(SEEnchantDict::Exception &ex)
	{
		se_debug_message(SE_DEBUG_SPELL_CHECKING, "Failed to set the dictionary '%s' : %s'", name.c_str(), ex.what());
	}
	return false;
}

/*
 * Returns the current dictionary as isocode. ("en_US", "de", ...)
 */
Glib::ustring SpellChecker::get_dictionary()
{
	se_debug(SE_DEBUG_SPELL_CHECKING);

	return m_spellcheckerDict->get_lang();
}

/*
 * Returns a list of the dictionaries available.
 */
std::vector<Glib::ustring> SpellChecker::get_dictionaries()
{
	se_debug(SE_DEBUG_SPELL_CHECKING);

	std::list<std::string> list_dicts;

	m_spellcheckerDict->get_dictionaries(list_dicts);

	return std::vector<Glib::ustring>(list_dicts.begin(), list_dicts.end());
}

/*
 * The current dictionary's changed.
 */
sigc::signal<void>& SpellChecker::signal_dictionary_changed()
{
	se_debug(SE_DEBUG_SPELL_CHECKING);

	return m_signal_dictionary_changed;
}

/*
 * Notes that you replaced 'bad' with 'good', so it's possibly more likely
 * that future occurrences of 'bad' will be replaced with 'good'. 
 * So it might bump 'good' up in the suggestion list.
 */
void SpellChecker::store_replacement(const Glib::ustring &utf8bad, const Glib::ustring &utf8good)
{
	se_debug_message(SE_DEBUG_SPELL_CHECKING, "store replacement '%s' to '%s'", utf8bad.c_str(), utf8good.c_str());

	m_spellcheckerDict->store_replacement(utf8bad, utf8good);
}
