/** 
 * @file llvfsthread.cpp
 * @brief LLVFSThread implementation
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2008, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "llvfsthread.h"
#include "llstl.h"

//============================================================================

/*static*/ std::string LLVFSThread::sDataPath = "";

/*static*/ LLVFSThread* LLVFSThread::sLocal = NULL;

//============================================================================
// Run on MAIN thread
//static
void LLVFSThread::initClass(bool local_is_threaded)
{
	llassert(sLocal == NULL);
	sLocal = new LLVFSThread(local_is_threaded);
}

//static
S32 LLVFSThread::updateClass(U32 ms_elapsed)
{
	sLocal->update(ms_elapsed);
	return sLocal->getPending();
}

//static
void LLVFSThread::cleanupClass()
{
	sLocal->setQuitting();
	while (sLocal->getPending())
	{
		sLocal->update(0);
	}
	delete sLocal;
	sLocal = 0;
}

//----------------------------------------------------------------------------

LLVFSThread::LLVFSThread(bool threaded) :
	LLQueuedThread("VFS", threaded)
{
}

LLVFSThread::~LLVFSThread()
{
	// ~LLQueuedThread() will be called here
}

//----------------------------------------------------------------------------

LLVFSThread::handle_t LLVFSThread::read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type,
										U8* buffer, S32 offset, S32 numbytes, U32 priority, U32 flags)
{
	handle_t handle = generateHandle();

	priority = llmax(priority, (U32)PRIORITY_LOW); // All reads are at least PRIORITY_LOW
	Request* req = new Request(handle, priority, flags, FILE_READ, vfs, file_id, file_type,
							   buffer, offset, numbytes);

	bool res = addRequest(req);
	if (!res)
	{
		llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl;
		req->deleteRequest();
		handle = nullHandle();
	}

	return handle;
}

S32 LLVFSThread::readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type,
							   U8* buffer, S32 offset, S32 numbytes)
{
	handle_t handle = generateHandle();

	Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_READ, vfs, file_id, file_type,
							   buffer, offset, numbytes);
	
	S32 res = addRequest(req) ? 1 : 0;
	if (res == 0)
	{
		llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl;
		req->deleteRequest();
	}
	else
	{
		llverify(waitForResult(handle, false) == true);
		res = req->getBytesRead();
		completeRequest(handle);
	}
	return res;
}

LLVFSThread::handle_t LLVFSThread::write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type,
										 U8* buffer, S32 offset, S32 numbytes, U32 flags)
{
	handle_t handle = generateHandle();

	Request* req = new Request(handle, 0, flags, FILE_WRITE, vfs, file_id, file_type,
							   buffer, offset, numbytes);

	bool res = addRequest(req);
	if (!res)
	{
		llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl;
		req->deleteRequest();
		handle = nullHandle();
	}
	
	return handle;
}

S32 LLVFSThread::writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type,
								 U8* buffer, S32 offset, S32 numbytes)
{
	handle_t handle = generateHandle();

	Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_WRITE, vfs, file_id, file_type,
							   buffer, offset, numbytes);

	S32 res = addRequest(req) ? 1 : 0;
	if (res == 0)
	{
		llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl;
		req->deleteRequest();
	}
	else
	{
		llverify(waitForResult(handle, false) == true);
		res = req->getBytesRead();
		completeRequest(handle);
	}
	return res;
}


// LLVFSThread::handle_t LLVFSThread::rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type,
// 										  const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags)
// {
// 	handle_t handle = generateHandle();

// 	LLUUID* new_idp = new LLUUID(new_id); // deleted with Request
// 	// new_type is passed as "numbytes"
// 	Request* req = new Request(handle, 0, flags, FILE_RENAME, vfs, file_id, file_type,
// 							   (U8*)new_idp, 0, (S32)new_type);

// 	bool res = addRequest(req);
// 	if (!res)
// 	{
// 		llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl;
// 		req->deleteRequest();
// 		handle = nullHandle();
// 	}
	
// 	return handle;
// }

//============================================================================

LLVFSThread::Request::Request(handle_t handle, U32 priority, U32 flags,
							  operation_t op, LLVFS* vfs,
							  const LLUUID &file_id, const LLAssetType::EType file_type,
							  U8* buffer, S32 offset, S32 numbytes) :
	QueuedRequest(handle, priority, flags),
	mOperation(op),
	mVFS(vfs),
	mFileID(file_id),
	mFileType(file_type),
	mBuffer(buffer),
	mOffset(offset),
	mBytes(numbytes),
	mBytesRead(0)
{
	llassert(mBuffer);

	if (numbytes <= 0 && mOperation != FILE_RENAME)
	{
		llwarns << "LLVFSThread: Request with numbytes = " << numbytes 
			<< " operation = " << op
			<< " offset " << offset 
			<< " file_type " << file_type << llendl;
	}
	if (mOperation == FILE_WRITE)
	{
		S32 blocksize =  mVFS->getMaxSize(mFileID, mFileType);
		if (blocksize < 0)
		{
			llwarns << "VFS write to temporary block (shouldn't happen)" << llendl;
		}
		mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND);
	}
	else if (mOperation == FILE_RENAME)
	{
		mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND);
	}
	else // if (mOperation == FILE_READ)
	{
		mVFS->incLock(mFileID, mFileType, VFSLOCK_READ);
	}
}

// dec locks as soon as a request finishes
void LLVFSThread::Request::finishRequest(bool completed)
{
	if (mOperation == FILE_WRITE)
	{
		mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND);
	}
	else if (mOperation == FILE_RENAME)
	{
		mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND);
	}
	else // if (mOperation == FILE_READ)
	{
		mVFS->decLock(mFileID, mFileType, VFSLOCK_READ);
	}
}

void LLVFSThread::Request::deleteRequest()
{
	if (getStatus() == STATUS_QUEUED)
	{
		llerrs << "Attempt to delete a queued LLVFSThread::Request!" << llendl;
	}	
	if (mOperation == FILE_WRITE)
	{
		if (mFlags & FLAG_AUTO_DELETE)
		{
			delete [] mBuffer;
		}
	}
	else if (mOperation == FILE_RENAME)
	{
		LLUUID* new_idp = (LLUUID*)mBuffer;
		delete new_idp;
	}
	LLQueuedThread::QueuedRequest::deleteRequest();
}

bool LLVFSThread::Request::processRequest()
{
	bool complete = false;
	if (mOperation ==  FILE_READ)
	{
		llassert(mOffset >= 0);
		mBytesRead = mVFS->getData(mFileID, mFileType, mBuffer, mOffset, mBytes);
		complete = true;
		//llinfos << llformat("LLVFSThread::READ '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl;
	}
	else if (mOperation ==  FILE_WRITE)
	{
		mBytesRead = mVFS->storeData(mFileID, mFileType, mBuffer, mOffset, mBytes);
		complete = true;
		//llinfos << llformat("LLVFSThread::WRITE '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl;
	}
	else if (mOperation ==  FILE_RENAME)
	{
		LLUUID* new_idp = (LLUUID*)mBuffer;
		LLAssetType::EType new_type = (LLAssetType::EType)mBytes;
		mVFS->renameFile(mFileID, mFileType, *new_idp, new_type);
		mFileID = *new_idp;
		complete = true;
		//llinfos << llformat("LLVFSThread::RENAME '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl;
	}
	else
	{
		llerrs << llformat("LLVFSThread::unknown operation: %d", mOperation) << llendl;
	}
	return complete;
}

//============================================================================
