/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

/*
 * Includes _________________________________________________________________
 *
 */
extern "C" {
#include <afs/param.h>
#include <afs/stds.h>
}

#include "afscfg.h"
#include <stdio.h>
#include "volume_utils.h"
#include "resource.h"
#include "partition_utils.h"


/*
 * Definitions _________________________________________________________________
 *
 */
static HWND m_hDriveList = 0;

//static const UINT MAX_DRIVES = 26;
static const UINT DISK_DRIVE_IMAGE = 0;
static const UINT DISABLED_DISK_DRIVE_IMAGE = 1;
static const UINT DISK_DRIVE_WITH_WARNING_IMAGE = 2;
static const UINT AFS_DISK_DRIVE_IMAGE = 3;


struct DRIVE_INFO
{
	TCHAR szRootDir[4];		// Drive letter plus colon plus slash, ex: "c:\"
	TCHAR szVolName[256];
	TCHAR szSize[32];		// Drive size in megabytes
	DWORD dwFlags;
	BOOL bDisabled;			// Disabled is TRUE if partition not suitable for AFS
	UINT nImage;			// Image to show in the FastList
};


/*
 * Prototypes ____________________________________________________________________
 *
 */


/*
 * Static Functions _________________________________________________________________
 *
 */
LPTSTR GetString(UINT nStrID)
{
	static TCHAR szText[cchRESOURCE];

	GetString(szText, nStrID);

	return szText;
}

static BOOL SetupImageLists()
{
	HIMAGELIST hiList = ImageList_Create(16, 16, TRUE, 1, 1);

	AfsAppLib_AddToImageList(hiList, IDI_DISK_DRIVE, FALSE);
	AfsAppLib_AddToImageList(hiList, IDI_DISABLED_DISK_DRIVE, FALSE);
	AfsAppLib_AddToImageList(hiList, IDI_DISK_DRIVE_WITH_WARNING, FALSE);
	AfsAppLib_AddToImageList(hiList, IDI_AGGREGATE, FALSE);

	FastList_SetImageLists(m_hDriveList, hiList, 0);

	return TRUE;
}

static void AddColumn(int nWidth, LPCTSTR pszTitle)
{
	static int nCol = 1;
	FASTLISTCOLUMN col;
	
	col.dwFlags = FLCF_JUSTIFY_LEFT;
	col.cxWidth = nWidth;
	lstrcpy(col.szText, pszTitle);
	
	FastList_SetColumn(m_hDriveList, nCol++, &col);
}

static void SetupDriveListCols()
{
	// Set width of cols based on width of the list		
	RECT rect;
	GetClientRect(m_hDriveList, &rect);
	
	int nWidth = rect.right - rect.left;

	// If the width of the list is too small to show all
	// three cols in a reasonable size that will fit without
	// scrolling, then set the width to the ideal size and
	// make the user scroll.
	if (nWidth < 150)	// Minimum we will accept
		nWidth = 370;	// The ideal size

	AddColumn(50, GetString(IDS_DRIVE));
	AddColumn(nWidth - 100, GetString(IDS_NAME_OR_ERROR));
	AddColumn(50, GetString(IDS_SIZE));
}

static LPCTSTR GetDriveSizeAsString(LPCTSTR pszRootDir)
{
	_ASSERTE(pszRootDir != 0);

	ULARGE_INTEGER liDummy;
	ULARGE_INTEGER liSize;
	static TCHAR szSize[64];

	*szSize = 0;
	
	// Get partition size in bytes	
	if (GetDiskFreeSpaceEx(pszRootDir, &liDummy, &liSize, &liDummy)) {
		// Covert to megabytes
		ULONG nSize = (ULONG)(liSize.QuadPart / (1024 * 1024));
		// Convert to a string
		_ultot(nSize, szSize, 10);
		lstrcat(szSize, TEXT(" MB"));
	}

	return szSize;
}

