/*
 * Copyright (C) 2023 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2023 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 "pbd/unwind.h"

#include "ardour/surround_send.h"
#include "ardour/amp.h"
#include "ardour/audioengine.h"
#include "ardour/buffer.h"
#include "ardour/delayline.h"
#include "ardour/gain_control.h"
#include "ardour/internal_send.h"
#include "ardour/surround_pannable.h"
#include "ardour/route.h"
#include "ardour/session.h"

#include "pbd/i18n.h"

using namespace ARDOUR;

SurroundSend::SurroundSend (Session& s, std::shared_ptr<MuteMaster> mm)
	: Processor (s, _("Surround"), Temporal::TimeDomainProvider (Temporal::AudioTime))
	, _surround_id (s.next_surround_send_id ())
	, _current_gain (GAIN_COEFF_ZERO)
	, _has_state (false)
	, _ignore_enable_change (false)
	, _mute_master (mm)

{
	_send_delay.reset (new DelayLine (_session, "Send-" + name ()));
	_thru_delay.reset (new DelayLine (_session, "Thru-" + name ()));

	std::shared_ptr<AutomationList> gl (new AutomationList (Evoral::Parameter (SurroundSendLevel), *this));
	_gain_control = std::shared_ptr<GainControl> (new GainControl (_session, Evoral::Parameter (SurroundSendLevel), gl));
	_amp.reset (new Amp (_session, _("Surround"), _gain_control, false));
	_amp->activate ();

	_gain_control->set_flag (PBD::Controllable::InlineControl);
	//_gain_control->set_value (GAIN_COEFF_ZERO, PBD::Controllable::NoGroup);

	add_control (_gain_control);

	_send_enable_control = std::shared_ptr<AutomationControl> (new AutomationControl (_session, BusSendEnable, ParameterDescriptor(BusSendEnable)));
	_send_enable_control->Changed.connect_same_thread (*this, std::bind (&SurroundSend::send_enable_changed, this));
	_send_enable_control->clear_flag (PBD::Controllable::RealTime);

	ActiveChanged.connect_same_thread (*this, std::bind (&SurroundSend::proc_active_changed, this));

	InternalSend::CycleStart.connect_same_thread (*this, std::bind (&SurroundSend::cycle_start, this, _1));
}

SurroundSend::~SurroundSend ()
{
	_send_enable_control->drop_references ();
}

std::shared_ptr<SurroundPannable>
SurroundSend::pannable (size_t chn) const
{
	return _pannable[chn];
}

std::shared_ptr<SurroundPannable> const&
SurroundSend::pan_param (size_t chn, timepos_t& s, timepos_t& e) const
{
	s = _cycle_start;
	e = _cycle_end;
	return _pannable[chn];
}


gain_t
SurroundSend::target_gain () const
{
	return _mute_master->mute_gain_at (MuteMaster::SurroundSend);
}

void
SurroundSend::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool)
{
	automation_run (start_sample, nframes);

	if (!check_active ()) {
		_mixbufs.silence (nframes, 0);
		return;
	}

	/* Copy inputs to mixbufs, since (a) we may need to adjust gain (b) the
	 * contents need to be available for the Surround return (later)
	 */

	BufferSet::iterator o = _mixbufs.begin (DataType::AUDIO);
	BufferSet::iterator i = bufs.begin (DataType::AUDIO);

	for (; i != bufs.end (DataType::AUDIO) && o != _mixbufs.end (DataType::AUDIO); ++i, ++o) {
		o->read_from (*i, nframes);
	}

	/* main gain control: * mute & bypass/enable */
	gain_t tgain = target_gain ();

	if (tgain != _current_gain) {
		/* target gain has changed, fade in/out */
		_current_gain = Amp::apply_gain (_mixbufs, _session.nominal_sample_rate (), nframes, _current_gain, tgain);
	} else if (tgain == GAIN_COEFF_ZERO) {
		/* we were quiet last time, and we're still supposed to be quiet. */
		Amp::apply_simple_gain (_mixbufs, nframes, GAIN_COEFF_ZERO);
		return;
	} else if (tgain != GAIN_COEFF_UNITY) {
		/* target gain has not changed, but is not zero or unity */
		Amp::apply_simple_gain (_mixbufs, nframes, tgain);
	}

	/* apply fader gain automation */
	_amp->set_gain_automation_buffer (_session.send_gain_automation_buffer ());
	_amp->setup_gain_automation (start_sample, end_sample, nframes);
	_amp->run (_mixbufs, start_sample, end_sample, speed, nframes, true);

	_send_delay->run (_mixbufs, start_sample, end_sample, speed, nframes, true);

	for (uint32_t chn = 0; chn < n_pannables (); ++ chn) {
		_pannable[chn]->automation_run (start_sample, nframes);
	}

	_cycle_start = timepos_t (start_sample);
	_cycle_end   = timepos_t (end_sample);

	_thru_delay->run (bufs, start_sample, end_sample, speed, nframes, true);
}

