////////////////////////////////////////////////////////////////////////////
//	File:		GlobalUtil.cpp
//	Author:		Changchang Wu
//	Description : Global Utility class for SiftGPU
//
//
//
//	Copyright (c) 2007 University of North Carolina at Chapel Hill
//	All Rights Reserved
//
//	Permission to use, copy, modify and distribute this software and its
//	documentation for educational, research and non-profit purposes, without
//	fee, and without a written agreement is hereby granted, provided that the
//	above copyright notice and the following paragraph appear in all copies.
//	
//	The University of North Carolina at Chapel Hill make no representations
//	about the suitability of this software for any purpose. It is provided
//	'as is' without express or implied warranty. 
//
//	Please send BUG REPORTS to ccwu@cs.unc.edu
//
////////////////////////////////////////////////////////////////////////////
#include <string.h>
#include <iostream>
using std::cout;

#include "GL/glew.h"
#include "GlobalUtil.h"

//for windows, the default timing uses timeGetTime, you can define TIMING_BY_CLOCK to use clock()
//for other os, the timing uses gettimeofday


#if defined(_WIN32)
	#if defined(TIMING_BY_CLOCK)
		#include <time.h>
	#else
	    #define WIN32_LEAN_AND_MEAN
	    #include <windows.h>
		#include <mmsystem.h>
	#endif
#else
	#include <sys/time.h>
	#include <stdio.h>
#endif

#include "LiteWindow.h"

//
int GlobalParam::		_verbose =  1;   
int	GlobalParam::       _timingS = 1;  //print out information of each step
int	GlobalParam::       _timingO = 0;  //print out information of each octave
int	GlobalParam::       _timingL = 0;	//print out information of each level
GLuint GlobalParam::	_texTarget = GL_TEXTURE_RECTANGLE_ARB; //only this one is supported
GLuint GlobalParam::	_iTexFormat =GL_RGBA32F_ARB;	//or GL_RGBA16F_ARB
int	GlobalParam::		_debug = 0;		//enable debug code?
int	GlobalParam::		_usePackedTex = 1;//packed implementation
int	GlobalParam::		_UseCUDA = 0;
int GlobalParam::       _UseOpenCL = 0;
int GlobalParam::		_MaxFilterWidth = -1;	//maximum filter width, use when GPU is not good enough
float GlobalParam::     _FilterWidthFactor	= 4.0f;	//the filter size will be _FilterWidthFactor*sigma*2+1
float GlobalParam::     _DescriptorWindowFactor = 3.0f; //descriptor sampling window factor
int GlobalParam::		_SubpixelLocalization = 1; //sub-pixel and sub-scale localization 	
int	GlobalParam::       _MaxOrientation = 2;	//whether we find multiple orientations for each feature 
int	GlobalParam::       _OrientationPack2 = 0;  //use one float to store two orientations
float GlobalParam::		_MaxFeaturePercent = 0.005f;//at most 0.005 of all pixels
int	GlobalParam::		_MaxLevelFeatureNum = 4096; //maximum number of features of a level
int GlobalParam::		_FeatureTexBlock = 4; //feature texture storagte alignment
int	GlobalParam::		_NarrowFeatureTex = 0; 

//if _ForceTightPyramid is not 0, pyramid will be reallocated to fit the size of input images.
//otherwise, pyramid can be reused for smaller input images. 
int GlobalParam::		_ForceTightPyramid = 0;

//use gpu or cpu to generate feature list ...gpu is a little bit faster
int GlobalParam::		_ListGenGPU =	1;	
int	GlobalParam::       _ListGenSkipGPU = 6;  //how many levels are skipped on gpu
int GlobalParam::		_PreProcessOnCPU = 1; //convert rgb 2 intensity on gpu, down sample on GPU

//hardware parameter,   automatically retrieved
int GlobalParam::		_texMaxDim = 3200;	//Maximum working size for SiftGPU, 3200 for packed
int	GlobalParam::		_texMaxDimGL = 4096;        //GPU texture limit
int GlobalParam::       _texMinDim = 16; //
int	GlobalParam::		_MemCapGPU = 0;
int GlobalParam::		_FitMemoryCap = 0;
int	GlobalParam::		_IsNvidia = 0;				//GPU vendor
int GlobalParam::		_KeepShaderLoop = 0;

//you can't change the following 2 values
//all other versions of code are now dropped
int GlobalParam::       _DescriptorPPR = 8;
int	GlobalParam::		_DescriptorPPT = 16;

//whether orientation/descriptor is supported by hardware
int GlobalParam::		_SupportNVFloat = 0;
int GlobalParam::       _SupportTextureRG = 0;
int	GlobalParam::		_UseDynamicIndexing = 0; 
int GlobalParam::		_FullSupported = 1;

