/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2009.
     ssss    gggg x   x         Steven Goodwin. All Rights Reserved.
    
This version has been released under the GNU Public License v2.0.

Please see the license files for details.

File: sgx/src/drivers/wintel32/filesystem/win32device.cpp

*/

#include "sgx/core/core.hpp"
#include "sgx/filesystem/filesystem.hpp"
#include "drivers/wintel32/device.hpp"
#include "drivers/wintel32/filesystem/filesystem.hpp"

// Here we have:
// 1. Setup block: One per file. Contains general file information that you
//      don't want to search for. e.g. size
// 2. State block: Dyanmically created for each file that is opened. Acts like a
//      file handle. Use this to retrieve the data. Brave users can combine file handles
//      in Win32Disc class
// 3. DeviceHandler class.
//      open & close: Creates state block for the file requested (by setup block)
//      mountDirectory:  Attach file 'setup blocks'[1] to heirarchy

CSGXDeviceWin32Disc::CSGXDeviceWin32Disc(const sgxString &path, const sgxString &access) : CSGXDevice()
{
    m_Root = path;
	m_bReadOnly = (access=="rw")?FALSE:TRUE;
}

CSGXFileEntry *CSGXDeviceWin32Disc::addNewFile(CSGXFileEntry *pNewEntry)
{
    // The real Windows path can be deduced from the parents full
    // windows path very easily.
    sgxString winpath = m_Root + "\\" + pNewEntry->m_Data.m_Name;
    pNewEntry->m_pBlock = new CSGXDeviceBlockWin32Disc(winpath);
    pNewEntry->m_pFSDevice = this;

    return pNewEntry;
}

CSGXFileEntry *CSGXDeviceWin32Disc::addNewDirectory(CSGXFileEntry *pNewEntry)
{
    pNewEntry->m_pBlock = NULL;

	sgxString newNativePath;
	pNewEntry->m_pParent->m_pFSDevice->getRoot(newNativePath);
	newNativePath += "/";
	newNativePath += pNewEntry->m_Data.m_Name;

	pNewEntry->m_pFSDevice = new CSGXDeviceWin32Disc(newNativePath);

	return pNewEntry;
}


CSGXFileEntry *CSGXDeviceWin32Disc::createNewFile(CSGXFileEntry *pParentEntry, const sgxString &filename)
{
	if (m_bReadOnly) {
		return NULL;
	}
	CSGXFileSysData FileData;
	FileData.m_Name = filename;
	FileData.m_Filesize = 0;

	CSGXFileEntry *pFileEntry = CSGXFileSystem::get()->createFileEntry(pParentEntry, filename);
	addNewFile(pFileEntry);
	return pFileEntry;
}

CSGXFileEntry *CSGXDeviceWin32Disc::createNewDirectory(CSGXFileEntry *pParentEntry, const sgxString &dirName)
{
	if (m_bReadOnly) {
		return NULL;
	}

	sgxString nativePath;
	pParentEntry->m_pFSDevice->getRoot(nativePath);
	nativePath += "/";
	nativePath += dirName;

	if (CreateDirectory(nativePath.c_str(), NULL)) {

		CSGXFileEntry *pNewDir = CSGXFileSystem::get()->createFileEntry(pParentEntry, dirName);
		addNewDirectory(pNewDir);
		return pNewDir;
	}

	return NULL;
}


tBOOL CSGXDeviceWin32Disc::refreshFile(const sgxString &filePath) {
    CSGXFileSystem *pFS = CSGXFileSystem::get();
	// There are four cases here:
	// 1. The file existed, and has been (potentially) updated
	// 2. The file is new.
	// 3. The file has been deleted.
	// 4. The file didn't exist, and still doesn't.
    CSGXFileEntry *pCurrentEntry = pFS->getFileEntry(filePath);	// previous state

	// current state
	WIN32_FILE_ATTRIBUTE_DATA info;
	sgxString nativeFile = CSGXFileSystem::get()->getNativePath(filePath);
	if (GetFileAttributesEx(nativeFile.c_str(), GetFileExInfoStandard, &info) == 0) {
		// fail - doesn't exist
		info.dwFileAttributes = 0;
	}

	if (info.dwFileAttributes) {	// file exists
		if (pCurrentEntry) {
			// 1. file changed
			pCurrentEntry->m_Data.m_Filesize = info.nFileSizeLow;
			getFileModifiedDate(pCurrentEntry->m_Data.m_LastModifiedDate, info.ftLastWriteTime);

		} else {
			// 2. new file
			sgxString filehead;
			sgxString filetail;
			sgxFilename::splitPathAndFilename(filehead, filetail, filePath);
			
			CSGXFileEntry *pParentEntry = pFS->getFileEntry(filehead);
			createNewFile(pParentEntry, filetail);

		}
	} else {	// no file
		if (pCurrentEntry) {
			// 3. file deleted
			//pFS->d
		}
	}

	// if file exists
	//pFS->addNewFile(FileData);
	return TRUE;
}

