/*
 * Copyright (C) 2014 David Robillard <d@drobilla.net>
 * Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
 * Copyright (C) 2016 Paul Davis <paul@linuxaudiosystems.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 <algorithm>
#include <boost/algorithm/string.hpp>

#include "pbd/control_math.h"

#include "ardour/amp.h"
#include "ardour/dB.h"
#include "ardour/parameter_descriptor.h"
#include "ardour/parameter_types.h"
#include "ardour/rc_configuration.h"
#include "ardour/types.h"
#include "ardour/utils.h"

#include "pbd/i18n.h"

namespace ARDOUR {

ParameterDescriptor::ParameterDescriptor(const Evoral::Parameter& parameter)
	: Evoral::ParameterDescriptor()
	, key((uint32_t)-1)
	, datatype(Variant::NOTHING)
	, type((AutomationType)parameter.type())
	, unit(NONE)
	, step(0)
	, smallstep(0)
	, largestep(0)
	, integer_step(parameter_is_midi (parameter.type ()))
	, sr_dependent(false)
	, enumeration(false)
	, inline_ctrl(false)
	, display_priority(0)
{
	ScalePoints sp;

	/* Note: defaults in Evoral::ParameterDescriptor */

	switch((AutomationType)parameter.type()) {
	case BusSendLevel:
		/* fallthrough */
	case InsertReturnLevel:
		inline_ctrl = true;
		/* fallthrough */
	case GainAutomation:
		upper  = Config->get_max_gain();
		normal = 1.0f;
		break;
	case BusSendEnable:
		upper  = 1.f;
		normal = 1.f;
		toggled = true;
		break;
	case TrimAutomation:
		upper  = 10; // +20dB
		lower  = .1; // -20dB
		normal = 1.0f;
		logarithmic = true;
		break;
	case MainOutVolume:
		upper  = 100; // +40dB
		lower  = .01; // -40dB
		normal = 1.0f;
		logarithmic = true;
		break;
	case PanAzimuthAutomation:
		normal = 0.5f; // there really is no _normal but this works for stereo, sort of
		upper  = 1.0f;
		break;
	case PanWidthAutomation:
		lower  = -1.0;
		upper  = 1.0;
		normal = 0.0f;
		break;
	case RecEnableAutomation:
	case RecSafeAutomation:
		lower  = 0.0;
		upper  = 1.0;
		toggled = true;
		break;
	case FadeInAutomation:
	case FadeOutAutomation:
	case EnvelopeAutomation:
		upper  = 2.0f;
		normal = 1.0f;
		break;
	case SoloAutomation:
	case MuteAutomation:
		upper  = 1.0f;
		normal = 0.0f;
		toggled = true;
		break;
	case MidiCCAutomation:
	case MidiPgmChangeAutomation:
	case MidiChannelPressureAutomation:
	case MidiNotePressureAutomation:
		lower  = 0.0;
		normal = 0.0;
		upper  = 127.0;
		print_fmt = "%.0f";
		break;
	case MidiPitchBenderAutomation:
		lower  = 0.0;
		normal = 8192.0;
		upper  = 16383.0;
		print_fmt = "%.0f";
		break;
	case PhaseAutomation:
		toggled = true;
		scale_points = std::shared_ptr<ScalePoints>(new ScalePoints());
		scale_points->insert (std::make_pair (_("Normal"), 0));
		scale_points->insert (std::make_pair (_("Invert"), 1));
		break;
	case MonitoringAutomation:
		enumeration = true;
		integer_step = true;
		lower = MonitorAuto;
		upper = MonitorCue;
		scale_points = std::shared_ptr<ScalePoints>(new ScalePoints());
		scale_points->insert (std::make_pair (_("Auto"), MonitorAuto));
		scale_points->insert (std::make_pair (_("Input"), MonitorInput));
		scale_points->insert (std::make_pair (_("Disk"), MonitorDisk));
		break;
	case SoloIsolateAutomation:
	case SoloSafeAutomation:
		toggled = true;
		break;
	default:
		break;
	}

	update_steps();
}

ParameterDescriptor::ParameterDescriptor()
	: Evoral::ParameterDescriptor()
	, key((uint32_t)-1)
	, datatype(Variant::NOTHING)
	, type(NullAutomation)
	, unit(NONE)
	, step(0)
	, smallstep(0)
	, largestep(0)
	, integer_step(false)
	, sr_dependent(false)
	, enumeration(false)
	, inline_ctrl(false)
	, display_priority(0)
{}

