/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "hsTypes.h"
#include "plDeviceSelector.h"
#include "hsStream.h"
#include "hsUtils.h"

#include <algorithm>

DeviceSelector::DeviceSelector() :
	fSelDevType(hsG3DDeviceSelector::kDevTypeUnknown),
	fSelDev(0),
	fSelMode(0),
	fDevDesc(0),
	fModeDesc(0),
	fPerformance(0),
	fFilterBPP(0),
	fFilterWidth(0),
	fFilterHeight(0),
	fWindowed(false)
{
	memset(fStr, 0x00, sizeof(fStr));
}

const char	*DeviceSelector::GetErrorString( void )
{
	return fSelector.GetErrorString();
}

hsBool DeviceSelector::Enumerate(HWND hWnd, hsBool expertMode )
{
	plDemoDebugFile::Enable( true );		/// ALWAYS enable (well, for now at least)

	if( !fSelector.Init() )
		return false;

	fSelector.Enumerate(hWnd);

	// 11.25.2000 mcn - Now we are tough if we're not in expert mode
	fSelector.RemoveUnusableDevModes( !expertMode );

	// Sort the modes
	hsTArray<hsG3DDeviceRecord> &recs = fSelector.GetDeviceRecords();
	for (Int32 i = 0; i < recs.Count(); i++)
	{
		hsTArray<hsG3DDeviceMode> &modes = recs[i].GetModes();
		std::sort(modes.FirstIter(), modes.StopIter());
	}

	IRefreshFilter();

	return true;
}

void	DeviceSelector::SetModeFilter( int bitDepth, int minWidth, int minHeight )
{
	fFilterBPP = bitDepth;
	fFilterWidth = minWidth;
	fFilterHeight = minHeight;

	IRefreshFilter();
}

void	DeviceSelector::IRefreshFilter( void )
{
	if (fSelDev >= fRecords.Count() )
		return;

	// Make sure to preserve fSelMode if possible
	const hsG3DDeviceMode *oldMode = nil;
	if( fSelMode < fFilteredModes.GetCount() && fFilteredModes[ fSelMode ]<fSelRec.GetModes().GetCount() )
		oldMode = fSelRec.GetMode( fFilteredModes[ fSelMode ] );

	fFilteredModes.Reset();

	int i;
	for( i = 0; i < fRecords[ fSelDev ].GetModes().Count(); i++ )
	{
		hsG3DDeviceMode* mode = fRecords[ fSelDev ].GetMode( i );

		// Filter out modes we don't want listed
		if( fFilterBPP != 0 && fFilterBPP != mode->GetColorDepth() )
			continue;

		if( mode->GetWidth() < fFilterWidth || mode->GetHeight() < fFilterHeight )
			continue;

		// Remove any non 4:3 modes
		bool goodAspectRatio = (mode->GetWidth() / 4 == mode->GetHeight() / 3) &&
								(mode->GetWidth() % 4 == 0) &&
								(mode->GetHeight() % 3 == 0);

		if (!goodAspectRatio && !(mode->GetWidth() == 1280 && mode->GetHeight() == 1024))
		{
			continue;
		}

		// Add the remaining to our filter index
		fFilteredModes.Append( i );
	}

	if( oldMode != nil )
	{
		fSelMode = IFindFiltered( GetModeNum( oldMode ) );
		if( fSelMode == -1 )
		{
			// Try w/o bpp
			fSelMode = IFindFiltered( IGetModeNumNoBPP( oldMode ) );
			if( fSelMode == -1 )
				fSelMode = 0;
		}
	}
	else
		fSelMode = 0;

}

int		DeviceSelector::IFindFiltered( int realIndex )
{
	int idx = fFilteredModes.Find( realIndex );
	if( idx == fFilteredModes.kMissingIndex )
		return -1;

	return idx;
}

hsBool DeviceSelector::CheckDeviceType(UInt32 type)
{
	hsTArray<hsG3DDeviceRecord>& records = fSelector.GetDeviceRecords();

	for (Int32 i = 0; i < records.Count(); i++)
	{
		if (type == records[i].GetG3DDeviceType())
			return true;
	}

	return false;
}