void
SurroundSend::set_delay_in (samplecnt_t delay)
{
	if (_delay_in == delay) {
		return;
	}
	_delay_in = delay;
	update_delaylines (false);
}

void
SurroundSend::set_delay_out (samplecnt_t delay, size_t /*bus*/)
{
	if (_delay_out == delay) {
		return;
	}
	_delay_out = delay;
	update_delaylines (true);
}

void
SurroundSend::update_delaylines (bool rt_ok)
{
	if (!rt_ok && AudioEngine::instance ()->running () && AudioEngine::instance ()->in_process_thread ()) {
		if (_delay_out > _delay_in) {
			if (_send_delay->delay () != 0 || _thru_delay->delay () != _delay_out - _delay_in) {
				QueueUpdate (); /* EMIT SIGNAL */
			}
		} else {
			if (_thru_delay->delay () != 0 || _send_delay->delay () != _delay_in - _delay_out) {
				QueueUpdate (); /* EMIT SIGNAL */
			}
		}
		return;
	}

	bool changed;
	if (_delay_out > _delay_in) {
		changed = _thru_delay->set_delay (_delay_out - _delay_in);
		_send_delay->set_delay (0);
	} else {
		changed = _thru_delay->set_delay (0);
		_send_delay->set_delay (_delay_in - _delay_out);
	}

	if (changed && !AudioEngine::instance ()->in_process_thread ()) {
		ChangedLatency (); /* EMIT SIGNAL */
	}
}

samplecnt_t
SurroundSend::signal_latency () const
{
	if (!_pending_active) {
		return 0;
	}
	if (_delay_out > _delay_in) {
		return _delay_out - _delay_in;
	}
	return 0;
}

bool
SurroundSend::display_to_user() const
{
#ifdef MIXBUS
	return false;
#endif
	return true;
}

uint32_t
SurroundSend::n_pannables () const
{
	/* do not use _pannable.size(),
	 * if we would do so, state of removed pannables would be saved.
	 */
#ifdef MIXBUS
	return std::min<uint32_t> (2, _configured_input.n_audio ());
#endif
	return _configured_input.n_audio ();
}

void
SurroundSend::add_pannable ()
{
	std::shared_ptr<SurroundPannable> p = std::shared_ptr<SurroundPannable> (new SurroundPannable (_session, _pannable.size (), Temporal::TimeDomainProvider (Temporal::AudioTime)));

	add_control (p->pan_pos_x);
	add_control (p->pan_pos_y);
	add_control (p->pan_pos_z);
	add_control (p->pan_size);
	add_control (p->pan_snap);
	add_control (p->binaural_render_mode);

	for (uint32_t i = 0; i < _pannable.size (); ++i) {
		_pannable[i]->sync_auto_state_with (p);
		p->sync_auto_state_with (_pannable[i]);
	}

	_pannable.push_back (p);

	_change_connections.drop_connections ();
	for (auto const& c: _controls) {
		std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl>(c.second);
		ac->Changed.connect_same_thread (_change_connections, [this](bool, PBD::Controllable::GroupControlDisposition) { PanChanged (); /* EMIT SIGNAL*/});
	}
}

