#pragma once
#include <vector>
#include <set>

#include <jansson.h>

#include <common.hpp>
#include <context.hpp>


namespace rack {
/** Abstraction for all MIDI drivers in Rack */
namespace midi {


struct Message {
	/** Initialized to 3 empty bytes. */
	std::vector<uint8_t> bytes;
	/** The Engine frame timestamp of the Message.
	For output messages, the frame when the message was generated.
	For input messages, the frame when it is intended to be processed.
	-1 for undefined, to be sent or processed immediately.
	*/
	int64_t frame = -1;

	Message() : bytes(3) {}

	int getSize() const {
		return bytes.size();
	}
	void setSize(int size) {
		bytes.resize(size);
	}

	uint8_t getChannel() const {
		if (bytes.size() < 1)
			return 0;
		return bytes[0] & 0xf;
	}
	void setChannel(uint8_t channel) {
		if (bytes.size() < 1)
			return;
		bytes[0] = (bytes[0] & 0xf0) | (channel & 0xf);
	}

	uint8_t getStatus() const {
		if (bytes.size() < 1)
			return 0;
		return bytes[0] >> 4;
	}
	void setStatus(uint8_t status) {
		if (bytes.size() < 1)
			return;
		bytes[0] = (bytes[0] & 0xf) | (status << 4);
	}

	uint8_t getNote() const {
		if (bytes.size() < 2)
			return 0;
		return bytes[1];
	}
	void setNote(uint8_t note) {
		if (bytes.size() < 2)
			return;
		bytes[1] = note & 0x7f;
	}

	uint8_t getValue() const {
		if (bytes.size() < 3)
			return 0;
		return bytes[2];
	}
	void setValue(uint8_t value) {
		if (bytes.size() < 3)
			return;
		bytes[2] = value & 0x7f;
	}

	std::string toString() const;

	int64_t getFrame() const {
		return frame;
	}

	void setFrame(int64_t frame) {
		this->frame = frame;
	}
};

////////////////////
// Driver
////////////////////

struct InputDevice;
struct Input;
struct OutputDevice;
struct Output;

/** Wraps a MIDI driver API containing any number of MIDI devices.
*/
struct Driver {
	virtual ~Driver() {}
	/** Returns the name of the driver. E.g. "ALSA". */
	virtual std::string getName() {
		return "";
	}
	/** Returns a list of all input device IDs that can be subscribed to. */
	virtual std::vector<int> getInputDeviceIds() {
		return {};
	}
	/** Returns the default device to use when the driver is selected, or -1 for none. */
	virtual int getDefaultInputDeviceId() {
		return -1;
	}
	/** Returns the name of an input device without obtaining it. */
	virtual std::string getInputDeviceName(int deviceId) {
		return "";
	}
	/** Adds the given port as a reference holder of a device and returns the it.
	Creates the Device if no ports are subscribed before calling.
	*/
	virtual InputDevice* subscribeInput(int deviceId, Input* input) {
		return NULL;
	}
	/** Removes the give port as a reference holder of a device.
	Deletes the Device if no ports are subscribed after calling.
	*/
	virtual void unsubscribeInput(int deviceId, Input* input) {}

	// The following behave identically as the above methods except for outputs.

	virtual std::vector<int> getOutputDeviceIds() {
		return {};
	}
	virtual int getDefaultOutputDeviceId() {
		return -1;
	}
	virtual std::string getOutputDeviceName(int deviceId) {
		return "";
	}
	virtual OutputDevice* subscribeOutput(int deviceId, Output* output) {
		return NULL;
	}
	virtual void unsubscribeOutput(int deviceId, Output* output) {}
};

////////////////////
// Device
////////////////////

/** A single MIDI device of a driver API.

Modules and the UI should not interact with this API directly. Use Port instead.

Methods throw `rack::Exception` if the driver API has an exception.
*/
struct Device {
	virtual ~Device() {}
	virtual std::string getName() {
		return "";
	}
};

struct InputDevice : Device {
	std::set<Input*> subscribed;
	/** Not public. Use Driver::subscribeInput(). */
	void subscribe(Input* input);
	/** Not public. Use Driver::unsubscribeInput(). */
	void unsubscribe(Input* input);
	/** Called when a MIDI message is received from the device. */
	void onMessage(const Message& message);
};

struct OutputDevice : Device {
	std::set<Output*> subscribed;
	/** Not public. Use Driver::subscribeOutput(). */
	void subscribe(Output* output);
	/** Not public. Use Driver::unsubscribeOutput(). */
	void unsubscribe(Output* output);
	/** Sends a MIDI message to the device. */
	virtual void sendMessage(const Message& message) {}
};

////////////////////
// Port
////////////////////

/** A handle to a Device, typically owned by modules to have shared access to a single Device.

All Port methods safely wrap Drivers methods.
That is, if the active Device throws a `rack::Exception`, it is caught and logged inside all Port methods, so they do not throw exceptions.

Use Input or Output subclasses in your module, not Port directly.
*/
struct Port {
	/** For MIDI output, the channel to automatically set outbound messages.
	If -1, the channel is not overwritten and must be set by MIDI generator.

	For MIDI input, messages will be filtered by the channel.
	If -1, all MIDI channels pass through.
	*/
	int channel = -1;

	// private
	int driverId = -1;
	int deviceId = -1;
	/** Not owned */
	Driver* driver = NULL;
	Device* device = NULL;
	Context* context;

	Port();
	virtual ~Port();

	Driver* getDriver();
	int getDriverId();
	void setDriverId(int driverId);

	Device* getDevice();
	virtual std::vector<int> getDeviceIds() = 0;
	virtual int getDefaultDeviceId() = 0;
	int getDeviceId();
	virtual void setDeviceId(int deviceId) = 0;
	virtual std::string getDeviceName(int deviceId) = 0;

	virtual std::vector<int> getChannels() = 0;
	int getChannel();
	void setChannel(int channel);
	std::string getChannelName(int channel);

	json_t* toJson();
	void fromJson(json_t* rootJ);
};


struct Input : Port {
	/** Not owned */
	InputDevice* inputDevice = NULL;

	Input();
	~Input();
	void reset();

	std::vector<int> getDeviceIds() override;
	int getDefaultDeviceId() override;
	void setDeviceId(int deviceId) override;
	std::string getDeviceName(int deviceId) override;

	std::vector<int> getChannels() override;

	virtual void onMessage(const Message& message) {}
};


/** An Input port that stores incoming MIDI messages and releases them when ready according to their frame timestamp.
*/
struct InputQueue : Input {
	struct Internal;
	Internal* internal;

	InputQueue();
	~InputQueue();
	void onMessage(const Message& message) override;
	/** Pops and returns the next message (by setting `messageOut`) if its frame timestamp is `maxFrame` or earlier.
	Returns whether a message was returned.
	*/
	bool tryPop(Message* messageOut, int64_t maxFrame);
	size_t size();
};


struct Output : Port {
	/** Not owned */
	OutputDevice* outputDevice = NULL;

	Output();
	~Output();
	void reset();

	std::vector<int> getDeviceIds() override;
	int getDefaultDeviceId() override;
	void setDeviceId(int deviceId) override;
	std::string getDeviceName(int deviceId) override;

	std::vector<int> getChannels() override;

	void sendMessage(const Message& message);
};


PRIVATE void init();
PRIVATE void destroy();
/** Registers a new MIDI driver. Takes pointer ownership. */
void addDriver(int driverId, Driver* driver);
std::vector<int> getDriverIds();
Driver* getDriver(int driverId);


} // namespace midi
} // namespace rack