void
ParameterDescriptor::update_steps()
{
	/* sanitize flags */
	if (toggled || enumeration) {
		logarithmic = false;
	}
	if (logarithmic && sr_dependent && upper > lower && lower == 0) {
		/* work-around for plugins with a log-scale control 0..SR; log (0) is not defined */
		lower = upper / 1000.f;
	}
	if (logarithmic && (upper <= lower || lower * upper <= 0)) {
		/* log-scale params need upper > lower and both values need the same sign */
		logarithmic = false;
	}
	if (rangesteps < 2) {
		rangesteps = 0;
	}
	if (enumeration) {
		/* enums need scale-points.
		 * The GUI is more restrictive, a dropdown is displayed
		 * IIF  scale_points.size() == (1 + upper - lower)
		 */
		if (!scale_points || scale_points->empty ()) {
			enumeration = false;
		}
	}
	if (integer_step) {
		if (lower >= upper) {
			integer_step = false;
		}
	}

	/* upper == lower does not make any sense */
	if (lower == upper) {
		upper = lower + 0.01; // add some arbitrary value
	}

	/* set steps */

	if (unit == ParameterDescriptor::MIDI_NOTE) {
		step      = smallstep = 1;  // semitone
		largestep = 12;             // octave
	} else if (type == GainAutomation || type == TrimAutomation || type == BusSendLevel || type == MainOutVolume || type == InsertReturnLevel) {
		/* dB_coeff_step gives a step normalized for [0, max_gain].  This is
		   like "slider position", so we convert from "slider position" to gain
		   to have the correct unit here. */
		largestep = position_to_gain (dB_coeff_step(upper));
		step      = position_to_gain (largestep / 10.0);
		smallstep = step;
	} else if (logarithmic) {
		/* ignore logscale rangesteps. {small|large}steps are used with the spinbox.
		 * gtk-spinbox shows the internal (not interface) value and up/down
		 * arrows linearly increase.
		 * The AutomationController uses internal_to_interface():
		 *   ui-step [0..1] -> log (1 + largestep / lower) / log (upper / lower)
		 * so we use a step that's a multiple of "lower" for the interface step:
		 *   log (1 + x) / log (upper / lower)
		 */
		smallstep = step = lower / 11;
		largestep = lower / 3;
		/* NOTE: the actual value does use rangesteps via
		 * logscale_to_position_with_steps(), position_to_logscale_with_steps()
		 * when it is converted.
		 */
	} else if (rangesteps > 1) {
		const float delta = upper - lower;
		if (integer_step) {
			smallstep = step = 1.0;
			largestep = std::max(1.f, rintf (delta / (rangesteps - 1.f)));
		} else {
			step = smallstep = delta / (rangesteps - 1.f);
			largestep = std::min ((delta / 4.0f), 10.f * smallstep);
		}
	} else {
		const float delta = upper - lower;
		/* 30 steps between min/max (300 for fine-grained) */
		if (integer_step) {
			smallstep = step = 1.0;
			largestep = std::max(1.f, rintf (delta / 30.f));
		} else {
			step      = smallstep = (delta / 300.0f);
			largestep = (delta / 30.0f);
		}
	}
}

std::string
ParameterDescriptor::midi_note_name (const uint8_t b, bool translate)
{
	char buf[16];
	if (b > 127) {
		snprintf(buf, sizeof(buf), "%d", b);
		return buf;
	}

	static const char* en_notes[] = {
		"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
	};

	static const char* notes[] = {
		S_("Note|C"),
		S_("Note|C#"),
		S_("Note|D"),
		S_("Note|D#"),
		S_("Note|E"),
		S_("Note|F"),
		S_("Note|F#"),
		S_("Note|G"),
		S_("Note|G#"),
		S_("Note|A"),
		S_("Note|A#"),
		S_("Note|B")
	};

	/* MIDI note 0 is in octave -1 (in scientific pitch notation) */
	const int octave = b / 12 - 1;
	const size_t p = b % 12;
	snprintf (buf, sizeof (buf), "%s%d", translate ? notes[p] : en_notes[p], octave);
	return buf;
}

std::string
ParameterDescriptor::normalize_note_name(const std::string& name)
{
	// Remove whitespaces and convert to lower case for a more resilient parser
	return boost::to_lower_copy(boost::erase_all_copy(name, " "));
};

ParameterDescriptor::NameNumMap
ParameterDescriptor::build_midi_name2num()
{
	NameNumMap name2num;
	for (uint8_t num = 0; num < 128; num++) {
		name2num[normalize_note_name(midi_note_name(num))] = num;
	}
	return name2num;
}

uint8_t
ParameterDescriptor::midi_note_num (const std::string& name)
{
	static NameNumMap name2num = build_midi_name2num();

	uint8_t num = -1; // -1 (or 255) is returned in case of failure

	NameNumMap::const_iterator it = name2num.find(normalize_note_name(name));
	if (it != name2num.end())
		num = it->second;

	return num;
}

