/*
 * galaxy collision code based on nvidia OpenCL / OpenGL interop example
 * version for the CPU or GPU
 */


#ifdef _WIN32
#  define WINDOWS_LEAN_AND_MEAN
#  define NOMINMAX
#  include <windows.h>
#endif

// OpenGL Graphics Includes
#include <GL/glew.h>
#if defined (__APPLE__) || defined(MACOSX)
    #include <OpenGL/OpenGL.h>
    #include <GLUT/glut.h>
#else
    #include <GL/freeglut.h>
    #ifdef UNIX
       #include <GL/glx.h>
    #endif
#endif

// Includes
#include <memory>
#include <iostream>
#include <cassert>

// Utilities, OpenCL and system includes
#include <oclUtils.h>
#include <shrQATest.h>

#if defined (__APPLE__) || defined(MACOSX)
   #define GL_SHARING_EXTENSION "cl_APPLE_gl_sharing"
#else
   #define GL_SHARING_EXTENSION "cl_khr_gl_sharing"
#endif

// Constants, defines, typedefs and global declarations
//*****************************************************************************
#define REFRESH_DELAY	  10 //ms

// Rendering window vars
const unsigned int window_width = 512;
const unsigned int window_height = 512;

// OpenCL vars
cl_platform_id cpPlatform;
cl_context cxGPUContext;
cl_device_id* cdDevices;
cl_uint uiDevCount;
cl_command_queue cqCommandQueue;
cl_kernel ckKernel;
cl_mem vbo_cl;
cl_program cpProgram;
cl_int ciErrNum;
char* cPathAndName = NULL;          // var for full paths to data, src, etc.
char* cSourceCL = NULL;             // Buffer to hold source for compilation 
const char* cExecutableName = NULL;

// vbo variables
GLuint vbo;
int iGLUTWindowHandle = 0;          // handle to the GLUT window

// mouse controls
int mouse_old_x, mouse_old_y;
int mouse_buttons = 0;
float rotate_x = 0.0, rotate_y = 0.0;


// Sim and Auto-Verification parameters 
unsigned int anim = 0;
int iTestSets = 3;
int g_Index = 0;
shrBOOL bQATest = shrFALSE;
shrBOOL bNoPrompt = shrFALSE;  

int *pArgc = NULL;
char **pArgv = NULL;

// galaxy variables
float * d_particleData;
float * h_particleData;

float targetX, targetY, targetZ;

float origStars[82000][7];
float stars[82000][7];

int origNumStars = 0;
int numStars;

int mode = 3; // how many stars to skip - was 5
int nd = 7;

int computationHeight = 1;

// Forward Function declarations
//*****************************************************************************
// OpenCL functionality
void runKernel();

// GL functionality
void InitGL(int* argc, char** argv);
void createVBO(GLuint* vbo);
void DisplayGL();
void KeyboardGL(unsigned char key, int x, int y);
void mouse(int button, int state, int x, int y);
void motion(int x, int y);
void timerEvent(int value);

// Helpers
void Cleanup(int iExitCode);
void (*pCleanup)(int) = &Cleanup;

////////////////////////////////////////////////////////////////

int loadInStars(void)
{
	FILE * starFile = NULL;
	char line[255];

	float mass, x, y, z, vx, vy, vz;

	starFile = fopen("galaxy.txt", "r");
    
	if (starFile == NULL)
		{
		fprintf(stderr, "can not find galaxy data file\n");
		return(0);
		}

	numStars = 0;
	origNumStars = 0;

	fgets(line, 255, starFile);

	while (!feof(starFile))
		{
		fgets(line, 255, starFile);
    
		mass    = 0.0f;
		sscanf(line, "%f %f %f %f %f %f %f",
			&mass, &x, &y, &z, &vx, &vy, &vz);
    
		origStars[origNumStars][0] = x;
		origStars[origNumStars][1] = y;
		origStars[origNumStars][2] = z;
		origStars[origNumStars][3] = vx;
		origStars[origNumStars][4] = vy;
		origStars[origNumStars][5] = vz;
		origStars[origNumStars][6] = mass;
	
		origNumStars++;
    	}

	fclose(starFile);
	fprintf(stderr, "%d stars found\n", origNumStars);

	int s;

	for (s=0; s< 49152; s+= (1+mode)) // exclude the halo stars
	{
	stars[numStars][0] = origStars[s][0];
	stars[numStars][1] = origStars[s][1];
	stars[numStars][2] = origStars[s][2];
	stars[numStars][3] = origStars[s][3];
	stars[numStars][4] = origStars[s][4];
	stars[numStars][5] = origStars[s][5];
	stars[numStars][6] = origStars[s][6];

	numStars += 1;
	}    

	fprintf(stderr, "viewing %d stars\n", numStars);

// 81920 total stars in file
// 49152 stars excluding the halo stars
// max stars might be around 32000 on this machine

	return(1);
}

