/* ============================================================

   Copyright (c) 2009 Advanced Micro Devices, Inc.  All rights reserved.
 
   Redistribution and use of this material is permitted under the following 
   conditions:
 
   Redistributions must retain the above copyright notice and all terms of this 
   license.
 
   In no event shall anyone redistributing or accessing or using this material 
   commence or participate in any arbitration or legal action relating to this 
   material against Advanced Micro Devices, Inc. or any copyright holders or 
   contributors. The foregoing shall survive any expiration or termination of 
   this license or any agreement or access or use related to this material. 

   ANY BREACH OF ANY TERM OF THIS LICENSE SHALL RESULT IN THE IMMEDIATE REVOCATION 
   OF ALL RIGHTS TO REDISTRIBUTE, ACCESS OR USE THIS MATERIAL.

   THIS MATERIAL IS PROVIDED BY ADVANCED MICRO DEVICES, INC. AND ANY COPYRIGHT 
   HOLDERS AND CONTRIBUTORS "AS IS" IN ITS CURRENT CONDITION AND WITHOUT ANY 
   REPRESENTATIONS, GUARANTEE, OR WARRANTY OF ANY KIND OR IN ANY WAY RELATED TO 
   SUPPORT, INDEMNITY, ERROR FREE OR UNINTERRUPTED OPERA TION, OR THAT IT IS FREE 
   FROM DEFECTS OR VIRUSES.  ALL OBLIGATIONS ARE HEREBY DISCLAIMED - WHETHER 
   EXPRESS, IMPLIED, OR STATUTORY - INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED 
   WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, 
   ACCURACY, COMPLETENESS, OPERABILITY, QUALITY OF SERVICE, OR NON-INFRINGEMENT. 
   IN NO EVENT SHALL ADVANCED MICRO DEVICES, INC. OR ANY COPYRIGHT HOLDERS OR 
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, PUNITIVE,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
   OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, REVENUE, DATA, OR PROFITS; OR 
   BUSINESS INTERRUPTION) HOWEVER CAUSED OR BASED ON ANY THEORY OF LIABILITY 
   ARISING IN ANY WAY RELATED TO THIS MATERIAL, EVEN IF ADVISED OF THE POSSIBILITY 
   OF SUCH DAMAGE. THE ENTIRE AND AGGREGATE LIABILITY OF ADVANCED MICRO DEVICES, 
   INC. AND ANY COPYRIGHT HOLDERS AND CONTRIBUTORS SHALL NOT EXCEED TEN DOLLARS 
   (US $10.00). ANYONE REDISTRIBUTING OR ACCESSING OR USING THIS MATERIAL ACCEPTS 
   THIS ALLOCATION OF RISK AND AGREES TO RELEASE ADVANCED MICRO DEVICES, INC. AND 
   ANY COPYRIGHT HOLDERS AND CONTRIBUTORS FROM ANY AND ALL LIABILITIES, 
   OBLIGATIONS, CLAIMS, OR DEMANDS IN EXCESS OF TEN DOLLARS (US $10.00). THE 
   FOREGOING ARE ESSENTIAL TERMS OF THIS LICENSE AND, IF ANY OF THESE TERMS ARE 
   CONSTRUED AS UNENFORCEABLE, FAIL IN ESSENTIAL PURPOSE, OR BECOME VOID OR 
   DETRIMENTAL TO ADVANCED MICRO DEVICES, INC. OR ANY COPYRIGHT HOLDERS OR 
   CONTRIBUTORS FOR ANY REASON, THEN ALL RIGHTS TO REDISTRIBUTE, ACCESS OR USE 
   THIS MATERIAL SHALL TERMINATE IMMEDIATELY. MOREOVER, THE FOREGOING SHALL 
   SURVIVE ANY EXPIRATION OR TERMINATION OF THIS LICENSE OR ANY AGREEMENT OR 
   ACCESS OR USE RELATED TO THIS MATERIAL.

   NOTICE IS HEREBY PROVIDED, AND BY REDISTRIBUTING OR ACCESSING OR USING THIS 
   MATERIAL SUCH NOTICE IS ACKNOWLEDGED, THAT THIS MATERIAL MAY BE SUBJECT TO 
   RESTRICTIONS UNDER THE LAWS AND REGULATIONS OF THE UNITED STATES OR OTHER 
   COUNTRIES, WHICH INCLUDE BUT ARE NOT LIMITED TO, U.S. EXPORT CONTROL LAWS SUCH 
   AS THE EXPORT ADMINISTRATION REGULATIONS AND NATIONAL SECURITY CONTROLS AS 
   DEFINED THEREUNDER, AS WELL AS STATE DEPARTMENT CONTROLS UNDER THE U.S. 
   MUNITIONS LIST. THIS MATERIAL MAY NOT BE USED, RELEASED, TRANSFERRED, IMPORTED,
   EXPORTED AND/OR RE-EXPORTED IN ANY MANNER PROHIBITED UNDER ANY APPLICABLE LAWS, 
   INCLUDING U.S. EXPORT CONTROL LAWS REGARDING SPECIFICALLY DESIGNATED PERSONS, 
   COUNTRIES AND NATIONALS OF COUNTRIES SUBJECT TO NATIONAL SECURITY CONTROLS. 
   MOREOVER, THE FOREGOING SHALL SURVIVE ANY EXPIRATION OR TERMINATION OF ANY 
   LICENSE OR AGREEMENT OR ACCESS OR USE RELATED TO THIS MATERIAL.

   NOTICE REGARDING THE U.S. GOVERNMENT AND DOD AGENCIES: This material is 
   provided with "RESTRICTED RIGHTS" and/or "LIMITED RIGHTS" as applicable to 
   computer software and technical data, respectively. Use, duplication, 
   distribution or disclosure by the U.S. Government and/or DOD agencies is 
   subject to the full extent of restrictions in all applicable regulations, 
   including those found at FAR52.227 and DFARS252.227 et seq. and any successor 
   regulations thereof. Use of this material by the U.S. Government and/or DOD 
   agencies is acknowledgment of the proprietary rights of any copyright holders 
   and contributors, including those of Advanced Micro Devices, Inc., as well as 
   the provisions of FAR52.227-14 through 23 regarding privately developed and/or 
   commercial computer software.

   This license forms the entire agreement regarding the subject matter hereof and 
   supersedes all proposals and prior discussions and writings between the parties 
   with respect thereto. This license does not affect any ownership, rights, title,
   or interest in, or relating to, this material. No terms of this license can be 
   modified or waived, and no breach of this license can be excused, unless done 
   so in a writing signed by all affected parties. Each term of this license is 
   separately enforceable. If any term of this license is determined to be or 
   becomes unenforceable or illegal, such term shall be reformed to the minimum 
   extent necessary in order for this license to remain in effect in accordance 
   with its terms as modified by such reformation. This license shall be governed 
   by and construed in accordance with the laws of the State of Texas without 
   regard to rules on conflicts of law of any state or jurisdiction or the United 
   Nations Convention on the International Sale of Goods. All disputes arising out 
   of this license shall be subject to the jurisdiction of the federal and state 
   courts in Austin, Texas, and all defenses are hereby waived concerning personal 
   jurisdiction and venue of these courts.

   ============================================================ */
