/*
 * Copyright (C) 2008-2009 Sakari Bergen <sakari.bergen@beatwaves.net>
 * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
 * Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
 * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2015-2018 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 "ardour/audio_track_importer.h"

#include "ardour/audio_playlist_importer.h"
#include "ardour/disk_reader.h"
#include "ardour/session.h"

#include "pbd/controllable.h"
#include "pbd/failed_constructor.h"
#include "pbd/string_convert.h"

#include <sstream>
#include <algorithm>

#include "pbd/i18n.h"

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

/*** AudioTrackImportHandler ***/

AudioTrackImportHandler::AudioTrackImportHandler (XMLTree const & source, Session & session, AudioPlaylistImportHandler & pl_handler) :
  ElementImportHandler (source, session)
{
	XMLNode const * root = source.root();
	XMLNode const * routes;

	if (!(routes = root->child ("Routes"))) {
		throw failed_constructor();
	}

	XMLNodeList const & route_list = routes->children();
	for (XMLNodeList::const_iterator it = route_list.begin(); it != route_list.end(); ++it) {
		XMLProperty const * type = (*it)->property("default-type");
		if ( (!type || type->value() == "audio") &&  ((*it)->property ("diskstream") != 0 || (*it)->property ("diskstream-id") != 0)) {
			try {
				elements.push_back (ElementPtr ( new AudioTrackImporter (source, session, *this, **it, pl_handler)));
			} catch (failed_constructor const&) {
				set_dirty();
			}
		}
	}
}

string
AudioTrackImportHandler::get_info () const
{
	return _("Audio Tracks");
}


/*** AudioTrackImporter ***/

AudioTrackImporter::AudioTrackImporter (XMLTree const & source,
                                        Session & session,
                                        AudioTrackImportHandler & track_handler,
                                        XMLNode const & node,
                                        AudioPlaylistImportHandler & pl_handler) :
  ElementImporter (source, session),
  track_handler (track_handler),
  xml_track (node),
  pl_handler (pl_handler)
{
	XMLProperty * prop;

	if (!parse_route_xml ()) {
		throw failed_constructor();
	}

	if (!parse_io ()) {
		throw failed_constructor();
	}

	XMLNodeList const & controllables = node.children (Controllable::xml_node_name);
	for (XMLNodeList::const_iterator it = controllables.begin(); it != controllables.end(); ++it) {
		parse_controllable (**it);
	}

	XMLNode * remote_control = xml_track.child ("RemoteControl");
	if (remote_control && (prop = remote_control->property ("id"))) {
		uint32_t control_id = session.ntracks() + session.nbusses() + 1;
		prop->set_value (to_string (control_id));
	}

	xml_track.remove_nodes_and_delete ("Extra");
}

AudioTrackImporter::~AudioTrackImporter ()
{
	playlists.clear ();
}

bool
AudioTrackImporter::parse_route_xml ()
{
	bool ds_ok = false;

	// Remove order keys, new ones will be generated
	xml_track.remove_property ("order-keys");

	XMLPropertyList const & props = xml_track.properties();
	for (XMLPropertyList::const_iterator it = props.begin(); it != props.end(); ++it) {
		string prop = (*it)->name();
		if (!prop.compare ("default-type") || !prop.compare ("flags") ||
		  !prop.compare ("active") || !prop.compare ("muted") ||
		  !prop.compare ("soloed") || !prop.compare ("phase-invert") ||
		  !prop.compare ("denormal-protection") || !prop.compare("mute-affects-pre-fader") ||
		  !prop.compare ("mute-affects-post-fader") || !prop.compare("mute-affects-control-outs") ||
		  !prop.compare ("mute-affects-main-outs") || !prop.compare("mode")) {
			// All ok
		} else if (!prop.compare("diskstream-id")) {
			old_ds_id = (*it)->value();
			(*it)->set_value (new_ds_id.to_s());
			ds_ok = true;
		} else {
			std::cerr << string_compose (X_("AudioTrackImporter: did not recognise XML-property \"%1\""), prop) << endmsg;
		}
	}

	if (!ds_ok) {
		error << X_("AudioTrackImporter: did not find necessary XML-property \"diskstream-id\"") << endmsg;
		return false;
	}

	return true;
}

bool
AudioTrackImporter::parse_io ()
{
	XMLNode * io;
	bool name_ok = false;
	bool id_ok = false;

	if (!(io = xml_track.child ("IO"))) {
		return false;
	}

	XMLPropertyList const & props = io->properties();

	for (XMLPropertyList::const_iterator it = props.begin(); it != props.end(); ++it) {
		string prop = (*it)->name();
		if (!prop.compare ("gain") || !prop.compare ("iolimits")) {
			// All ok
		} else if (!prop.compare("name")) {
			name = (*it)->value();
			name_ok = true;
		} else if (!prop.compare("id")) {
			PBD::ID id;
			(*it)->set_value (id.to_s());
			id_ok = true;
		} else if (!prop.compare("inputs")) {
			// TODO Handle this properly!
			/* Input and output ports are counted and added empty, so that no in/output connecting function fails. */
			uint32_t num_inputs = std::count ((*it)->value().begin(), (*it)->value().end(), '{');
			std::string value;
			for (uint32_t i = 0; i < num_inputs; i++) { value += "{}"; }
			(*it)->set_value (value);
		} else if (!prop.compare("outputs")) {
			// TODO See comments above
			uint32_t num_outputs = std::count ((*it)->value().begin(), (*it)->value().end(), '{');
			std::string value;
			for (uint32_t i = 0; i < num_outputs; i++) { value += "{}"; }
			(*it)->set_value (value);
		} else {
			std::cerr << string_compose (X_("AudioTrackImporter: did not recognise XML-property \"%1\""), prop) << endmsg;
		}
	}

	if (!name_ok) {
		error << X_("AudioTrackImporter: did not find necessary XML-property \"name\"") << endmsg;
		return false;
	}

	if (!id_ok) {
		error << X_("AudioTrackImporter: did not find necessary XML-property \"id\"") << endmsg;
		return false;
	}

	XMLNodeList const & controllables = io->children (Controllable::xml_node_name);
	for (XMLNodeList::const_iterator it = controllables.begin(); it != controllables.end(); ++it) {
		parse_controllable (**it);
	}

	XMLNodeList const & processors = io->children ("Processor");
	for (XMLNodeList::const_iterator it = processors.begin(); it != processors.end(); ++it) {
		parse_processor (**it);
	}

	XMLNodeList const & automations = io->children ("Automation");
	for (XMLNodeList::const_iterator it = automations.begin(); it != automations.end(); ++it) {
		parse_automation (**it);
	}

	return true;
}

