// SwiftShader Software Renderer
//
// Copyright(c) 2005-2013 TransGaming Inc.
//
// All rights reserved. No part of this software may be copied, distributed, transmitted,
// transcribed, stored in a retrieval system, translated into any human or computer
// language by any means, or disclosed to third parties without the explicit written
// agreement of TransGaming Inc. Without such an agreement, no rights or licenses, express
// or implied, including but not limited to any patent rights, are granted to you.
//

// Display.cpp: Implements the Display class, representing the abstract
// display on which graphics are drawn.

#include "Display.h"

#include "main.h"
#include "mathutil.h"
#include "Device.hpp"
#include "common/debug.h"

#include <algorithm>
#include <vector>
#include <map>

namespace gl
{
typedef std::map<NativeDisplayType, Display*> DisplayMap; 
DisplayMap displays;

Display *Display::getDisplay(NativeDisplayType displayId)
{
    if(displays.find(displayId) != displays.end())
    {
        return displays[displayId];
    }

    // FIXME: Check if displayId is a valid display device context
    Display *display = new Display(displayId);

    displays[displayId] = display;
    return display;
}

Display::Display(NativeDisplayType displayId) : displayId(displayId)
{
    mMinSwapInterval = 1;
    mMaxSwapInterval = 0;
}

Display::~Display()
{
    terminate();

	displays.erase(displayId);
}

static void cpuid(int registers[4], int info)
{
	#if defined(_WIN32)
        __cpuid(registers, info);
    #else
        __asm volatile("cpuid": "=a" (registers[0]), "=b" (registers[1]), "=c" (registers[2]), "=d" (registers[3]): "a" (info));
    #endif
}

static bool detectSSE()
{
	#if defined(__APPLE__)
		int SSE = false;
		size_t length = sizeof(SSE);
		sysctlbyname("hw.optional.sse", &SSE, &length, 0, 0);
		return SSE;
	#else
		int registers[4];
		cpuid(registers, 1);
		return (registers[3] & 0x02000000) != 0;
	#endif
}

bool Display::initialize()
{
    if(isInitialized())
    {
        return true;
    }

	if(!detectSSE())
	{
		 return false;
	}
		
    mMinSwapInterval = 0;
    mMaxSwapInterval = 4;

    if(!isInitialized())
    {
        terminate();

        return false;
    }

    return true;
}

void Display::terminate()
{
    while(!mSurfaceSet.empty())
    {
        destroySurface(*mSurfaceSet.begin());
    }

    while(!mContextSet.empty())
    {
        destroyContext(*mContextSet.begin());
    }
}

gl::Context *Display::createContext(const gl::Context *shareContext)
{
    gl::Context *context = new gl::Context(shareContext);
    mContextSet.insert(context);

    return context;
}

void Display::destroySurface(Surface *surface)
{
    delete surface;
    mSurfaceSet.erase(surface);
}

void Display::destroyContext(gl::Context *context)
{
    delete context;

	if(context == gl::getContext())
	{
		gl::makeCurrent(NULL, NULL, NULL);
	}

    mContextSet.erase(context);
}

bool Display::isInitialized() const
{
    return mMinSwapInterval <= mMaxSwapInterval;
}

bool Display::isValidContext(gl::Context *context)
{
    return mContextSet.find(context) != mContextSet.end();
}

bool Display::isValidSurface(Surface *surface)
{
    return mSurfaceSet.find(surface) != mSurfaceSet.end();
}

bool Display::isValidWindow(NativeWindowType window)
{
	#if defined(_WIN32)
		return IsWindow(window) == TRUE;
	#else
		XWindowAttributes windowAttributes;
		Status status = XGetWindowAttributes(displayId, window, &windowAttributes);
			
		return status == True;
	#endif
}

GLint Display::getMinSwapInterval()
{
    return mMinSwapInterval;
}

GLint Display::getMaxSwapInterval()
{
    return mMaxSwapInterval;
}

Surface *Display::getPrimarySurface()
{
    if(mSurfaceSet.size() == 0)
	{
		Surface *surface = new Surface(this, WindowFromDC(displayId));

		if(!surface->initialize())
		{
			delete surface;
			return 0;
		}

		mSurfaceSet.insert(surface);

		gl::setCurrentDrawSurface(surface);
	}

    return *mSurfaceSet.begin();
}

NativeDisplayType Display::getNativeDisplay() const
{
	return displayId;
}

DisplayMode Display::getDisplayMode() const
{
	DisplayMode displayMode = {0};

	#if defined(_WIN32)
		HDC deviceContext = GetDC(0);
	
		displayMode.width = ::GetDeviceCaps(deviceContext, HORZRES);
		displayMode.height = ::GetDeviceCaps(deviceContext, VERTRES);
		unsigned int bpp = ::GetDeviceCaps(deviceContext, BITSPIXEL);
	
		switch(bpp)
		{
		case 32: displayMode.format = sw::FORMAT_X8R8G8B8; break;
		case 24: displayMode.format = sw::FORMAT_R8G8B8;   break;
		case 16: displayMode.format = sw::FORMAT_R5G6B5;   break;
		default:
			ASSERT(false);   // Unexpected display mode color depth
		}
	
		ReleaseDC(0, deviceContext);
	#else
		Screen *screen = XDefaultScreenOfDisplay(displayId);
		displayMode.width = XWidthOfScreen(screen);
		displayMode.height = XHeightOfScreen(screen);
		unsigned int bpp = XPlanesOfScreen(screen);

		switch(bpp)
		{
		case 32: displayMode.format = sw::FORMAT_X8R8G8B8; break;
		case 24: displayMode.format = sw::FORMAT_R8G8B8;   break;
		case 16: displayMode.format = sw::FORMAT_R5G6B5;   break;
		default:
			ASSERT(false);   // Unexpected display mode color depth
		}
	#endif

	return displayMode;
}

}