#include "plm_config.h"

#include "plm_timer.h"
#include "opencl_test.h"

/*
 * \brief Host Initialization 
 *        Allocate and initialize memory 
 *        on the host. Print input array. 
 */
int
initializeHost(void)
{
    //width				= 256;
    width				= 128;
    input				= NULL;
    output				= NULL;
    multiplier			= 2;

    /////////////////////////////////////////////////////////////////
    // Allocate and initialize memory used by host 
    /////////////////////////////////////////////////////////////////
    cl_uint sizeInBytes = width * sizeof(cl_uint);
    input = (cl_uint *) malloc(sizeInBytes);
    if(input == NULL)
    {
	std::cout<<"Error: Failed to allocate input memory on host\n";
	return 1; 
    }

    output = (cl_uint *) malloc(sizeInBytes);
    if(output == NULL)
    {
	std::cout<<"Error: Failed to allocate output memory on host\n";
	return 1; 
    }

    for(cl_uint i = 0; i < width; i++)
        input[i] = i;

    // print input array
    print1DArray(std::string("Input").c_str(), input, width);
    return 0;
}

/*
 * Converts the contents of a file into a string
 */
std::string
convertToString(const char *filename)
{
    size_t size;
    char*  str;
    std::string s;

    std::fstream f(filename, (std::fstream::in | std::fstream::binary));

    if(f.is_open())
    {
	size_t fileSize;
	f.seekg(0, std::fstream::end);
	size = fileSize = f.tellg();
	f.seekg(0, std::fstream::beg);

	str = new char[size+1];
	if(!str)
	{
	    f.close();
	    return NULL;
	}

	f.read(str, fileSize);
	f.close();
	str[size] = '\0';
	
	s = str;
		
	return s;
    }
    return NULL;
}