string
AudioTrackImporter::get_info () const
{
	// TODO
	return name;
}

/** @return true if everything is ok */
bool
AudioTrackImporter::_prepare_move ()
{
	/* Copy dependent playlists */

	pl_handler.playlists_by_diskstream (old_ds_id, playlists);

	for (PlaylistList::iterator it = playlists.begin(); it != playlists.end(); ++it) {
		if (!(*it)->prepare_move ()) {
			playlists.clear ();
			return false;
		}
		(*it)->set_diskstream (new_ds_id);
	}

	/* Rename */

	while (session.route_by_name (name) || !track_handler.check_name (name)) {
		std::pair<bool, string> rename_pair = *Rename (_("A playlist with this name already exists, please rename it."), name);
		if (!rename_pair.first) {
			return false;
		}
		name = rename_pair.second;
	}

	XMLNode* c = xml_track.child ("IO");
	if (!c) {
		error << _("badly-formed XML in imported track") << endmsg;
		return false;
	}

	XMLProperty * p = c->property ("name");
	if (!p) {
		error << _("badly-formed XML in imported track") << endmsg;
		return false;
	}

	p->set_value (name);

	track_handler.add_name (name);

	return true;
}

void
AudioTrackImporter::_cancel_move ()
{
	track_handler.remove_name (name);
	playlists.clear ();
}

void
AudioTrackImporter::_move ()
{
	/* XXX DISK */
#if 0
	/* Add diskstream */

	std::shared_ptr<XMLSharedNodeList> ds_node_list;
	string xpath = "/Session/DiskStreams/AudioDiskstream[@id='" + old_ds_id.to_s() + "']";
	ds_node_list = source.find (xpath);

	if (ds_node_list->size() != 1) {
		error << string_compose (_("Error Importing Audio track %1"), name) << endmsg;
		return;
	}

	std::shared_ptr<XMLNode> ds_node = ds_node_list->front();
	XMLProperty * p = ds_node->property (X_("id"));
	assert (p);
	p->set_value (new_ds_id.to_s());

	std::shared_ptr<DiskReader> new_ds (new DiskReader (session, *ds_node));
	new_ds->set_name (name);
	new_ds->do_refill_with_alloc (true, false);
	new_ds->set_block_size (session.get_block_size ());

	/* Import playlists */

	for (PlaylistList::const_iterator it = playlists.begin(); it != playlists.end(); ++it) {
		(*it)->move ();
	}

	/* Import track */

	XMLNode routes ("Routes");
	routes.add_child_copy (xml_track);
	session.load_routes (routes, 3000);
#endif
}

bool
AudioTrackImporter::parse_processor (XMLNode & node)
{
	XMLNode * automation = node.child ("Automation");
	if (automation) {
		parse_automation (*automation);
	}

	return true;
}

bool
AudioTrackImporter::parse_controllable (XMLNode & node)
{
	XMLProperty * prop;

	if ((prop = node.property ("id"))) {
		PBD::ID new_id;
		prop->set_value (new_id.to_s());
	} else {
		return false;
	}

	return true;
}

bool
AudioTrackImporter::parse_automation (XMLNode & node)
{

	XMLNodeList const & lists = node.children ("AutomationList");
	for (XMLNodeList::const_iterator it = lists.begin(); it != lists.end(); ++it) {
		XMLProperty * prop;

		if ((prop = (*it)->property ("id"))) {
			PBD::ID id;
			prop->set_value (id.to_s());
		}

		if (!(*it)->name().compare ("events")) {
			rate_convert_events (**it);
		}
	}

	return true;
}

bool
AudioTrackImporter::rate_convert_events (XMLNode & node)
{
	if (node.children().empty()) {
		return false;
	}

	XMLNode* content_node = node.children().front();

	if (content_node->content().empty()) {
		return false;
	}

	std::stringstream str (content_node->content());
	std::ostringstream new_content;

	samplecnt_t x;
	double y;
	bool ok = true;

	while (str) {
		str >> x;
		if (!str) {
			break;
		}
		str >> y;
		if (!str) {
			ok = false;
			break;
		}

		new_content << rate_convert_samples (x) << ' ' << y;
	}

	if (!ok) {
		error << X_("AudioTrackImporter: error in rate converting automation events") << endmsg;
		return false;
	}

	content_node->set_content (new_content.str());

	return true;
}
