//! \~\file piusb.h
//! \~\ingroup USB
//! \~\brief
//! \~english USB input/output device declarations
//! \~russian Объявления USB-устройства ввода/вывода
/*
    PIP - Platform Independent Primitives
    USB, based on libusb
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
//! \defgroup USB USB
//! \~\brief
//! \~english USB support via libusb
//! \~russian Поддержка USB с помощью libusb
//!
//! \~\details
//! \~english \section cmake_module_USB Building with CMake
//! \~russian \section cmake_module_USB Сборка с использованием CMake
//!
//! \~\code
//! find_package(PIP REQUIRED)
//! target_link_libraries([target] PIP::USB)
//! \endcode
//!
//! \~english \par Common
//! \~russian \par Общее
//!
//! \~english
//! This module declares raw USB input/output devices built on libusb.
//!
//! \~russian
//! Модуль объявляет USB-устройства ввода/вывода для сырого обмена на базе libusb.
//!
//! \~\authors
//! \~english
//! Ivan Pelipenko peri4ko@yandex.ru;
//! Andrey Bychkov work.a.b@yandex.ru;
//! \~russian
//! Иван Пелипенко peri4ko@yandex.ru;
//! Андрей Бычков work.a.b@yandex.ru;
//!

#ifndef PIUSB_H
#define PIUSB_H

#include "piiodevice.h"
#include "pip_usb_export.h"

struct usb_dev_handle;

//! \~\ingroup USB
//! \~\brief
//! \~english USB implementation of \a PIIODevice.
//! \~russian USB-реализация \a PIIODevice.
//! \~\details
//! \~english The PIUSB class provides functionality for working with USB devices through the libusb library.
//! \~russian Класс PIUSB предоставляет функциональность для работы с USB-устройствами через библиотеку libusb.
class PIP_USB_EXPORT PIUSB: public PIIODevice {
	PIIODEVICE(PIUSB, "usb");

public:
	//! \~english Constructs USB device wrapper for vendor ID "vid" and product ID "pid".
	//! \~russian Создает обертку USB-устройства для vendor ID "vid" и product ID "pid".
	explicit PIUSB(ushort vid = 0, ushort pid = 0);

	//! \~english Destroys the USB device wrapper.
	//! \~russian Уничтожает обертку USB-устройства.
	virtual ~PIUSB();

	//! \~\ingroup USB
	//! \~\brief
	//! \~english Parsed USB endpoint descriptor.
	//! \~russian Разобранный дескриптор USB endpoint.
	struct PIP_USB_EXPORT Endpoint {
		//! \~english Constructs endpoint descriptor and parses cached properties.
		//! \~russian Создает дескриптор endpoint и разбирает кэшируемые свойства.
		Endpoint(uchar a = 0, uchar at = 0, ushort mps = 0) {
			address         = a;
			attributes      = at;
			max_packet_size = mps;
			parse();
		}

		//! \~english Transfer direction encoded in endpoint address.
		//! \~russian Направление передачи, закодированное в адресе endpoint.
		enum Direction {
			Write = 0 /** \~english Host-to-device endpoint \~russian Endpoint от хоста к устройству */,
			Read  = 1 /** \~english Device-to-host endpoint \~russian Endpoint от устройства к хосту */
		};

		//! \~english USB transfer type encoded in endpoint attributes.
		//! \~russian Тип USB-передачи, закодированный в атрибутах endpoint.
		enum TransferType {
			Control     = 0 /** \~english Control endpoint \~russian Control-endpoint */,
			Isochronous = 1 /** \~english Isochronous endpoint \~russian Isochronous-endpoint */,
			Bulk        = 2 /** \~english Bulk endpoint \~russian Bulk-endpoint */,
			Interrupt   = 3 /** \~english Interrupt endpoint \~russian Interrupt-endpoint */
		};

		//! \~english Synchronisation mode for isochronous endpoints.
		//! \~russian Режим синхронизации для isochronous-endpoint.
		enum SynchronisationType {
			NoSynchonisation = 0 /** \~english No synchronisation \~russian Без синхронизации */,
			Asynchronous     = 2 /** \~english Asynchronous synchronisation \~russian Асинхронная синхронизация */,
			Adaptive         = 1 /** \~english Adaptive synchronisation \~russian Адаптивная синхронизация */,
			Synchronous      = 3 /** \~english Synchronous synchronisation \~russian Синхронная синхронизация */
		};

		//! \~english Usage mode for isochronous endpoints.
		//! \~russian Режим использования для isochronous-endpoint.
		enum UsageType {
			DataEndpoint     = 0 /** \~english Data endpoint \~russian Endpoint данных */,
			FeedbackEndpoint = 2 /** \~english Feedback endpoint \~russian Endpoint обратной связи */,
			ExplicitFeedbackDataEndpoint =
				1 /** \~english Explicit feedback data endpoint \~russian Endpoint данных с явной обратной связью */
		};

		//! \~english Parses direction and transfer information from \a address and \a attributes.
		//! \~russian Разбирает направление и тип передачи из \a address и \a attributes.
		void parse();

		//! \~english Returns true if the endpoint is not selected.
		//! \~russian Возвращает true, если endpoint не выбран.
		bool isNull() const { return address == 0; }

		//! \~english Raw USB endpoint address.
		//! \~russian Сырой адрес USB endpoint.
		uchar address;

		//! \~english Raw USB endpoint attributes.
		//! \~russian Сырые атрибуты USB endpoint.
		uchar attributes;

		//! \~english Maximum packet size in bytes.
		//! \~russian Максимальный размер пакета в байтах.
		ushort max_packet_size;

		//! \~english Parsed transfer direction.
		//! \~russian Разобранное направление передачи.
		Direction direction;

		//! \~english Parsed transfer type.
		//! \~russian Разобранный тип передачи.
		TransferType transfer_type;

		//! \~english Parsed synchronisation type for isochronous transfers.
		//! \~russian Разобранный тип синхронизации для isochronous-передач.
		SynchronisationType synchronisation_type = NoSynchonisation;

		//! \~english Parsed usage type for isochronous transfers.
		//! \~russian Разобранный режим использования для isochronous-передач.
		UsageType usage_type                     = DataEndpoint;
	};

	//! \~\ingroup USB
	//! \~\brief
	//! \~english USB interface description with its endpoints.
	//! \~russian Описание USB-интерфейса и его endpoint.
	struct PIP_USB_EXPORT Interface {
		//! \~english Index inside the configuration descriptor.
		//! \~russian Индекс внутри дескриптора конфигурации.
		uchar index           = 0;

		//! \~english Value used to select this interface.
		//! \~russian Значение, используемое для выбора этого интерфейса.
		uchar value_to_select = 0;

		//! \~english USB interface class code.
		//! \~russian Код класса USB-интерфейса.
		ushort class_code     = 0;

		//! \~english USB interface subclass code.
		//! \~russian Код подкласса USB-интерфейса.
		ushort subclass_code  = 0;

		//! \~english USB interface protocol code.
		//! \~russian Код протокола USB-интерфейса.
		ushort protocol_code  = 0;

		//! \~english Endpoints exposed by this interface.
		//! \~russian Endpoint, доступные у этого интерфейса.
		PIVector<PIUSB::Endpoint> endpoints;
	};

	//! \~\ingroup USB
	//! \~\brief
	//! \~english USB configuration description with available interfaces.
	//! \~russian Описание USB-конфигурации с доступными интерфейсами.
	struct PIP_USB_EXPORT Configuration {
		//! \~english Index inside the device descriptor.
		//! \~russian Индекс внутри дескриптора устройства.
		uchar index           = 0;

		//! \~english Value used to select this configuration.
		//! \~russian Значение, используемое для выбора этой конфигурации.
		uchar value_to_select = 0;

		//! \~english Raw USB configuration attributes.
		//! \~russian Сырые атрибуты USB-конфигурации.
		uchar attributes      = 0;

		//! \~english Maximum bus power in mA.
		//! \~russian Максимальное потребление по шине в мА.
		ushort max_power      = 0;

		//! \~english True if the device is self-powered in this configuration.
		//! \~russian True, если устройство в этой конфигурации имеет собственное питание.
		bool self_powered     = false;

		//! \~english True if remote wakeup is supported.
		//! \~russian True, если поддерживается remote wakeup.
		bool remote_wakeup    = false;

		//! \~english Interfaces available in this configuration.
		//! \~russian Интерфейсы, доступные в этой конфигурации.
		PIVector<PIUSB::Interface> interfaces;
	};

	//! \~\ingroup USB
	//! \~\brief
	//! \~english Top-level USB device descriptor collected during opening.
	//! \~russian Верхнеуровневый USB-дескриптор, собранный при открытии.
	struct PIP_USB_EXPORT Descriptor {
		//! \~english USB specification version in BCD form.
		//! \~russian Версия спецификации USB в BCD-форме.
		ushort usb_spec_number   = 0;

		//! \~english Device class code.
		//! \~russian Код класса устройства.
		uchar device_class       = 0;

		//! \~english Device subclass code.
		//! \~russian Код подкласса устройства.
		uchar device_subclass    = 0;

		//! \~english Device protocol code.
		//! \~russian Код протокола устройства.
		uchar device_protocol    = 0;

		//! \~english Maximum packet size for endpoint zero.
		//! \~russian Максимальный размер пакета для endpoint zero.
		uchar max_packet_size    = 0;

		//! \~english Vendor identifier.
		//! \~russian Идентификатор производителя.
		ushort id_vendor         = 0;

		//! \~english Product identifier.
		//! \~russian Идентификатор продукта.
		ushort id_product        = 0;

		//! \~english Device release number in BCD form.
		//! \~russian Номер релиза устройства в BCD-форме.
		ushort id_device_release = 0;

		//! \~english Index of manufacturer string descriptor.
		//! \~russian Индекс строкового дескриптора производителя.
		uchar index_manufacturer = 0;

		//! \~english Index of product string descriptor.
		//! \~russian Индекс строкового дескриптора продукта.
		uchar index_product      = 0;

		//! \~english Index of serial number string descriptor.
		//! \~russian Индекс строкового дескриптора серийного номера.
		uchar index_serial       = 0;

		//! \~english Available device configurations.
		//! \~russian Доступные конфигурации устройства.
		PIVector<PIUSB::Configuration> configurations;
	};

	//! \~english Returns descriptor collected for the currently opened device.
	//! \~russian Возвращает дескриптор, собранный для текущего открытого устройства.
	const PIUSB::Descriptor & currentDescriptor() const { return desc_; }

	//! \~english Returns the currently selected configuration description.
	//! \~russian Возвращает описание текущей выбранной конфигурации.
	const PIUSB::Configuration & currentConfiguration() const { return conf_; }

	//! \~english Returns the currently selected interface description.
	//! \~russian Возвращает описание текущего выбранного интерфейса.
	const PIUSB::Interface & currentInterface() const { return iface_; }

	//! \~english Returns current vendor ID filter.
	//! \~russian Возвращает текущий фильтр vendor ID.
	ushort vendorID() const { return vid_; }

	//! \~english Returns current product ID filter.
	//! \~russian Возвращает текущий фильтр product ID.
	ushort productID() const { return pid_; }

	//! \~english Returns ordinal number among devices with matching vendor and product IDs.
	//! \~russian Возвращает порядковый номер среди устройств с теми же vendor ID и product ID.
	int deviceNumber() const { return property("deviceNumber").toInt(); }

	//! \~english Returns read timeout in milliseconds.
	//! \~russian Возвращает таймаут чтения в миллисекундах.
	int timeoutRead() const { return property("timeoutRead").toInt(); }

	//! \~english Returns write timeout in milliseconds.
	//! \~russian Возвращает таймаут записи в миллисекундах.
	int timeoutWrite() const { return property("timeoutWrite").toInt(); }

	//! \~english Returns endpoint used by \a read().
	//! \~russian Возвращает endpoint, используемый методом \a read().
	const PIUSB::Endpoint & endpointRead() const { return ep_read; }

	//! \~english Returns endpoint used by \a write().
	//! \~russian Возвращает endpoint, используемый методом \a write().
	const PIUSB::Endpoint & endpointWrite() const { return ep_write; }

	//! \~english Returns endpoints of the currently selected interface.
	//! \~russian Возвращает endpoint текущего выбранного интерфейса.
	const PIVector<PIUSB::Endpoint> & endpoints() const { return eps; }

	//! \~english Returns only readable endpoints from \a endpoints().
	//! \~russian Возвращает только endpoint для чтения из \a endpoints().
	PIVector<PIUSB::Endpoint> endpointsRead();

	//! \~english Returns only writable endpoints from \a endpoints().
	//! \~russian Возвращает только endpoint для записи из \a endpoints().
	PIVector<PIUSB::Endpoint> endpointsWrite();

	//! \~english Returns endpoint with address "address", or null endpoint if it is absent.
	//! \~russian Возвращает endpoint с адресом "address" или нулевой endpoint, если он отсутствует.
	PIUSB::Endpoint getEndpointByAddress(uchar address);

	//! \~english Sets vendor ID filter and updates device path.
	//! \~russian Устанавливает фильтр vendor ID и обновляет путь устройства.
	void setVendorID(ushort vid);

	//! \~english Sets product ID filter and updates device path.
	//! \~russian Устанавливает фильтр product ID и обновляет путь устройства.
	void setProductID(ushort pid);

	//! \~english Selects configuration by its public value and switches to its first interface.
	//! \~russian Выбирает конфигурацию по её публичному значению и переключается на её первый интерфейс.
	bool setConfiguration(uchar value);

	//! \~english Selects interface by its public value and refreshes active endpoints.
	//! \~russian Выбирает интерфейс по его публичному значению и обновляет активные endpoint.
	bool setInterface(uchar value);

	//! \~english Sets endpoint used by \a read().
	//! \~russian Устанавливает endpoint, используемый методом \a read().
	void setEndpointRead(const PIUSB::Endpoint & ep) { ep_read = ep; }

	//! \~english Sets endpoint used by \a write().
	//! \~russian Устанавливает endpoint, используемый методом \a write().
	void setEndpointWrite(const PIUSB::Endpoint & ep) { ep_write = ep; }

	//! \~english Selects which matching device instance should be opened.
	//! \~russian Выбирает, какой экземпляр среди совпадающих устройств должен быть открыт.
	void setDeviceNumber(int dn) { setProperty("deviceNumber", dn); }

	//! \~english Sets read timeout in milliseconds.
	//! \~russian Устанавливает таймаут чтения в миллисекундах.
	void setTimeoutRead(int t) { setProperty("timeoutRead", t); }

	//! \~english Sets write timeout in milliseconds.
	//! \~russian Устанавливает таймаут записи в миллисекундах.
	void setTimeoutWrite(int t) { setProperty("timeoutWrite", t); }

	//! \~english Reserved control-transfer write helper.
	//! \~russian Зарезервированный helper для записи через control-transfer.
	int controlWrite(const void * data, int max_size);

	//! \~english Resets currently selected read and write endpoints.
	//! \~russian Сбрасывает текущие выбранные endpoint чтения и записи.
	virtual void flush() override;

protected:
	bool configureDevice(const void * e_main, const void * e_parent = 0) override;
	PIString constructFullPathDevice() const override;
	void configureFromFullPathDevice(const PIString & full_path) override;
	ssize_t readDevice(void * read_to, ssize_t max_size) override;
	ssize_t writeDevice(const void * data, ssize_t max_size) override;
	bool openDevice() override;
	bool closeDevice() override;
	DeviceInfoFlags deviceInfoFlags() const override { return PIIODevice::Reliable; }

	PIVector<PIUSB::Endpoint> eps;
	ushort vid_, pid_;
	int intefrace_, timeout_r, timeout_w;
	int interface_claimed;
	PIUSB::Endpoint ep_read, ep_write;
	Descriptor desc_;
	Configuration conf_;
	Interface iface_;
	usb_dev_handle * hdev;
};


//! \relatesalso PICout
//! \~english Writes endpoint description to \a PICout.
//! \~russian Выводит описание endpoint в \a PICout.
PIP_USB_EXPORT PICout operator<<(PICout s, const PIUSB::Endpoint & v);


#endif
