/*
 *	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 <extension/action.h>
#include <player.h>
#include <utility.h>
#include <gui/dialogfilechooser.h>
#include <vector>
#include <subtitleformatsystem.h>

/*
 *
 */
class DialogAskToSaveOnExit : public Gtk::MessageDialog
{
public:
	DialogAskToSaveOnExit()
	:Gtk::MessageDialog("", false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE)
	{
		utility::set_transient_parent(*this);
		
		add_button(_("Close _without Saving"), Gtk::RESPONSE_NO);
		add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
		add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_YES);
	}

	int run(Document *doc)
	{
		Glib::ustring document_name = doc->getName();
		Glib::ustring primary_text = build_message(_("Save the changes to document \"%s\" before closing?"), document_name.c_str());
		Glib::ustring secondary_text = _("If you don't save, the last changes will be permanently lost.");

		set_message(primary_text);
		set_secondary_text(secondary_text);

		return Gtk::Dialog::run();
	}
};

/*
 *
 */
class DocumentManagementPlugin : public Action
{
public:

	DocumentManagementPlugin()
	{
		activate();
		update_ui();
	}

	~DocumentManagementPlugin()
	{
		deactivate();
	}

	/*
	 */
	void activate()
	{
		se_debug(SE_DEBUG_PLUGINS);

		// actions
		action_group = Gtk::ActionGroup::create("DocumentManagementPlugin");

		// new document
		action_group->add(
				Gtk::Action::create("new-document", Gtk::Stock::NEW, "", _("Create a new document")),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_new));

		// open & save document
		action_group->add(
				Gtk::Action::create("open-document", Gtk::Stock::OPEN, "", _("Open a file")), 
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_open));

		action_group->add(
				Gtk::Action::create("open-project", _("Open Project"), _("Open a Subtitle Editor Project")),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_open_project));
#if GTKMM_CHECK_VERSION(2,16,0)
		action_group->get_action("open-project")->set_stock_id(Gtk::Stock::OPEN);
#endif//GTKMM_CHECK_VERSION(2,16,0)

		action_group->add(
				Gtk::Action::create("save-document", Gtk::Stock::SAVE, "", _("Save the current file")), Gtk::AccelKey("<Control>S"),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_save));

		action_group->add(
				Gtk::Action::create("save-project", _("Save Project"), _("Save the current file as Subtitle Editor Project")),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_save_project));
#if GTKMM_CHECK_VERSION(2,16,0)
		action_group->get_action("save-project")->set_stock_id(Gtk::Stock::SAVE);
