//! \~\file pihidevice.h
//! \~\ingroup System
//! \~\brief
//! \~english HID device
//! \~russian HID устройство
/*
    PIP - Platform Independent Primitives
    HID device
    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/>.
*/

#ifndef PIHIDEVICE_H
#define PIHIDEVICE_H

#include "pithread.h"


//! \~\ingroup System
//! \~\brief
//! \~english Information about a discovered HID device.
//! \~russian Информация об обнаруженном HID-устройстве.
struct PIP_EXPORT PIHIDeviceInfo {
	friend class PIHIDevice;

	//! \~\ingroup System
	//! \~\brief
	//! \~english Common information for HID controls addressed by index.
	//! \~russian Общая информация об HID-элементах управления, адресуемых по индексу.
	struct PIP_EXPORT ValueInfoBase {
		//! \~english Returns \b true when this control description is initialized.
		//! \~russian Возвращает \b true, если описание элемента управления инициализировано.
		bool isValid() const { return index >= 0; }

		//! \~english Sequential index in the corresponding public vector.
		//! \~russian Порядковый индекс в соответствующем публичном векторе.
		int index      = -1;

		//! \~english Platform data index used in input reports.
		//! \~russian Платформенный индекс данных, используемый во входных отчетах.
		int data_index = -1;
	};

	//! \~\ingroup System
	//! \~\brief
	//! \~english Description of a HID axis.
	//! \~russian Описание HID-оси.
	struct PIP_EXPORT AxisInfo: public ValueInfoBase {
		//! \~english Axis resolution in bits when the platform reports it.
		//! \~russian Разрешение оси в битах, если платформа его сообщает.
		int bits         = 0;

		//! \~english Minimal raw axis value.
		//! \~russian Минимальное сырое значение оси.
		int min          = 0;

		//! \~english Maximal raw axis value.
		//! \~russian Максимальное сырое значение оси.
		int max          = 1;

		//! \~english Marks relative axes such as wheels or deltas.
		//! \~russian Отмечает относительные оси, например колесо или дельты.
		bool is_relative = false;
	};

	//! \~\ingroup System
	//! \~\brief
	//! \~english Description of a HID button.
	//! \~russian Описание HID-кнопки.
	struct PIP_EXPORT ButtonInfo: public ValueInfoBase {
		//! \~english Platform button code when it is available.
		//! \~russian Платформенный код кнопки, если он доступен.
		int code = 0;
	};

	//! \~english Device path that can be passed to \a PIHIDevice::open().
	//! \~russian Путь к устройству, который можно передать в \a PIHIDevice::open().
	PIString path;

	//! \~english Manufacturer name.
	//! \~russian Имя производителя.
	PIString manufacturer;

	//! \~english Product name.
	//! \~russian Имя продукта.
	PIString product;

	//! \~english Device serial number when available.
	//! \~russian Серийный номер устройства, если доступен.
	PIString serial;

	//! \~english Device version string.
	//! \~russian Строка версии устройства.
	PIString version;

	//! \~english USB vendor identifier.
	//! \~russian Идентификатор производителя USB.
	PIString VID;

	//! \~english USB product identifier.
	//! \~russian Идентификатор продукта USB.
	PIString PID;

	//! \~english All discovered axes in report order.
	//! \~russian Все обнаруженные оси в порядке отчетов.
	PIVector<AxisInfo> axes;

	//! \~english All discovered buttons.
	//! \~russian Все обнаруженные кнопки.
	PIVector<ButtonInfo> buttons;

	//! \~english Returns \b true when this descriptor does not point to a device.
	//! \~russian Возвращает \b true, если этот дескриптор не указывает на устройство.
	bool isNull() const { return path.isEmpty(); }

	//! \~english Returns \b true when this descriptor points to a device.
	//! \~russian Возвращает \b true, если этот дескриптор указывает на устройство.
	bool isNotNull() const { return !isNull(); }

	//! \~english Checks whether product name or path contains \a str ignoring case.
	//! \~russian Проверяет, содержит ли имя продукта или путь строку \a str без учета регистра.
	bool match(const PIString & str) const;

	//! \~english Returns total number of discovered axes.
	//! \~russian Возвращает общее число обнаруженных осей.
	int axesCount() const { return axes.size_s(); }

	//! \~english Returns number of absolute axes.
	//! \~russian Возвращает число абсолютных осей.
	int axesAbsoluteCount() const;

	//! \~english Returns number of relative axes.
	//! \~russian Возвращает число относительных осей.
	int axesRelativeCount() const;

	//! \~english Returns total number of discovered buttons.
	//! \~russian Возвращает общее число обнаруженных кнопок.
	int buttonsCount() const { return buttons.size_s(); }

private:
	void prepare();
	PIMap<int, AxisInfo> axis_by_dataindex;
	PIMap<int, ButtonInfo> button_by_dataindex;
	int input_report_size   = 0;
	int output_report_size  = 0;
	int feature_report_size = 0;
};

