#include <prague.h>
#include <iface/i_io.h>
#include <iface/i_root.h>
#include <string.h>
#include "al_file_prague.h"

typedef struct tag_alFilePraguei {
	alFilePrague iface;
	long   m_RefCount;
	hIO    handle;
	tQWORD nPos;
	bool   bAutoCloseIo;
} alFilePraguei;

bool AL_CALLTYPE alFilePrague_Read(void* _this, void* pData, size_t nSize, size_t* pnBytesRead)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	tDWORD nRead;
	bool bRes;
	bRes = PR_SUCC(CALL_IO_SeekRead(_data->handle, &nRead, _data->nPos, (tPTR)pData, (tDWORD)nSize));
	_data->nPos += nRead;
	if (pnBytesRead)
		*pnBytesRead = nRead;
	if (bRes)
	{
		if (pnBytesRead && !nRead)
			bRes = false;
		if (!pnBytesRead && nRead != nSize)
			bRes = false;
	}
	return bRes;
}

bool AL_CALLTYPE alFilePrague_Write(void* _this, const void* pData, size_t nSize, size_t* pnBytesWritten)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	tDWORD nWritten;
	bool bRes = PR_SUCC(CALL_IO_SeekWrite(_data->handle, &nWritten, _data->nPos, (tPTR)pData, (tDWORD)nSize));
	_data->nPos += nWritten;
	if (pnBytesWritten)
		*pnBytesWritten = nWritten;
	if (bRes)
	{
		if (pnBytesWritten && !nWritten)
			bRes = false;
		if (!pnBytesWritten && nWritten != nSize)
			bRes = false;
	}
	return bRes;
}

bool AL_CALLTYPE alFilePrague_Seek(void* _this, int64 nSeek, int nSeekMethod, uint64* pnNewPos)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	switch (nSeekMethod)
	{
	case SEEK_CUR:
		_data->nPos += nSeek;
		break;
	case SEEK_END:
		CALL_IO_GetSize(_data->handle, &_data->nPos, IO_SIZE_TYPE_EXPLICIT);
		_data->nPos += nSeek;
		break;
	case SEEK_SET:
		_data->nPos = nSeek;
		break;
	default:
		return false;
	}
	if (_data->nPos < 0)
		_data->nPos = 0;
	if (pnNewPos)
		*pnNewPos = _data->nPos;
	return true;
}

bool AL_CALLTYPE alFilePrague_SeekRead(void* _this, uint64 nPos, void* pData, size_t nSize, size_t* pnBytesRead)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	tDWORD nRead;
	bool bRes;
	bRes = PR_SUCC(CALL_IO_SeekRead(_data->handle, &nRead, nPos, pData, (tDWORD)nSize));
	if (pnBytesRead)
		*pnBytesRead = nRead;
	if (bRes)
	{
		if (pnBytesRead && !nRead)
			bRes = false;
		if (!pnBytesRead && nRead != nSize)
			bRes = false;
	}
	return bRes;
}

bool AL_CALLTYPE alFilePrague_SeekWrite(void* _this, uint64 nPos, const void* pData, size_t nSize, size_t* pnBytesWritten)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	tDWORD nWritten;
	bool bRes = PR_SUCC(CALL_IO_SeekWrite(_data->handle, &nWritten, nPos, (tPTR)pData, (tDWORD)nSize));
	if (pnBytesWritten)
		*pnBytesWritten = nWritten;
	if (bRes)
	{
		if (pnBytesWritten && !nWritten)
			bRes = false;
		if (!pnBytesWritten && nWritten != nSize)
			bRes = false;
	}
	return bRes;
}

bool AL_CALLTYPE alFilePrague_GetSize(void* _this, uint64* pnSize)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	if (PR_FAIL(CALL_IO_GetSize(_data->handle, pnSize, IO_SIZE_TYPE_EXPLICIT)))
		return false;
	return true;
}

bool AL_CALLTYPE alFilePrague_SetSize(void* _this, uint64 nSize)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	if (PR_FAIL(CALL_IO_SetSize(_data->handle, nSize)))
		return false;
	return true;
}