// Main program
//*****************************************************************************
int main(int argc, char** argv)
{
	pArgc = &argc;
	pArgv = argv;

    // start logs 
    shrQAStart(argc, argv);
	cExecutableName = argv[0];
    shrSetLogFileName ("oclAndyGalaxy.txt");
    shrLog("%s Starting...\n\n", argv[0]); 


    // Initialize OpenGL items (if not No-GL QA test)
	shrLog("%sInitGL...\n\n", bQATest ? "Skipping " : "Calling "); 
    if(!bQATest)
    {
        InitGL(&argc, argv);
    }

	//read in the text file of star positions and velocities
	loadInStars();

    //Get the NVIDIA platform
    ciErrNum = oclGetPlatformID(&cpPlatform);
    oclCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);

    // Get the number of GPU devices available to the platform
    ciErrNum = clGetDeviceIDs(cpPlatform, CL_DEVICE_TYPE_CPU, 0, NULL, &uiDevCount);
    oclCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);

    // Create the device list
    cdDevices = new cl_device_id [uiDevCount];
    ciErrNum = clGetDeviceIDs(cpPlatform, CL_DEVICE_TYPE_CPU, uiDevCount, cdDevices, NULL);
    oclCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);

    // Get device requested on command line, if any
    unsigned int uiDeviceUsed = 0;
    unsigned int uiEndDev = uiDevCount - 1;
    if(shrGetCmdLineArgumentu(argc, (const char**)argv, "device", &uiDeviceUsed ))
    {
      uiDeviceUsed = CLAMP(uiDeviceUsed, 0, uiEndDev);
      uiEndDev = uiDeviceUsed; 
    } 


    cxGPUContext = clCreateContext(NULL, 1,&cdDevices[uiDeviceUsed], NULL, NULL, &ciErrNum);
    shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);

    // Log device used (reconciled for requested requested and/or CL-GL interop capable devices, as applies)
    shrLog("Device # %u, ", uiDeviceUsed);
    oclPrintDevName(LOGBOTH, cdDevices[uiDeviceUsed]);
    shrLog("\n");

    // create a command-queue use 0 for no profiling or CL_QUEUE_PROFILING_ENABLE to enable profiling
    cqCommandQueue = clCreateCommandQueue(cxGPUContext, cdDevices[uiDeviceUsed], CL_QUEUE_PROFILING_ENABLE, &ciErrNum);
    shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);


    cl_mem cl_membufferData;

    // initialize the particles
    h_particleData = (float *) malloc (nd * numStars * sizeof(float));
    int pCounter, i;
    for (pCounter = 0; pCounter < numStars; pCounter ++)
		for (i=0; i< nd; i++)
	    	h_particleData[nd * pCounter + i] = stars[pCounter][i];
	
	// combine creation of memory on the device and copying values over
	cl_membufferData = clCreateBuffer(cxGPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
                                    nd * numStars * sizeof(float), h_particleData, &ciErrNum);
    oclCheckError(ciErrNum, CL_SUCCESS);


    // Program Setup
    size_t program_length;
    cPathAndName = shrFindFilePath("oclAndyGalaxy.cl", argv[0]);
    shrCheckErrorEX(cPathAndName != NULL, shrTRUE, pCleanup);
    cSourceCL = oclLoadProgSource(cPathAndName, "", &program_length);
    shrCheckErrorEX(cSourceCL != NULL, shrTRUE, pCleanup);

    // create the program
    cpProgram = clCreateProgramWithSource(cxGPUContext, 1,
					  (const char **) &cSourceCL, &program_length, &ciErrNum);
    shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);

    // build the program
   ciErrNum = clBuildProgram(cpProgram, 0, NULL, NULL, NULL, NULL);
    if (ciErrNum != CL_SUCCESS)
    {
        // write out standard error, Build Log and PTX, then cleanup and exit
        shrLogEx(LOGBOTH | ERRORMSG, ciErrNum, STDERROR);
        oclLogBuildInfo(cpProgram, oclGetFirstDev(cxGPUContext));
        oclLogPtx(cpProgram, oclGetFirstDev(cxGPUContext), "oclAndyGalaxy.ptx");
        Cleanup(EXIT_FAILURE); 
    }
    

    // create the kernel
    ckKernel = clCreateKernel(cpProgram, "galaxy", &ciErrNum);
    //shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);

    // if there is a problem creating the kernel print out some detailed information about it
   if (ciErrNum) {
   		char log[10240] = "";
   		ciErrNum = clGetProgramBuildInfo(cpProgram, cdDevices[uiDeviceUsed], CL_PROGRAM_BUILD_LOG, sizeof(log), log, NULL);
   		fprintf(stderr, "Error(s) creating the kernel:\n%s\n", log);
   }
	shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);

    // create VBO (if using standard GL or CL-GL interop), otherwise create Cl buffer
    createVBO(&vbo);
    

	int computationWidth = numStars/computationHeight;

    // set the args values 
    ciErrNum  = clSetKernelArg(ckKernel, 0, sizeof(cl_mem), (void *) &vbo_cl);
    ciErrNum |= clSetKernelArg(ckKernel, 1, sizeof(cl_mem), (void *) &cl_membufferData); 
    ciErrNum |= clSetKernelArg(ckKernel, 2, sizeof(unsigned int), (void *) &computationWidth);
    ciErrNum |= clSetKernelArg(ckKernel, 3, sizeof(unsigned int), (void *) &computationHeight);
    ciErrNum |= clSetKernelArg(ckKernel, 4, sizeof(unsigned int), (void *) &numStars);
    shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);
	// note that there is one more kernel argument set later since it changes each time through the loop

    // init timer 1 for fps measurement 
    shrDeltaT(1);  

    // Start main GLUT rendering loop for processing and rendering, 
	// or otherwise run No-GL Q/A test sequence
    shrLog("\n%s...\n", bQATest ? "No-GL test sequence" : "Standard GL Loop"); 
    if(!bQATest) 
    {
        glutMainLoop();
    }

    // Normally unused return path
    Cleanup(EXIT_SUCCESS);
}

