
#include <assert.h>
#include <iostream>

//---------------------------------------------------------------------
//for getMicrosecondTime

#include <time.h>
#ifdef WIN32
    #include <windows.h>
#else
    #include <sys/time.h>
    #include <unistd.h>
#endif

//---------------------------------------------------------------------

#include <GlobalDevice.h>

//---------------------------------------------------------------------

IrrlichtDevice* GlobalDevice::device = NULL;
EventManager* GlobalDevice::eventManager = NULL;

static long long int frameDuration = 0;
static long long int clockTime = 0;

//---------------------------------------------------------------------
//get the current microsecond time

long long int getMicrosecondTime()
{
#ifdef WIN32
    LARGE_INTEGER ticksPerSecond;
    LARGE_INTEGER tick;
    QueryPerformanceFrequency(&ticksPerSecond);
    QueryPerformanceCounter(&tick);
    return (long long int)((double)tick.QuadPart*1000000.0/(double)ticksPerSecond.QuadPart);
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    static long int diminutor = tv.tv_sec;
    long long int tmp = tv.tv_sec - diminutor;
    return tmp * (long long int)1000000 + (long long int)tv.tv_usec;
#endif
}

//---------------------------------------------------------------------

class MyEventReceiver : public IEventReceiver
{
public:
	MyEventReceiver(EventManager* parManager)
	{
		manager = parManager;
	}

    // here we get the events
	virtual bool OnEvent(const SEvent& event)
	{
	    if(event.EventType == EET_KEY_INPUT_EVENT)
	    {
       	    if(event.KeyInput.PressedDown)
            {
                manager->keys[event.KeyInput.Key] = true;
            }
            else if(!event.KeyInput.PressedDown)
            {
                manager->keys[event.KeyInput.Key] = false;
            }
        }
        else if(event.EventType == EET_MOUSE_INPUT_EVENT)
        {
            if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
                manager->mouseLeft = true;
            else if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
                manager->mouseRight = true;
            else if(event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
                manager->mouseMiddle = true;
            else if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
                manager->mouseLeft = false;
            else if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
                manager->mouseRight = false;
            else if(event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP)
                manager->mouseMiddle = false;
		    else if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
            {
                if (event.MouseInput.Wheel>0)
	       	        manager->mouseWheel++;
	   	        else
				    manager->mouseWheel--;
            }

            if(event.MouseInput.Event == EMIE_MOUSE_MOVED)
            {
                manager->mouseDX = event.MouseInput.X - manager->mouseX;
                manager->mouseDY = event.MouseInput.Y - manager->mouseY;
                manager->mouseX = event.MouseInput.X;
                manager->mouseY = event.MouseInput.Y;
            }
        }
        return false;
    }
private:
	EventManager* manager;
};

//---------------------------------------------------------------------

EventManager::EventManager()
{
	for (int i = 0; i < KEY_KEY_CODES_COUNT; ++i)
		keys[i] = false;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------

GlobalDevice::GlobalDevice(const dimension2d<s32>& windowSize, bool isFullscreen, irr::video::E_DRIVER_TYPE driverType)
{
	device = irr::createDevice( driverType, windowSize, 32, isFullscreen, true, false, 0);
	IVideoDriver* driver = device->getVideoDriver();
	ISceneManager* smgr = device->getSceneManager();
	IGUIEnvironment* guienv = device->getGUIEnvironment();
	assert(device && driver && smgr && guienv);
	//assert(driver->queryFeature(EVDF_RENDER_TO_TARGET));
	
	eventManager = new EventManager();
	IEventReceiver* receiver = reinterpret_cast<IEventReceiver*>(new MyEventReceiver(eventManager));
	device->setEventReceiver(receiver);

	srand(device->getTimer()->getRealTime());//init random seed
}

//---------------------------------------------------------------------

GlobalDevice::~GlobalDevice()
{
	delete eventManager;
	device->drop();
}

//---------------------------------------------------------------------

IBillboardSceneNode* GlobalDevice::loadBillboardNodeFromTexture(const char* filename, float ratioPixelTo3D, dimension2d<s32>& outTextureOriginalSize, bool hasAlphaChannel)
{
	ITexture* tex = GlobalDevice::getVideoDriver()->getTexture(filename);
	assert(tex);
	dimension2d<f32> dim = dimension2d<f32>(tex->getOriginalSize().Width / ratioPixelTo3D, tex->getOriginalSize().Height / ratioPixelTo3D);
	IBillboardSceneNode* node = GlobalDevice::getSceneManager()->addBillboardSceneNode(0, dim);
	assert(node);
	if (hasAlphaChannel) node->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL);
	node->setMaterialFlag(video::EMF_LIGHTING, false);
	node->setMaterialTexture(0, tex);
	outTextureOriginalSize = tex->getOriginalSize();
	return node;
}

//---------------------------------------------------------------------

IMeshSceneNode* GlobalDevice::loadNodeFromMeshFile(const char* filename, bool hasAlphaChannel)
{
	ISceneManager* smgr = GlobalDevice::getDevice()->getSceneManager();
	IAnimatedMesh* mesh = smgr->getMesh(filename);
	assert(mesh);
	IMeshSceneNode* node = smgr->addMeshSceneNode( mesh );
	assert(node);
	if (hasAlphaChannel) node->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL);
	node->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
	for (unsigned int i = 0; i < node->getMaterialCount(); ++i)
		node->getMaterial(i).SpecularColor.set(0,0,0,0);
	return node;
}

