/*
								+--------------------------------+
								|                                |
								|  ***  OpenCL auto-tuning  ***  |
								|                                |
								|  Copyright © -tHE SWINe- 2016  |
								|                                |
								|         AutotuneInfo.h         |
								|                                |
								+--------------------------------+
*/

#pragma once
#ifndef __OPENCL_AUTOTUNING_KERNEL_INFO_STRUCT_INCLUDED
#define __OPENCL_AUTOTUNING_KERNEL_INFO_STRUCT_INCLUDED

/**
 *	@file gpgpu/AutotuneInfo.h
 *	@date 2016
 *	@author -tHE SWINe-
 *	@brief contains only the autotune info storage required by implementations making use of autotuned kernels
 */

#include "ClUtils.h" // needed for CCLDeviceClassId below
#include "../StlUtils.h"

#if defined(_WIN32) || defined(_WIN64)
#define strncasecmp(a,b,n) _strnicmp((a), (b), (n))
#define strcasecmp(a,b) _stricmp((a), (b))
#endif // _WIN32 || _WIN64

// todo - make a workgroup size / tile size structure, std::pair is error prone

/**
 *	@brief a simple API-agnostic kernel tuning information
 *
 *	@tparam _n_max_specialization_num is the number of tuning specializations
 *	@tparam TTuningData is type for storing the tuning paramteters
 *	@tparam TConfigurationDescriptor is type for storing the configuration thresholds
 *	@tparam CConfigurationCompare is type for less-than comparison of the
 *		configuration and a threshold
 */
template <const unsigned int _n_max_specialization_num = 3,
	class TTuningData = std::pair<size_t, size_t>, // workgroup size / tile size
	class TConfigurationDescriptor = size_t, // input data size
	class CConfigurationCompare = std::less<size_t>, // input data size comparison
	class TAlgorithmSpecializationDescriptor = size_t> // algorithm specialization (e.g. data type size, data type, operation performed)
struct TKernelAutotuneInfo {
	/**
	 *	@brief parameters stored as enum
	 */
	enum {
		n_max_specialization_num = _n_max_specialization_num /**< @brief number of tuning specializations */
	};

	typedef TTuningData _TyTuningData; /**< @brief type for storing the tuning paramteters */
	typedef TConfigurationDescriptor _TyConfigurationDescriptor; /**< @brief type for storing the configuration thresholds */
	typedef CConfigurationCompare _TyConfigurationCompare; /**< @brief type for less-than comparison of the configuration and a threshold */
	typedef TAlgorithmSpecializationDescriptor _TyAlgorithmSpecializationDescriptor; /**< @brief type for storïng algorithm specialization (data type size by default) */

	const char *p_s_device_id; /**< @brief device id as returned by \ref CCLDeviceClassId::Get() */
	_TyAlgorithmSpecializationDescriptor t_algorithm_specialization; /**< @brief algorithm specialization */
	// primary key

	size_t n_tuning_num; /**< @brief the number of actual different tunings (up to n_max_specialization_num) */ // need to store because of the compiler but could be reconstructed from p_tuning_index
	_TyTuningData p_tuning[n_max_specialization_num]; /**< @brief the employed tunings parameters (only the first n_tuning_num entries are significant) */ // workgroup size for each input size
	// palette of tunings

	_TyConfigurationDescriptor p_configuration[(n_max_specialization_num == 1)? 1 : n_max_specialization_num - 1]; /**< @brief sorted configuration space thresholds (one less than n_max_specialization_num) */ // input data size, the last one is always SIZE_MAX (avoids zero-sized arrays)
	size_t p_tuning_index[n_max_specialization_num]; /**< @brief indices into the p_tuning array, pointing to a tuning employed by the configurations up to each threshold */ // index to p_tuning
	// secondary key

	/**
	 *	@brief default constructor; initializes members to zero
	 */
	TKernelAutotuneInfo()
		:p_s_device_id(""), t_algorithm_specialization(), n_tuning_num(0)
	{
		for(int i = 0; i < n_max_specialization_num; ++ i) {
			p_configuration[i] = _TyConfigurationDescriptor();
			p_tuning_index[i] = 0;
		}
	}

	/**
	 *	@brief constructor for size 2 autotunings
	 */
	TKernelAutotuneInfo(const char *_p_s_device_id,
		_TyAlgorithmSpecializationDescriptor _t_algorithm_specialization,
		const TConfigurationDescriptor &r_config_0, size_t n_tuning_index_0,
		const TConfigurationDescriptor &r_config_1, size_t n_tuning_index_1,
		size_t _n_tuning_num, const _TyTuningData &r_tuning_0,
		const _TyTuningData &r_tuning_1)
		:p_s_device_id(_p_s_device_id), t_algorithm_specialization(_t_algorithm_specialization),
		n_tuning_num(_n_tuning_num)
	{
		_ASSERTE(2 == unsigned(n_max_specialization_num)); // must match, not even stored
		_ASSERTE(_n_tuning_num <= unsigned(n_max_specialization_num)); // must fit, can be lower

		p_tuning[0] = r_tuning_0;
		p_tuning[1] = r_tuning_1;
		p_configuration[0] = r_config_0;
		_ASSERTE(n_tuning_index_0 < _n_tuning_num);
		p_tuning_index[0] = n_tuning_index_0;
		_ASSERTE(n_tuning_index_1 < _n_tuning_num);
		p_tuning_index[1] = n_tuning_index_1;
	}