#endif//GTKMM_CHECK_VERSION(2,16,0)

		action_group->add(
				Gtk::Action::create("save-as-document", Gtk::Stock::SAVE_AS, "", _("Save the current file with a different name")), Gtk::AccelKey("<Shift><Control>S"),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_save_as));

		action_group->add(
				Gtk::Action::create("save-all-documents", Gtk::Stock::SAVE_AS, _("Save _All"), _("Save all open files")),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_save_all_documents));

		// open & save translation
		action_group->add(
				Gtk::Action::create("open-translation", Gtk::Stock::OPEN, _("Open _Translation"), _("Open translation from file")), Gtk::AccelKey("<Control>T"),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_open_translation));

		action_group->add(
				Gtk::Action::create("save-translation", Gtk::Stock::SAVE, _("Save Trans_lation"), _("Save translation to file")), Gtk::AccelKey("<Shift><Control>T"),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_save_translation));

		// recent files
		Glib::RefPtr<Gtk::RecentAction> recentAction = Gtk::RecentAction::create("menu-recent-open-document", _("Open _Recent"));

		Glib::RefPtr<Gtk::RecentFilter> filter = Gtk::RecentFilter::create();
		filter->set_name("subtitleeditor");
		filter->add_group("subtitleeditor");
		recentAction->set_filter(filter);

		recentAction->set_show_icons(false);
		recentAction->set_show_numbers(true);
		recentAction->set_show_tips(true);
		//recentAction->set_show_not_found(false);
		recentAction->set_sort_type(Gtk::RECENT_SORT_MRU);

		recentAction->signal_item_activated().connect(
				sigc::mem_fun(*this, &DocumentManagementPlugin::on_recent_item_activated));
		action_group->add(recentAction);

		// close
		action_group->add(
				Gtk::Action::create("close-document", Gtk::Stock::CLOSE, "", _("Close the current file")),
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_close));

		// quit the program
		action_group->add(
				Gtk::Action::create("exit", Gtk::Stock::QUIT, _("E_xit"), _("Quit the program")), 
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_exit));

		// ui
		Glib::RefPtr<Gtk::UIManager> ui = get_ui_manager();

		ui->insert_action_group(action_group);


		DocumentSystem::getInstance().signal_document_create().connect(
				sigc::mem_fun(*this, &DocumentManagementPlugin::on_document_create));

		Gtk::Window *window = dynamic_cast<Gtk::Window*>(get_subtitleeditor_window());
		if(window)
			window->signal_delete_event().connect(
					sigc::mem_fun(*this, &DocumentManagementPlugin::on_subtitleeditor_window_delete_event));

		// config connection
		m_config_interface_connection = get_config().signal_changed("interface").connect(
			sigc::mem_fun(*this, &DocumentManagementPlugin::on_config_interface_changed));

		init_autosave();

		ui_id = ui->new_merge_id();

		#define ADD_UI(name) ui->add_ui(ui_id, Glib::ustring::compose("%1/%2","/menubar/menu-file", name), name, name);
		#define ADD_OPEN_UI(name) ui->add_ui(ui_id, Glib::ustring::compose("%1/%2","/menubar/menu-file/menu-open", name), name, name);
		#define ADD_SAVE_UI(name) ui->add_ui(ui_id, Glib::ustring::compose("%1/%2","/menubar/menu-file/menu-save", name), name, name);

		ADD_UI("new-document");
		ADD_OPEN_UI("open-document");
		ADD_OPEN_UI("open-project");
		ADD_OPEN_UI("open-translation");
		ADD_UI("menu-recent-open-document");
		ADD_SAVE_UI("save-document");
		ADD_SAVE_UI("save-project");
		ADD_SAVE_UI("save-as-document");
		ADD_SAVE_UI("save-all-documents");
		ADD_SAVE_UI("save-translation");
		ADD_UI("close-document");
		ADD_UI("exit");

		#undef ADD_UI
		#undef ADD_OPEN_UI
		#undef ADD_SAVE_UI
	}

	/*
	 */
	void deactivate()
	{
		se_debug(SE_DEBUG_PLUGINS);

		Glib::RefPtr<Gtk::UIManager> ui = get_ui_manager();

		ui->remove_ui(ui_id);
		ui->remove_action_group(action_group);

		m_config_interface_connection.disconnect();
		m_autosave_timeout.disconnect();
	}

	/*
	 *
	 */
	void update_ui()
	{
		se_debug(SE_DEBUG_PLUGINS);

		bool visible = (get_current_document() != NULL);

		action_group->get_action("open-translation")->set_sensitive(visible);
		action_group->get_action("save-document")->set_sensitive(visible);
		action_group->get_action("save-project")->set_sensitive(visible);
		action_group->get_action("save-as-document")->set_sensitive(visible);
		action_group->get_action("save-all-documents")->set_sensitive(visible);
		action_group->get_action("save-translation")->set_sensitive(visible);
		action_group->get_action("close-document")->set_sensitive(visible);
	}

