/*
 * PROJECT:     Recycle bin management
 * LICENSE:     GPL v2 - See COPYING in the top level directory
 * FILE:        lib/recyclebin/recyclebin_generic_enumerator.c
 * PURPOSE:     Enumerates contents of all recycle bins
 * PROGRAMMERS: Copyright 2007 Hervé Poussineau (hpoussin@reactos.org)
 */

#include "recyclebin_private.h"

struct RecycleBinGenericEnum
{
	ULONG ref;
	IRecycleBinEnumList recycleBinEnumImpl;
	IRecycleBinEnumList *current;
	DWORD dwLogicalDrives;
	SIZE_T skip;
};

static HRESULT STDMETHODCALLTYPE
RecycleBinGenericEnum_RecycleBinEnumList_QueryInterface(
	IN IRecycleBinEnumList *This,
	IN REFIID riid,
	OUT void **ppvObject)
{
	struct RecycleBinGenericEnum *s = CONTAINING_RECORD(This, struct RecycleBinGenericEnum, recycleBinEnumImpl);

	TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppvObject);

	if (!ppvObject)
		return E_POINTER;

	if (IsEqualIID(riid, &IID_IUnknown))
		*ppvObject = &s->recycleBinEnumImpl;
	else if (IsEqualIID(riid, &IID_IRecycleBinEnumList))
		*ppvObject = &s->recycleBinEnumImpl;
	else
	{
		*ppvObject = NULL;
		return E_NOINTERFACE;
	}

	IUnknown_AddRef(This);
	return S_OK;
}

static ULONG STDMETHODCALLTYPE
RecycleBinGenericEnum_RecycleBinEnumList_AddRef(
	IN IRecycleBinEnumList *This)
{
	struct RecycleBinGenericEnum *s = CONTAINING_RECORD(This, struct RecycleBinGenericEnum, recycleBinEnumImpl);
	ULONG refCount = InterlockedIncrement((PLONG)&s->ref);
	TRACE("(%p)\n", This);
	return refCount;
}

static VOID
RecycleBinGenericEnum_Destructor(
	struct RecycleBinGenericEnum *s)
{
	TRACE("(%p)\n", s);

	if (s->current)
		IRecycleBinEnumList_Release(s->current);
	CoTaskMemFree(s);
}

static ULONG STDMETHODCALLTYPE
RecycleBinGenericEnum_RecycleBinEnumList_Release(
	IN IRecycleBinEnumList *This)
{
	struct RecycleBinGenericEnum *s = CONTAINING_RECORD(This, struct RecycleBinGenericEnum, recycleBinEnumImpl);
	ULONG refCount;

	TRACE("(%p)\n", This);

	refCount = InterlockedDecrement((PLONG)&s->ref);

	if (refCount == 0)
		RecycleBinGenericEnum_Destructor(s);

	return refCount;
}