/*
 * \brief OpenCL related initialization 
 *        Create Context, Device list, Command Queue
 *        Create OpenCL memory buffer objects
 *        Load CL file, compile, link CL source 
 *		  Build program and kernel objects
 */
int
initializeCL(void)
{
    cl_int status = 0;
    size_t deviceListSize;

    /*
     * Have a look at the available platforms and pick either
     * the AMD one if available or a reasonable default.
     */

    cl_uint numPlatforms;
    cl_platform_id platform = NULL;
    status = clGetPlatformIDs(0, NULL, &numPlatforms);
    if(status != CL_SUCCESS)
    {
        std::cout << "Error: Getting Platforms. (clGetPlatformsIDs)\n";
        return 1;
    }
    
    if(numPlatforms > 0)
    {
        cl_platform_id* platforms = new cl_platform_id[numPlatforms];
        status = clGetPlatformIDs(numPlatforms, platforms, NULL);
        if(status != CL_SUCCESS)
        {
            std::cout << "Error: Getting Platform Ids. (clGetPlatformsIDs)\n";
            return 1;
        }
        for(unsigned int i=0; i < numPlatforms; ++i)
        {
            char pbuff[100];
            status = clGetPlatformInfo(
		platforms[i],
		CL_PLATFORM_VENDOR,
		sizeof(pbuff),
		pbuff,
		NULL);
            platform = platforms[i];
            if(!strcmp(pbuff, "Advanced Micro Devices, Inc."))
            {
                break;
            }
        }
        delete[] platforms;
    }

    /* 
     * If we could find our platform, use it. Otherwise pass a NULL and get whatever the
     * implementation thinks we should be using.
     */

    cl_context_properties cps[3] = { CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0 };
    cl_context_properties* cprops = (NULL == platform) ? NULL : cps;

    /////////////////////////////////////////////////////////////////
    // Create an OpenCL context
    /////////////////////////////////////////////////////////////////
    context = clCreateContextFromType(cprops, 
	CL_DEVICE_TYPE_GPU, 
	NULL, 
	NULL, 
	&status);
    if(status != CL_SUCCESS) 
    {  
	std::cout<<"Error: Creating Context. (clCreateContextFromType)\n";
	return 1; 
    }

    /* First, get the size of device list data */
    status = clGetContextInfo(context, 
	CL_CONTEXT_DEVICES, 
	0, 
	NULL, 
	&deviceListSize);
    if(status != CL_SUCCESS) 
    {  
	std::cout<<
	    "Error: Getting Context Info \
		    (device list size, clGetContextInfo)\n";
	return 1;
    }

    /////////////////////////////////////////////////////////////////
    // Detect OpenCL devices
    /////////////////////////////////////////////////////////////////
    devices = (cl_device_id *)malloc(deviceListSize);
    if(devices == 0)
    {
	std::cout<<"Error: No devices found.\n";
	return 1;
    }

    /* Now, get the device list data */
    status = clGetContextInfo(
	context, 
	CL_CONTEXT_DEVICES, 
	deviceListSize, 
	devices, 
	NULL);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<
	    "Error: Getting Context Info \
		    (device list, clGetContextInfo)\n";
	return 1;
    }

    /////////////////////////////////////////////////////////////////
    // Create an OpenCL command queue
    /////////////////////////////////////////////////////////////////
    commandQueue = clCreateCommandQueue(
	context, 
	devices[0], 
	0, 
	&status);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<"Creating Command Queue. (clCreateCommandQueue)\n";
	return 1;
    }

    /////////////////////////////////////////////////////////////////
    // Create OpenCL memory buffers
    /////////////////////////////////////////////////////////////////
    inputBuffer = clCreateBuffer(
	context, 
	CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
	sizeof(cl_uint) * width,
	input, 
	&status);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<"Error: clCreateBuffer (inputBuffer)\n";
	return 1;
    }

    outputBuffer = clCreateBuffer(
	context, 
	CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
	sizeof(cl_uint) * width,
	output, 
	&status);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<"Error: clCreateBuffer (outputBuffer)\n";
	return 1;
    }


    /////////////////////////////////////////////////////////////////
    // Load CL file, build CL program object, create CL kernel object
    /////////////////////////////////////////////////////////////////
    const char * filename  = "opencl_test.cl";
    std::string  sourceStr = convertToString(filename);
    const char * source    = sourceStr.c_str();
    size_t sourceSize[]    = { strlen(source) };

    program = clCreateProgramWithSource(
	context, 
	1, 
	&source,
	sourceSize,
	&status);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<
	    "Error: Loading Binary into cl_program \
			   (clCreateProgramWithBinary)\n";
	return 1;
    }

    /* create a cl program executable for all the devices specified */
    status = clBuildProgram(program, 1, devices, NULL, NULL, NULL);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<"Error: Building Program (clBuildProgram)\n";
	return 1; 
    }

    /* get a kernel object handle for a kernel with the given name */
    kernel = clCreateKernel(program, "templateKernel", &status);
    if(status != CL_SUCCESS) 
    {  
	std::cout<<"Error: Creating Kernel from program. (clCreateKernel)\n";
	return 1;
    }

    return 0;
}