bool AL_CALLTYPE alFilePrague_GetTime(void* _this, uint64* pnCreateTime, uint64* pnLastWriteTime, uint64* pnLastAccessTime)
{
	// time as Windows FILETIME
	alFilePraguei* _data = (alFilePraguei*)_this;
	uint64 time;
	if (pnCreateTime)
	{
		if (PR_FAIL(CALL_SYS_PropertyGet(_data->handle, NULL, pgOBJECT_CREATION_TIME, &time, sizeof(time))))
			return false;
		*pnCreateTime = time / 10;
	}
	if (pnLastWriteTime)
	{
		if (PR_FAIL(CALL_SYS_PropertyGet(_data->handle, NULL, pgOBJECT_LAST_WRITE_TIME, &time, sizeof(time))))
			return false;
		*pnLastWriteTime = time / 10;
	}
	if (pnLastAccessTime)
	{
		if (PR_FAIL(CALL_SYS_PropertyGet(_data->handle, NULL, pgOBJECT_LAST_ACCESS_TIME, &time, sizeof(time))))
			return false;
		*pnLastAccessTime = time / 10;
	}
	return true;
}

bool AL_CALLTYPE alFilePrague_SetTime(void* _this, uint64 nCreateTime, uint64 nLastWriteTime, uint64 nLastAccessTime)
{
	// time as Windows FILETIME
	alFilePraguei* _data = (alFilePraguei*)_this;
	if (nCreateTime)
	{
		if (PR_FAIL(CALL_SYS_PropertySetQWord(_data->handle, pgOBJECT_CREATION_TIME, nCreateTime*10)))
			return false;
	}
	if (nLastWriteTime)
	{
		if (PR_FAIL(CALL_SYS_PropertySetQWord(_data->handle, pgOBJECT_LAST_WRITE_TIME, nLastWriteTime*10)))
			return false;
	}
	if (nLastAccessTime)
	{
		if (PR_FAIL(CALL_SYS_PropertySetQWord(_data->handle, pgOBJECT_LAST_ACCESS_TIME, nLastAccessTime*10)))
			return false;
	}
	return true;
}

bool AL_CALLTYPE alFilePrague_Flush(void* _this)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	if (PR_FAIL(CALL_IO_Flush(_data->handle)))
		return false;
	return true;
}

bool AL_CALLTYPE alFilePrague_Close(void* _this)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	if (_data->handle == NULL)
		return false;
	if (_data->bAutoCloseIo)
		CALL_SYS_ObjectClose(_data->handle);
	_data->handle = NULL;
	return true;
}

bool AL_CALLTYPE alFilePrague_QueryInterface(void* _this, char* szIFaceName, void** ppIFace)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	if (!szIFaceName || !ppIFace)
		return false;
	if (0 == strcmp(szIFaceName, "IO"))
	{
		*ppIFace = _data->handle;
		return true;
	}
	return false;
}

long AL_CALLTYPE alFilePrague_AddRef(void* _this)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	return ++_data->m_RefCount;
}

long AL_CALLTYPE alFilePrague_Release(void* _this)
{
	alFilePraguei* _data = (alFilePraguei*)_this;
	long count = --_data->m_RefCount;
	if (count == 0)
	{
		hIO io = _data->handle;
		bool bClose = _data->bAutoCloseIo;
		_data->handle = NULL;
		CALL_SYS_ObjHeapFree(io, _data);
		if (bClose)
			CALL_SYS_ObjectClose(io);
	}
	return count;
}

static const alFilePrague_vtbl _alFilePrague_vtbl = {
	alFilePrague_QueryInterface,
	alFilePrague_AddRef,
	alFilePrague_Release,
	alFilePrague_SeekRead,
	alFilePrague_SeekWrite,
	alFilePrague_Read,
	alFilePrague_Write,
	alFilePrague_Seek,
	alFilePrague_GetSize,
	alFilePrague_SetSize,
	alFilePrague_GetTime,
	alFilePrague_SetTime,
	alFilePrague_Flush,
	alFilePrague_Close,
};

alFilePrague* AL_CALLTYPE new_alFilePrague(hIO io, bool bAutoCloseIo)
{
	alFilePraguei* _data = NULL;
	if (PR_FAIL(CALL_SYS_ObjHeapAlloc(io, &_data, sizeof(alFilePraguei))))
		return 0;
	_data->iface.vtbl = &_alFilePrague_vtbl;
	_data->handle = io;
	_data->m_RefCount = 1;
	_data->bAutoCloseIo = bAutoCloseIo;
	return &_data->iface;
}

alFilePrague* AL_CALLTYPE new_alFilePragueTemp(hOBJECT parent)
{
	alFilePrague* pFile;
	hIO io;
	if (PR_FAIL(CALL_SYS_ObjectCreateQuick(parent, (hOBJECT*)&io, IID_IO, PID_TMPFILE, SUBTYPE_ANY)))
		return false;
	pFile = new_alFilePrague(io, true);
	if (!pFile)
		CALL_SYS_ObjectClose(io);
	return pFile;
}