	/**
	 *	@brief constructor for size 3 autotunings
	 */
	TKernelAutotuneInfo(const char *_p_s_device_id,
		_TyAlgorithmSpecializationDescriptor _t_algorithm_specialization,
		const TConfigurationDescriptor &r_config_0, size_t n_tuning_index_0,
		const TConfigurationDescriptor &r_config_1, size_t n_tuning_index_1,
		const TConfigurationDescriptor &r_config_2, size_t n_tuning_index_2,
		size_t _n_tuning_num, const _TyTuningData &r_tuning_0,
		const _TyTuningData &r_tuning_1, const _TyTuningData &r_tuning_2)
		:p_s_device_id(_p_s_device_id), t_algorithm_specialization(_t_algorithm_specialization),
		n_tuning_num(_n_tuning_num)
	{
		_ASSERTE(3 == unsigned(n_max_specialization_num)); // must match, not even stored
		_ASSERTE(_n_tuning_num <= unsigned(n_max_specialization_num)); // must fit, can be lower

		p_tuning[0] = r_tuning_0;
		p_tuning[1] = r_tuning_1;
		p_tuning[2] = r_tuning_2;
		p_configuration[0] = r_config_0;
		p_configuration[1] = r_config_1;
		_ASSERTE(!_TyConfigurationCompare()(r_config_1, r_config_0)); // make sure r_config_0 <= r_config_1
		_ASSERTE(n_tuning_index_0 < _n_tuning_num);
		p_tuning_index[0] = n_tuning_index_0;
		_ASSERTE(n_tuning_index_1 < _n_tuning_num);
		p_tuning_index[1] = n_tuning_index_1;
		_ASSERTE(n_tuning_index_2 < _n_tuning_num);
		p_tuning_index[2] = n_tuning_index_2;
	}

	/**
	 *	@brief constructor for size 4 autotunings
	 */
	TKernelAutotuneInfo(const char *_p_s_device_id,
		_TyAlgorithmSpecializationDescriptor _t_algorithm_specialization,
		const TConfigurationDescriptor &r_config_0, size_t n_tuning_index_0,
		const TConfigurationDescriptor &r_config_1, size_t n_tuning_index_1,
		const TConfigurationDescriptor &r_config_2, size_t n_tuning_index_2,
		const TConfigurationDescriptor &r_config_3, size_t n_tuning_index_3,
		size_t _n_tuning_num, const _TyTuningData &r_tuning_0,
		const _TyTuningData &r_tuning_1, const _TyTuningData &r_tuning_2,
		const _TyTuningData &r_tuning_3)
		:p_s_device_id(_p_s_device_id), t_algorithm_specialization(_t_algorithm_specialization),
		n_tuning_num(_n_tuning_num)
	{
		_ASSERTE(4 == unsigned(n_max_specialization_num)); // must match, not even stored
		_ASSERTE(_n_tuning_num <= unsigned(n_max_specialization_num)); // must fit, can be lower

		p_tuning[0] = r_tuning_0;
		p_tuning[1] = r_tuning_1;
		p_tuning[2] = r_tuning_2;
		p_tuning[3] = r_tuning_3;
		p_configuration[0] = r_config_0;
		p_configuration[1] = r_config_1;
		_ASSERTE(!_TyConfigurationCompare()(r_config_1, r_config_0)); // make sure r_config_0 <= r_config_1
		p_configuration[2] = r_config_2;
		_ASSERTE(!_TyConfigurationCompare()(r_config_2, r_config_1)); // make sure r_config_1 <= r_config_2
		_ASSERTE(n_tuning_index_0 < _n_tuning_num);
		p_tuning_index[0] = n_tuning_index_0;
		_ASSERTE(n_tuning_index_1 < _n_tuning_num);
		p_tuning_index[1] = n_tuning_index_1;
		_ASSERTE(n_tuning_index_2 < _n_tuning_num);
		p_tuning_index[2] = n_tuning_index_2;
		_ASSERTE(n_tuning_index_3 < _n_tuning_num);
		p_tuning_index[3] = n_tuning_index_3;
	}

