/*
 * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
 * Copyright (C) 2015-2020 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.
 */

#ifndef _libardour_port_engine_shared_h_
#define _libardour_port_engine_shared_h_

#include <atomic>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>


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

#include "ardour/libardour_visibility.h"
#include "ardour/port_engine.h"
#include "ardour/types.h"

namespace ARDOUR {

class PortEngineSharedImpl;
class PortManager;

class BackendPort;

typedef std::shared_ptr<BackendPort> BackendPortPtr;
typedef std::shared_ptr<BackendPort> const & BackendPortHandle;

class LIBARDOUR_API BackendPort : public ProtoPort
{
  protected:
	BackendPort (PortEngineSharedImpl& b, const std::string&, PortFlags);

  public:
	virtual ~BackendPort ();

	const std::string& name ()        const { return _name; }
	const std::string& pretty_name () const { return _pretty_name; }
	const std::string& hw_port_name () const { return _hw_port_name; }

	int set_name (const std::string& name) {
		_name = name;
		return 0;
	}

	/* called from PortEngineSharedImpl */
	int set_pretty_name (const std::string& name) {
		_pretty_name = name;
		return 0;
	}

	/* called from backends only */
	int set_hw_port_name (const std::string& name) {
		_hw_port_name = name;
		return 0;
	}

	virtual DataType type () const = 0;

	PortFlags flags ()   const { return _flags; }
	bool is_input ()     const { return flags () & IsInput; }
	bool is_output ()    const { return flags () & IsOutput; }
	bool is_physical ()  const { return flags () & IsPhysical; }
	bool is_terminal ()  const { return flags () & IsTerminal; }
	bool is_connected () const { return _connections.size () != 0; }

	bool is_connected (BackendPortHandle port) const;
	bool is_physically_connected () const;

	const std::set<BackendPortPtr>& get_connections () const {
		return _connections;
	}

	int  connect (BackendPortHandle port, BackendPortHandle self);
	int  disconnect (BackendPortHandle port, BackendPortHandle self);
	void disconnect_all (BackendPortHandle self);

	virtual void* get_buffer (pframes_t nframes) = 0;

	const LatencyRange latency_range (bool for_playback) const
	{
		return for_playback ? _playback_latency_range : _capture_latency_range;
	}

	void set_latency_range (const LatencyRange& latency_range, bool for_playback);

	void update_connected_latency (bool for_playback);

protected:
	PortEngineSharedImpl& _backend;

private:
	std::string            _name;
	std::string            _pretty_name;
	std::string            _hw_port_name;
	const PortFlags        _flags;
	LatencyRange           _capture_latency_range;
	LatencyRange           _playback_latency_range;
	std::set<BackendPortPtr> _connections;

	void store_connection (BackendPortHandle);
	void remove_connection (BackendPortHandle);

}; // class BackendPort

class LIBARDOUR_API BackendMIDIEvent
{
public:
	virtual ~BackendMIDIEvent () {}
	virtual size_t size () const = 0;
	virtual pframes_t timestamp () const = 0;
	virtual const uint8_t* data () const = 0;
	bool operator< (const BackendMIDIEvent &other) const;
};

class LIBARDOUR_API PortEngineSharedImpl
{
public:
	PortEngineSharedImpl (PortManager& mgr, std::string const& instance_name);
	virtual ~PortEngineSharedImpl ();

	/* Discovering physical ports */

	bool      port_is_physical (PortEngine::PortHandle) const;
	void      get_physical_outputs (DataType type, std::vector<std::string>&);
	void      get_physical_inputs (DataType type, std::vector<std::string>&);
	ChanCount n_physical_outputs () const;
	ChanCount n_physical_inputs () const;

	uint32_t port_name_size () const;

	int                    set_port_name (PortEngine::PortHandle, const std::string&);
	std::string            get_port_name (PortEngine::PortHandle) const;
	PortFlags              get_port_flags (PortEngine::PortHandle) const;
	PortEngine::PortPtr    get_port_by_name (const std::string&) const;

	int get_port_property (PortEngine::PortHandle, const std::string& key, std::string& value, std::string& type) const;
	int set_port_property (PortEngine::PortHandle, const std::string& key, const std::string& value, const std::string& type);

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

	DataType port_data_type (PortEngine::PortHandle) const;

	PortEngine::PortPtr register_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags);
	virtual void        unregister_port (PortEngine::PortHandle);

	int connect (const std::string& src, const std::string& dst);
	int disconnect (const std::string& src, const std::string& dst);
	int connect (PortEngine::PortHandle, const std::string&);
	int disconnect (PortEngine::PortHandle, const std::string&);
	int disconnect_all (PortEngine::PortHandle);

	bool connected (PortEngine::PortHandle, bool process_callback_safe);
	bool connected_to (PortEngine::PortHandle, const std::string&, bool process_callback_safe);
	bool physically_connected (PortEngine::PortHandle, bool process_callback_safe);
	int  get_connections (PortEngine::PortHandle, std::vector<std::string>&, bool process_callback_safe);

protected:
	friend class BackendPort;
	std::string _instance_name;

	std::vector<BackendPortPtr> _system_inputs;
	std::vector<BackendPortPtr> _system_outputs;
	std::vector<BackendPortPtr> _system_midi_in;
	std::vector<BackendPortPtr> _system_midi_out;

	struct PortConnectData {
		std::string a;
		std::string b;
		bool c;

		PortConnectData (const std::string& a, const std::string& b, bool c)
			: a (a) , b (b) , c (c) {}
	};

	std::vector<PortConnectData *> _port_connection_queue;
	pthread_mutex_t _port_callback_mutex;

	std::atomic<int> _port_change_flag; /* atomic */

	void port_connect_callback (const std::string& a, const std::string& b, bool conn) {
		pthread_mutex_lock (&_port_callback_mutex);
		_port_connection_queue.push_back(new PortConnectData(a, b, conn));
		pthread_mutex_unlock (&_port_callback_mutex);
	}

	void port_connect_add_remove_callback () {
		_port_change_flag.store (1);
	}

	virtual void update_system_port_latencies ();

	void clear_ports ();

	BackendPortPtr add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags);
	void                unregister_ports (bool system_only = false);

	struct SortByPortName {
		bool operator() (BackendPortHandle lhs, BackendPortHandle rhs) const {
			return PBD::naturally_less (lhs->name ().c_str (), rhs->name ().c_str ());
		}
	};

	typedef std::map<std::string, BackendPortPtr>    PortMap;       // fast-lookup by name
	typedef std::set<BackendPortPtr, SortByPortName> PortIndex;     // name-based
	typedef std::set<BackendPortPtr>                 PortRegistry;  // std::less<>, safe during rename

	SerializedRCUManager<PortMap>      _portmap;
	SerializedRCUManager<PortIndex>    _ports;
	SerializedRCUManager<PortRegistry> _portregistry;

	bool valid_port (BackendPortHandle port) const {
		std::shared_ptr<PortRegistry const> p = _portregistry.reader ();
		return p->find (port) != p->end ();
	}

	BackendPortPtr find_port (const std::string& port_name) const {
		std::shared_ptr<PortMap const> p  = _portmap.reader ();
		PortMap::const_iterator        it = p->find (port_name);
		if (it == p->end ()) {
			return BackendPortPtr();
		}
		return (*it).second;
	}

	virtual BackendPort* port_factory (std::string const& name, ARDOUR::DataType dt, ARDOUR::PortFlags flags) = 0;

#ifndef NDEBUG
	void list_ports () const;
#endif
};

} /* namespace ARDOUR */

#endif
