/*
 * Copyright (C) 2022 Robin Gareus <robin@gareus.org>
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <gtkmm/messagedialog.h>
#include <gtkmm/stock.h>

#include <glib.h>

#include "pbd/openuri.h"

#include "ardour/export_channel_configuration.h"
#include "ardour/export_filename.h"
#include "ardour/export_preset.h"
#include "ardour/export_profile_manager.h"
#include "ardour/export_status.h"
#include "ardour/export_timespan.h"
#include "ardour/profile.h"
#include "ardour/session_directory.h"

#include "nag.h"
#include "simple_export_dialog.h"

#include "pbd/i18n.h"

using namespace std;
using namespace ARDOUR;
using namespace PBD;
using namespace Gtk;

SimpleExportDialog::SimpleExportDialog (PublicEditor& editor)
	: ARDOUR::SimpleExport ()
	, ArdourDialog (_("Quick Audio Export"), true, false)
	, _editor (editor)
	, _eps (true)
{
	if (_eps.the_combo ().get_parent ()) {
		_eps.the_combo ().get_parent ()->remove (_eps.the_combo ());
	}

	_range_list = ListStore::create (_range_cols);
	_range_combo.set_model (_range_list);
	_range_combo.pack_start (_range_cols.label);

	Table* t = manage (new Table);
	int    r = 0;

	t->set_spacings (4);

#define LBL(TXT) *manage (new Label (_(TXT), ALIGN_END))

	/* clang-format off */
	t->attach (LBL ("Format preset:"),  0, 1, r, r + 1, FILL,          SHRINK, 0, 0);
	t->attach (_eps.the_combo (),       1, 2, r, r + 1, EXPAND,        SHRINK, 0, 0);
	++r;
	t->attach (LBL ("Export range:"),   0, 1, r, r + 1, FILL,          SHRINK, 0, 0);
	t->attach (_range_combo,            1, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0);
	++r;
	t->attach (LBL ("After export:"),   0, 1, r, r + 1, FILL,          SHRINK, 0, 0);
	t->attach (_post_export_combo,      1, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0);
	++r;
	t->attach (_error_label,            0, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0);
	++r;
	t->attach (_progress_bar,           0, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0);
	/* clang-format on */

#undef LBL

	_post_export_combo.append (_("Open the folder where files are exported"));
	_post_export_combo.append (_("Do nothing"));
	_post_export_combo.set_active (0);

	get_vbox ()->pack_start (*t, false, false);

	_cancel_button = add_button (Gtk::Stock::CANCEL, RESPONSE_CANCEL);
	_export_button = add_button (_("_Export"), RESPONSE_OK);
	_cancel_button->signal_clicked ().connect (sigc::mem_fun (*this, &SimpleExportDialog::close_dialog));
	_export_button->signal_clicked ().connect (sigc::mem_fun (*this, &SimpleExportDialog::start_export));

	_progress_bar.set_no_show_all (true);
	_error_label.set_no_show_all (true);

	_export_button->set_sensitive (false);
	_range_combo.set_sensitive (false);

	t->show_all ();
}

XMLNode&
SimpleExportDialog::get_state () const
{
	XMLNode* node = new XMLNode (X_("QuickExport"));
	node->set_property (X_("PresetUUID"), preset_uuid ());
	node->set_property (X_("PostExport"), _post_export_combo.get_active_row_number ());
	return *node;
}

void
SimpleExportDialog::set_state (XMLNode const& node)
{
	int         post_export;
	std::string pset_uuid;
	if (node.get_property (X_("PresetUUID"), pset_uuid)) {
		set_preset (pset_uuid);
	}
	if (node.get_property (X_("PostExport"), post_export)) {
		_post_export_combo.set_active (post_export);
	}
}

void
SimpleExportDialog::set_session (ARDOUR::Session* s)
{
	SimpleExport::set_session (s);
	ArdourDialog::set_session (s);

	_range_list->clear ();
	_preset_cfg_connection.disconnect ();

	if (!s) {
		_export_button->set_sensitive (false);
		_range_combo.set_sensitive (false);
		return;
	}

	XMLNode* node = s->extra_xml (X_("QuickExport"));
	if (node) {
		set_state (*node);
	}

	_eps.set_manager (_manager);

	if (!check_outputs ()) {
		set_error ("Error: Session has no master bus");
		return;
	}

	/* check range */
	Location*            srl (s->locations ()->session_range_location ());
	TimeSelection const& tsel (_editor.get_selection ().time);

	if (!tsel.empty ()) {
		TreeModel::Row row     = *_range_list->append ();
		row[_range_cols.label] = _("Using time selection");
		row[_range_cols.start] = tsel.start_sample ();
		row[_range_cols.end]   = tsel.end_sample ();
		row[_range_cols.name]  = string_compose (_("%1 (selection)"), SimpleExport::_session->snap_name ());
	}

	if (srl) {
		TreeModel::Row row     = *_range_list->append ();
		row[_range_cols.label] = _("Session start to session end"); // same text as ExportVideoDialog::apply_state
		row[_range_cols.start] = srl->start_sample ();
		row[_range_cols.end]   = srl->end_sample ();
		row[_range_cols.name]  = SimpleExport::_session->snap_name ();
	}

	struct LocationSorter {
		bool operator() (Location const* a, Location const* b) {
			return a->start_sample () < b->start_sample ();
		}
	};

	Locations::LocationList ll (s->locations ()->list ());
	ll.sort (LocationSorter ());

	for (auto const& l : ll) {
		if (l->is_session_range () || !l->is_range_marker () || l->name ().empty ()) {
			continue;
		}
		TreeModel::Row row     = *_range_list->append ();
		row[_range_cols.label] = l->name (); // string_compose (_("Range '%1'"), l->name ());
		row[_range_cols.start] = l->start_sample ();
		row[_range_cols.end]   = l->end_sample ();
		row[_range_cols.name]  = string_compose (_("%1 - %2"), SimpleExport::_session->snap_name (), l->name ());
	}

	if (_range_list->children ().size () == 0) {
		set_error ("Error: No valid range to export. Select a range or create session start/end markers");
		return;
	}

	_range_combo.set_active (0);
	_range_combo.set_sensitive (true);
	_export_button->set_sensitive (true);

	_preset_cfg_connection = _eps.CriticalSelectionChanged.connect (sigc::mem_fun (*this, &SimpleExportDialog::check_manager));
}