#if 0

    FileData.m_Name = rootmount + "/" + sgxString(finddata.cFileName);
    FileData.m_Filesize = finddata.nFileSizeLow;

	getFileModifiedDate(FileData.m_LastModifiedDate, finddata.ftLastWriteTime);
#endif
                

tBOOL CSGXDeviceWin32Disc::mountDirectory(CSGXFileEntry *pEntry, const sgxString &rootmount, tBOOL recursive)
{
    WIN32_FIND_DATA finddata;
    HANDLE hFind;
    sgxString search = m_Root + "/*.*";
    CSGXFileSystem *pFS = CSGXFileSystem::get();
	sgxString nativePath;

	pEntry->m_pFSDevice->getRoot(nativePath);

    hFind = ::FindFirstFile(search.c_str(), &finddata);
    if (hFind == INVALID_HANDLE_VALUE) {
		sgxTrace("Couldn't mount %s", search.c_str());
        return FALSE;
    } else {
        // Scan directory for files
        do {
            if (finddata.cFileName[0] == '.') {
                // Ignore all 'dot' files
            } else if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
				if (recursive) {
					sgxString fullName = rootmount + ("/") + sgxString(finddata.cFileName);
					CSGXDevice *pDevice = new CSGXDeviceWin32Disc(nativePath + "/" + finddata.cFileName);
					pFS->mountDirectory(pDevice, fullName, TRUE);
				}
            } else {
                CSGXFileSysData FileData;

                FileData.m_Name = rootmount + "/" + sgxString(finddata.cFileName);
                FileData.m_Filesize = finddata.nFileSizeLow;

				getFileModifiedDate(FileData.m_LastModifiedDate, finddata.ftLastWriteTime);

                pFS->addNewFile(FileData);
            }
        } while (::FindNextFile(hFind, &finddata));
  
        ::FindClose(hFind);
    }

    return true;
}

void 		
CSGXDeviceWin32Disc::getFileModifiedDate(UTCDate &date, const FILETIME &filetime) {
	SYSTEMTIME systime;

	::FileTimeToSystemTime(&filetime, &systime);
	
	date.day.year = systime.wYear;
	date.day.month = SGX_STATIC_CAST(UTCMonth, systime.wMonth-1);
	date.day.day = systime.wDay;
	date.day.dow = SGX_STATIC_CAST(UTCDayOfWeek, systime.wDayOfWeek);
	//
	date.time.hours = systime.wHour;
	date.time.minutes = systime.wMinute;
	date.time.seconds = systime.wSecond;
	date.time.milliseconds = systime.wMilliseconds;
}

CSGXDeviceBlockState *CSGXDeviceWin32Disc::open(CSGXDeviceBlock *pBlk, const sgxString &access)
{
    CSGXDeviceBlockWin32Disc *pBlock = SGX_STATIC_CAST(CSGXDeviceBlockWin32Disc *, pBlk);
    HANDLE hFile;

    if (access == "rw") {
        hFile = ::CreateFile(pBlock->m_Filename.c_str(), FILE_WRITE_DATA, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    } else {
        hFile = ::CreateFile(pBlock->m_Filename.c_str(), FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    }

    if (hFile == INVALID_HANDLE_VALUE ) {
        return NULL;
    }

    CSGXDeviceBlockStateWin32Disc *pState = new CSGXDeviceBlockStateWin32Disc(hFile);
 
    return pState;
}

void CSGXDeviceWin32Disc::getRoot(sgxString &rootPath) {
	rootPath = m_Root;
}