//when SiftGPUEX is not used, display VBO generation is skipped
int GlobalParam::		_UseSiftGPUEX = 0;
int GlobalParam::		_InitPyramidWidth=0;
int GlobalParam::		_InitPyramidHeight=0;
int	GlobalParam::		_octave_min_default=0;
int	GlobalParam::		_octave_num_default=-1;


//////////////////////////////////////////////////////////////////
int	GlobalParam::		_GoodOpenGL = -1;      //indicates OpenGl initialization status
int	GlobalParam::		_FixedOrientation = 0; //upright
int	GlobalParam::		_LoweOrigin = 0;       //(0, 0) to be at the top-left corner.
int	GlobalParam::       _NormalizedSIFT = 1;   //normalize descriptor
int GlobalParam::       _BinarySIFT = 0;       //saving binary format
int	GlobalParam::		_ExitAfterSIFT = 0;    //exif after saving result
int	GlobalParam::		_KeepExtremumSign = 0; // if 1, scales of dog-minimum will be multiplied by -1
///
int GlobalParam::       _KeyPointListForceLevel0 = 0;
int GlobalParam::		_DarknessAdaption = 0;
int	GlobalParam::		_ProcessOBO = 0;
int GlobalParam::       _TruncateMethod = 0;
int	GlobalParam::		_PreciseBorder = 1;

// parameter changing for better matching with Lowe's SIFT
float GlobalParam::		_OrientationWindowFactor = 2.0f;	// 1.0(-v292), 2(v293-), 
float GlobalParam::		_OrientationGaussianFactor = 1.5f;	// 4.5(-v292), 1.5(v293-)
float GlobalParam::     _MulitiOrientationThreshold = 0.8f;
///
int GlobalParam::       _FeatureCountThreshold = -1;

///////////////////////////////////////////////
int	GlobalParam::			_WindowInitX = -1;
int GlobalParam::			_WindowInitY = -1;
int GlobalParam::           _DeviceIndex = 0; 
const char * GlobalParam::	_WindowDisplay = NULL;



/////////////////
////
ClockTimer GlobalUtil::	_globalTimer;


#ifdef _DEBUG
void GlobalUtil::CheckErrorsGL(const char* location)
{
	GLuint errnum;
	const char *errstr;
	while (errnum = glGetError()) 
	{
		errstr = (const char *)(gluErrorString(errnum));
		if(errstr) {
			std::cerr << errstr; 
		}
		else {
			std::cerr  << "Error " << errnum;
		}
		
		if(location) std::cerr  << " at " << location;		
		std::cerr  << "\n";
	}
	return;
}

#endif

void GlobalUtil::CleanupOpenGL()
{
	glActiveTexture(GL_TEXTURE0);
}

void GlobalUtil::SetDeviceParam(int argc, char** argv)
{
    if(GlobalParam::_GoodOpenGL!= -1) return;

    #define CHAR1_TO_INT(x)         ((x >= 'A' && x <= 'Z') ? x + 32 : x)
    #define CHAR2_TO_INT(str, i)    (str[i] ? CHAR1_TO_INT(str[i]) + (CHAR1_TO_INT(str[i+1]) << 8) : 0)  
    #define CHAR3_TO_INT(str, i)    (str[i] ? CHAR1_TO_INT(str[i]) + (CHAR2_TO_INT(str, i + 1) << 8) : 0)
    #define STRING_TO_INT(str)      (CHAR1_TO_INT(str[0]) +  (CHAR3_TO_INT(str, 1) << 8))

	char* arg, * opt;
	for(int i = 0; i< argc; i++)
	{
		arg = argv[i];
		if(arg == NULL || arg[0] != '-')continue;
		opt = arg+1;
 
        ////////////////////////////////
        switch( STRING_TO_INT(opt))
        {
        case 'w' + ('i' << 8) + ('n' << 16) + ('p' << 24): 
            if(_GoodOpenGL != 2 && i + 1 < argc)
            {
                int x =0, y=0;
                if(sscanf(argv[++i], "%dx%d", &x, &y) == 2)
                {
                    GlobalParam::_WindowInitX = x;
                    GlobalParam::_WindowInitY = y;
                }  
            }
            break;  
        case 'd' + ('i' << 8) + ('s' << 16) + ('p' << 24):
            if(_GoodOpenGL != 2 && i + 1 < argc)
            {
                GlobalParam::_WindowDisplay = argv[++i];
            }
            break;   
        case 'c' + ('u' << 8) + ('d' << 16) + ('a' << 24):
            if(i + 1 < argc)
            {
               int device =  0; 
               scanf(argv[++i], "%d", &device) ;
               GlobalParam::_DeviceIndex = device;
            }
            break;  
        default:
            break;
        }	        
    }
}