// Initialize GL
//*****************************************************************************
void InitGL(int* argc, char** argv)
{
    // initialize GLUT 
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
    glutInitWindowPosition (glutGet(GLUT_SCREEN_WIDTH)/2 - window_width/2, 
                            glutGet(GLUT_SCREEN_HEIGHT)/2 - window_height/2);
    glutInitWindowSize(window_width, window_height);
    iGLUTWindowHandle = glutCreateWindow("OpenCL/GL Interop (VBO)");
#if !(defined (__APPLE__) || defined(MACOSX))
    glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
#endif

    // register GLUT callback functions
    glutDisplayFunc(DisplayGL);
    glutKeyboardFunc(KeyboardGL);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
	glutTimerFunc(REFRESH_DELAY, timerEvent,0);

	// initialize necessary OpenGL extensions
    glewInit();
    GLboolean bGLEW = glewIsSupported("GL_VERSION_2_0 GL_ARB_pixel_buffer_object"); 
    shrCheckErrorEX(bGLEW, shrTRUE, pCleanup);

    // default initialization
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glDisable(GL_DEPTH_TEST);

 	glPointSize(2.0);
 
    // viewport
    glViewport(0, 0, window_width, window_height);

    // projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLfloat)window_width / (GLfloat) window_height, 0.1, 1000.0);
    gluLookAt(0, 0, 40,    0, 0, 0,    0, 1, 0);

    return;
}

// Run the OpenCL part of the computation
//*****************************************************************************
void runKernel()
{
	size_t szGlobalWorkSize[] = {numStars/computationHeight, computationHeight};

    ciErrNum = CL_SUCCESS;
     
 #ifdef GL_INTEROP   
    // map OpenGL buffer object for writing from OpenCL
    glFinish();
    ciErrNum = clEnqueueAcquireGLObjects(cqCommandQueue, 1, &vbo_cl, 0,0,0);
    shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);
#endif

 
 cl_event eventGlobal;
 cl_int errcode_ret;
 
    // Set arg 4 and execute the kernel
    ciErrNum = clSetKernelArg(ckKernel, 5, sizeof(unsigned int), &anim);
    ciErrNum |= clEnqueueNDRangeKernel(cqCommandQueue, ckKernel, 2, NULL, szGlobalWorkSize, NULL, 0, 0, &eventGlobal );
cl_ulong end, start;

// lets do some profiling

errcode_ret = clWaitForEvents(1, &eventGlobal);
oclCheckError(errcode_ret, CL_SUCCESS);
errcode_ret = clGetEventProfilingInfo(eventGlobal, CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &end, 0);
errcode_ret |= clGetEventProfilingInfo(eventGlobal, CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &start, 0);
oclCheckError(errcode_ret, CL_SUCCESS);
fprintf(stderr, "Global kernel time: %0.3f ms\n",(end-start)*1.0e-6f);

    shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);


#ifdef GL_INTEROP
    // unmap buffer object
    ciErrNum = clEnqueueReleaseGLObjects(cqCommandQueue, 1, &vbo_cl, 0,0,0);
    shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);
    clFinish(cqCommandQueue);
#else

    // Explicit Copy 
    // map the PBO to copy data from the CL buffer via host
    glBindBufferARB(GL_ARRAY_BUFFER, vbo);    

    // map the buffer object into client's memory
    void* ptr = glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY_ARB);

    ciErrNum = clEnqueueReadBuffer(cqCommandQueue, vbo_cl, CL_TRUE, 0, 8 * numStars * sizeof(float), ptr, 0, NULL, NULL);
    shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);

    glUnmapBufferARB(GL_ARRAY_BUFFER); 