hsBool DeviceSelector::IsDirect3DAvailable()
{
	return CheckDeviceType(hsG3DDeviceSelector::kDevTypeDirect3D);
}

hsBool DeviceSelector::IsDirect3DTnLAvailable()
{
	return CheckDeviceType(hsG3DDeviceSelector::kDevTypeDirect3DTnL);
}

hsBool DeviceSelector::IsGlideAvailable()
{
	return CheckDeviceType(hsG3DDeviceSelector::kDevTypeGlide);
}

hsBool DeviceSelector::IsOpenGLAvailable()
{
	return CheckDeviceType(hsG3DDeviceSelector::kDevTypeOpenGL);
}

void DeviceSelector::SetDirect3D()
{
	SetDeviceType(hsG3DDeviceSelector::kDevTypeDirect3D);
}

void DeviceSelector::SetDirect3DTnL()
{
	SetDeviceType(hsG3DDeviceSelector::kDevTypeDirect3DTnL);
}

void DeviceSelector::SetGlide()
{
	SetDeviceType(hsG3DDeviceSelector::kDevTypeGlide);
}

void DeviceSelector::SetOpenGL()
{
	SetDeviceType(hsG3DDeviceSelector::kDevTypeOpenGL);
}

void DeviceSelector::SetDeviceType (UInt32 type)
{
	Int32 i;
	for(i = 0; i < fRecords.GetCount(); i++)
		fRecords[i].Clear();
	fRecords.Reset();

	hsTArray<hsG3DDeviceRecord>& records = fSelector.GetDeviceRecords();
	for (i = 0; i < records.Count(); i++)
	{
		if (records[i].GetG3DDeviceType() == type)
			fRecords.Push(records[i]);
	}

	fSelDevType = type;
	fSelDev  = 0;
	fDevDesc = 0;
	fModeDesc = 0;

	IRefreshFilter();
}

hsBool DeviceSelector::IsDirect3D()
{
	if (fSelDevType == hsG3DDeviceSelector::kDevTypeDirect3D)
		return true;
	else
		return false;
}

hsBool DeviceSelector::IsDirect3DTnL()
{
	return ( fSelDevType == hsG3DDeviceSelector::kDevTypeDirect3DTnL ) ? true : false;
}

hsBool DeviceSelector::IsGlide()
{
	if (fSelDevType == hsG3DDeviceSelector::kDevTypeGlide)
		return true;
	else
		return false;
}

hsBool DeviceSelector::IsOpenGL()
{
	if (fSelDevType == hsG3DDeviceSelector::kDevTypeOpenGL)
		return true;
	else
		return false;
}

hsBool DeviceSelector::SetDevice(UInt32 index)
{
	if (index < fRecords.Count())
	{
		fSelDev = index;
		fSelMode = 0;
		fSelRec = fRecords[index];
		fSelRec.SetMaxAnisotropicSamples(0);

		IRefreshFilter();
		return true;
	}

	return false;
}

hsBool DeviceSelector::SetMode(UInt32 index)
{
	if (fSelDev >= fRecords.Count())
		return false;

	if (index < fFilteredModes.GetCount())
	{
		fSelMode = index;
		return true;
	}

	return false;
}

char* DeviceSelector::GetDeviceDescription()
{
	if (fDevDesc == fRecords.Count())
	{
		fDevDesc = 0;
		return nil;
	}

	sprintf(fStr, "%s [%s]", fRecords[fDevDesc].GetDriverDesc(), fRecords[fDevDesc].GetDeviceDesc());
	fDevDesc++;
	return fStr;
}

char* DeviceSelector::GetModeDescription( void )
{
	if (fSelDev >= fRecords.Count() )
		return nil;

	if (fModeDesc == fFilteredModes.GetCount())
	{
		fModeDesc = 0;
		return nil;
	}

	hsG3DDeviceMode* mode = fRecords[fSelDev].GetMode( fFilteredModes[ fModeDesc ] );
	fModeDesc++;

	if( fFilterBPP != 0 )
		sprintf( fStr, "%ux%u", mode->GetWidth(), mode->GetHeight() );
	else
		sprintf(fStr, "%ux%u %u bit", mode->GetWidth(), mode->GetHeight(), mode->GetColorDepth());

	return fStr;
}

