/*
 * Copyright (C) 2013-2019 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2015-2017 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.
 */

#pragma once

#include <atomic>
#include <cstdint>
#include <exception>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include "pbd/natsort.h"
#include "pbd/rcu.h"
#include "pbd/ringbuffer.h"

#include "ardour/chan_count.h"
#include "ardour/midiport_manager.h"
#include "ardour/monitor_port.h"
#include "ardour/port.h"

namespace ARDOUR {

class PortEngine;
class AudioBackend;
class Session;

class CircularSampleBuffer;
class CircularEventBuffer;

class LIBARDOUR_API PortManager
{
public:
	struct DPM {
		DPM ()
		{
			reset ();
		}
		void reset ()
		{
			level = 0;
			peak  = 0;
		}
		Sample level;
		Sample peak;
	};

	struct MPM {
		MPM ()
		{
			reset ();
		}
		void reset ()
		{
			memset (chn_active, 0, sizeof (float) * 17);
		}
		bool active (int chn) const {
			if (chn < 0 || chn > 16) {
				return false;
			}
			return chn_active[chn] > 0.1;
		}
		/* 0..15: MIDI Channel Event, 16: System Common Message */
		float chn_active[17];
	};

	struct SortByPortName {
		bool operator() (std::string const& a, std::string const& b) const {
			return PBD::naturally_less (a.c_str (), b.c_str ());
		}
	};

	typedef std::map<std::string, std::shared_ptr<Port>, SortByPortName> Ports;
	typedef std::list<std::shared_ptr<Port> >                            PortList;

	typedef std::shared_ptr<CircularSampleBuffer> AudioPortScope;
	typedef std::shared_ptr<CircularEventBuffer>  MIDIPortMonitor;
	typedef std::shared_ptr<DPM>                  AudioPortMeter;
	typedef std::shared_ptr<MPM>                  MIDIPortMeter;

	struct AudioInputPort {
		AudioInputPort (samplecnt_t);
		AudioPortScope scope;
		AudioPortMeter meter;
		void apply_falloff (pframes_t, samplecnt_t sr, bool reset = false);
		void silence (pframes_t);
		void process (Sample const*, pframes_t, bool reset = false);
	};

	struct MIDIInputPort {
		MIDIInputPort (samplecnt_t);
		MIDIPortMonitor monitor;
		MIDIPortMeter   meter;
		void apply_falloff (pframes_t, samplecnt_t sr, bool reset = false);
		void process_event (uint8_t const*, size_t);
	};

	typedef std::map<std::string, AudioInputPort, SortByPortName> AudioInputPorts;
	typedef std::map<std::string, MIDIInputPort, SortByPortName>  MIDIInputPorts;

	PortManager ();
	virtual ~PortManager () {}

	PortEngine& port_engine ();

	static void falloff_cache_calc (pframes_t, samplecnt_t);

	uint32_t    port_name_size () const;
	std::string my_name () const;

	size_t total_port_count () const { return _ports.reader ()->size (); }
	size_t session_port_count () const;

#ifndef NDEBUG
	void list_cycle_ports () const;
	void list_all_ports () const;
#endif

	/* Port registration */

	std::shared_ptr<Port> register_input_port (DataType, const std::string& portname, bool async = false, PortFlags extra_flags = PortFlags (0));
	std::shared_ptr<Port> register_output_port (DataType, const std::string& portname, bool async = false, PortFlags extra_flags = PortFlags (0));
	int                     unregister_port (std::shared_ptr<Port>);

	/* Port connectivity */

	int connect (const std::string& source, const std::string& destination);
	int disconnect (const std::string& source, const std::string& destination);
	int disconnect (std::shared_ptr<Port>);
	int disconnect (std::string const&);
	int reestablish_ports ();
	int reconnect_ports (Session* s = nullptr);

	bool connected (const std::string&);
	bool physically_connected (const std::string&);
	int  get_connections (const std::string&, std::vector<std::string>&, bool process_context_safe = true);

	/* Naming */