float
ParameterDescriptor::to_interface (float val, bool rotary) const
{
	val = std::min (upper, std::max (lower, val));
	switch(type) {
		case GainAutomation:
			/* fallthrough */
		case BusSendLevel:
			/* fallthrough */
		case InsertReturnLevel:
			/* fallthrough */
		case EnvelopeAutomation:
			val = gain_to_slider_position_with_max (val, upper);
			break;
		case TrimAutomation:
			/* fallthrough */
		case MainOutVolume:
			{
				const float lower_db = accurate_coefficient_to_dB (lower);
				const float range_db = accurate_coefficient_to_dB (upper) - lower_db;
				val = (accurate_coefficient_to_dB (val) - lower_db) / range_db;
			}
			break;
		case PanAzimuthAutomation:
			if (!rotary) {
				val = 1.0 - val;
			}
			break;
		case PanElevationAutomation:
			// val = val;
			break;
		case PanWidthAutomation:
			val = .5f + val * .5f;
			break;
		default:
			if (logarithmic) {
				if (rangesteps > 1) {
					val = logscale_to_position_with_steps (val, lower, upper, rangesteps);
				} else {
					val = logscale_to_position (val, lower, upper);
				}
			} else if (toggled) {
				return (val - lower) / (upper - lower) >= 0.5f ? 1.f : 0.f;
			} else if (integer_step) {
				/* evenly-divide steps. lower,upper inclusive
				 * e.g. 5 integers 0,1,2,3,4 are mapped to a fader
				 * [0.0 ... 0.2 | 0.2 ... 0.4 | 0.4 ... 0.6 | 0.6 ... 0.8 | 0.8 ... 1.0]
				 *       0             1             2             3             4
				 *      0.1           0.3           0.5           0.7           0.9
				 */
				val = (val + .5f - lower) / (1.f + upper - lower);
			} else {
				val = (val - lower) / (upper - lower);
			}
			break;
	}
	val = std::max (0.f, std::min (1.f, val));
	return val;
}

float
ParameterDescriptor::from_interface (float val, bool rotary) const
{
	val = std::max (0.f, std::min (1.f, val));

	switch(type) {
		case GainAutomation:
		case EnvelopeAutomation:
		case BusSendLevel:
		case InsertReturnLevel:
			val = slider_position_to_gain_with_max (val, upper);
			break;
		case TrimAutomation:
			{
				const float lower_db = accurate_coefficient_to_dB (lower);
				const float range_db = accurate_coefficient_to_dB (upper) - lower_db;
				val = dB_to_coefficient (lower_db + val * range_db);
			}
			break;
		case PanAzimuthAutomation:
			if (!rotary) {
				val = 1.0 - val;
			}
			break;
		case PanElevationAutomation:
			 // val = val;
			break;
		case PanWidthAutomation:
			val = 2.f * val - 1.f;
			break;
		default:
			if (logarithmic) {
				assert (!toggled && !integer_step); // update_steps() should prevent that.
				if (rangesteps > 1) {
					val = position_to_logscale_with_steps (val, lower, upper, rangesteps);
				} else {
					val = position_to_logscale (val, lower, upper);
				}
			} else if (toggled) {
				val = val >= 0.5 ? upper : lower;
			} else if (integer_step) {
				/* upper and lower are inclusive. use evenly-divided steps
				 * e.g. 5 integers 0,1,2,3,4 are mapped to a fader
				 * [0.0 .. 0.2 | 0.2 .. 0.4 | 0.4 .. 0.6 | 0.6 .. 0.8 | 0.8 .. 1.0]
				 */
				val = floor (lower + val * (1.f + upper - lower));
			} else if (rangesteps > 1) {
				/* similar to above, but for float controls */
				val = round (val * (rangesteps - 1.f)) / (rangesteps - 1.f); // XXX
				val = val * (upper - lower) + lower;
			} else {
				val = val * (upper - lower) + lower;
			}
			break;
	}
	val = std::min (upper, std::max (lower, val));
	return val;
}

bool
ParameterDescriptor::is_linear () const
{
	if (logarithmic) {
		return false;
	}
	switch(type) {
		case GainAutomation:
		case EnvelopeAutomation:
		case BusSendLevel:
		case InsertReturnLevel:
			return false;
		default:
			break;
	}
	return true;
}

float
ParameterDescriptor::compute_delta (float from, float to) const
{
	if (is_linear ()) {
		return to - from;
	}
	if (from == 0) {
		return 0;
	}
	return to / from;
}

float
ParameterDescriptor::apply_delta (float val, float delta) const
{
	if (is_linear ()) {
		return val + delta;
	} else {
		return val * delta;
	}
}

float
ParameterDescriptor::step_enum (float val, bool prev) const
{
	if (!enumeration) {
		return val;
	}
	assert (scale_points && !scale_points->empty ());
	float rv = scale_points->begin()->second;
	float delta = fabsf (val - rv);
	std::vector<float> avail;

	for (ScalePoints::const_iterator i = scale_points->begin (); i != scale_points->end (); ++i) {
		float s = i->second;
		avail.push_back (s);
		if (fabsf (val - s) < delta) {
			rv = s;
			delta = fabsf (val - s);
		}
	}
	/* ScalePoints map is sorted by text string */
	std::sort (avail.begin (), avail.end ());
	std::vector<float>::const_iterator it = std::find (avail.begin (), avail.end (), rv);
	assert (it != avail.end());

	if (prev) {
		if (it == avail.begin()) {
			return rv;
		}
		return *(--it);
	} else {
		if (++it == avail.end()) {
			return rv;
		}
		return *(it);
	}
}

} // namespace ARDOUR