UInt32 DeviceSelector::GetNumModes()
{
	return fFilteredModes.GetCount();
}

void DeviceSelector::GetMode(UInt32 i, int& width, int& height, int& depth)
{
	if (i >= fFilteredModes.GetCount())
		return;

	hsG3DDeviceMode* mode = fRecords[fSelDev].GetMode(fFilteredModes[i]);

	width  = mode->GetWidth();
	height = mode->GetHeight();
	depth  = mode->GetColorDepth();
}

hsBool DeviceSelector::SetDefault()
{
	hsG3DDeviceModeRecord dmr;
	if (fSelector.GetDefault(&dmr))
	{
		SetDeviceType(dmr.GetDevice()->GetG3DDeviceType());
		fSelDev = GetDeviceNum(dmr.GetDevice());
		fSelMode = IFindFiltered( GetModeNum(dmr.GetMode()) );
		fSelRec = fRecords[fSelDev];
		fSelRec.SetMaxAnisotropicSamples( 0 );	// Also off unless explicitly requested

		// Set a default detail level based on the available memory
		if (hsMemorySpec() == kBlows)
			fPerformance = 25;
		else
			fPerformance = 100;

		IRefreshFilter();

		return true;
	}

	return false;
}

hsBool DeviceSelector::Save()
{
	hsUNIXStream stream;
	if (!stream.Open(DEV_MODE_DAT, "wb"))
		return false;

	hsG3DDeviceRecord selRec = fSelRec;
	hsG3DDeviceMode selMode = *(selRec.GetMode( fFilteredModes[ fSelMode ] ));
	selRec.ClearModes();

	selRec.Write(&stream);

	if (fWindowed)
		selMode.SetColorDepth(0);
	selMode.Write(&stream);

	stream.WriteSwap16(fPerformance);

	stream.Close();

	return true;
}

hsBool DeviceSelector::Load()
{
	hsUNIXStream stream;
	if (!stream.Open(DEV_MODE_DAT, "rb"))
		return false;

	hsG3DDeviceRecord	LoadRec;	// Device copy for reading/writing
	hsG3DDeviceMode		LoadMode;	// Modes copy for reading/writing

	LoadRec.Read(&stream);
	if (LoadRec.IsInvalid())
	{
		stream.Close();
		return false;
	}

	LoadMode.Read(&stream);

	fPerformance = stream.ReadSwap16();

	stream.Close();

	// If selected device is available use it, otherwise return false
	if ((LoadRec.GetG3DDeviceType() == hsG3DDeviceSelector::kDevTypeDirect3D) && IsDirect3DAvailable())
		SetDirect3D();
	else if ((LoadRec.GetG3DDeviceType() == hsG3DDeviceSelector::kDevTypeDirect3DTnL) && IsDirect3DTnLAvailable())
		SetDirect3DTnL();
	else if ((LoadRec.GetG3DDeviceType() == hsG3DDeviceSelector::kDevTypeGlide) && IsGlideAvailable())
		SetGlide();
	else
		return false;

	////////////////////////////////////////////////////////////////////////////
	// Attempt to match the saved device and mode to the ones that are currently
	// available.
	////////////////////////////////////////////////////////////////////////////
	int num = GetDeviceNum(&LoadRec);
	if (num == -1)
		return false;
	SetDevice(num);

	// Copy the flags
	fSelRec.SetCap(hsG3DDeviceSelector::kCapsCompressTextures,
		LoadRec.GetCap(hsG3DDeviceSelector::kCapsCompressTextures));
	fSelRec.SetAASetting( LoadRec.GetAASetting() );
	fSelRec.SetMaxAnisotropicSamples( LoadRec.GetMaxAnisotropicSamples() );

	if (LoadMode.GetColorDepth() == 0)
	{
		fWindowed = true;
		LoadMode.SetColorDepth(32);
	}
	num = GetModeNum(&LoadMode);
	if (num == -1)
		return false;

	SetMode(IFindFiltered(num));

	return true;
}