/*
 * \brief Run OpenCL program 
 *		  
 *        Bind host variables to kernel arguments 
 *		  Run the CL kernel
 */
int 
runCLKernels(void)
{
    cl_int   status;
    cl_uint maxDims;
    cl_event events[2];
    size_t globalThreads[1];
    size_t localThreads[1];
    size_t maxWorkGroupSize;
    size_t maxWorkItemSizes[3];

    /**
     * Query device capabilities. Maximum 
     * work item dimensions and the maximmum
     * work item sizes
     */ 
    clGetDeviceInfo(
	devices[0], 
	CL_DEVICE_MAX_WORK_GROUP_SIZE, 
	sizeof(size_t), 
	(void*)&maxWorkGroupSize, 
	NULL);
	
    clGetDeviceInfo(
	devices[0], 
	CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, 
	sizeof(cl_uint), 
	(void*)&maxDims, 
	NULL);

    clGetDeviceInfo(
	devices[0], 
	CL_DEVICE_MAX_WORK_ITEM_SIZES, 
	sizeof(size_t)*maxDims,
	(void*)maxWorkItemSizes,
	NULL);
    
    globalThreads[0] = width;
    localThreads[0]  = 1;

    if(globalThreads[0] > maxWorkItemSizes[0] || 
	localThreads[0] > maxWorkGroupSize)
    {
	std::cout<<"Unsupported: Device does not support requested number of work items.";
				 
	return 1;
    }

    /*** Set appropriate arguments to the kernel ***/
    /* the output array to the kernel */
    status = clSetKernelArg(
	kernel, 
	0, 
	sizeof(cl_mem), 
	(void *)&outputBuffer);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<"Error: Setting kernel argument. (output)\n";
	return 1;
    }

    /* the input array to the kernel */
    status = clSetKernelArg(
	kernel, 
	1, 
	sizeof(cl_mem), 
	(void *)&inputBuffer);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<"Error: Setting kernel argument. (input)\n";
	return 1;
    }

    /*multiplier*/
    status = clSetKernelArg(
	kernel, 
	2, 
	sizeof(cl_uint), 
	(void *)&multiplier);
    if(status != CL_SUCCESS) 
    { 
	std::cout<< "Error: Setting kernel argument. (multiplier)\n";
	return 1;
    }

    /* 
     * Enqueue a kernel run call.
     */
    status = clEnqueueNDRangeKernel(
	commandQueue,
	kernel,
	1,
	NULL,
	globalThreads,
	localThreads,
	0,
	NULL,
	&events[0]);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<
	    "Error: Enqueueing kernel onto command queue. \
			(clEnqueueNDRangeKernel)\n";
	return 1;
    }

    /* wait for the kernel call to finish execution */
    status = clWaitForEvents(1, &events[0]);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<
	    "Error: Waiting for kernel run to finish. \
			(clWaitForEvents)\n";
	return 1;
    }

    clReleaseEvent(events[0]);

    /* Enqueue readBuffer*/
    status = clEnqueueReadBuffer(
	commandQueue,
	outputBuffer,
	CL_TRUE,
	0,
	width * sizeof(cl_uint),
	output,
	0,
	NULL,
	&events[1]);
    
    if(status != CL_SUCCESS) 
    { 
        std::cout << 
	    "Error: clEnqueueReadBuffer failed. \
             (clEnqueueReadBuffer)\n";

	return 1;
    }
    
    /* Wait for the read buffer to finish execution */
    status = clWaitForEvents(1, &events[1]);
    if(status != CL_SUCCESS) 
    { 
	std::cout<<
	    "Error: Waiting for read buffer call to finish. \
			(clWaitForEvents)\n";
	return 1;
    }
    
    clReleaseEvent(events[1]);

    return 0;
}