static BOOL DoesDriveContainData(LPCTSTR pszDriveRootDir)
{
	TCHAR szSearchSpec[16];
	WIN32_FIND_DATA findData;

	_stprintf(szSearchSpec, TEXT("%s*.*"), pszDriveRootDir);

	HANDLE hFind = FindFirstFile(szSearchSpec, &findData);
	if (hFind == INVALID_HANDLE_VALUE)
		return FALSE;

	FindClose(hFind);

	return TRUE;
}

static BOOL OnlyHasFolder(LPCTSTR pszRootDir, LPCTSTR pszFolder)
{
	TCHAR szSearchSpec[MAX_PATH];
	WIN32_FIND_DATA findData;
	BOOL bFound = FALSE;

	_stprintf(szSearchSpec, TEXT("%s*.*"), pszRootDir);

	// If there is nothing in the root dir, then return FALSE
	HANDLE hFind = FindFirstFile(szSearchSpec, &findData);
	if (hFind == INVALID_HANDLE_VALUE)
		return bFound;

	// Is the first thing on the disk the recycle bin?  If not
	// the the recycle bin is not the only thing on the disk.
	if (_tcsicmp(findData.cFileName, pszFolder) == 0) {
		// Is anything else on the disk?
		if (!FindNextFile(hFind, &findData))
			bFound = TRUE;
	}

	FindClose(hFind);

	return bFound;
}

static BOOL DriveHasRecycleBin(LPCTSTR pszDriveRootDir)
{
	if (OnlyHasFolder(pszDriveRootDir, TEXT("Recycled")))
		return TRUE;
	
	if (OnlyHasFolder(pszDriveRootDir, TEXT("Recycle Bin")))
		return TRUE;

	if (OnlyHasFolder(pszDriveRootDir, TEXT("Recycler")))
		return TRUE;

	return FALSE;
}

static BOOL DoesDriveContainNT(LPCTSTR pszDriveRootDir)
{
	UINT nBufSize = MAX_PATH;
	LPTSTR pszWinDir = 0;

	while (!pszWinDir) {
		pszWinDir = new TCHAR[nBufSize];

		UINT nWinDirLen = GetWindowsDirectory(pszWinDir, nBufSize);

		if (nWinDirLen > nBufSize) {
			nBufSize = nWinDirLen;
			delete [] pszWinDir;
			pszWinDir = 0;
		}
	}

	BOOL bNT = (_tcsncmp(pszDriveRootDir, pszWinDir, 3) == 0);

	delete [] pszWinDir;

	return bNT;
}

static BOOL ValidateDrive(BOOL bInvalid, UINT uiErrorMsgID, LPTSTR pszErrorStr)
{
	if (!bInvalid)
		return FALSE;

	if (*pszErrorStr)
		lstrcat(pszErrorStr, GetString(IDS_ERROR_SEP));
	lstrcat(pszErrorStr, GetString(uiErrorMsgID));

	return TRUE;
}