	std::shared_ptr<Port> get_port_by_name (const std::string&);
	void                    port_renamed (const std::string&, const std::string&);
	std::string             make_port_name_relative (const std::string& name) const;
	std::string             make_port_name_non_relative (const std::string& name) const;
	std::string             get_pretty_name_by_name (const std::string& portname) const;
	std::string             get_hardware_port_name_by_name (const std::string& portname) const;
	std::string             short_port_name_from_port_name (std::string const& full_name) const;
	bool                    port_is_mine (const std::string& fullname) const;

	static bool port_is_virtual_piano (std::string const&);
	static bool port_is_control_only (std::string const&);
	static bool port_is_physical_input_monitor_enable (std::string const&);

	/* other Port management */

	bool      port_is_physical (const std::string&) const;
	void      get_physical_outputs (DataType      type, std::vector<std::string>&,
	                                MidiPortFlags include = MidiPortFlags (0),
	                                MidiPortFlags exclude = MidiPortFlags (0));
	void      get_physical_inputs (DataType      type, std::vector<std::string>&,
	                               MidiPortFlags include = MidiPortFlags (0),
	                               MidiPortFlags exclude = MidiPortFlags (0));

	ChanCount n_physical_outputs () const;
	ChanCount n_physical_inputs () const;

	int get_ports (const std::string& port_name_pattern, DataType type, PortFlags flags, std::vector<std::string>&);
	int get_ports (DataType, PortList&);

	void set_port_pretty_name (std::string const&, std::string const&);

	void remove_session_ports ();

	/** reset port-buffers. e.g. after freewheeling */
	void reinit (bool with_ratio = false);

	void clear_pending_port_deletions ();

	virtual void add_pending_port_deletion (Port*) = 0;

	PBD::RingBuffer<Port*>& port_deletions_pending ()
	{
		return _port_deletions_pending;
	}

	bool check_for_ambiguous_latency (bool log = false) const;

	/* per-Port monitoring */

	bool can_request_input_monitoring () const;
	void request_input_monitoring (const std::string&, bool) const;
	void ensure_input_monitoring (const std::string&, bool) const;

	class PortRegistrationFailure : public std::exception
	{
	public:
		PortRegistrationFailure (std::string const& why = "")
			: reason (why) {}

		~PortRegistrationFailure () throw () {}

		const char* what () const throw ()
		{
			return reason.c_str ();
		}

	private:
		std::string reason;
	};

	/* the port engine will invoke these callbacks when the time is right */

	void registration_callback ();
	int  graph_order_callback ();
	void connect_callback (const std::string&, const std::string&, bool connection);

	bool port_remove_in_progress () const
	{
		return _port_remove_in_progress;
	}

	MidiPortFlags midi_port_metadata (std::string const&);

	void get_configurable_midi_ports (std::vector<std::string>&, bool for_input);
	void get_midi_selection_ports (std::vector<std::string>&);
	void add_midi_port_flags (std::string const&, MidiPortFlags);
	void remove_midi_port_flags (std::string const&, MidiPortFlags);

	/** Emitted if the list of ports to be used for MIDI selection tracking changes */
	PBD::Signal<void()> MidiSelectionPortsChanged;
	/** Emitted if anything other than the selection property for a MIDI port changes */
	PBD::Signal<void()> MidiPortInfoChanged;
	/** Emitted if pretty-name of a port changed */
	PBD::Signal<void(std::string)> PortPrettyNameChanged;

	/** Emitted if the backend notifies us of a graph order event */
	PBD::Signal<void()> GraphReordered;

	/** Emitted if a Port is registered or unregistered */
	PBD::Signal<void()> PortRegisteredOrUnregistered;

	/** Emitted if a Port is connected or disconnected.
	 *  The Port parameters are the ports being connected / disconnected, or 0 if they are not known to Ardour.
	 *  The std::string parameters are the (long) port names.
	 *  The bool parameter is true if ports were connected, or false for disconnected.
	 */
	PBD::Signal<void(std::weak_ptr<Port>, std::string, std::weak_ptr<Port>, std::string, bool)> PortConnectedOrDisconnected;