void GlobalUtil::SetTextureParameter()
{

	glTexParameteri (_texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 
	glTexParameteri (_texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 
	glTexParameteri(_texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 
	glTexParameteri(_texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}

//if image need to be up sampled ..use this one

void GlobalUtil::SetTextureParameterUS()
{

	glTexParameteri (_texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 
	glTexParameteri (_texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 
	glTexParameteri(_texTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
	glTexParameteri(_texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}


void GlobalUtil::FitViewPort(int width, int height)
{
	GLint port[4];
	glGetIntegerv(GL_VIEWPORT, port);
	if(port[2] !=width || port[3] !=height)
	{
		glViewport(0, 0, width, height);      
		glMatrixMode(GL_PROJECTION);    
		glLoadIdentity();               
		glOrtho(0, width, 0, height,  0, 1);		
		glMatrixMode(GL_MODELVIEW);     
		glLoadIdentity();  
	}
}


bool GlobalUtil::CheckFramebufferStatus() {
    GLenum status;
    status=(GLenum)glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    switch(status) {
        case GL_FRAMEBUFFER_COMPLETE_EXT:
            return true;
        case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
            std::cerr<<("Framebuffer incomplete,incomplete attachment\n");
            return false;
        case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
            std::cerr<<("Unsupported framebuffer format\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
            std::cerr<<("Framebuffer incomplete,missing attachment\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
            std::cerr<<("Framebuffer incomplete,attached images must have same dimensions\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
             std::cerr<<("Framebuffer incomplete,attached images must have same format\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
            std::cerr<<("Framebuffer incomplete,missing draw buffer\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
            std::cerr<<("Framebuffer incomplete,missing read buffer\n");
            return false;
    }
	return false;
}


int ClockTimer::ClockMS()
{
#if defined(_WIN32)
	#if defined(TIMING_BY_CLOCK)
		return clock() * 1000 / CLOCKS_PER_SEC;
	#else
		static int    started = 0;
		static int	tstart;
		if(started == 0)
		{
			tstart = timeGetTime();
			started = 1;
			return 0;
		}else
		{
			return timeGetTime() - tstart;
		}
	#endif
#else
	static int    started = 0;
	static struct timeval tstart;
	if(started == 0) 
	{
		gettimeofday(&tstart, NULL);
		started = 1;
		return 0;
	}else
	{	
		struct timeval now;
		gettimeofday(&now, NULL) ;
		return (now.tv_usec - tstart.tv_usec + (now.tv_sec - tstart.tv_sec) * 1000000)/1000;
	}
#endif
}

double ClockTimer::CLOCK()
{
	return ClockMS() * 0.001;
}

void ClockTimer::InitHighResolution()
{
#if defined(_WIN32) 
	timeBeginPeriod(1);
#endif
}

void ClockTimer::StartTimer(const char* event, int verb)
{	
	strcpy(_current_event, event);
	_time_start = ClockMS();
	if(verb && GlobalUtil::_verbose)
	{
		std::cout<<"\n["<<_current_event<<"]:\tbegin ...\n";
	}
} 

void ClockTimer::StopTimer(int verb)
{
	_time_stop = ClockMS();
	if(verb && GlobalUtil::_verbose)
	{
		std::cout<<"["<<_current_event<<"]:\t"<<GetElapsedTime()<<"\n";
	}
}

float ClockTimer::GetElapsedTime()
{
	return (_time_stop - _time_start)  * 0.001f;
}

void GlobalUtil::SetGLParam()
{
    if(GlobalUtil::_UseCUDA) return;
    else if(GlobalUtil::_UseOpenCL) return;
	glEnable(GlobalUtil::_texTarget);
	glActiveTexture(GL_TEXTURE0);
}

void GlobalUtil::InitGLParam(int NotTargetGL)
{
    //IF the OpenGL context passed the check
    if(GlobalUtil::_GoodOpenGL == 2) return;
    //IF the OpenGl context failed the check
    if(GlobalUtil::_GoodOpenGL == 0) return; 
    //IF se use CUDA or OpenCL
    if(NotTargetGL && !GlobalUtil::_UseSiftGPUEX)
    {
        GlobalUtil::_GoodOpenGL = 1;
    }else
    {
        //first time in this function
        glewInit();

	    GlobalUtil::_GoodOpenGL = 2;

	    const char * vendor = (const char * )glGetString(GL_VENDOR);
	    if(vendor)
	    {
		    GlobalUtil::_IsNvidia  = (strstr(vendor, "NVIDIA") !=NULL ? 1 : 0);

			// Let nVidia compiler to take care of the unrolling.
			if (GlobalUtil::_IsNvidia) 		GlobalUtil::_KeepShaderLoop = 1;

#ifndef WIN32
			else if(!strstr(vendor, "ATI") )
			{
				// For non-nVidia non-ATI cards...simply assume it is Mesa
				// Keep the original shader loop, because some of the unrolled
				// loopes are too large, and it may take too much time to compile
				GlobalUtil::_KeepShaderLoop = 1;
			}
#endif

			if(GlobalUtil::_IsNvidia && glewGetExtension("GL_NVX_gpu_memory_info"))
			{
				glGetIntegerv(0x9049/*GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX*/, &_MemCapGPU);
				_MemCapGPU /= (1024); 
			  if(GlobalUtil::_verbose) std::cout << "[GPU VENDOR]:\t" << vendor << ' ' <<_MemCapGPU << "MB\n";
			}else if(strstr(vendor, "ATI") && glewGetExtension("GL_ATI_meminfo"))
			{
				int info[4]; 	glGetIntegerv(0x87FC/*GL_TEXTURE_FREE_MEMORY_ATI*/, info);
				_MemCapGPU = info[0] / (1024);
			    if(GlobalUtil::_verbose) std::cout << "[GPU VENDOR]:\t" << vendor << ' ' <<_MemCapGPU << "MB\n";
			}else
			{
				if(GlobalUtil::_verbose) std::cout << "[GPU VENDOR]:\t" << vendor << "\n";
			}

	    }
	    if(GlobalUtil::_IsNvidia == 0 )GlobalUtil::_UseCUDA = 0;

	    if (glewGetExtension("GL_ARB_fragment_shader")    != GL_TRUE ||
		    glewGetExtension("GL_ARB_shader_objects")       != GL_TRUE ||
		    glewGetExtension("GL_ARB_shading_language_100") != GL_TRUE)
	    {
		    std::cerr << "Shader not supported by your hardware!\n";
		    GlobalUtil::_GoodOpenGL = 0;
	    }

	    if (glewGetExtension("GL_EXT_framebuffer_object") != GL_TRUE) 
	    {
		    std::cerr << "Framebuffer object not supported!\n";
		    GlobalUtil::_GoodOpenGL = 0;
	    }

	    if(glewGetExtension("GL_ARB_texture_rectangle")==GL_TRUE)
	    {
	        GLint value;
		    GlobalUtil::_texTarget =  GL_TEXTURE_RECTANGLE_ARB;
		    glGetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT, &value);
		    GlobalUtil::_texMaxDimGL = value; 
		    if(GlobalUtil::_verbose) std::cout << "TEXTURE:\t" << GlobalUtil::_texMaxDimGL << "\n";

		    if(GlobalUtil::_texMaxDim == 0 || GlobalUtil::_texMaxDim > GlobalUtil::_texMaxDimGL)
		    {
			    GlobalUtil::_texMaxDim = GlobalUtil::_texMaxDimGL; 
		    }
		    glEnable(GlobalUtil::_texTarget);
	    }else
	    {
		    std::cerr << "GL_ARB_texture_rectangle not supported!\n";
		    GlobalUtil::_GoodOpenGL = 0;
	    }

	    GlobalUtil::_SupportNVFloat = glewGetExtension("GL_NV_float_buffer");
	    GlobalUtil::_SupportTextureRG = glewGetExtension("GL_ARB_texture_rg");


	    glShadeModel(GL_FLAT);
	    glPolygonMode(GL_FRONT, GL_FILL);

	    GlobalUtil::SetTextureParameter();

    }
}

void GlobalUtil::SelectDisplay()
{
#ifdef WIN32
	if(_WindowDisplay == NULL) return;

	HDC hdc = CreateDC(_WindowDisplay, _WindowDisplay, NULL, NULL);
	_WindowDisplay = NULL;
	if(hdc == NULL) 
	{
		std::cout << "ERROR: invalid dispaly specified\n";
		return;
	}

	PIXELFORMATDESCRIPTOR pfd = 
	{
		sizeof(PIXELFORMATDESCRIPTOR),1,                             
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,		
		PFD_TYPE_RGBA,24,0, 0, 0, 0, 0, 0,0,0,0,0, 0, 0, 0,16,0,0,                          
		PFD_MAIN_PLANE,0,0, 0, 0                     
	};
	ChoosePixelFormat(hdc, &pfd);
#endif
}

int GlobalUtil::CreateWindowEZ(LiteWindow* window)
{
	if(window == NULL) return 0;
    if(!window->IsValid())window->Create(_WindowInitX, _WindowInitY, _WindowDisplay);
    if(window->IsValid()) 
    {
        window->MakeCurrent();
        return 1;
    }
    else  
    {
        std::cerr << "Unable to create OpenGL Context!\n";
		std::cerr << "For nVidia cards, you can try change to CUDA mode in this case\n";
        return 0;
    }
}

int GlobalUtil::CreateWindowEZ()
{
	static LiteWindow window;
    return CreateWindowEZ(&window);
}

int CreateLiteWindow(LiteWindow* window)
{
    return GlobalUtil::CreateWindowEZ(window);
}