static BOOL GetDriveInfo(TCHAR chDrive, DRIVE_INFO& di)
{
	DWORD dwDummy;
	TCHAR szFileSys[64];
	DWORD dwDriveFlags;

	_stprintf(di.szRootDir, TEXT("%c:\\"), chDrive);
	
	if (GetDriveType(di.szRootDir) != DRIVE_FIXED)
		return FALSE;

	if (!GetVolumeInformation(di.szRootDir, di.szVolName, sizeof(di.szVolName), 0, &dwDummy, &dwDriveFlags, szFileSys, sizeof(szFileSys)))
		return FALSE;

	TCHAR szError[256] = TEXT("");

	BOOL bInvalid = FALSE, bHasData = FALSE;
	BOOL bIsAfs = ValidateDrive(IsAnAfsPartition(di.szRootDir), IDS_ERROR_DRIVE_ALREADY_HAS_AFS, szError);
	if (!bIsAfs) {
		bInvalid |= ValidateDrive(dwDriveFlags & FS_VOL_IS_COMPRESSED, IDS_ERROR_DRIVE_COMPRESSED, szError);
		bInvalid |= ValidateDrive(lstrcmp(szFileSys, TEXT("NTFS")) != 0, IDS_ERROR_FS_IS_NOT_NTFS, szError);

            /* 
             *  We are no longer going to require that afs drives be empty; we will give a warning instead.
             *
	     *	bInvalid |= ValidateDrive(DoesDriveContainNT(di.szRootDir), IDS_ERROR_DRIVE_CONTAINS_NT, szError);
	     *	bRecycle = ValidateDrive(DriveHasRecycleBin(di.szRootDir), IDS_WARNING_DRIVE_HAS_RECYCLE_BIN, szError);
	     */
	     	if (!bInvalid)
	     		bHasData = ValidateDrive(DoesDriveContainData(di.szRootDir), IDS_ERROR_DRIVE_HAS_DATA, szError);
	}

	if (*szError) {
		lstrcpy(di.szVolName, szError);
		
		if (bIsAfs)
			di.nImage = AFS_DISK_DRIVE_IMAGE;
		else if (bHasData)
			di.nImage = DISK_DRIVE_WITH_WARNING_IMAGE;      // Treat this as a warning
		else
			di.nImage = DISABLED_DISK_DRIVE_IMAGE;
		
		if (bIsAfs || bInvalid) {
			di.bDisabled = TRUE;
			di.dwFlags |= FLIF_DISALLOW_SELECT;
		}
	} else {
		di.nImage = DISK_DRIVE_IMAGE;
		
		if (lstrlen(di.szVolName) == 0)
			GetString(di.szVolName, IDS_VOLUME_HAS_NO_NAME);
	}

	lstrncpy(di.szSize, GetDriveSizeAsString(di.szRootDir), sizeof(di.szSize));

	return TRUE;
}

static BOOL FillDriveList()
{
	DRIVE_INFO di;

	TCHAR chDrive = TEXT('A');
	DWORD dwDrives = GetLogicalDrives();

	// This failing is not fatal to this function
	// Make sure the partition table is up to date
	afs_status_t nStatus;
	int nResult = ReadPartitionTable(&nStatus);
	ASSERT(nResult);

	while (dwDrives) {
		if (dwDrives & 1) {
			memset(&di, 0, sizeof(di));
			
			if (GetDriveInfo(chDrive, di)) {
				FASTLISTADDITEM ai = { 0, di.nImage, IMAGE_NOIMAGE, di.szRootDir, di.bDisabled, di.dwFlags };
				HLISTITEM hItem = FastList_AddItem(m_hDriveList, &ai);
				FastList_SetItemText(m_hDriveList, hItem, 1, di.szVolName);
				FastList_SetItemText(m_hDriveList, hItem, 2, di.szSize);
			}
		}

		chDrive++;
		dwDrives >>= 1;
	}

	return TRUE;
}

static int CALLBACK DriveListSortFunc(HWND hList, HLISTITEM hItem1, LPARAM lpItem1, HLISTITEM hItem2, LPARAM lpItem2)
{
	_ASSERTE(hList == m_hDriveList);

	// Ignore the first call which is only used to initialize static data
	if (!hItem1 && !hItem2)
		return 0;

	// The lpItem vars contain 1 if the item is disabled, 0 if not
	// Show enabled items before disabled items
	if (lpItem1 != lpItem2)
		return lpItem1 - lpItem2;

	LPCTSTR pszItem1 = FastList_GetItemText(m_hDriveList, hItem1, 0);
	LPCTSTR pszItem2 = FastList_GetItemText(m_hDriveList, hItem2, 0);

	return lstrcmp(pszItem1, pszItem2);
}


/*
 * Exported Functions _________________________________________________________________
 *
 */
void SetupDriveList(HWND driveList)
{
	m_hDriveList = driveList;

	SetupImageLists();

	SetupDriveListCols();

	FastList_SetSortFunction(m_hDriveList, DriveListSortFunc);
}

BOOL UpdateDriveList()
{
	FastList_RemoveAll(m_hDriveList);

	return FillDriveList();
}

