#include "win32_avi_reader.h"
#include <strsafe.h>

const int _BUFFER_SIZE_ = 16384;

AVIReaderWin32::AVIReaderWin32()
{
	hFile = 0;

	_ring_buffer.reserve( _BUFFER_SIZE_ );

	end_of_file       = false;

	position.QuadPart = 0;
	local_filename    = 0;
}

AVIReaderWin32::~AVIReaderWin32()
{
	free( local_filename );
}

void AVIReaderWin32::Close()
{
	if ( hFile && hFile != INVALID_HANDLE_VALUE )
	{
		//CancelIo(hFile);
		CloseHandle( hFile );
	}
}

uint64_t AVIReaderWin32::GetContentLength()
{
	LARGE_INTEGER position;
	position.QuadPart = 0;
	position.LowPart  = GetFileSize( hFile, (LPDWORD)&position.HighPart );

	if ( position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR )
		return 0;
	else
		return position.QuadPart;
}

void AVIReaderWin32::GetFilename( wchar_t *fn, size_t len )
{
	StringCchCopyW( fn, len, local_filename );
}

int AVIReaderWin32::Open( const wchar_t *filename )
{
	free( local_filename );
	local_filename = _wcsdup( filename );

	hFile = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0 );
	if ( hFile == INVALID_HANDLE_VALUE )
		return nsavi::READ_NOT_FOUND;

	return nsavi::READ_OK;
}

/* used by RingBuffer::fill() */
size_t AVIReaderWin32::Read( void *dest, size_t len )
{
	// TODO: use overlapped I/O so can we wait on the read simultaneously with the killswitch and seek_event
	DWORD bytes_read = 0;	
	if ( ReadFile( hFile, dest, (DWORD)len, &bytes_read, NULL ) && bytes_read != len )
		end_of_file = true;

	return bytes_read;
}

int AVIReaderWin32::Read( void *p_read_buffer, uint32_t read_length, uint32_t *bytes_read )
{
	if ( end_of_file && _ring_buffer.empty() )
		return nsavi::READ_EOF;

	size_t total_bytes_read = 0;

	while ( read_length && !( end_of_file && _ring_buffer.empty() ) )
	{
		// read what we can from the buffer
		size_t bytes_read  = _ring_buffer.read( p_read_buffer, read_length );
		p_read_buffer      = (uint8_t *)p_read_buffer + bytes_read;
		read_length       -= (uint32_t)bytes_read;
		total_bytes_read  += bytes_read;
		position.QuadPart += bytes_read;

		if ( read_length > _BUFFER_SIZE_ )
		{
			// read directly from the file if we have a large read
			bytes_read         = Read( p_read_buffer, read_length );
			p_read_buffer      = (uint8_t *)p_read_buffer + bytes_read;
			read_length       -= (uint32_t)bytes_read;
			total_bytes_read  += bytes_read;
			position.QuadPart += bytes_read;
		}
		else
		{
			// refill buffer if necessary
			_ring_buffer.fill( this, _BUFFER_SIZE_ );
		}
	}

	*bytes_read = (uint32_t)total_bytes_read;

	return nsavi::READ_OK;
}

int AVIReaderWin32::Peek( void *read_buffer, uint32_t read_length, uint32_t *bytes_read )
{
	if ( end_of_file && _ring_buffer.empty() )
		return nsavi::READ_EOF;

	// refill buffer if necessary
	if ( _ring_buffer.size() < read_length )
		_ring_buffer.fill( this, _BUFFER_SIZE_ );

	*bytes_read = (uint32_t)_ring_buffer.peek( read_buffer, read_length );

	return nsavi::READ_OK;
}

static LONGLONG Seek64( HANDLE hf, __int64 distance, DWORD MoveMethod )
{
	LARGE_INTEGER li;
	li.QuadPart = distance;
	li.LowPart = SetFilePointer( hf, li.LowPart, &li.HighPart, MoveMethod );
	if ( li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR )
	{
		li.QuadPart = -1;
	}

	return li.QuadPart;
}

int AVIReaderWin32::Seek( uint64_t new_position )
{
	_ring_buffer.clear();

	position.QuadPart = Seek64( hFile, new_position, SEEK_SET );
	end_of_file       = ( position.QuadPart != new_position );

	return nsavi::READ_OK;
}

uint64_t AVIReaderWin32::Tell()
{
	return position.QuadPart;
}

int AVIReaderWin32::Skip( uint32_t skip_bytes )
{
	if ( end_of_file && _ring_buffer.empty() )
		return nsavi::READ_EOF;

	if ( skip_bytes < _ring_buffer.size() )
	{
		_ring_buffer.advance( skip_bytes );

		position.QuadPart += skip_bytes;

		return nsavi::READ_OK;
	}
	else
	{
		return Seek( position.QuadPart + skip_bytes );
	}
}

void AVIReaderWin32::OverlappedHint( uint32_t read_length )
{
	if ( read_length > _ring_buffer.size() )
		_ring_buffer.fill( this, _BUFFER_SIZE_ );
}