#include "OpenCLWrapperClass.h"


OpenCLWrapperClass::OpenCLWrapperClass():
    use_device(0),
    openCL_device_num(0)
{
    this->getOpenCLDeviceNum();
}


OpenCLWrapperClass::~OpenCLWrapperClass()
{
    delete [] ocl;
}


void OpenCLWrapperClass::getOpenCLDeviceNum()
{try{
    cl_int status;
    // プラットフォームの数を取得する
    cl_uint num_platforms;
    status = clGetPlatformIDs(0,
                              NULL,
                              &num_platforms);
    if(status != CL_SUCCESS)throw clErrorClass(status, "get num of platforms failed.", __FUNCTION__);
    if(num_platforms){
        std::cout << num_platforms << " platforms found." << std::endl;
    }else{
        throw clErrorClass(-1, "cl_platforms not found.", __FUNCTION__);
    }

    // プラットフォームのIDを取得する
    cl_platform_id *platform_id = new cl_platform_id[num_platforms];
    status = clGetPlatformIDs(num_platforms,
                              platform_id,
                              NULL);
    if(status != CL_SUCCESS)throw clErrorClass(status, "clGetPlatformIds faild", __FUNCTION__);

    // デバイスの総数を取得する
    cl_uint num_devices;
    cl_uint num_devices_all=0;
    cl_uint *count_devices = new cl_uint[num_platforms];
    for(unsigned int i=0; i<num_platforms; ++i){
        status = clGetDeviceIDs(platform_id[i],
                                CL_DEVICE_TYPE_ALL,
                                0,
                                NULL,
                                &num_devices);
        if(status != CL_SUCCESS)throw clErrorClass(status, "get num of devices failed.", __FUNCTION__);
        count_devices[i] = num_devices;
        num_devices_all += num_devices;
    }
    if(count_devices){
        std::cout << num_devices_all << " devices found." << std::endl;
    }else{
        delete [] platform_id;
        delete [] count_devices;
        throw clErrorClass(-1, "cl_device not found.", __FUNCTION__);
    }

    // デバイスのIDをすべて取得する
    cl_device_id *device_id = new cl_device_id[num_devices_all];
    unsigned int index=0;
    for(unsigned int i=0; i<num_platforms; ++i){
        // そのプラットフォームに存在するデバイスの数だけ領域を確保
        cl_device_id *tmp_d = new cl_device_id[count_devices[i]];
        status = clGetDeviceIDs(platform_id[i],
                                CL_DEVICE_TYPE_ALL,
                                count_devices[i],
                                tmp_d,
                                NULL);
        if(status != CL_SUCCESS){
            delete [] device_id;
            delete [] platform_id;
            delete [] count_devices;
            throw clErrorClass(status, "get deviceIDs failed.", __FUNCTION__);
        }
        for(unsigned int j=0; j<count_devices[i]; ++j){
            device_id[index++] = tmp_d[j];
        }
        delete [] tmp_d;
    }
    for(unsigned int i=0; i<num_devices_all; ++i){
        std::cout << "device " << i << ", ID = " << device_id[i] << std::endl;
    }

    // 求まったOpenCLデバイスの数をクラス変数に格納
    this->openCL_device_num = num_devices_all;

    // OpenCLのデバイスIDをもとにクラスをセットする
    ocl = new clClass[this->openCL_device_num];
    for(unsigned int i=0; i< this->openCL_device_num; ++i){
        ocl[i].set(device_id[i]);
    }

    delete [] device_id;
    delete [] count_devices;
    delete [] platform_id;
}
catch(clErrorClass &err){err.showErrMsg();}catch(...){std::cerr << "unknown error." << std::endl;}}


unsigned int OpenCLWrapperClass::getUsableOpenCLDeviceNum() const
{
    return this->openCL_device_num;
}

void OpenCLWrapperClass::setUseDevice(unsigned int num)
{
    if(num >= this->openCL_device_num){
        std::cerr << "OpenCL device not so many." << std::endl;
        return;
    }
    this->use_device = num;
}

const
unsigned int OpenCLWrapperClass::getUseDevice() const
{
    return this->use_device;
}













// プラットフォーム
const 
char *OpenCLWrapperClass::getPlatformInfo(cl_platform_info platform_info) const
{
    return this->ocl[this->use_device].getPlatformInfo(platform_info);
}