//---------------------------------------------------------------------

IAnimatedMeshSceneNode* GlobalDevice::loadAnimatedNodeFromMeshFile(const char* filename, bool hasAlphaChannel)
{
	ISceneManager* smgr = GlobalDevice::getDevice()->getSceneManager();
	IAnimatedMesh* mesh = smgr->getMesh(filename);
	assert(mesh);
	IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );
	assert(node);
	if (hasAlphaChannel) node->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL);
	node->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
	for (unsigned int i = 0; i < node->getMaterialCount(); ++i)
		node->getMaterial(i).SpecularColor.set(0,0,0,0);
	return node;
	//node->setMaterialFlag(video::EMF_TRILINEAR_FILTER, true);
}

//---------------------------------------------------------------------

void GlobalDevice::updateFrameDuration()
{
	frameDuration = getMicrosecondTime() - clockTime;
	clockTime = getMicrosecondTime();
}

//---------------------------------------------------------------------

float GlobalDevice::getFrameDuration()
{
	return (float)frameDuration / 1000000.f;
}

//---------------------------------------------------------------------

const position2d<s32>& GlobalDevice::getMainWindowSize()
{
	return GlobalDevice::getDevice()->getVideoDriver()->getViewPort().LowerRightCorner;
}

//---------------------------------------------------------------------

void GlobalDevice::println(const char* text)
{
	std::cout << text << std::endl;
}

//---------------------------------------------------------------------

dimension2d<s32> GlobalDevice::getDesktopResolution()
{
	IrrlichtDevice* tmpdevice = irr::createDevice( video::EDT_NULL, dimension2d<s32>(320, 240), 32, false, false, false, 0);
	dimension2d<s32> desktopResolution = tmpdevice->getVideoModeList()->getDesktopResolution();
	tmpdevice->drop();
	return desktopResolution;
}

//---------------------------------------------------------------------

void GlobalDevice::draw2DImageTranspChannel(ITexture* tex, position2d<s32> pos)
{
	getVideoDriver()->draw2DImage(
		tex, pos, rect<s32>(0,0, 
			tex->getSize().Width-1, 
			tex->getSize().Height-1),
		0,SColor(255,255,255,255), true);
}

//---------------------------------------------------------------------

float GlobalDevice::getRandomNumber(float min, float max)
{
	assert(max > min);
	return (((float)rand())*(max-min) / (float)RAND_MAX ) + min;
}

//---------------------------------------------------------------------