int DeviceSelector::GetDeviceNum(const hsG3DDeviceRecord *pLoadRec)
{
	hsTArray<hsG3DDeviceRecord>& records = fRecords;

	for (int i = 0; i < records.Count(); i++)
	{
		if (!strcmp(records[i].GetDriverDesc(), pLoadRec->GetDriverDesc()) &&
			!strcmp(records[i].GetDriverName(), pLoadRec->GetDriverName()) &&
			!strcmp(records[i].GetDriverVersion(), pLoadRec->GetDriverVersion()) &&
			!strcmp(records[i].GetDeviceDesc(), pLoadRec->GetDeviceDesc()))
			return i;
	}	

	return -1;
}

int		DeviceSelector::IGetModeNumNoBPP( const hsG3DDeviceMode *pLoadMode )
{
	hsTArray<hsG3DDeviceMode>& modes = fRecords[fSelDev].GetModes();

	for (int i = 0; i < modes.Count(); i++)
	{
		if ((modes[i].GetWidth()		== pLoadMode->GetWidth()) &&
		    (modes[i].GetHeight()		== pLoadMode->GetHeight())
			)
		{
			if( fFilteredModes.Find( i ) != fFilteredModes.kMissingIndex )
			{
#ifndef M3DRELEASE
				if (pLoadMode->GetColorDepth() == 0)
					fSelRec.GetMode( i )->SetColorDepth(0);
#endif
				return i;
			}
		}
	}

	return -1;
}

int DeviceSelector::GetModeNum(const hsG3DDeviceMode *pLoadMode)
{
	hsTArray<hsG3DDeviceMode>& modes = fRecords[fSelDev].GetModes();

	for (int i = 0; i < modes.Count(); i++)
	{
		if ((modes[i].GetWidth()		== pLoadMode->GetWidth()) &&
		    (modes[i].GetHeight()		== pLoadMode->GetHeight()) &&
		    (modes[i].GetColorDepth()	== pLoadMode->GetColorDepth()))
		{
			return i;
		}
	}

	return -1;
}

UInt8	DeviceSelector::CanAntiAlias()
{
	hsG3DDeviceMode *mode = fRecords[ fSelDev ].GetMode( fFilteredModes[ fSelMode ] );

	return mode->GetNumFSAATypes();
}

UInt8 DeviceSelector::IsAntiAliased()
{
	return fSelRec.GetAASetting();
}

void DeviceSelector::SetAntiAlias(UInt8 numSamples)
{
	fSelRec.SetAASetting( numSamples );
}

UInt8	DeviceSelector::CanAnisotropicFilter()
{
	UInt8	hi = fRecords[ fSelDev ].GetMaxAnisotropicSamples();
	if( hi > 1 )
		return hi;

	return 0;
}

UInt8	DeviceSelector::GetAnisotropicLevel()
{
	return fSelRec.GetMaxAnisotropicSamples();
}

void	DeviceSelector::SetAnisotropicLevel( UInt8 level )
{
	fSelRec.SetMaxAnisotropicSamples( level );
}

bool DeviceSelector::CanWindow ()
{
	return !fSelRec.GetCap(hsG3DDeviceSelector::kCapsNoWindow);
}

bool DeviceSelector::IsWindowed()
{
	return fWindowed;
}

void DeviceSelector::SetWindowed(bool state)
{
	fWindowed = state;
}

hsBool DeviceSelector::CanCompress ()
{
	return fRecords[fSelDev].GetCap(hsG3DDeviceSelector::kCapsCompressTextures);
}

hsBool DeviceSelector::IsCompressed()
{
	return fSelRec.GetCap(hsG3DDeviceSelector::kCapsCompressTextures);
}

void DeviceSelector::SetCompressed(hsBool state)
{
	fSelRec.SetCap(hsG3DDeviceSelector::kCapsCompressTextures, state);
}

bool DeviceSelector::GetCap(UInt32 cap)
{
	return fSelRec.GetCap(cap) != 0;
}