///////////////////////////////////////////////////////////////////////////////
/// @file DriverLoad.cpp
///
/// @brief functions used to load drivers
///
/// @author Lightning
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Aug 2001
/// @author Lightning
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include <windows.h>
//#include <cstdio>

#include "deFile.hpp"
#include "deFileSystem.hpp"
#include "deDriver_priv.hpp"
#include "deString.hpp"

#ifdef __GNUC__
#define DRIVER_EXTENSION "mddriver"
#else
#define DRIVER_EXTENSION "ddriver"
#endif

IdeDriverLoad* IdeDriverLoad_CreateDriverLoad(char const * path)
{
	return new deDriverLoad(path);
}

deDriverLoad::deDriverLoad(char const * Path)
{
	long					Counter;
	DriverEntry *			Entry = NULL;
	DriverEntry *			CurEntry = NULL;
	IdeFSReal	*			Directory = IdeFileSystem_CreateFSReal();
	deFileProperties		Properties;
	//char *					DLLPath;
	deString2				DLLPath;
	const deDriverInfo *	DriverInfo;
	deFSFindHandle			Finder = NULL;
	deBoolean				FoundNextEntry;

	//blank a few things out first
	m_DriverCount = 0;
	m_DriverList = NULL;

	//set the directory's current path
	if(!Directory->SetDirectory(Path))
		return;
	
	//do a search for all the dll's

	//loop thru the possible dll's
	Counter = 0;
	Finder = Directory->FindFirst("*." DRIVER_EXTENSION, &Properties);
	FoundNextEntry = deTRUE;
	if(Finder)
	{
		for(;;FoundNextEntry = Directory->FindNext(Finder, &Properties))
		{
			if(!FoundNextEntry)
				break;

			//put a string together based on the dll's full path and name
			//DLLPath = (char *)malloc(strlen(Path) + strlen(Properties.Filename) + 3);
			//sprintf(DLLPath, "%s\\%s", Path, Properties.Filename);
			DLLPath = Path;
			DLLPath += "\\";
			DLLPath += Properties.Filename;

			//if we don't have an entry, allocate one
			if(!Entry)
			{
				Entry = (DriverEntry *)malloc(sizeof(DriverEntry));
				if(!Entry)
				{
					Directory->Release();
					return;
				}
			}

			//clear the entry
			memset(Entry, 0, sizeof(*Entry));
			
			//see if it will load
			Entry->DriverHandle = LoadLibrary(DLLPath.const_str());
			//free(DLLPath);

			//if the load failed, continue the loop
			if(!Entry->DriverHandle)
				continue;
	
			//see if it has the identifying function, if not, continue
		#ifdef _DEBUG
			Entry->DriverProc = (ISDESTINY3DDRIVER)GetProcAddress(Entry->DriverHandle, "IsDestiny3DDriverDebug");
		#else
			Entry->DriverProc = (ISDESTINY3DDRIVER)GetProcAddress(Entry->DriverHandle, "IsDestiny3DDriver");
		#endif
			if(!Entry->DriverProc)
			{
				FreeLibrary(Entry->DriverHandle);
				continue;
			}
			//make sure it contains the one other functions we need
			Entry->DriverInstance = (GETDRIVERINSTANCE)GetProcAddress(Entry->DriverHandle, "GetDriverInstance");
			if(!Entry->DriverInstance)
			{
				FreeLibrary(Entry->DriverHandle);
				continue;
			}

			Entry->DriverInfo = (GETDRIVERINFO)GetProcAddress(Entry->DriverHandle, "GetDriverInfo");
			if(!Entry->DriverInfo)
			{
				FreeLibrary(Entry->DriverHandle);
				continue;
			}
			//put the DLL name into the entry
			Entry->DLLName = (char *)malloc(strlen(Properties.Filename) + 1);
			memcpy(Entry->DLLName, Properties.Filename, strlen(Properties.Filename) + 1);

			//get the info about the driver so we can get the name
			DriverInfo = Entry->DriverInfo(sizeof(deDriverInfo));
			if(!DriverInfo)
			{
				//failed to get the info
				FreeLibrary(Entry->DriverHandle);
				free(Entry);
				continue;
			} 
      			//grab the name
			Entry->DriverName = DriverInfo->Name;

			//increment how many drivers we area aware of
			Counter++;

			//add it to the list
			Entry->Next = NULL;
			if(!m_DriverList)
			{
				m_DriverList = Entry;
				CurEntry = Entry;
			}
			else
			{
				CurEntry->Next = Entry;
				CurEntry = Entry;
			}

			//blank the pointer out so we get a new one
			Entry = NULL;
		}
	}
	//erase the find
	Directory->FindEnd(Finder);

	//wipe out our directory search
	Directory->Release();

	//set the number we got
	m_DriverCount = Counter;

	//clear the unique driver id
	m_UniqueDriverID = 0xffff;
	return;
}

deDriverLoad::~deDriverLoad()
{
	DriverEntry *Entry;
	DriverEntry *TempEntry;

	//see if an idiot is trying to free a blank list
	if(!m_DriverList)
		return;

	//go thru and free the driver handles
	Entry = m_DriverList;
	while(Entry)
	{
		//grab a temp entry and go to the next one
		TempEntry = Entry;
		Entry = Entry->Next;

		//free it all
		free(TempEntry->DLLName);

#pragma todo(Possibly modify DriverLoad class so a class destruction does not unload driver dlls until DDRVLib dll is unloaded)
		FreeLibrary(TempEntry->DriverHandle);
		free(TempEntry);
	}

	m_DriverList = NULL;
	return;
}