	/**
	 *	@brief constructor for generic size autotunings
	 */
	TKernelAutotuneInfo(const char *_p_s_device_id,
		_TyAlgorithmSpecializationDescriptor _t_algorithm_specialization,
		size_t n_specialization_num, const TConfigurationDescriptor *_p_configuration,
		const size_t *_p_tuning_index, size_t _n_tuning_num, const _TyTuningData *_p_tuning)
		:p_s_device_id(_p_s_device_id), t_algorithm_specialization(_t_algorithm_specialization),
		n_tuning_num(_n_tuning_num)
	{
		_ASSERTE(n_specialization_num == unsigned(n_max_specialization_num)); // must match, not even stored
		_ASSERTE(_n_tuning_num <= unsigned(n_max_specialization_num)); // must fit, can be lower

		for(size_t i = 0; i < _n_tuning_num; ++ i)
			p_tuning[i] = _p_tuning[i];
		for(size_t i = 0; i < n_specialization_num - 1; ++ i) { // the last one is ignored and not even stored
			p_configuration[i] = _p_configuration[i];
			_ASSERTE(!i || !_TyConfigurationCompare()(p_configuration[i], p_configuration[i - 1]));
			// make sure the sequence is sorted or at least nondecreasing
		}
		for(size_t i = 0; i < n_specialization_num; ++ i) {
			_ASSERTE(_p_tuning_index[i] < _n_tuning_num);
			p_tuning_index[i] = _p_tuning_index[i];
		}
	}

	/**
	 *	@brief comparison operator for std::find()
	 */
	bool operator ==(const std::pair<const char*,
		_TyAlgorithmSpecializationDescriptor> &r_device_id_algorithm_specialization) const
	{
		return !strcasecmp(p_s_device_id, r_device_id_algorithm_specialization.first) &&
			r_device_id_algorithm_specialization.second == t_algorithm_specialization;
	}

	/**
	 *	@brief comparison operator for std::find()
	 */
	bool operator ==(const std::pair<std::string,
		_TyAlgorithmSpecializationDescriptor> &r_device_id_algorithm_specialization) const
	{
		return !strcasecmp(p_s_device_id, r_device_id_algorithm_specialization.first.c_str()) &&
			r_device_id_algorithm_specialization.second == t_algorithm_specialization;
	}

	/**
	 *	@brief comparison operator for std::find()
	 */
	bool operator ==(const char *_p_s_device_id) const
	{
		return !strcasecmp(p_s_device_id, _p_s_device_id);
	}

	/**
	 *	@brief comparison operator for std::find()
	 */
	bool operator ==(const std::string &r_s_device_id) const
	{
		return !strcasecmp(p_s_device_id, r_s_device_id.c_str());
	}

	/**
	 *	@brief lookup for tuning to use, given an input configuration
	 */
	inline size_t n_Tuning(_TyConfigurationDescriptor n_configuration) const
	{
		size_t n_off = std::lower_bound(p_configuration, p_configuration +
			(n_max_specialization_num - 1), n_configuration,
			_TyConfigurationCompare()) - p_configuration;
		_ASSERTE(n_off < n_max_specialization_num);
		return p_tuning_index[n_off];
	}
};

/**
 *	@brief a simple class id for NVIDIA GPU devices
 */
class CCLDeviceClassId {
public:
	static bool Get(std::string &r_s_class_id, const CCLDeviceParams &r_dev)
	{
		r_s_class_id.clear(); // !!
		if(!r_dev.Get_SafeName(r_s_class_id, false, '_'))
			return false;
		std::transform(r_s_class_id.begin(), r_s_class_id.end(), r_s_class_id.begin(), tolower);
		// get device name without spaces and other strange characters (replaced by underscores)

		if(r_dev.b_Is_NVIDIA()) {
			int n_nv_cc_major = r_dev.n_NV_ComputeCap_Major(),
				n_nv_cc_minor = r_dev.n_NV_ComputeCap_Minor();
			bool b_geforce = r_s_class_id.find("geforce") != std::string::npos ||
				r_s_class_id.find("gtx") != std::string::npos;
			bool b_tesla = r_s_class_id.find("tesla") != std::string::npos ||
				r_s_class_id.find("grid") != std::string::npos;
			bool b_quadro = r_s_class_id.find("quadro") != std::string::npos;
			bool b_mobile = !r_s_class_id.empty() && *(r_s_class_id.end() - 1) == 'm';
			// get device type

			return stl_ut::Format(r_s_class_id, "nv_%d_%d_%s%s",
				n_nv_cc_major, n_nv_cc_minor, (b_geforce)? "gf" : (b_tesla)? "tes" :
				(b_quadro)? "qdr" : "gen", (b_mobile)? "m" : "");
		} else
			return true; // just use the device name (already in r_s_class_id)
	}
};

#endif // __OPENCL_AUTOTUNING_KERNEL_INFO_STRUCT_INCLUDED
