#include "global.h"
#include "RageFileManager_ReadAhead.h"
#include "RageThreads.h"
#include "RageLog.h"

#include <fcntl.h>
#include <cerrno>
#if defined(WIN32)
#include <io.h>
#endif

#if defined(HAVE_POSIX_FADVISE)
void RageFileManagerReadAhead::Init() { }
void RageFileManagerReadAhead::Shutdown() { }
void RageFileManagerReadAhead::ReadAhead( RageFileBasic *pFile, int iBytes )
{
	int iFD = pFile->GetFD();
	if( iFD == -1 )
		return;

	int iStart = lseek( iFD, 0, SEEK_CUR );
	posix_fadvise( iFD, iStart, iBytes, POSIX_FADV_WILLNEED );
}

void RageFileManagerReadAhead::DiscardCache( RageFileBasic *pFile, int iRelativePosition, int iBytes )
{
	int iFD = pFile->GetFD();
	if( iFD == -1 )
		return;

	int iStart = lseek( iFD, 0, SEEK_CUR );
	iStart += iRelativePosition;
	if( iStart < 0 )
	{
		iBytes -= iStart;
		iStart = 0;
	}

	if( iBytes == 0 )
		return;

	posix_fadvise( iFD, iStart, iBytes, POSIX_FADV_DONTNEED );
}

#else
#if 0
/* This doesn't currently work, because dup() locks the file position of the new FD with the old
 * (which makes no sense at all), so it's not easy to read from a copy of the FD without
 * interfering with the original. */
class RageFileReadAheadThread
{
public:
	RageFileReadAheadThread( int iFD, int iFrom, int iBytes )
	{
		m_iFD = iFD;
		m_iFrom = iFrom;
		m_iBytes = iBytes;
		m_bFinished = false;

		m_WorkerThread.SetName( "Read-ahead thread" );
		m_WorkerThread.Create( StartWorkerMain, this );
	}

	~RageFileReadAheadThread()
	{
		close( m_iFD );
		m_WorkerThread.Wait();
	}
	bool IsFinished() const { return m_bFinished; }

private:
	static int StartWorkerMain( void *pThis )
	{
		((RageFileReadAheadThread *) (pThis))->WorkerMain();
		return 0;
	}
	
	void WorkerMain()
	{
		RString sBuffer;
		int iFDCopy = dup( m_iFD );
		if( iFDCopy != -1 )
		{
			char *pBuf = sBuffer.GetBuffer( m_iBytes );
			lseek( iFDCopy, m_iFrom, SEEK_SET );
			read( iFDCopy, pBuf, m_iBytes );
			sBuffer.ReleaseBuffer( m_iBytes );

			close( iFDCopy );
			LOG->Trace("read");
		}

		m_bFinished = true;
	}

	RageThread m_WorkerThread;

	bool m_bFinished;
	int m_iFD;
	int m_iFrom;
	int m_iBytes;
};


static vector<RageFileReadAheadThread *> g_apReadAheads;

void RageFileManagerReadAhead::Init() { }
void RageFileManagerReadAhead::Shutdown()
{
	for( size_t i = 0; i < g_apReadAheads.size(); ++i )
		delete g_apReadAheads[i];
	g_apReadAheads.clear();
}

/* This emulates posix_fadvise(POSIX_FADV_WILLNEED), to force data into cache and hint the
 * kernel to start read-ahead from that point. */
void RageFileManagerReadAhead::ReadAhead( RageFileBasic *pFile, int iBytes )
{
	int iFD = pFile->GetFD();
	if( iFD == -1 )
		return;

	iFD = dup( iFD );
	if( iFD == -1 )
	{
		LOG->Trace( "error: dup(): %s", strerror(errno) );
		return;
	}

	int iStart = lseek( iFD, 0, SEEK_CUR );

	RageFileReadAheadThread *pReadAhead = new RageFileReadAheadThread( iFD, iStart, iBytes );
	g_apReadAheads.push_back( pReadAhead );
	
	for( size_t i = 0; i < g_apReadAheads.size(); ++i )
	{
		if( g_apReadAheads[i]->IsFinished() )
		{
			LOG->Trace("cleaned");
			delete g_apReadAheads[i];
			g_apReadAheads.erase( g_apReadAheads.begin()+i, g_apReadAheads.begin()+i+1 );
			--i;
		}
	}
}
void RageFileManagerReadAhead::DiscardCache( RageFileBasic *pFile, int iRelativePosition, int iBytes ) { }
#else
void RageFileManagerReadAhead::Init() { }
void RageFileManagerReadAhead::Shutdown() { }
void RageFileManagerReadAhead::ReadAhead( RageFileBasic *pFile, int iBytes ) { }
void RageFileManagerReadAhead::DiscardCache( RageFileBasic *pFile, int iRelativePosition, int iBytes ) { }
#endif
#endif

#if defined(HAVE_POSIX_FADVISE)
void RageFileManagerReadAhead::CacheHintStreaming( RageFileBasic *pFile )
{
	/* This guesses at the actual size of the file on disk, which may be smaller if this file is compressed.
	 * Since this is usually used on music and video files, it generally shouldn't be. */
	int iFD = pFile->GetFD();
	if( iFD == -1 )
		return;
	int iPos = pFile->Tell();
	int iFrom = lseek( iFD, 0, SEEK_CUR );
	int iBytes = pFile->GetFileSize() - iPos;
	posix_fadvise( iFD, iFrom, iBytes, POSIX_FADV_SEQUENTIAL );
}
#else
void RageFileManagerReadAhead::CacheHintStreaming( RageFileBasic *pFile ) { }
#endif


/*
 * Copyright (c) 2010 Glenn Maynard
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
