/* ************************************************************************
 * Copyright 2015 Vratis, Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ************************************************************************ */

#include "opencl_utils.h"
#include <cassert>

cl_int getPlatforms( cl_platform_id **platforms, cl_uint* num_platforms )
{
    cl_int status = CL_SUCCESS;

    status = clGetPlatformIDs( 0, NULL, num_platforms );

    *platforms =
        (cl_platform_id*)malloc( *num_platforms * sizeof( cl_platform_id ) );

    status = clGetPlatformIDs( *num_platforms, *platforms, NULL );
    if( status != CL_SUCCESS )
    {
        std::cout << "Problem with getting platfofm values" << std::endl;
        return status;
    }
    return status;
}

void printPlatforms( const cl_platform_id* platforms,
                     const cl_uint num_platforms )
{
    const char* attributeNames[ 5 ] = { "Name", "Vendor", "Version", "Profile",
        "Extensions" };

    const cl_platform_info attributeTypes[ 5 ] =
    { CL_PLATFORM_NAME, CL_PLATFORM_VENDOR, CL_PLATFORM_VERSION,
    CL_PLATFORM_PROFILE, CL_PLATFORM_EXTENSIONS };

    const int attributeCount = sizeof( attributeNames ) / sizeof( char* );


    // for each platform print all attributes
    for( cl_uint i = 0; i < num_platforms; i++ )
    {
        std::cout << "\n" << i + 1 << ". Platform(id = " << i << ")" << std::endl;

        for( int j = 0; j < attributeCount; j++ ) {

            size_t infoSize;
            char* info;
            // get platform attribute value size
            clGetPlatformInfo( platforms[ i ], attributeTypes[ j ], 0, NULL, &infoSize );
            info = (char*)malloc( infoSize );

            // get platform attribute value
            clGetPlatformInfo( platforms[ i ], attributeTypes[ j ], infoSize, info, NULL );

            printf( "  %d.%d %-11s: %s\n", i + 1, j + 1, attributeNames[ j ], info );

            free( info );
        }
        printf( "\n" );
    }

}

cl::Device getDevice( cl_platform_type pID, cl_uint dID )
{
    std::vector<cl::Platform> platforms;
    cl::Platform::get( &platforms );

    assert( platforms.size( ) > 0 );

    std::map<std::string, int> pNames;

    //search for AMD or NVIDIA
    cl_int pIndex = -1;
    for( const auto& p : platforms )
    {
        // getInfo<CL_PLATFORM_NAME> can return strings on Nvidia platforms (352.79) that
        // return null terminated char* in strange format "blabla\000"
        // I don't know how to properly trim the string, but we can truncate platform to the first word
        std::string pName = p.getInfo<CL_PLATFORM_NAME>( );
        size_t first_word_boundary = pName.find_first_of( " \t\r\n" );
        pNames.insert( std::make_pair( pName.substr( 0, first_word_boundary ), ++pIndex ) );
    }

    //get index of desired platform;
    std::string desired_platform_name;
    if( pID == AMD )
    {
        desired_platform_name = amd_platform_str;
    }
    else if( pID == NVIDIA )
    {
        desired_platform_name = nvidia_platform_str;
    }
    else
    {
        throw std::string( "No such platform pID: " + std::to_string( pID ) );
    }

    auto pIterator = pNames.find( desired_platform_name );
    if( pIterator != pNames.end( ) )
    {
        std::cout << pIterator->first
            << " " << pIterator->second << std::endl;
        pIndex = pIterator->second;
    }
    else
    {
        throw std::string( desired_platform_name + " was not found" );
    }

    std::vector<cl::Device> devices;
    platforms[ pIndex ].getDevices( CL_DEVICE_TYPE_GPU, &devices );

    assert( dID < devices.size( ) );

    cl::Device device = devices[ dID ];

    return device;

}

//get the first available device from given platform
cl_int getDevice( const cl_platform_id platform,
                  cl_device_id* device,
                  cl_device_type type )
{
    cl_int status;
    cl_uint num_devices = 0;

    //get count of given device types
    status = clGetDeviceIDs( platform, type, 0, NULL, &num_devices );
    if( status != CL_SUCCESS )
        return status;

    cl_device_id* devices = (cl_device_id*)
        malloc( num_devices*sizeof( cl_device_id ) );
    status = clGetDeviceIDs( platform, type, num_devices, devices, NULL );
    if( status != CL_SUCCESS )
        return status;

    *device = devices[ 0 ];

    free( devices );
    return status;
}

void printDeviceInfo( const cl_device_id device )
{
    size_t size;
    char* value;

    clGetDeviceInfo( device, CL_DEVICE_NAME, 0, NULL, &size );
    value = (char*)malloc( size );
    clGetDeviceInfo( device, CL_DEVICE_NAME, size, value, NULL );
    std::cout << "Device Name: " << value << std::endl;
    free( value );
}