static HRESULT STDMETHODCALLTYPE
RecycleBinGenericEnum_RecycleBinEnumList_Next(
	IN IRecycleBinEnumList *This,
	IN DWORD celt,
	IN OUT IRecycleBinFile **rgelt,
	OUT DWORD *pceltFetched)
{
	struct RecycleBinGenericEnum *s = CONTAINING_RECORD(This, struct RecycleBinGenericEnum, recycleBinEnumImpl);
	IRecycleBin *prb;
	DWORD i;
	DWORD fetched = 0, newFetched;
	HRESULT hr;

	TRACE("(%p, %u, %p, %p)\n", This, celt, rgelt, pceltFetched);

	if (!rgelt)
		return E_POINTER;
	if (!pceltFetched && celt > 1)
		return E_INVALIDARG;

	while (TRUE)
	{
		/* Get enumerator implementation */
		if (!s->current && s->dwLogicalDrives)
		{
			for (i = 0; i < 26; i++)
				if (s->dwLogicalDrives & (1 << i))
				{
					WCHAR szVolumeName[4];
					szVolumeName[0] = (WCHAR)('A' + i);
					szVolumeName[1] = ':';
					szVolumeName[2] = '\\';
					szVolumeName[3] = UNICODE_NULL;
					if (GetDriveTypeW(szVolumeName) != DRIVE_FIXED)
					{
						s->dwLogicalDrives &= ~(1 << i);
						continue;
					}
					hr = GetDefaultRecycleBin(szVolumeName, &prb);
					if (!SUCCEEDED(hr))
						return hr;
					hr = IRecycleBin_EnumObjects(prb, &s->current);
					IRecycleBin_Release(prb);
					if (!SUCCEEDED(hr))
						return hr;
					s->dwLogicalDrives &= ~(1 << i);
					break;
				}
		}
		if (!s->current)
		{
			/* Nothing more to enumerate */
			if (pceltFetched)
				*pceltFetched = fetched;
			return S_FALSE;
		}

		/* Skip some elements */
		while (s->skip > 0)
		{
			IRecycleBinFile *rbf;
			hr = IRecycleBinEnumList_Next(s->current, 1, &rbf, NULL);
			if (hr == S_OK)
				hr = IRecycleBinFile_Release(rbf);
			else if (hr == S_FALSE)
				break;
			else if (!SUCCEEDED(hr))
				return hr;
		}
		if (s->skip > 0)
			continue;

		/* Fill area */
		hr = IRecycleBinEnumList_Next(s->current, celt - fetched, &rgelt[fetched], &newFetched);
		if (SUCCEEDED(hr))
			fetched += newFetched;
		if (hr == S_FALSE || newFetched == 0)
		{
			hr = IRecycleBinEnumList_Release(s->current);
			s->current = NULL;
		}
		else if (!SUCCEEDED(hr))
			return hr;
		if (fetched == celt)
		{
			if (pceltFetched)
				*pceltFetched = fetched;
			return S_OK;
		}
	}

	/* Never go here */
}

static HRESULT STDMETHODCALLTYPE
RecycleBinGenericEnum_RecycleBinEnumList_Skip(
	IN IRecycleBinEnumList *This,
	IN DWORD celt)
{
	struct RecycleBinGenericEnum *s = CONTAINING_RECORD(This, struct RecycleBinGenericEnum, recycleBinEnumImpl);
	TRACE("(%p, %u)\n", This, celt);
	s->skip += celt;
	return S_OK;
}

static HRESULT STDMETHODCALLTYPE
RecycleBinGenericEnum_RecycleBinEnumList_Reset(
	IN IRecycleBinEnumList *This)
{
	struct RecycleBinGenericEnum *s = CONTAINING_RECORD(This, struct RecycleBinGenericEnum, recycleBinEnumImpl);

	TRACE("(%p)\n", This);

	if (s->current)
	{
		IRecycleBinEnumList_Release(s->current);
		s->current = NULL;
		s->skip = 0;
	}
	s->dwLogicalDrives = GetLogicalDrives();
	return S_OK;
}

CONST_VTBL struct IRecycleBinEnumListVtbl RecycleBinGenericEnumVtbl =
{
	RecycleBinGenericEnum_RecycleBinEnumList_QueryInterface,
	RecycleBinGenericEnum_RecycleBinEnumList_AddRef,
	RecycleBinGenericEnum_RecycleBinEnumList_Release,
	RecycleBinGenericEnum_RecycleBinEnumList_Next,
	RecycleBinGenericEnum_RecycleBinEnumList_Skip,
	RecycleBinGenericEnum_RecycleBinEnumList_Reset,
};

HRESULT
RecycleBinGenericEnum_Constructor(
	OUT IRecycleBinEnumList **pprbel)
{
	struct RecycleBinGenericEnum *s;

	s = CoTaskMemAlloc(sizeof(struct RecycleBinGenericEnum));
	if (!s)
		return E_OUTOFMEMORY;
	ZeroMemory(s, sizeof(struct RecycleBinGenericEnum));
	s->ref = 1;
	s->recycleBinEnumImpl.lpVtbl = &RecycleBinGenericEnumVtbl;

	*pprbel = &s->recycleBinEnumImpl;
	return IRecycleBinEnumList_Reset(*pprbel);
}