bool
SurroundSend::configure_io (ChanCount in, ChanCount out)
{
	bool     changed = false;
	uint32_t n_audio = in.n_audio ();

#ifdef MIXBUS
	n_audio = std::min<uint32_t> (2, n_audio);
#endif

	if (_configured) {
		changed = n_audio != n_pannables ();
	}

	while (_pannable.size () < n_audio) {
		add_pannable ();
	}

	if (changed) {
		for (uint32_t i = 0; i < n_audio; ++i) {
			_pannable[i]->foreach_pan_control ([](std::shared_ptr<AutomationControl> ac) { ac->clear_flag (PBD::Controllable::HiddenControl); });
		}
		for (uint32_t i = n_audio; i < _pannable.size (); ++i) {
			_pannable[i]->foreach_pan_control ([](std::shared_ptr<AutomationControl> ac) { ac->set_flag (PBD::Controllable::HiddenControl); });
		}
	}

#ifdef MIXBUS
	/* Link visibility - currently only for Mixbus which has a custom UI, and at most stereo */
	for (uint32_t i = 0; i < _pannable.size (); ++i) {
		_pannable[i]->foreach_pan_control ([](std::shared_ptr<AutomationControl> ac) { ac->clear_visually_linked_control (); });
	}
	/* first link local controls */
	for (uint32_t i = 0; i < n_audio; ++i) {
		_pannable[i]->setup_visual_links ();
	}
	for (uint32_t i = 0; i < n_audio; ++i) {
		for (uint32_t j = 0; j < n_audio; ++j) {
			if (i == j) {
				continue;
			}
			_pannable[i]->sync_visual_link_to (_pannable[j]);
		}
	}
#endif

	if (!_configured && !_has_state) {
		switch (n_audio) {
			case 2:
				_pannable[0]->pan_pos_x->set_value (0.0, PBD::Controllable::NoGroup);
				_pannable[1]->pan_pos_x->set_value (1.0, PBD::Controllable::NoGroup);
				break;
			case 3:
				_pannable[0]->pan_pos_x->set_value (0.0, PBD::Controllable::NoGroup);
				_pannable[1]->pan_pos_x->set_value (1.0, PBD::Controllable::NoGroup);
				_pannable[2]->pan_pos_x->set_value (0.5, PBD::Controllable::NoGroup);
				break;
			case 5:
				_pannable[0]->pan_pos_x->set_value (0.0, PBD::Controllable::NoGroup);
				_pannable[1]->pan_pos_x->set_value (1.0, PBD::Controllable::NoGroup);
				_pannable[2]->pan_pos_x->set_value (0.5, PBD::Controllable::NoGroup);
				_pannable[3]->pan_pos_x->set_value (0.0, PBD::Controllable::NoGroup);
				_pannable[4]->pan_pos_x->set_value (1.0, PBD::Controllable::NoGroup);
				_pannable[3]->pan_pos_y->set_value (1.0, PBD::Controllable::NoGroup);
				_pannable[4]->pan_pos_y->set_value (1.0, PBD::Controllable::NoGroup);
				break;
			default:
				break;
		}
	}

	ChanCount ca (DataType::AUDIO, n_audio);
	_amp->configure_io (ca, ca);

	if (!_send_delay->configure_io (ca, ca)) {
		return false;
	}
	if (!_thru_delay->configure_io (in, out)) {
		return false;
	}

	if (_configured && changed) {
		/* We cannot emit `processors_changed` while holing the `process lock` */
		dynamic_cast<Route*> (_owner)->queue_surround_processors_changed (); /* EMIT SIGNAL */
	}

	Processor::configure_io (in, out); /* may EMIT SIGNAL ConfigurationChanged */

	set_block_size (_session.get_block_size ());

	if (changed) {
		NPannablesChanged (); /* EMIT SIGNAL */
	}
	return true;
}

