/*
	PIP - Platform Independent Primitives
	OpenCL wrappers
	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 PIOPENCL_H
#define PIOPENCL_H

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


class PIP_OPENCL_EXPORT PIOpenCL {
public:

	struct KernelArg;
	struct Device;
	struct Platform;
	class Context;
	class Buffer;
	class Program;
	class Kernel;

	typedef PIVector<Device> DeviceList;


	enum AddressQualifier {
		AddressGlobal,
		AddressLocal,
		AddressConstant,
		AddressPrivate,
	};

	enum AccessQualifier {
		AccessReadOnly,
		AccessWriteOnly,
		AccessReadWrite,
		AccessNone,
	};

	enum Direction {
		Input  = 0x01,
		Output = 0x02,
		InputOutput = Input | Output,
	};

	enum TypeQualifier {
		TypeConst,
		TypeRestrict,
		TypeVolatile,
		TypeNone,
	};

	enum ArgType {
		Char = 1,
		UChar,
		Short,
		UShort,
		Int,
		UInt,
		Long,
		ULong,
		Float,
		Double,
	};


	struct PIP_OPENCL_EXPORT KernelArg {
		KernelArg();
		AddressQualifier address_qualifier;
		AccessQualifier access_qualifier;
		Direction direction;
		TypeQualifier type_qualifier;
		PIString arg_name;
		PIString type_name;
		PIString base_type_name;
		bool is_pointer;
		ArgType arg_type;
		int dims;
	private:
		friend class Kernel;
		void init(void * _k, uint index);
	};


	struct PIP_OPENCL_EXPORT Device {
		Device() {id = platform_id = 0; max_compute_units = max_clock_frequency = 0; max_memory_size = 0;}
		bool isValid() const {return id != 0;}
		PIString displayText() const {return name.trimmed() + " (" + device_version.trimmed() + ")";}
		void * id;
		void * platform_id;
		PIString name;
		PIString vendor;
		PIString device_version;
		PIString driver_version;
		int max_compute_units;
		int max_clock_frequency;
		ullong max_memory_size;
	};


	struct PIP_OPENCL_EXPORT Platform {
		Platform() {id = 0;}
		bool isValid() const {return id != 0;}
		PIString displayText() const {return name.trimmed() + " (" + version.trimmed() + ", " + profile.trimmed() + ")";}
		void * id;
		PIString name;
		PIString vendor;
		PIString profile;
		PIString version;
		PIStringList extensions;
		PIVector<Device> devices;
	};


	class PIP_OPENCL_EXPORT Context {
		friend class Buffer;
		friend class Program;
		friend class Kernel;
	public:
		~Context();
		void * handle();
		void * queue();
		static Context * create(const DeviceList & dl);
		static Context * create(const Device & d) {return create(DeviceList() << d);}
		static Context * create(const PIString & part_name);
		Program * createProgram(const PIString & source, const PIStringList & args = PIStringList(), PIString * error = 0);
		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());
		}
		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());
		}
		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());
		}
		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)
	};


	class PIP_OPENCL_EXPORT Buffer {
		friend class Context;
		friend class Kernel;
	public:
		~Buffer();
		void * handle();
		bool resize(uint new_elements);
		void clear();
		void copyToContainer();
		void copyTo(void * data);
		void copyTo(void * data, int elements_count, int elements_offset = 0);
		void copyFromContainer();
		void copyFrom(void * data);
		void copyFrom(void * data, int elements_count, int elements_offset = 0);
		uint elementsCount() const {return elements;}
	private:
		enum Container {
			cNone,
			cVector,
			cDeque,
			cVector2D,
		};
		Buffer();
		void zero();
		bool init();
		void * containerData();
		Context * context_;
		Direction dir;
		Container type;
		void * container;
		PIByteArray def;
		uint elements;
		PRIVATE_DECLARATION(PIP_OPENCL_EXPORT)
	};


	class PIP_OPENCL_EXPORT Program {
		friend class Context;
		friend class Kernel;
		friend class Buffer;
	public:
		~Program();
		Context * context() const {return context_;}
		const PIString & sourceCode() const {return source_;}
		Kernel * kernel(int index = 0) const {return kernels_[index];}
		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)
	};


	class PIP_OPENCL_EXPORT Kernel {
		friend class Program;
		friend class Buffer;
	public:
		Program * program() const {return program_;}
		bool execute();
		void setExecuteRange(int size) {setExecuteRanges(PIVector<int>() << size);}
		void setExecuteRanges(const PIVector<int> & ranges);
		const PIString & name() const {return name_;}
		const PIVector<KernelArg> & args() const {return args_;}
		template <typename T> bool setArgValue(int index, const T & value) {return setArgValueS(index, PIVariant::fromValue(value));}
		template <typename T> bool setArgValue(const PIString & arg, const T & value) {return setArgValue(argIndex(arg), value);}
		bool setArgValue(const PIString & arg, const PIVariant & value) {return setArgValueS(argIndex(arg), value);}
		bool bindArgValue(int index, Buffer * buffer);
		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)
	};


	static void init();
	static const PIVector<Platform> & platforms();
	static const PIVector<Device> devices();
	static Device deviceByID(void * id);
	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_;
	};
};


PIP_OPENCL_EXPORT PICout operator <<(PICout s, const PIOpenCL::KernelArg & v);


#endif // PIOPENCL_H