/*
 * \brief Release OpenCL resources (Context, Memory etc.) 
 */
int  
cleanupCL(void)
{
    cl_int status;

    status = clReleaseKernel(kernel);
    if(status != CL_SUCCESS)
    {
	std::cout<<"Error: In clReleaseKernel \n";
	return 1; 
    }
    status = clReleaseProgram(program);
    if(status != CL_SUCCESS)
    {
	std::cout<<"Error: In clReleaseProgram\n";
	return 1; 
    }
    status = clReleaseMemObject(inputBuffer);
    if(status != CL_SUCCESS)
    {
	std::cout<<"Error: In clReleaseMemObject (inputBuffer)\n";
	return 1; 
    }
    status = clReleaseMemObject(outputBuffer);
    if(status != CL_SUCCESS)
    {
	std::cout<<"Error: In clReleaseMemObject (outputBuffer)\n";
	return 1; 
    }
    status = clReleaseCommandQueue(commandQueue);
    if(status != CL_SUCCESS)
    {
	std::cout<<"Error: In clReleaseCommandQueue\n";
	return 1;
    }
    status = clReleaseContext(context);
    if(status != CL_SUCCESS)
    {
	std::cout<<"Error: In clReleaseContext\n";
	return 1;
    }

    return 0;
}


/* 
 * \brief Releases program's resources 
 */
void
cleanupHost(void)
{
    if(input != NULL)
    {
        free(input);
        input = NULL;
    }
    if(output != NULL)
    {
	free(output);
	output = NULL;
    }
    if(devices != NULL)
    {
        free(devices);
        devices = NULL;
    }
}


/*
 * \brief Print no more than 256 elements of the given array.
 *
 *        Print Array name followed by elements.
 */
void print1DArray(
    const std::string arrayName, 
    const unsigned int * arrayData, 
    const unsigned int length)
{
    cl_uint i;
    cl_uint numElementsToPrint = (256 < length) ? 256 : length;

    std::cout << std::endl;
    std::cout << arrayName << ":" << std::endl;
    for(i = 0; i < numElementsToPrint; ++i)
    {
        std::cout << arrayData[i] << " ";
    }
    std::cout << std::endl;

}

int 
main(int argc, char * argv[])
{
    Plm_timer* timer = new Plm_timer;
    timer->start ();

    // Initialize Host application 
    if(initializeHost()==1)
	return 1;

    // Initialize OpenCL resources
    if(initializeCL()==1)
	return 1;

    printf ("Initialization: %f seconds\n", timer->report ());

    // Run the CL program
    timer->start ();
    if(runCLKernels()==1)
	return 1;
    printf ("Run kernel: %f seconds\n", timer->report ());

    delete timer;

    // Print output array
    print1DArray(std::string("Output"), output, width);

    // Releases OpenCL resources 
    if(cleanupCL()==1)
	return 1;

    // Release host resources
    cleanupHost();

    return 0;
}