#endif

}

// Create VBO
//*****************************************************************************
void createVBO(GLuint* vbo)
{
    // create VBO
    unsigned int size = numStars * 8 * sizeof( float); //4 position, 4 color
    
    if(!bQATest)
    {
        // create buffer object
        glGenBuffers(1, vbo);
        glBindBuffer(GL_ARRAY_BUFFER, *vbo);

        // initialize buffer object
        glBufferData(GL_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW);

        #ifdef GL_INTEROP
            // create OpenCL buffer from GL VBO
            vbo_cl = clCreateFromGLBuffer(cxGPUContext, CL_MEM_WRITE_ONLY, *vbo, NULL);
        #else
            // create standard OpenCL mem buffer
            vbo_cl = clCreateBuffer(cxGPUContext, CL_MEM_WRITE_ONLY, size, NULL, &ciErrNum);
        #endif
        shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);
    }
    else 
    {
        // create standard OpenCL mem buffer
        vbo_cl = clCreateBuffer(cxGPUContext, CL_MEM_WRITE_ONLY, size, NULL, &ciErrNum);
        shrCheckErrorEX(ciErrNum, CL_SUCCESS, pCleanup);
    }


}

// Display callback
//*****************************************************************************
void DisplayGL()
{
    anim += 1;
 
    // run OpenCL kernel to generate vertex positions
    runKernel();

    // set view matrix
    glClear(GL_COLOR_BUFFER_BIT);
        
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(rotate_x, 1.0, 0.0, 0.0);
    glRotatef(rotate_y, 0.0, 1.0, 0.0);
    
    
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexPointer(4, GL_FLOAT, 0, 0);
    glColorPointer(4, GL_FLOAT, 0, (GLvoid *) (numStars * sizeof(float)*4));

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);

   
    glDrawArrays(GL_POINTS, 0, numStars);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);

    // flip backbuffer to screen
    glutSwapBuffers();
    glutPostRedisplay();

}

void timerEvent(int value)
{
    glutPostRedisplay();
	glutTimerFunc(REFRESH_DELAY, timerEvent,0);
}

// Keyboard events handler
//*****************************************************************************
void KeyboardGL(unsigned char key, int x, int y)
{
    switch(key) 
    {
        case '\033': // escape quits 0
            // Cleanup up and quit
            bNoPrompt = shrTRUE;
	        Cleanup(EXIT_SUCCESS);
            break;
    }
}

// Mouse event handlers
//*****************************************************************************
void mouse(int button, int state, int x, int y)
{
    if (state == GLUT_DOWN) {
        mouse_buttons |= 1<<button;
    } else if (state == GLUT_UP) {
        mouse_buttons = 0;
    }

    mouse_old_x = x;
    mouse_old_y = y;
}

void motion(int x, int y)
{
    float dx, dy;
    dx = (float)(x - mouse_old_x);
    dy = (float)(y - mouse_old_y);

    if (mouse_buttons & 1) {
        rotate_x += dy * 0.2f;
        rotate_y += dx * 0.2f;
    } 

    mouse_old_x = x;
    mouse_old_y = y;
    
}


// Function to clean up and exit
//*****************************************************************************
void Cleanup(int iExitCode)
{
    // Cleanup allocated objects
    shrLog("\nStarting Cleanup...\n\n");
	if(ckKernel)clReleaseKernel(ckKernel); 
    if(cpProgram)clReleaseProgram(cpProgram);
    if(cqCommandQueue)clReleaseCommandQueue(cqCommandQueue);
    if(vbo)
    {
        glBindBuffer(1, vbo);
        glDeleteBuffers(1, &vbo);
        vbo = 0;
    }
    if(vbo_cl)clReleaseMemObject(vbo_cl);
    if(cxGPUContext)clReleaseContext(cxGPUContext);
    if(cPathAndName)free(cPathAndName);
    if(cSourceCL)free(cSourceCL);
    if(cdDevices)delete(cdDevices);

    // finalize logs and leave
    shrQAFinish2(bQATest, *pArgc, (const char **)pArgv, (iExitCode == 0) ? QA_PASSED : QA_FAILED ); 
    if (bQATest || bNoPrompt)
    {
        shrLogEx(LOGBOTH | CLOSELOG, 0, "%s Exiting...\n", cExecutableName);
    }
    else 
    {
        shrLogEx(LOGBOTH | CLOSELOG, 0, "%s Exiting...\nPress <Enter> to Quit\n", cExecutableName);
        #ifdef WIN32
            getchar();
        #endif
    }
    exit (iExitCode);
}