protected:

	/*
	 * Create a new document with unique name
	 */
	void on_new()
	{
		se_debug(SE_DEBUG_PLUGINS);

		Document *doc = new Document();

		g_return_if_fail(doc);

		Glib::ustring ext = SubtitleFormatSystem::instance().get_extension_of_format(doc->getFormat());
		doc->setFilename(DocumentSystem::getInstance().create_untitled_name(ext));

		DocumentSystem::getInstance().append(doc);
	}

	/*
	 */
	void on_open()
	{
		open_filechooser();
	}

	/*
	 */
	void on_open_project()
	{
		open_filechooser("Subtitle Editor Project");
	}

	/*
	 * Launch a filechooser dialog and open a document
	 */
	void open_filechooser(const Glib::ustring &filterformat = Glib::ustring())
	{
		se_debug(SE_DEBUG_PLUGINS);

		DialogOpenDocument::unique_ptr dialog = DialogOpenDocument::create();

		if(!filterformat.empty())
			dialog->set_current_filter(filterformat);
		dialog->show();
		
		if(dialog->run() != Gtk::RESPONSE_OK)
			return;

		dialog->hide();

		Glib::ustring charset = dialog->get_encoding();

		std::vector<Glib::ustring> uris = dialog->get_uris();

		for(std::vector<Glib::ustring>::const_iterator it=uris.begin();
				it != uris.end(); ++it)
		{
			open_document(*it, charset);
		}

		Glib::ustring video_uri = dialog->get_video_uri();
		if(video_uri.empty() == false)
		{
			// TODO
			// check and ask if already exist ?
			SubtitleEditorWindow::get_instance()->get_player()->open(video_uri);
		}
	}

	/*
	 */
	bool open_document(const Glib::ustring &uri, const Glib::ustring &charset)
	{
		se_debug_message(SE_DEBUG_PLUGINS, "uri=%s charset=%s", uri.c_str(), charset.c_str());

		Glib::ustring filename = Glib::filename_from_uri(uri);

		// check if is not already open
		Document* already = DocumentSystem::getInstance().getDocument(filename);

		if(already)
		{
			already->flash_message(_("I am already open"));
			return false;
		}
		// Don't need to catch error, create_from_file catch and 
		// display errors messages if needs.
		Document *doc = Document::create_from_file(uri, charset);
		if(!doc)
			return false;

		DocumentSystem::getInstance().append(doc);
		return true;
	}

	/*
	 * Save a document. If file doesn't exist use save_as
	 */
	bool save_document(Document *doc)
	{
		se_debug(SE_DEBUG_PLUGINS);

		g_return_val_if_fail(doc, false);

		if(Glib::file_test(doc->getFilename(), Glib::FILE_TEST_EXISTS) == false)
			return save_as_document(doc);

		Glib::ustring filename = doc->getFilename();
		Glib::ustring uri = Glib::filename_to_uri(filename);
		Glib::ustring format = doc->getFormat();
		Glib::ustring charset = doc->getCharset();
		Glib::ustring newline = doc->getNewLine();

		if(doc->save(uri) == false)
		{
			// "The file FILENAME (FORMAT, CHARSET, NEWLINE) has not been saved."
			doc->message(_("The file %s (%s, %s, %s) has not been saved."), 
						filename.c_str(), format.c_str(), charset.c_str(), newline.c_str());
			return false;
		}
		// "Saving file FILENAME (FORMAT, CHARSET, NEWLINE)."
		doc->flash_message(_("Saving file %s (%s, %s, %s)."), 
					filename.c_str(), format.c_str(), charset.c_str(), newline.c_str());
		return true;
	}

	/*
	 */
	bool save_as_document(Document *doc, const Glib::ustring &default_format = Glib::ustring())
	{
		se_debug(SE_DEBUG_PLUGINS);

		g_return_val_if_fail(doc, false);

		Glib::ustring format = default_format.empty() ? doc->getFormat() : default_format;

		DialogSaveDocument::unique_ptr dialog = DialogSaveDocument::create();

		if(Glib::file_test(doc->getFilename(), Glib::FILE_TEST_EXISTS))
		{
			dialog->set_current_folder_and_name(doc->getFilename());
		}
		else if(SubtitleEditorWindow::get_instance()->get_player()->get_state() != Player::NONE)
		{
			dialog->set_filename_from_another_uri(
				SubtitleEditorWindow::get_instance()->get_player()->get_uri(),
				SubtitleFormatSystem::instance().get_extension_of_format(format));
		}
		else
		{
			dialog->set_current_name(doc->getName());
		}

		dialog->set_format(format);
		dialog->set_encoding(doc->getCharset());
		dialog->set_newline(doc->getNewLine());
		dialog->set_do_overwrite_confirmation(true);
		dialog->show();

		int response = dialog->run();

		dialog->hide();
		if(response != Gtk::RESPONSE_OK)
			return false;

		Glib::ustring filename = dialog->get_filename();
		Glib::ustring uri = dialog->get_uri();
		format = dialog->get_format();
		Glib::ustring encoding = dialog->get_encoding();
		Glib::ustring newline = dialog->get_newline();

		doc->setFormat(format);
		doc->setCharset(encoding);
		doc->setNewLine(newline);

		if(doc->save(uri) == false)
		{
			doc->message(_("The file %s (%s, %s, %s) has not been saved."), 
					filename.c_str(), format.c_str(), encoding.c_str(), newline.c_str());
			return false;
		}
		doc->flash_message(_("Saving file %s (%s, %s, %s)."), 
				filename.c_str(), format.c_str(), encoding.c_str(), newline.c_str());
		// update in recent manager
		add_document_in_recent_manager(doc);
		return true;
	}

	/*
	 * Save a document. If file doesn't exist use on_save_as
	 */
	void on_save()
	{
		se_debug(SE_DEBUG_PLUGINS);

		Document *doc = get_current_document();
		g_return_if_fail(doc);

		save_document(doc);
	}

	/*
	 */
	void on_save_project()
	{
		se_debug(SE_DEBUG_PLUGINS);

		Document *doc = get_current_document();
		g_return_if_fail(doc);

		save_as_document(doc, "Subtitle Editor Project");
	}

	/*
	 * Save document with new name
	 */
	void on_save_as()
	{
		se_debug(SE_DEBUG_PLUGINS);

		Document *doc = get_current_document();
		g_return_if_fail(doc);

		save_as_document(doc);
	}

	/*
	 * Save all open files
	 */
	void on_save_all_documents()
	{
		se_debug(SE_DEBUG_PLUGINS);

		DocumentList list = get_subtitleeditor_window()->get_documents();

		for(DocumentList::const_iterator it = list.begin(); it != list.end(); ++it)
		{
			save_document(*it);
		}
	}
	
	/*
	 * Open translation from file.
	 * Create a new document with a translation
	 * and move the text of this new document (trans) to the current document
	 * at the column "translation". After that delete the new document (trans)
	 */
	void on_open_translation()
	{
		se_debug(SE_DEBUG_PLUGINS);

		Document *current = get_current_document();
		g_return_if_fail(current);

		DialogOpenDocument::unique_ptr ui = DialogOpenDocument::create();

		ui->show_video(false);
		ui->set_select_multiple(false);
		ui->show();
		
		if(ui->run() != Gtk::RESPONSE_OK)
			return;

		ui->hide();
	
		Glib::ustring encoding = ui->get_encoding();
		Glib::ustring uri = ui->get_uri();

		// We don't need to catch error because Document::create_from_file already 
		// catch and show a error dialog if needs.
		Document *doc = Document::create_from_file(uri, encoding);
		if(doc == NULL)
			return;

		current->start_command(_("Open translation"));

		Subtitle s1 = current->subtitles().get_first();
		Subtitle s2 = doc->subtitles().get_first();

		while(s1 && s2)
		{
			s1.set_translation(s2.get_text());

			++s1;
			++s2;
		}

		// create new subtitle
		if(s2)
		{
			int size = doc->subtitles().size() - current->subtitles().size();

			while(s2)
			{
				s1 = current->subtitles().append();

				s1.set_translation(s2.get_text());
				s1.set_start_and_end(s2.get_start(), s2.get_end());
				++s2;
			}

			current->flash_message(ngettext(
					"1 subtitle was added with the translation",
					"%d subtitles were added with the translation",
					size), size);
		}
		current->finish_command();
		delete doc;
	}

	/*
	 * Save the current translation in a new document
	 */
	void on_save_translation()
	{
		se_debug(SE_DEBUG_PLUGINS);

		Document *current = get_current_document();
		g_return_if_fail(current);

		DialogSaveDocument::unique_ptr dialog = DialogSaveDocument::create();

		// default to the format of the original document
		dialog->set_format(current->getFormat());
		dialog->set_encoding(current->getCharset());
		dialog->set_newline(current->getNewLine());
		dialog->show();

		if(dialog->run() != Gtk::RESPONSE_OK)
			return;
		dialog->hide();

		Glib::ustring filename = dialog->get_filename();
		Glib::ustring uri = dialog->get_uri();
		Glib::ustring format = dialog->get_format();
		Glib::ustring encoding = dialog->get_encoding();
		Glib::ustring newline = dialog->get_newline();

		Document doc_translation(*current);

		doc_translation.setFilename(filename);
		doc_translation.setFormat(format);
		doc_translation.setCharset(encoding);
		doc_translation.setNewLine(newline);

		// apply translation
		Subtitle sub = doc_translation.subtitles().get_first();
		while(sub)
		{
			sub.set_text(sub.get_translation());
			++sub;
		}
		// display message for user
		if(doc_translation.save(uri) == false)
			current->message(_("The translation file %s (%s, %s, %s) has not been saved."), 
					filename.c_str(), format.c_str(), encoding.c_str(), newline.c_str());
		else
			current->flash_message(_("Saving translation file %s (%s, %s, %s)."), 
					filename.c_str(), format.c_str(), encoding.c_str(), newline.c_str());
	}

	/*
	 */
	bool on_subtitleeditor_window_delete_event(GdkEventAny* /*ev*/)
	{
		while(get_current_document() != NULL)
		{
			if(close_current_document() == false)
				return true;	// true to stop the closing
		}
		return false;
	}

	/*
	 */
	bool close_current_document()
	{
		Document *doc = get_current_document();
		g_return_val_if_fail(doc, false);

		if(get_config().get_value_bool("interface", "ask-to-save-on-exit") == false)
		{
			DocumentSystem::getInstance().remove(doc);
		}
		else if(!doc->get_document_changed())
		{
			DocumentSystem::getInstance().remove(doc);
		}
		else
		{
			DialogAskToSaveOnExit dialog;
		
			int response = dialog.run(doc);

			if(response == Gtk::RESPONSE_YES)
			{
				on_save();

				//if(doc->get_document_changed() == false)
					DocumentSystem::getInstance().remove(doc);
			}
			else if(response == Gtk::RESPONSE_NO)
			{
				DocumentSystem::getInstance().remove(doc);
			}
			else if(response == Gtk::RESPONSE_CANCEL)
			{
				// nothing
				return false;
			}
		}
		return true;
	}

	/*
	 * Remove the current document
	 */
	void on_close()
	{
		se_debug(SE_DEBUG_PLUGINS);

		close_current_document();
	}

	/*
	 * Quit the program
	 * Close all document with support to ask to save if is enable
	 */
	void on_exit()
	{
		while(get_current_document() != NULL)
		{
			if(!close_current_document())
				return;
		}
		Gtk::Main::quit();
	}


	/*
	 * A new document has been create, update the recent manager 
	 */
	void on_document_create(Document *doc)
	{
		se_debug(SE_DEBUG_PLUGINS);

		add_document_in_recent_manager(doc);
	}

	/*
	 */
	void add_document_in_recent_manager(Document *doc)
	{
		se_debug(SE_DEBUG_PLUGINS);

		if(doc == NULL)
			return;

		Glib::ustring filename = doc->getFilename();

		if(!Glib::file_test(filename, Glib::FILE_TEST_EXISTS))
			return;

		Glib::ustring uri = Glib::filename_to_uri(filename);

		se_debug_message(SE_DEBUG_PLUGINS, "uri=%s", uri.c_str());

		Gtk::RecentManager::Data data;
		//data.mime_type = "subtitle/";
		data.app_name = Glib::get_application_name();
		data.app_exec = Glib::get_prgname();
		data.groups.push_back("subtitleeditor");
		data.is_private = false;
		Gtk::RecentManager::get_default()->add_item(uri, data);
	}

	/*
	 * Open a recent document
	 */
	void on_recent_item_activated()
	{
		Glib::RefPtr<Gtk::Action> action = action_group->get_action("menu-recent-open-document");

		Glib::RefPtr<Gtk::RecentAction> recentAction = Glib::RefPtr<Gtk::RecentAction>::cast_static(action);

		Glib::RefPtr<Gtk::RecentInfo> cur = recentAction->get_current_item();
		
		if(cur)
		{
			se_debug_message(SE_DEBUG_PLUGINS, "uri=%s", cur->get_uri().c_str());

			open_document(cur->get_uri(), "");
		}
	}

	/*
	 * Only for "used-autosave" and "autosave-minutes".
	 */
	void on_config_interface_changed(const Glib::ustring &key, const Glib::ustring &/*value*/)
	{
		if(key == "used-autosave" || key == "autosave-minutes")
			init_autosave();
	}

	/*
	 */
	void init_autosave()
	{
		se_debug(SE_DEBUG_PLUGINS);

		m_autosave_timeout.disconnect();

		if(Config::getInstance().get_value_bool("interface", "used-autosave") == false)
			return;

		int autosave_minutes = Config::getInstance().get_value_int("interface", "autosave-minutes");

		long mseconds = SubtitleTime(0, autosave_minutes, 0, 0).totalmsecs;

		m_autosave_timeout = Glib::signal_timeout().connect(
				sigc::mem_fun(*this, &DocumentManagementPlugin::on_autosave_files), mseconds);

		se_debug_message(SE_DEBUG_PLUGINS, "save files every %d minutes", autosave_minutes);
	}

	/*
	 * Save files every "auto-save-minutes" value.
	 */
	bool on_autosave_files()
	{
		se_debug(SE_DEBUG_PLUGINS);

		on_save_all_documents();
		return true;
	}

protected:
	Gtk::UIManager::ui_merge_id ui_id;
	Glib::RefPtr<Gtk::ActionGroup> action_group;
	sigc::connection m_config_interface_connection;
	sigc::connection m_autosave_timeout;
};

REGISTER_EXTENSION(DocumentManagementPlugin)