// -------------------------------------------------------
//! \brief  プラットフォームの詳細情報を表示する.
//! \param  platform_info  知りたい情報.
//! \return なし.
void OpenCLWrapperClass::showPlatformInfo(cl_platform_info platform_info) const
{
    this->ocl[this->use_device].showPlatformInfo(platform_info);
}

// -------------------------------------------------------
//! \brief  プラットフォームの詳細情報をすべて表示する.
//! \param  platform_index プラットフォームの参照番号.
//! \return なし.
void OpenCLWrapperClass::showPlatformInfoAll() const
{
    this->ocl[this->use_device].showPlatformInfoAll();
}





// デバイス

// -------------------------------------------------------
//! \brief  デバイスの詳細情報を取得する.
//! \param  platform_info  知りたい情報.
//! \return デバイスの詳細情報を返却する.
const
cl_device_id OpenCLWrapperClass::getDeviceID() const
{
    return this->ocl[this->use_device].getDeviceId();
}

// -------------------------------------------------------
//! \brief  デバイスの詳細情報を取得する.
//! \param  platform_info  知りたい情報.
//! \return デバイスの詳細情報を返却する.
const 
char* OpenCLWrapperClass::getDeviceInfo(cl_device_info device_info) const
{
    return this->ocl[this->use_device].getDeviceInfo(device_info);
}


// -------------------------------------------------------
//! \brief  デバイスの詳細情報を取得する.
//! \param  platform_info  知りたい情報.
//! \return デバイスの詳細情報を返却する.
void OpenCLWrapperClass::showDeviceInfo(cl_device_info device_info) const
{
    this->ocl[this->use_device].showDeviceInfo(device_info);
}

// -------------------------------------------------------
//! \brief  デバイスの詳細情報を表示する.
//! \param  なし.
//! \return なし.
void OpenCLWrapperClass::showDeviceInfoAll() const
{
    this->ocl[this->use_device].showDeviceInfoAll();
}


// コンテキスト
// -------------------------------------------------------
//! \brief  コンテキストを取得する.
//! \param  なし.
//! \return コンテキストを返却する.
const 
cl_context OpenCLWrapperClass::getContext() const
{
    return this->ocl[this->use_device].getContext();
}



// プログラムオブジェクト
void OpenCLWrapperClass::createProgramObject(const char *filename)
{
    for(unsigned int i=0; i < this->openCL_device_num; ++i){
        this->ocl[i].createProgramObject(filename);
    }
}




// カーネル
void OpenCLWrapperClass::createKernel(const char *file_name ,const char *kernel_name)
{
    for(unsigned int i=0; i < this->openCL_device_num; ++i){
        this->ocl[i].createKernel(file_name, kernel_name);
    }
}

//! \brief  カーネルを返却する.
//! \param  なし.
//! \return カーネルを返却する.
const
cl_kernel OpenCLWrapperClass::getKernel(std::string kernel_name) const
{
    return this->ocl[this->use_device].getKernel(kernel_name);
}






// コマンドキュー
//! \brief  コマンドキューを取得する.
//! \param  なし.
//! \return コマンドキューを返却する.
const 
cl_command_queue OpenCLWrapperClass::getCommandQueue() const
{
    return this->ocl[this->use_device].getCommandQueue();
}





// メモリー
void OpenCLWrapperClass::createMemory(cl_mem_flags flag, size_t memory_size, void *mem_pointer,std::string mem_name)
{
    this->ocl[this->use_device].createMemory(flag,
                                             memory_size,
                                             mem_pointer,
                                             mem_name);
}
const
cl_mem *OpenCLWrapperClass::getMemory(std::string name)
{
    return this->ocl[this->use_device].getMemory(name);
}





// カーネルの実行
void OpenCLWrapperClass::runKernel(std::string       kernel_name, 
                                   cl_uint           num_dimension, 
                                   const size_t      *offset,
                                   size_t            *global_work_size, 
                                   size_t            *local_work_size,
                                   cl_uint           num_event,
                                   const cl_event    *event_list,
                                   cl_event          *event_ ) const
{
    this->ocl[this->use_device].runKernel(kernel_name,
                                          num_dimension,
                                          offset,
                                          global_work_size,
                                          local_work_size,
                                          num_event,
                                          event_list,
                                          event_ );
}


void OpenCLWrapperClass::runKernel(std::string       kernel_name, 
                                   cl_uint           num_dimension, 
                                   size_t            *global_work_size, 
                                   size_t            *local_work_size,
                                   cl_uint           num_event,
                                   const cl_event    *event_list,
                                   cl_event          *event_ ) const
{
    this->ocl[this->use_device].runKernel(kernel_name,
                                          num_dimension,
                                          global_work_size,
                                          local_work_size,
                                          num_event,
                                          event_list,
                                          event_ );
}