	PBD::Signal<void(DataType, std::vector<std::string>, bool)> PhysInputChanged;

	/* Input port meters and monitors */
	void reset_input_meters ();

	AudioInputPorts audio_input_ports () const;
	MIDIInputPorts  midi_input_ports () const;

	MonitorPort& monitor_port () {
		return _monitor_port;
	}

protected:
	std::shared_ptr<AudioBackend> _backend;

	SerializedRCUManager<Ports> _ports;

	bool                   _port_remove_in_progress;
	PBD::RingBuffer<Port*> _port_deletions_pending;

	std::shared_ptr<Port> register_port (DataType type, const std::string& portname, bool input, bool async = false, PortFlags extra_flags = PortFlags (0));
	void                    port_registration_failure (const std::string& portname);

	/** List of ports to be used between \ref cycle_start() and \ref cycle_end() */
	std::shared_ptr<Ports const> _cycle_ports;

	void silence (pframes_t nframes, Session* s = 0);
	void silence_outputs (pframes_t nframes);
	void check_monitoring ();
	/** Signal the start of an audio cycle.
	 * This MUST be called before any reading/writing for this cycle.
	 * Realtime safe.
	 */
	void cycle_start (pframes_t nframes, Session* s = 0);

	/** Signal the end of an audio cycle.
	 * This signifies that the cycle began with @ref cycle_start has ended.
	 * This MUST be called at the end of each cycle.
	 * Realtime safe.
	 */
	void cycle_end (pframes_t nframes, Session* s = 0);

	void cycle_end_fade_out (gain_t, gain_t, pframes_t, Session* s = 0);

	static std::string port_info_file ();
	static std::string midi_port_info_file ();

	void filter_midi_ports (std::vector<std::string>&, MidiPortFlags, MidiPortFlags);

	void set_port_buffer_sizes (pframes_t);

private:
	void run_input_meters (pframes_t, samplecnt_t);
	void set_pretty_names (std::vector<std::string> const&, DataType, bool);
	void fill_midi_port_info_locked ();
	void load_port_info ();
	void save_port_info ();
	void update_input_ports (bool);

	MonitorPort _monitor_port;

	struct PortID {
		PortID (std::shared_ptr<AudioBackend>, DataType, bool, std::string const&);
		PortID (XMLNode const&, bool old_midi_format = false);

		std::string backend;
		std::string device_name;
		std::string port_name;
		DataType    data_type;
		bool        input;

		XMLNode& state () const;

		bool operator< (PortID const& o) const {
			if (backend != o.backend) {
				return backend < o.backend;
			}
			if (device_name != o.device_name) {
				return device_name < o.device_name;
			}
			if (port_name != o.port_name) {
				return PBD::naturally_less (port_name.c_str (), o.port_name.c_str ());
			}
			if (input != o.input) {
				return input;
			}
			return (uint32_t) data_type < (uint32_t) o.data_type;
		}

		bool operator== (PortID const& o) const {
			if (backend != o.backend) {
				return false;
			}
			if (device_name != o.device_name) {
				return false;
			}
			if (port_name != o.port_name) {
				return false;
			}
			if (input != o.input) {
				return false;
			}
			if (data_type != o.data_type) {
				return false;
			}
			return true;
		}
	};

	struct PortMetaData {
		PortMetaData () : properties (MidiPortFlags (0)) {}
		PortMetaData (XMLNode const&);

		std::string pretty_name;
		MidiPortFlags properties;
	};

	typedef std::map<PortID, PortMetaData> PortInfo;

	mutable PBD::Mutex _port_info_mutex;
	PortInfo                     _port_info;
	bool                         _midi_info_dirty;

	SerializedRCUManager<AudioInputPorts> _audio_input_ports;
	SerializedRCUManager<MIDIInputPorts>  _midi_input_ports;
	std::atomic<int>                     _reset_meters;
};

} // namespace ARDOUR

