/*
 * Copyright (C) 2020 Luciano Iam <oss@lucianoiam.com>
 *
 * 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/dB.h"
#include "ardour/meter.h"
#include "ardour/plugin_insert.h"
#include "ardour/session.h"
#include "pbd/controllable.h"

#include "mixer.h"

using namespace ARDOUR;
using namespace ArdourSurface;

ArdourMixerPlugin::ArdourMixerPlugin (std::shared_ptr<ARDOUR::PluginInsert> insert)
	: _insert (insert)
{}

ArdourMixerPlugin::~ArdourMixerPlugin ()
{
	drop_connections ();
}

std::shared_ptr<ARDOUR::PluginInsert>
ArdourMixerPlugin::insert () const
{
	return _insert;
}

bool
ArdourMixerPlugin::enabled () const
{
	return insert ()->enabled ();
}

void
ArdourMixerPlugin::set_enabled (bool enabled)
{
	insert ()->enable (enabled);
}

uint32_t
ArdourMixerPlugin::param_count () const
{
	return _insert->plugin ()->parameter_count ();
}

TypedValue
ArdourMixerPlugin::param_value (uint32_t param_id)
{
	return param_value (param_control (param_id));
}

void
ArdourMixerPlugin::set_param_value (uint32_t param_id, TypedValue value)
{
	std::shared_ptr<AutomationControl> control = param_control (param_id);
	ParameterDescriptor pd = control->desc ();
	double              dbl_val;

	if (pd.toggled) {
		dbl_val = static_cast<double> (static_cast<bool> (value));
	} else if (pd.enumeration || pd.integer_step) {
		dbl_val = static_cast<double> (static_cast<int> (value));
	} else {
		dbl_val = static_cast<double> (value);
	}

	control->set_value (dbl_val, PBD::Controllable::NoGroup);
}

std::shared_ptr<ARDOUR::AutomationControl>
ArdourMixerPlugin::param_control (uint32_t param_id) const
{
	bool                      ok         = false;
	std::shared_ptr<Plugin> plugin     = _insert->plugin ();
	uint32_t                  control_id = plugin->nth_parameter (param_id, ok);

	if (!ok || !plugin->parameter_is_input (control_id)) {
		throw ArdourMixerNotFoundException ("invalid automation control for param id = "
			+ std::to_string(param_id));
	}

	return _insert->automation_control (Evoral::Parameter (PluginAutomation, 0, control_id));
}

TypedValue
ArdourMixerPlugin::param_value (std::shared_ptr<ARDOUR::AutomationControl> control)
{
	ParameterDescriptor pd    = control->desc ();
	TypedValue          value = TypedValue ();

	if (pd.toggled) {
		value = TypedValue (static_cast<bool> (control->get_value ()));
	} else if (pd.enumeration || pd.integer_step) {
		value = TypedValue (static_cast<int> (control->get_value ()));
	} else {
		value = TypedValue (control->get_value ());
	}

	return value;
}

ArdourMixerStrip::ArdourMixerStrip (std::shared_ptr<ARDOUR::Stripable> stripable, PBD::EventLoop* event_loop)
	: _stripable (stripable)
{
	std::shared_ptr<Route> route = std::dynamic_pointer_cast<Route> (_stripable);

	if (!route) {
		return;
	}

	for (uint32_t plugin_id = 0;; ++plugin_id) {
		std::shared_ptr<Processor> processor = route->nth_plugin (plugin_id);

		if (!processor) {
			break;
		}

		std::shared_ptr<PluginInsert> insert = std::static_pointer_cast<PluginInsert> (processor);

		if (insert) {
			_plugins[plugin_id] = std::shared_ptr<ArdourMixerPlugin> (new ArdourMixerPlugin (insert));
			insert->DropReferences.connect (*_plugins[plugin_id], MISSING_INVALIDATOR,
			                                std::bind (&ArdourMixerStrip::on_drop_plugin, this, plugin_id), event_loop);
		}
	}
}

ArdourMixerStrip::~ArdourMixerStrip ()
{
	drop_connections ();
}

std::shared_ptr<ARDOUR::Stripable>
ArdourMixerStrip::stripable () const
{
	return _stripable;
}

ArdourMixerPlugin&
ArdourMixerStrip::plugin (uint32_t plugin_id)
{
	if (_plugins.find (plugin_id) == _plugins.end ()) {
		throw ArdourMixerNotFoundException ("plugin id = " + std::to_string(plugin_id) + " not found");
	}

	return *_plugins[plugin_id];
}

ArdourMixerStrip::PluginMap&
ArdourMixerStrip::plugins ()
{
	return _plugins;
}

double
ArdourMixerStrip::gain () const
{
	double val = _stripable->gain_control ()->get_value ();
	return is_midi () ? static_cast<double> (to_velocity (val)) : to_db (val);
}

void
ArdourMixerStrip::set_gain (double gain)
{
	double val = is_midi () ? from_velocity (static_cast<int> (gain)) : from_db (gain);
	_stripable->gain_control ()->set_value (val, PBD::Controllable::NoGroup);
}

bool
ArdourMixerStrip::has_pan () const
{
	return _stripable->pan_azimuth_control () != 0;
}

double
ArdourMixerStrip::pan () const
{
	std::shared_ptr<AutomationControl> ac = _stripable->pan_azimuth_control ();
	
	if (!ac) {
		throw ArdourMixerNotFoundException ("strip has no panner");
	}

	return ac->internal_to_interface (ac->get_value ());
}

void
ArdourMixerStrip::set_pan (double value)
{
	std::shared_ptr<AutomationControl> ac = _stripable->pan_azimuth_control ();

	if (!ac) {
		return;
	}

	ac->set_value (ac->interface_to_internal (value), PBD::Controllable::NoGroup);
}

bool
ArdourMixerStrip::mute () const
{
	return _stripable->mute_control ()->muted ();
}

void
ArdourMixerStrip::set_mute (bool mute)
{
	_stripable->mute_control ()->set_value (mute ? 1.0 : 0.0, PBD::Controllable::NoGroup);
}

float
ArdourMixerStrip::meter_level_db () const
{
	std::shared_ptr<PeakMeter> meter = _stripable->peak_meter ();
	return meter ? meter->meter_level (0, MeterMCP) : -193;
}

std::string
ArdourMixerStrip::name () const
{
	return _stripable->name ();
}

bool
ArdourMixerStrip::is_midi () const
{
	return _stripable->presentation_info ().flags () & PresentationInfo::MidiTrack;
}

void
ArdourMixerStrip::on_drop_plugin (uint32_t plugin_id)
{
	_plugins.erase (plugin_id);
}

double
ArdourMixerStrip::to_db (double k)
{
	if (k == 0) {
		return -std::numeric_limits<double>::infinity ();
	}

	float db = accurate_coefficient_to_dB (static_cast<float> (k));

	return static_cast<double> (db);
}

double
ArdourMixerStrip::from_db (double db)
{
	if (db < -192) {
		return 0;
	}

	float k = dB_to_coefficient (static_cast<float> (db));

	return static_cast<double> (k);
}
	
int
ArdourMixerStrip::to_velocity (double k)
{
	return static_cast<int> (127.0 * k / 2.0);
}

double
ArdourMixerStrip::from_velocity (int k)
{
	return 2.0 * static_cast<double> (k) / 127.0;
}

int
ArdourMixer::start ()
{
	/* take a snapshot of current strips */
	StripableList strips;
	session ().get_stripables (strips, PresentationInfo::AllStripables);
	uint32_t strip_id = 0;

	for (StripableList::iterator it = strips.begin (); it != strips.end (); ++it) {
		_strips[strip_id] = std::shared_ptr<ArdourMixerStrip> (new ArdourMixerStrip (*it, event_loop ()));
		(*it)->DropReferences.connect (*_strips[strip_id], MISSING_INVALIDATOR,
		                               std::bind (&ArdourMixer::on_drop_strip, this, strip_id), event_loop ());
		strip_id++;
	}

	return 0;
}

int
ArdourMixer::stop ()
{
	PBD::Mutex::Lock lock (mixer ().mutex ());
	_strips.clear ();
	return 0;
}

ArdourMixer::StripMap&
ArdourMixer::strips ()
{
	return _strips;
}


ArdourMixerStrip&
ArdourMixer::strip (uint32_t strip_id)
{
	if (_strips.find (strip_id) == _strips.end ()) {
		throw ArdourMixerNotFoundException ("strip id = " + std::to_string(strip_id) + " not found");
	}

	return *_strips[strip_id];
}

void
ArdourMixer::on_drop_strip (uint32_t strip_id)
{
	PBD::Mutex::Lock lock (_mutex);
	_strips.erase (strip_id);
}

PBD::Mutex&
ArdourMixer::mutex ()
{
	return _mutex;
}