//! \relatesalso PIHIDeviceInfo
//! \~english Prints short HID device information to \a PICout.
//! \~russian Выводит краткую информацию об HID-устройстве в \a PICout.
PIP_EXPORT PICout operator<<(PICout s, const PIHIDeviceInfo & v);


//! \~\ingroup System
//! \~\brief
//! \~english Provides access to HID (Human Interface Device) devices such as game controllers, joysticks, and other input devices.
//! \~russian Предоставляет доступ к HID (Human Interface Device) устройствам, таким как геймконтроллеры, джойстики и другие устройства
//! ввода.
class PIP_EXPORT PIHIDevice: public PIThread {
	PIOBJECT_SUBCLASS(PIHIDevice, PIThread)

public:
	//! \~english Stops polling and closes the device.
	//! \~russian Останавливает опрос и закрывает устройство.
	~PIHIDevice();

	//! \~\ingroup System
	//! \~\brief
	//! \~english Input event produced by %PIHIDevice.
	//! \~russian Событие ввода, создаваемое %PIHIDevice.
	struct PIP_EXPORT Event {
		//! \~english Kind of HID event.
		//! \~russian Вид HID-события.
		enum Type {
			tNone /** \~english Empty event \~russian Пустое событие */,
			tButton /** \~english Button state change \~russian Изменение состояния кнопки */,
			tAxisMove /** \~english Axis value change or relative axis delta \~russian Изменение значения оси или дельта относительной оси
					   */
				,
		};

		//! \~english Event kind.
		//! \~russian Вид события.
		Type type = tNone;

		//! \~english Axis description for axis events.
		//! \~russian Описание оси для событий оси.
		PIHIDeviceInfo::AxisInfo axis;

		//! \~english Button description for button events.
		//! \~russian Описание кнопки для событий кнопки.
		PIHIDeviceInfo::ButtonInfo button;

		//! \~english Normalized axis value, relative delta, or button state.
		//! \~russian Нормализованное значение оси, относительная дельта или состояние кнопки.
		float value = 0.;
	};

	//! \~english Returns \b true when the device handle is open.
	//! \~russian Возвращает \b true, если дескриптор устройства открыт.
	bool isOpened() const;

	//! \~english Opens the device described by \a device and stores it as current.
	//! \~russian Открывает устройство, описанное в \a device, и сохраняет его как текущее.
	bool open(const PIHIDeviceInfo & device);

	//! \~english Reopens the device stored by the last successful \a open().
	//! \~russian Повторно открывает устройство, сохраненное последним успешным вызовом \a open().
	bool open();

	//! \~english Stops reading and releases the current device handle.
	//! \~russian Останавливает чтение и освобождает дескриптор текущего устройства.
	void close();

	//! \~english Starts polling the opened device for input changes.
	//! \~russian Запускает опрос открытого устройства на изменения ввода.
	void start();

	//! \~english Stops device polling and waits for the thread to finish.
	//! \~russian Останавливает опрос устройства и ожидает завершения потока.
	void stop();

	//! \~english Sets dead zone for absolute axes in normalized range.
	//! \~russian Устанавливает мертвую зону для абсолютных осей в нормализованном диапазоне.
	void setDeadZone(float v) { dead_zone = v; }

	//! \~english Returns current dead zone for absolute axes.
	//! \~russian Возвращает текущую мертвую зону для абсолютных осей.
	float deadZone() const { return dead_zone; }

	//! \events
	//! \{

	//! \fn void event(PIHIDevice::Event e)
	//! \brief
	//! \~english Raised when a button changes state or an axis value changes.
	//! \~russian Генерируется при изменении состояния кнопки или значения оси.
	EVENT1(event, PIHIDevice::Event, e);

	//! \}

	//! \~english Enumerates available HID devices. When \a try_open is enabled, inaccessible devices are skipped.
	//! \~russian Перечисляет доступные HID-устройства. Если включен \a try_open, недоступные устройства пропускаются.
	static PIVector<PIHIDeviceInfo> allDevices(bool try_open = true);

	//! \~english Returns the first device whose path or product matches \a name.
	//! \~russian Возвращает первое устройство, у которого путь или имя продукта совпадает с \a name.
	static PIHIDeviceInfo findDevice(const PIString & name);

private:
	void run() override;
	double procDeadZone(double in);

	PRIVATE_DECLARATION(PIP_EXPORT)
	PIHIDeviceInfo di;
	PIMap<int, float> prev_axes, cur_axes;
	PIMap<int, int> prev_buttons, cur_buttons;
	float dead_zone = 0.f;
};


#endif