void
SimpleExportDialog::check_manager ()
{
	bool ok = _manager && _manager->preset ();

	if (ok && _manager->get_formats ().empty ()) {
		ok = false;
	}

	if (ok) {
		/* check for NULL ExportFormatSpecPtr */
		auto fms = _manager->get_formats ();
		for (auto const& fm : fms) {
			if (!fm->format) {
				ok = false;
				break;
			}
		}
	}

	_export_button->set_sensitive (ok);
}

void
SimpleExportDialog::set_error (std::string const& err)
{
	_export_button->set_sensitive (false);
	_range_combo.set_sensitive (false);
	_error_label.set_text (err);
	_error_label.show ();
}

void
SimpleExportDialog::close_dialog ()
{
	if (_status->running ()) {
		_status->abort ();
	}
}

void
SimpleExportDialog::start_export ()
{
	TreeModel::iterator r = _range_combo.get_active ();
	set_range ((*r)[_range_cols.start], (*r)[_range_cols.end]);
	SimpleExport::set_name ((*r)[_range_cols.name]);

	SimpleExport::_session->add_extra_xml (get_state ());

	_cancel_button->set_label (_("_Abort"));
	_export_button->set_sensitive (false);
	_progress_bar.set_fraction (0.0);
	_progress_bar.show ();

#if 0
	_eps.hide ();
	_range_combo.hide ();
	_post_export_combo,.hide ();
#endif

	_progress_connection = Glib::signal_timeout ().connect (sigc::mem_fun (*this, &SimpleExportDialog::progress_timeout), 100);

	if (run_export ()) {
		hide ();
		if (_post_export_combo.get_active_row_number () == 0) {
			PBD::open_folder (folder ());
		}
		if (!ARDOUR::Profile->get_mixbus ()) {
			NagScreen* ns = NagScreen::maybe_nag (_("Export"));
			if (ns) {
				ns->nag ();
				delete ns;
			}
		}
	} else if (!_status->aborted ()) {
		hide ();
		std::string        txt = _("Export has been aborted due to an error!\nSee the Log window for details.");
		Gtk::MessageDialog msg (txt, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
		msg.run ();
	}
}

bool
SimpleExportDialog::progress_timeout ()
{
	std::string status_text;
	float       progress = -1;

	switch (_status->active_job) {
		case ExportStatus::Exporting:
			status_text = string_compose (_("Exporting '%3' (timespan %1 of %2)"),
			                              _status->timespan, _status->total_timespans, _status->timespan_name);
			progress    = ((float)_status->processed_samples_current_timespan) / _status->total_samples_current_timespan;
			break;
		case ExportStatus::Normalizing:
			status_text = string_compose (_("Normalizing '%3' (timespan %1 of %2)"),
			                              _status->timespan, _status->total_timespans, _status->timespan_name);
			progress    = ((float)_status->current_postprocessing_cycle) / _status->total_postprocessing_cycles;
			break;
		case ExportStatus::Encoding:
			status_text = string_compose (_("Encoding '%3' (timespan %1 of %2)"),
			                              _status->timespan, _status->total_timespans, _status->timespan_name);
			progress    = ((float)_status->current_postprocessing_cycle) / _status->total_postprocessing_cycles;
			break;
		case ExportStatus::Tagging:
			status_text = string_compose (_("Tagging '%3' (timespan %1 of %2)"),
			                              _status->timespan, _status->total_timespans, _status->timespan_name);
		case ExportStatus::Uploading:
			break;
		case ExportStatus::Command:
			status_text = string_compose (_("Running Post-Export Command for '%1'"), _status->timespan_name);
			break;
	}

	_progress_bar.set_text (status_text);

	if (progress >= 0) {
		_progress_bar.set_fraction (progress);
	} else {
		_progress_bar.set_pulse_step (.1);
		_progress_bar.pulse ();
	}

	return true;
}
