//! \~\file piopencl.h
//! \~\ingroup OpenCL
//! \~\brief
//! \~english OpenCL wrapper classes
//! \~russian Классы-обертки для OpenCL
/*
    PIP - Platform Independent Primitives
	OpenCL wrapper classes
    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 OpenCL OpenCL
//! \~\brief
//! \~english OpenCL support
//! \~russian Поддержка OpenCL
//!
//! \~\details
//! \~english \section cmake_module_OpenCL Building with CMake
//! \~russian \section cmake_module_OpenCL Сборка с использованием CMake
//!
//! \~\code
//! find_package(PIP REQUIRED)
//! target_link_libraries([target] PIP::OpenCL)
//! \endcode
//!
//! \~english \par Common
//! \~russian \par Общее
//!
//! \~english
//! The module provides lightweight wrappers for OpenCL platforms, devices,
//! contexts, programs, kernels and buffers.
//!
//! \~russian
//! Модуль предоставляет легковесные обертки для платформ, устройств,
//! контекстов, программ, ядер и буферов OpenCL.
//!
//! \~\authors
//! \~english
//! Ivan Pelipenko peri4ko@yandex.ru;
//! \~russian
//! Иван Пелипенко peri4ko@yandex.ru;
//!

#ifndef PIOPENCL_H
#define PIOPENCL_H

#include "pip_opencl_export.h"
#include "pivariant.h"

//! \~\ingroup OpenCL
//! \~\brief
//! \~english Entry point for the OpenCL wrapper API.
//! \~russian Точка входа в API-обертку OpenCL.
class PIP_OPENCL_EXPORT PIOpenCL {
public:
	struct KernelArg;
	struct Device;
	struct Platform;
	class Context;
	class Buffer;
	class Program;
	class Kernel;

	//! \~english List of OpenCL devices.
	//! \~russian Список устройств OpenCL.
	typedef PIVector<Device> DeviceList;


	//! \~english Address space qualifier of a kernel argument.
	//! \~russian Квалификатор адресного пространства аргумента ядра.
	enum AddressQualifier {
		AddressGlobal /** \~english Global memory argument \~russian Аргумент в глобальной памяти */,
		AddressLocal /** \~english Local memory argument \~russian Аргумент в локальной памяти */,
		AddressConstant /** \~english Constant memory argument \~russian Аргумент в константной памяти */,
		AddressPrivate /** \~english Private argument \~russian Приватный аргумент */
	};

	//! \~english Access qualifier of an image or memory argument.
	//! \~russian Квалификатор доступа к аргументу изображения или памяти.
	enum AccessQualifier {
		AccessReadOnly /** \~english Read-only access \~russian Доступ только для чтения */,
		AccessWriteOnly /** \~english Write-only access \~russian Доступ только для записи */,
		AccessReadWrite /** \~english Read-write access \~russian Доступ для чтения и записи */,
		AccessNone /** \~english Access qualifier is not specified \~russian Квалификатор доступа не задан */
	};

	//! \~english Intended data flow direction for buffers.
	//! \~russian Предполагаемое направление обмена данными для буферов.
	enum Direction {
		Input       = 0x01 /** \~english Host-to-device input buffer \~russian Входной буфер host->device */,
		Output      = 0x02 /** \~english Device-to-host output buffer \~russian Выходной буфер device->host */,
		InputOutput = Input | Output /** \~english Bidirectional buffer \~russian Двунаправленный буфер */
	};

	//! \~english Type qualifier of a kernel argument.
	//! \~russian Квалификатор типа аргумента ядра.
	enum TypeQualifier {
		TypeConst /** \~english Constant argument \~russian Константный аргумент */,
		TypeRestrict /** \~english Restricted argument \~russian Аргумент с qualifier `restrict` */,
		TypeVolatile /** \~english Volatile argument \~russian Аргумент с qualifier `volatile` */,
		TypeNone /** \~english No type qualifier \~russian Квалификатор типа отсутствует */
	};

	//! \~english Scalar argument type recognized by the wrapper.
	//! \~russian Скалярный тип аргумента, распознаваемый оберткой.
	enum ArgType {
		Char = 1 /** \~english Signed 8-bit integer \~russian Знаковое 8-битное целое */,
		UChar /** \~english Unsigned 8-bit integer \~russian Беззнаковое 8-битное целое */,
		Short /** \~english Signed 16-bit integer \~russian Знаковое 16-битное целое */,
		UShort /** \~english Unsigned 16-bit integer \~russian Беззнаковое 16-битное целое */,
		Int /** \~english Signed 32-bit integer \~russian Знаковое 32-битное целое */,
		UInt /** \~english Unsigned 32-bit integer \~russian Беззнаковое 32-битное целое */,
		Long /** \~english Signed long integer \~russian Знаковое long-целое */,
		ULong /** \~english Unsigned long integer \~russian Беззнаковое long-целое */,
		Float /** \~english Single-precision floating point \~russian Число с плавающей точкой одинарной точности */,
		Double /** \~english Double-precision floating point \~russian Число с плавающей точкой двойной точности */
	};


	//! \~\brief
	//! \~english Reflected description of a kernel argument.
	//! \~russian Отраженное описание аргумента ядра.
	struct PIP_OPENCL_EXPORT KernelArg {
		//! \~english Constructs an empty argument description.
		//! \~russian Создает пустое описание аргумента.
		KernelArg();

		//! \~english Address qualifier reported by OpenCL.
		//! \~russian Адресный квалификатор, сообщенный OpenCL.
		AddressQualifier address_qualifier;

		//! \~english Access qualifier reported by OpenCL.
		//! \~russian Квалификатор доступа, сообщенный OpenCL.
		AccessQualifier access_qualifier;

		//! \~english Intended transfer direction used by this wrapper.
		//! \~russian Направление обмена, используемое этой оберткой.
		Direction direction;

		//! \~english Type qualifier reported by OpenCL.
		//! \~russian Квалификатор типа, сообщенный OpenCL.
		TypeQualifier type_qualifier;

		//! \~english Argument name from kernel metadata.
		//! \~russian Имя аргумента из метаданных ядра.
		PIString arg_name;

		//! \~english Original type name from kernel metadata.
		//! \~russian Исходное имя типа из метаданных ядра.
		PIString type_name;

		//! \~english Base type name without pointer or array suffixes.
		//! \~russian Базовое имя типа без указателей и массивных суффиксов.
		PIString base_type_name;

		//! \~english True when the argument is pointer-like.
		//! \~russian True, если аргумент ведет себя как указатель.
		bool is_pointer;

		//! \~english Wrapper-level scalar type classification.
		//! \~russian Классификация скалярного типа на уровне обертки.
		ArgType arg_type;

		//! \~english Pointer or array depth inferred from the type name.
		//! \~russian Глубина указателей или массивов, выведенная из имени типа.
		int dims;

	private:
		friend class Kernel;
		void init(void * _k, uint index);
	};


	//! \~\brief
	//! \~english Description of a discovered OpenCL device.
	//! \~russian Описание найденного устройства OpenCL.
	struct PIP_OPENCL_EXPORT Device {
		//! \~english Constructs an invalid device description.
		//! \~russian Создает недействительное описание устройства.
		Device() {
			id = platform_id  = 0;
			max_compute_units = max_clock_frequency = 0;
			max_memory_size                         = 0;
		}

		//! \~english Returns true when the native device handle is available.
		//! \~russian Возвращает true, если доступен нативный дескриптор устройства.
		bool isValid() const { return id != 0; }

		//! \~english Returns a short display label with name and device version.
		//! \~russian Возвращает краткую строку отображения с именем и версией устройства.
		PIString displayText() const { return name.trimmed() + " (" + device_version.trimmed() + ")"; }

		//! \~english Native OpenCL device handle.
		//! \~russian Нативный дескриптор устройства OpenCL.
		void * id;

		//! \~english Native platform handle that owns the device.
		//! \~russian Нативный дескриптор платформы, которой принадлежит устройство.
		void * platform_id;

		//! \~english Human-readable device name.
		//! \~russian Человекочитаемое имя устройства.
		PIString name;

		//! \~english Device vendor name.
		//! \~russian Имя производителя устройства.
		PIString vendor;

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

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

		//! \~english Maximum number of compute units.
		//! \~russian Максимальное число вычислительных блоков.
		int max_compute_units;

		//! \~english Maximum clock frequency in MHz.
		//! \~russian Максимальная тактовая частота в МГц.
		int max_clock_frequency;

		//! \~english Global memory size in bytes.
		//! \~russian Объем глобальной памяти в байтах.
		ullong max_memory_size;
	};


	//! \~\brief
	//! \~english Description of an OpenCL platform and its devices.
	//! \~russian Описание платформы OpenCL и ее устройств.
	struct PIP_OPENCL_EXPORT Platform {
		//! \~english Constructs an invalid platform description.
		//! \~russian Создает недействительное описание платформы.
		Platform() { id = 0; }

		//! \~english Returns true when the native platform handle is available.
		//! \~russian Возвращает true, если доступен нативный дескриптор платформы.
		bool isValid() const { return id != 0; }

		//! \~english Returns a short display label with version and profile.
		//! \~russian Возвращает краткую строку отображения с версией и профилем.
		PIString displayText() const { return name.trimmed() + " (" + version.trimmed() + ", " + profile.trimmed() + ")"; }

		//! \~english Native OpenCL platform handle.
		//! \~russian Нативный дескриптор платформы OpenCL.
		void * id;

		//! \~english Human-readable platform name.
		//! \~russian Человекочитаемое имя платформы.
		PIString name;

		//! \~english Platform vendor name.
		//! \~russian Имя производителя платформы.
		PIString vendor;

		//! \~english Platform profile string.
		//! \~russian Строка профиля платформы.
		PIString profile;

		//! \~english Platform version string.
		//! \~russian Строка версии платформы.
		PIString version;

		//! \~english Reported extension names.
		//! \~russian Имена поддерживаемых расширений.
		PIStringList extensions;

		//! \~english Devices discovered on the platform.
		//! \~russian Устройства, найденные на платформе.
		PIVector<Device> devices;
	};


	//! \~\brief
	//! \~english OpenCL context wrapper that owns created programs and buffers.
	//! \~russian Обертка над контекстом OpenCL, владеющая созданными программами и буферами.
	class PIP_OPENCL_EXPORT Context {
		friend class Buffer;
		friend class Program;
		friend class Kernel;

	public:
		//! \~english Destroys the context together with its programs and buffers.
		//! \~russian Уничтожает контекст вместе с его программами и буферами.
		~Context();

		//! \~english Returns the native OpenCL context handle.
		//! \~russian Возвращает нативный дескриптор контекста OpenCL.
		void * handle();

		//! \~english Returns the native command queue handle.
		//! \~russian Возвращает нативный дескриптор очереди команд.
		void * queue();

		//! \~english Creates a context for the given device list.
		//! \~russian Создает контекст для заданного списка устройств.
		static Context * create(const DeviceList & dl);

		//! \~english Creates a context for a single device.
		//! \~russian Создает контекст для одного устройства.
		static Context * create(const Device & d) { return create(DeviceList() << d); }

		//! \~english Creates a context for the first device whose display name contains the given text.
		//! \~russian Создает контекст для первого устройства, в строке отображения которого содержится заданный текст.
		static Context * create(const PIString & part_name);

		//! \~english Builds a program from source and returns null on failure.
		//! \~russian Собирает программу из исходного текста и возвращает null при ошибке.
		Program * createProgram(const PIString & source, const PIStringList & args = PIStringList(), PIString * error = 0);

		//! \~english Creates a buffer bound to a vector container.
		//! \~russian Создает буфер, связанный с контейнером-вектором.
		template<typename T>
		Buffer * createBuffer(PIOpenCL::Direction dir, PIVector<T> & container) {
			T def = T();
			return createBuffer(dir, &container, Buffer::cVector, PIByteArray(&def, sizeof(T)), container.size());
		}

		//! \~english Creates a buffer bound to a deque container.
		//! \~russian Создает буфер, связанный с контейнером-deque.
		template<typename T>
		Buffer * createBuffer(PIOpenCL::Direction dir, PIDeque<T> & container) {
			T def = T();
			return createBuffer(dir, &container, Buffer::cDeque, PIByteArray(&def, sizeof(T)), container.size());
		}

		//! \~english Creates a buffer bound to a two-dimensional vector container.
		//! \~russian Создает буфер, связанный с двумерным контейнером-вектором.
		template<typename T>
		Buffer * createBuffer(PIOpenCL::Direction dir, PIVector2D<T> & container) {
			T def = T();
			return createBuffer(dir, &container, Buffer::cVector2D, PIByteArray(&def, sizeof(T)), container.size());
		}

		//! \~english Creates an unbound buffer with the given element count and clears it to the default value.
		//! \~russian Создает несвязанный буфер с заданным числом элементов и очищает его значением по умолчанию.
		template<typename T>
		Buffer * createBuffer(PIOpenCL::Direction dir, uint elements) {
			T def        = T();
			Buffer * ret = createBuffer(dir, 0, Buffer::cNone, PIByteArray(&def, sizeof(T)), elements);
			if (ret) ret->clear();
			return ret;
		}

	private:
		Context();
		void zero();
		void deletePrograms();
		void deleteBuffers();
		Buffer * createBuffer(PIOpenCL::Direction dir, void * container, int type, PIByteArray def, uint elements);
		PIVector<Program *> programs_;
		PIVector<Buffer *> buffers_;
		PRIVATE_DECLARATION(PIP_OPENCL_EXPORT)
	};


	//! \~\brief
	//! \~english OpenCL buffer wrapper optionally bound to a host container.
	//! \~russian Обертка над буфером OpenCL, при необходимости связанная с контейнером host.
	class PIP_OPENCL_EXPORT Buffer {
		friend class Context;
		friend class Kernel;

	public:
		//! \~english Destroys the buffer object.
		//! \~russian Уничтожает объект буфера.
		~Buffer();

		//! \~english Returns the native OpenCL memory handle.
		//! \~russian Возвращает нативный дескриптор памяти OpenCL.
		void * handle();

		//! \~english Resizes the buffer to another element count.
		//! \~russian Изменяет размер буфера на другое число элементов.
		bool resize(uint new_elements);

		//! \~english Fills the whole buffer with the default element value.
		//! \~russian Заполняет весь буфер значением элемента по умолчанию.
		void clear();

		//! \~english Copies device data back to the bound host container.
		//! \~russian Копирует данные с устройства обратно в связанный host-контейнер.
		void copyToContainer();

		//! \~english Copies the whole buffer to raw host memory.
		//! \~russian Копирует весь буфер в сырую память host.
		void copyTo(void * data);

		//! \~english Copies a subrange of the buffer to raw host memory.
		//! \~russian Копирует поддиапазон буфера в сырую память host.
		void copyTo(void * data, int elements_count, int elements_offset = 0);

		//! \~english Copies data from this buffer to another buffer.
		//! \~russian Копирует данные из этого буфера в другой буфер.
		void copyTo(Buffer * buffer, int elements_count = -1, int elements_from_offset = 0, int elements_to_offset = 0);

		//! \~english Uploads data from the bound host container.
		//! \~russian Загружает данные из связанного host-контейнера.
		void copyFromContainer();

		//! \~english Copies the whole raw host memory block into the buffer.
		//! \~russian Копирует весь блок сырой памяти host в буфер.
		void copyFrom(void * data);

		//! \~english Copies a subrange from raw host memory into the buffer.
		//! \~russian Копирует поддиапазон из сырой памяти host в буфер.
		void copyFrom(void * data, int elements_count, int elements_offset = 0);

		//! \~english Copies data from another buffer into this buffer.
		//! \~russian Копирует данные из другого буфера в этот буфер.
		void copyFrom(Buffer * buffer, int elements_count = -1, int elements_from_offset = 0, int elements_to_offset = 0);

		//! \~english Returns the number of logical elements in the buffer.
		//! \~russian Возвращает число логических элементов в буфере.
		uint elementsCount() const { return elements; }

	private:
		enum Container {
			cNone,
			cVector,
			cDeque,
			cVector2D,
		};
		Buffer();
		void zero();
		bool init();
		void * containerData();
		static void copy(Buffer * buffer_from, Buffer * buffer_to, int elements_count, int elements_from_offset, int elements_to_offset);
		Context * context_;
		Direction dir;
		Container type;
		void * container;
		PIByteArray def;
		uint elements;
		PRIVATE_DECLARATION(PIP_OPENCL_EXPORT)
	};


	//! \~\brief
	//! \~english Compiled OpenCL program that owns discovered kernels.
	//! \~russian Скомпилированная программа OpenCL, владеющая найденными ядрами.
	class PIP_OPENCL_EXPORT Program {
		friend class Context;
		friend class Kernel;
		friend class Buffer;

	public:
		//! \~english Destroys the program and its kernels.
		//! \~russian Уничтожает программу и ее ядра.
		~Program();

		//! \~english Returns the context that created the program.
		//! \~russian Возвращает контекст, создавший программу.
		Context * context() const { return context_; }

		//! \~english Returns the original source code used for compilation.
		//! \~russian Возвращает исходный текст, использованный при компиляции.
		const PIString & sourceCode() const { return source_; }

		//! \~english Returns the kernel at the given index.
		//! \~russian Возвращает ядро по заданному индексу.
		Kernel * kernel(int index = 0) const { return kernels_[index]; }

		//! \~english Returns all kernels created from the program.
		//! \~russian Возвращает все ядра, созданные из программы.
		const PIVector<Kernel *> & kernels() const { return kernels_; }

	private:
		Program();
		void zero();
		bool initKernels(PIVector<void *> kerns);
		Context * context_;
		PIString source_;
		PIVector<Kernel *> kernels_;
		PRIVATE_DECLARATION(PIP_OPENCL_EXPORT)
	};


	//! \~\brief
	//! \~english OpenCL kernel wrapper with reflected argument metadata.
	//! \~russian Обертка над ядром OpenCL с отраженными метаданными аргументов.
	class PIP_OPENCL_EXPORT Kernel {
		friend class Program;
		friend class Buffer;

	public:
		//! \~english Returns the program that owns the kernel.
		//! \~russian Возвращает программу, которой принадлежит ядро.
		Program * program() const { return program_; }

		//! \~english Enqueues kernel execution for the configured global ranges.
		//! \~russian Помещает выполнение ядра в очередь для настроенных глобальных диапазонов.
		bool execute();

		//! \~english Waits until the context command queue finishes.
		//! \~russian Ожидает завершения очереди команд контекста.
		void waitForFinish();

		//! \~english Sets one-dimensional execution range.
		//! \~russian Устанавливает одномерный диапазон выполнения.
		void setExecuteRange(int size) { setExecuteRanges(PIVector<int>() << size); }

		//! \~english Sets multi-dimensional execution ranges.
		//! \~russian Устанавливает многомерные диапазоны выполнения.
		void setExecuteRanges(const PIVector<int> & ranges);

		//! \~english Returns the kernel function name.
		//! \~russian Возвращает имя функции ядра.
		const PIString & name() const { return name_; }

		//! \~english Returns reflected descriptions of kernel arguments.
		//! \~russian Возвращает отраженные описания аргументов ядра.
		const PIVector<KernelArg> & args() const { return args_; }

		//! \~english Sets a scalar argument by index.
		//! \~russian Устанавливает скалярный аргумент по индексу.
		template<typename T>
		bool setArgValue(int index, const T & value) {
			return setArgValueS(index, PIVariant::fromValue(value));
		}

		//! \~english Sets a scalar argument by name.
		//! \~russian Устанавливает скалярный аргумент по имени.
		template<typename T>
		bool setArgValue(const PIString & arg, const T & value) {
			return setArgValue(argIndex(arg), value);
		}

		//! \~english Sets a scalar argument from a %PIVariant value.
		//! \~russian Устанавливает скалярный аргумент из значения %PIVariant.
		bool setArgValue(const PIString & arg, const PIVariant & value) { return setArgValueS(argIndex(arg), value); }

		//! \~english Binds a buffer argument by index.
		//! \~russian Привязывает буферный аргумент по индексу.
		bool bindArgValue(int index, Buffer * buffer);

		//! \~english Binds a buffer argument by name.
		//! \~russian Привязывает буферный аргумент по имени.
		bool bindArgValue(const PIString & arg, Buffer * buffer) { return bindArgValue(argIndex(arg), buffer); }

	private:
		Kernel();
		~Kernel();
		void zero();
		bool init();
		bool setArgValueS(int index, const PIVariant & value);
		int argIndex(const PIString & an) const;
		KernelArg argByName(const PIString & an) const;
		Context * context_;
		Program * program_;
		PIString name_;
		PIVector<KernelArg> args_;
		PIVector<size_t> dims;
		PRIVATE_DECLARATION(PIP_OPENCL_EXPORT)
	};


	//! \~english Initializes platform and device discovery.
	//! \~russian Инициализирует поиск платформ и устройств.
	static void init();

	//! \~english Returns discovered OpenCL platforms.
	//! \~russian Возвращает найденные платформы OpenCL.
	static const PIVector<Platform> & platforms();

	//! \~english Returns devices from all discovered platforms.
	//! \~russian Возвращает устройства со всех найденных платформ.
	static const PIVector<Device> devices();

	//! \~english Returns a device description for the given native device handle.
	//! \~russian Возвращает описание устройства для заданного нативного дескриптора.
	static Device deviceByID(void * id);

	//! \~english Prepares OpenCL source code before compilation.
	//! \~russian Подготавливает исходный текст OpenCL перед компиляцией.
	static PIString prepareProgram(const PIString & prog);

private:
	static PIString prog_header;
	PIOpenCL() { ; }
	class PIP_OPENCL_EXPORT Initializer {
	public:
		Initializer();
		static Initializer * instance();
		void init();
		PIVector<Platform> platforms_;
		bool inited_;
	};
};


//! \~english Writes a readable kernel argument description to %PICout.
//! \~russian Выводит читаемое описание аргумента ядра в %PICout.
PIP_OPENCL_EXPORT PICout operator<<(PICout s, const PIOpenCL::KernelArg & v);


#endif // PIOPENCL_H