void
SurroundSend::ensure_mixbufs ()
{
	_mixbufs.ensure_buffers (DataType::AUDIO, n_pannables (), _session.get_block_size ());
}

int
SurroundSend::set_block_size (pframes_t)
{
	ensure_mixbufs ();
	return 0;
}

void
SurroundSend::cycle_start (pframes_t /*nframes*/)
{
	for (BufferSet::audio_iterator b = _mixbufs.audio_begin (); b != _mixbufs.audio_end (); ++b) {
		b->prepare ();
	}
}

std::string
SurroundSend::describe_parameter (Evoral::Parameter param)
{
	if (param.id() >=  n_pannables ()) {
		return X_("hidden");
	}

	if (n_pannables () < 2) {
		/* Use default names */
		return Automatable::describe_parameter (param);
	}

	std::string prefix;
	if (n_pannables () == 2) {
		prefix = string_compose ("[%1]", param.id() == 0 ? S_("Panner|L") : S_("Panner|R"));
	} else {
		prefix = string_compose ("[%1]", 1 + param.id());
	}

	if (param.type() == PanSurroundX) {
		return string_compose("%1 %2", prefix, _("Left/Right"));
	} else if (param.type() == PanSurroundY) {
		return string_compose("%1 %2", prefix, _("Front/Back"));
	} else if (param.type() == PanSurroundZ) {
		return string_compose("%1 %2", prefix, _("Elevation"));
	} else if (param.type() == PanSurroundSize) {
		return string_compose("%1 %2", prefix, _("Object Size"));
	} else if (param.type() == PanSurroundSnap) {
		return string_compose("%1 %2", prefix, _("Snap to Speaker"));
	} else if (param.type() == BinauralRenderMode) {
		return string_compose("%1 %2", prefix, _("Binaural Render mode"));
	}

	return Automatable::describe_parameter (param);
}

void
SurroundSend::send_enable_changed ()
{
	if (_ignore_enable_change) {
		return;
	}
	PBD::Unwinder<bool> uw (_ignore_enable_change, true);
	if (_send_enable_control->get_value () > 0) {
		activate ();
	} else {
		deactivate ();
	}
}

void
SurroundSend::proc_active_changed ()
{
	if (_ignore_enable_change) {
		return;
	}
	PBD::Unwinder<bool> uw (_ignore_enable_change, true);
	_send_enable_control->set_value (_pending_active ? 1 : 0, PBD::Controllable::UseGroup);
}

int
SurroundSend::set_state (const XMLNode& node, int version)
{
	XMLNode* gainnode = node.child (PBD::Controllable::xml_node_name.c_str());
	_gain_control->set_state (*gainnode, version);

	uint32_t npan;
	if (!node.get_property("n-pannables", npan))  {
		return -1;
	}

	while (_pannable.size () < npan) {
		add_pannable ();
	}

	XMLNodeList pans = node.children (X_("SurroundPannable"));
	for (auto const& c: pans) {
		uint32_t chn;
		if (!c->get_property("channel", chn)) {
			continue;
		}
		_pannable[chn]->set_state (*c, version);
	}

	_has_state = true;

	return Processor::set_state (node, version);
}

XMLNode&
SurroundSend::state () const
{
	XMLNode& node (Processor::state ());
	node.set_property ("type", "sursend");
	node.set_property ("n-pannables", n_pannables ());

	node.add_child_nocopy (_gain_control->get_state());
	for (uint32_t chn = 0; chn < n_pannables (); ++ chn) {
		node.add_child_nocopy (_pannable[chn]->get_state ());
	}
	return node;
}