long deDriverLoad::Release()
{
	delete this;
	return 0;
}

long deDriverLoad::GetCount()
{
	return m_DriverCount;
}

deDriverLoad::DriverEntry *deDriverLoad::GetEntry(long EntryNumber)
{
	DriverEntry *	Entry;
	long			Counter;

	//go thru the list and find the requested entry
	Counter = 0;
	Entry = m_DriverList;
	while(Counter < EntryNumber && Entry)
	{
		Counter++;
		Entry = Entry->Next;
	}

	//return the entry (it may be a null entry signalling an invalid count)
	return Entry;
}

deDriverLoad::DriverEntry *deDriverLoad::GetEntry(char *DriverName)
{
	DriverEntry *	Entry;
	long			Counter;

	//go thru the list and find the requested entry
	Counter = 0;
	Entry = m_DriverList;
	while(Entry)
	{
		if(strnicmp(Entry->DriverName, DriverName, strlen(DriverName)) == 0)
			break;
		
		Entry = Entry->Next;
	}

	//return the entry (it may be a null entry signalling an invalid count)
	return Entry;
}

const deDriverInfo *deDriverLoad::GetInfo(long DriverNumber)
{
	DriverEntry *			Entry;
	const deDriverInfo *	Info;
	
	Entry = GetEntry(DriverNumber);
	if(!Entry)
		return NULL;

	//get the info, pass in the size of the info struct to make sure they
	//can handle it
	Info = Entry->DriverInfo(sizeof(deDriverInfo));
	if(!Info)
		return NULL;
	
	//modify the pointer for the DLL name
	((deDriverInfo *)Info)->DLL = Entry->DLLName;

	return Info;
}

const deDriverInfo *deDriverLoad::GetInfo(char *DriverName)
{
	DriverEntry *			Entry;
	const deDriverInfo *	Info;
	
	Entry = GetEntry(DriverName);
	if(!Entry)
		return NULL;

	//get the info, pass in the size of the info struct to make sure they
	//can handle it
	Info = Entry->DriverInfo(sizeof(deDriverInfo));
	if(!Info)
		return NULL;
	
	//modify the pointer for the DLL name
	((deDriverInfo *)Info)->DLL = Entry->DLLName;

	return Info;
}

IdeDriver *deDriverLoad::GetDriver(long DriverNumber)
{
	DriverEntry *	Entry;
	
	Entry = GetEntry(DriverNumber);
	if(!Entry)
		return NULL;

	return Entry->DriverInstance(m_UniqueDriverID++);
}

IdeDriver *deDriverLoad::GetDriver(char *DriverName)
{
	DriverEntry *	Entry;
	
	Entry = GetEntry(DriverName);
	if(!Entry)
		return NULL;

	return Entry->DriverInstance(m_UniqueDriverID++);
}

deBoolean IdeDriver_FillVersionInfo(deDriverInfo *DriverInfo, char *Date, char *Time)
{
	//generate a version based on the time this was compiled

	//the year - 2000 for a major version
	DriverInfo->Major = atoi(&Date[7]) - 2000;

	//minor is set to the current day of the year so first set to minor to the number
	//of days before it
	//i setup the switch so that it falls thru and adds in all the other months
	//after it (before it in the calander) to count the days up to that month
	//this is why all the numbers are off by 1 month as it is the days in the month
	//before it
	DriverInfo->Minor = 0;
	switch(*((long *)&Date[0]))
	{
		case 0x20636544:			//"Dec "
			DriverInfo->Minor = DriverInfo->Minor + 30;
		
		case 0x20766F4E:			//"Nov "
			DriverInfo->Minor = DriverInfo->Minor + 31;

		case 0x2074634F:			//"Oct "
			DriverInfo->Minor = DriverInfo->Minor + 30;

		case 0x20706553:			//"Sep "
			DriverInfo->Minor = DriverInfo->Minor + 31;

		case 0x20677541:			//"Aug "
			DriverInfo->Minor = DriverInfo->Minor + 31;

		case 0x206C754A:			//"Jul "
			DriverInfo->Minor = DriverInfo->Minor + 30;

		case 0x206E754A:			//"Jun "
			DriverInfo->Minor = DriverInfo->Minor + 31;

		case 0x2079614D:			//"May "
			DriverInfo->Minor = DriverInfo->Minor + 30;

		case 0x20727041:			//"Apr "
			DriverInfo->Minor = DriverInfo->Minor + 31;

		case 0x2072614D:			//"Mar "
			DriverInfo->Minor = DriverInfo->Minor + 28;

			//count leap year. I'm not counting centuries as the next one is along ways off
			if(DriverInfo->Major % 4 == 0)
				DriverInfo->Minor++;

		case 0x20626546:			//"Feb "
			DriverInfo->Minor = DriverInfo->Minor + 31;

		case 0x206E614A:			//"Jan "
			DriverInfo->Minor = DriverInfo->Minor + 0;

			//stop the falling thru so we don't hit the return
			break;

		default:
			//something went wrong, fail it
			return NULL;
	}

	//add the days of this month to the minor
	DriverInfo->Minor = DriverInfo->Minor + atoi(&Date[4]);

	//multiply minor by 24 hours and add the current hour to it
	DriverInfo->Minor = (DriverInfo->Minor * 24) + atoi(&Time[0]);

	//set the revision so it is the number of seconds passed in this current hour
	DriverInfo->Revision = (atoi(&Time[3]) * 60) + atoi(&Time[6]);

	return deTRUE;
}