void OpenCLWrapperClass::runKernel(std::string       kernel_name, 
                                   cl_uint           num_dimension, 
                                   const size_t      *offset,
                                   size_t            *global_work_size, 
                                   size_t            *local_work_size) const
{
    this->ocl[this->use_device].runKernel(kernel_name,
                                          num_dimension,
                                          offset,
                                          global_work_size,
                                          local_work_size);
}


void OpenCLWrapperClass::runKernel(std::string        kernel_name, 
                                   cl_uint            num_dimension, 
                                   size_t            *global_work_size, 
                                   size_t            *local_work_size) const
{
    this->ocl[this->use_device].runKernel(kernel_name,
                                          num_dimension,
                                          global_work_size,
                                          local_work_size);
}




// 実行結果を取得する
void OpenCLWrapperClass::getResult(std::string        kernel_name, 
                                   std::string        src_memory_name,
                                   cl_bool            block,
                                   size_t             offset,
                                   size_t             memory_size,
                                   void               *dst_memory,
                                   cl_uint            num_event,
                                   const cl_event     *event_list,
                                   cl_event           *event_ ) const
{
    this->ocl[this->use_device].getResult(kernel_name,
                                          src_memory_name,
                                          block,
                                          offset,
                                          memory_size,
                                          dst_memory,
                                          num_event,
                                          event_list,
                                          event_ );
}

// オーバーロード2
void OpenCLWrapperClass::getResult(std::string        kernel_name, 
                                   std::string        src_memory_name,
                                   cl_bool            block,
                                   size_t             memory_size,
                                   void               *dst_memory,
                                   cl_uint            num_event,
                                   const cl_event     *event_list,
                                   cl_event           *event_ ) const
{
    this->ocl[this->use_device].getResult(kernel_name,
                                          src_memory_name,
                                          block,
                                          memory_size,
                                          dst_memory,
                                          num_event,
                                          event_list,
                                          event_ );
}

// オーバーロード3
void OpenCLWrapperClass::getResult(std::string        kernel_name, 
                                   std::string        src_memory_name,
                                   size_t             memory_size,
                                   void               *dst_memory,
                                   cl_uint            num_event,
                                   const cl_event     *event_list,
                                   cl_event           *event_ ) const
{
    this->ocl[this->use_device].getResult(kernel_name,
                                          src_memory_name,
                                          memory_size,
                                          dst_memory,
                                          num_event,
                                          event_list,
                                          event_ );
}

// オーバーロード4
void OpenCLWrapperClass::getResult(std::string        kernel_name, 
                                   std::string        src_memory_name,
                                   cl_bool            block,
                                   size_t             offset,
                                   size_t             memory_size,
                                   void               *dst_memory) const
{
    this->ocl[this->use_device].getResult(kernel_name,
                                          src_memory_name,
                                          block,
                                          offset,
                                          memory_size,
                                          dst_memory);
}

// オーバーロード5
void OpenCLWrapperClass::getResult(std::string        kernel_name, 
                                   std::string        src_memory_name,
                                   cl_bool            block,
                                   size_t             memory_size,
                                   void               *dst_memory) const
{
    this->ocl[this->use_device].getResult(kernel_name,
                                          src_memory_name,
                                          block,
                                          0,
                                          memory_size,
                                          dst_memory);
}

// オーバーロード6
void OpenCLWrapperClass::getResult(std::string        kernel_name, 
                                   std::string        src_memory_name,
                                   size_t             memory_size,
                                   void               *dst_memory) const
{
    cl_int status;
    status = clEnqueueReadBuffer(ocl[this->use_device].getCommandQueue(),
                                 *ocl[this->use_device].getMemory(src_memory_name), // 受け取る結果が格納されているメモリオブジェクト(src)
                                 CL_TRUE,                                           // ブロッキングするかしないか
                                 0,                                                 // 読み込みデータのオフセット
                                 memory_size,                                       // 読み込むデータのサイズ
                                 dst_memory,                                        // 結果を受け取る配列(dst)
                                 0,                                                 // イベントの数
                                 NULL,                                              // イベントのリスト
                                 NULL);                                             // イベントへのポインタ
    if(status != CL_SUCCESS){
        clErrorClass err(status, "clEnqueueReadBuffer failed.", __FUNCTION__);
        err.showErrMsg();
    }
}
