//------------------------------------------------------------------------------
// File: AsyncFlt.cpp
//
// Desc: DirectShow sample code - implementation of CAsyncCDFilter.
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

// See D:\Programming(D)\Workspaces\Video\VS2005\DirectShow_SDK_2005\AsyncCD
//  Info_Async_Build.txt re using unicode baseclasses.
// UNICODE and _UNICODE set in AsyncCD Properties - C++ - Command Line

#include <streams.h>
#include "asyncio.h"
#include "asyncrdr.h"

#pragma warning(disable:4710)	// 'function' not inlined (optimization)
#pragma warning(disable:4995)	// #pragma deprecated strings
#include "asyncflt.h"


// Setup data for filter registration.
//
const AMOVIESETUP_MEDIATYPE sudPinTypesOut[] = 
{ 
	{ 
		&MEDIATYPE_Stream,	
		&MEDIASUBTYPE_WAVE
	},
};
const AMOVIESETUP_PIN sudOpPin[] = 
{	
	{ 
		L"Output",					// Pin string name
		FALSE,						// Is it rendered
		TRUE,							// Is it an output
		FALSE,						// Can we have none
		FALSE,						// Can we have many
		&CLSID_NULL,					// Connects to filter
		NULL,							// Connects to pin
		sizeof(sudPinTypesOut) /
		  sizeof(sudPinTypesOut[0]),	// Number of types
		sudPinTypesOut				// Pin details
	},
};
const AMOVIESETUP_FILTER sudFilter = 
{ 
	&CLSID_AsyncCDFilter,	// Filter CLSID
	L"AsyncCD Filter",		// String name
	MERIT_UNLIKELY,			// Filter merit
	sizeof(sudOpPin) /
	sizeof(sudOpPin[0]),	// Number of pins
	sudOpPin					// Pin information
};
CFactoryTemplate g_Templates[] = 
{
	{ 
		L"AsyncCD Filter",
		&CLSID_AsyncCDFilter, //&__uuidof(CCDDAReader),
		CAsyncCDFilter::CreateInstance,
		NULL,
		&sudFilter
	}
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

// Exported entry points for registration and unregistration (in this 
//  case they only call through to default implementations).
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2(TRUE);
}
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2(FALSE);
}

// DllEntryPoint.
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);

BOOL APIENTRY DllMain(HANDLE hModule, 
                      DWORD  dwReason, 
                      LPVOID lpReserved)
{
	// Debug Log set up: log file on 03Serv X64 - c:\AsyncCdDbgLogFile.txt - set at:
	//	HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Debug\AsyncCd.ax
	//
	// Set level - from LAVAudio,cpp:
	//  Note: Messages must have a level less than or equal to this value 
	//  in order for the debug routines to log them. 
	#ifdef DEBUG
    DbgSetModuleLevel(LOG_CUSTOM1, 3); // FFMPEG messages use custom1
    DbgSetModuleLevel(LOG_ERROR, 3);
    DbgSetModuleLevel(LOG_TRACE, 3);
    // DbgSetModuleLevel (LOG_CUSTOM2, DWORD_MAX); // Jitter statistics
    // DbgSetModuleLevel (LOG_CUSTOM5, DWORD_MAX); // Extensive timing options	
	#endif	

	return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
}

// Create a new instance of this class.
CUnknown * WINAPI CAsyncCDFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    ASSERT(phr);

    //  DLLEntry does the right thing with the return code and
    //  the returned value on failure.
    return new CAsyncCDFilter(pUnk, phr);
}

//
// Todo - Property Page, settings interface.
//

////////////////////////////////////////////////////////////////
// class CAsyncCDFilter

// Construction.
CAsyncCDFilter::CAsyncCDFilter(LPUNKNOWN pUnk, HRESULT *phr) :
					CAsyncReader(NAME("AsyncCD Filter"),		// CAsyncReader Base class
								 pUnk, 
								 &m_stream, 
								 phr,
								 CLSID_AsyncCDFilter)			// 
{
}

// Clean up.
CAsyncCDFilter::~CAsyncCDFilter()
{
}

// Get IFileSourceFilter interface.
STDMETHODIMP CAsyncCDFilter::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
//#define QI(i) (riid == __uuidof(i)) ? GetInterface((i*)this, ppv) :
//#define QI2(i) (riid == IID_##i) ? GetInterface((i*)this, ppv) :

	if(riid == __uuidof(IFileSourceFilter) ) 
	{
		return GetInterface((IFileSourceFilter *)this, ppv);
	} 
	else if(riid == IID_IFileSourceFilter)
	{
		return GetInterface((IFileSourceFilter *)this, ppv);
	}
	else
	{
		return __super::NonDelegatingQueryInterface(riid, ppv);
	}
}

// Load a (new) file.
// Called with any .cda path, i.e. "E:\Track01.cda", via 
// the IFileSourceFilter interface after creating the
// AsyncCDFilter object during graph initialization.
STDMETHODIMP CAsyncCDFilter::Load(LPCOLESTR lpwszFileName, const AM_MEDIA_TYPE *pmt)
{
	CheckPointer(lpwszFileName, E_POINTER);
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::Load loading %s\n"), lpwszFileName ));

	// Get file name.
	std::wstring fileName(lpwszFileName);
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::Load fileName = %s\n"), fileName.c_str()));

	// Get the track number.
	int iOnes = 0, iTens = 0, iTrack = 0;
	size_t len = fileName.length();
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::Load fileName len = %d."), len));

	// Get 0 - 9.
	size_t index;
	index = fileName.find(L'.') - 1;
	if(index != std::string::npos && index < len)
	{
		wchar_t num = fileName[index];
		iOnes = _ttoi(&num);
	}
	// Get 10 - 90.
	index = fileName.find(L'.') - 2;
	if(index != std::string::npos && index < len)
	{
		wchar_t num = fileName[index];
		iTens = _ttoi(&num);		//_ttoi(&str[index]) is buggy
	}
	// Add up.
	iTrack = iTens * 10 + iOnes;

	// Ensure.
	if(iTrack < 1 || iTrack > 99)
	{				
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::Load iTrack out of bounds")));
		return E_FAIL;		
	}
	// Set members.
	m_fileName = fileName;
	m_iTrack = iTrack;		// ones based
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::Load m_filename %s, m_Itrack %d."), 
				m_fileName.c_str(), m_iTrack));

	// Load the CAsyncCDStream member CD TOC and track info/list.
	if(!m_stream.Load(m_fileName, m_iTrack))
	{		
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::Load m_stream.Load failed")));
		return E_FAIL;
	}
	// Set the CAsyncReader base class CMediaType m_mt member.
	CMediaType mt;
	mt.majortype = MEDIATYPE_Stream;
	mt.subtype = MEDIASUBTYPE_WAVE;
	m_mt = mt;

	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::Load success")));
	return S_OK;
}

// Modeled on IPersistFile::Load
// Caller needs to CoTaskMemFree or equivalent.
STDMETHODIMP CAsyncCDFilter::GetCurFile(LPOLESTR * ppszFileName, AM_MEDIA_TYPE *pmt)
{
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::GetCurFile entering.")));

	// From LAV Filters.
	CheckPointer(ppszFileName, E_POINTER);

	size_t strlen = m_fileName.length() + 1;
	*ppszFileName = (LPOLESTR)CoTaskMemAlloc(sizeof(wchar_t) * strlen);

	if(!(*ppszFileName))
	{
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::GetCurFile ppszFileName NULL.")));
		return E_OUTOFMEMORY;
	}
	wcsncpy_s(*ppszFileName, strlen, m_fileName.c_str(), _TRUNCATE); // truncate = (size_t)-1
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDFilter::GetCurFile ppszFileName set to %s."), *ppszFileName));
	return S_OK;
}

////////////////////////////////////////////////////////////////
// class CAsyncCDStream

// Constructor
CAsyncCDStream::CAsyncCDStream()        
{
	m_curReadPos = 0;
	m_hDrive = INVALID_HANDLE_VALUE;
	m_tracks.resize(0);					// empty
	m_waveHeaderSz = 0;					// m_waveHeader sz
	m_curTrack.Zero();
}

// Clean up.
CAsyncCDStream::~CAsyncCDStream()
{
	if(m_hDrive != INVALID_HANDLE_VALUE)
	{
		CloseHandle(m_hDrive);
		m_hDrive = INVALID_HANDLE_VALUE;
	}
}

// Param fileName - "E:\\Track01.cda".
// Param iTrack is ones based.
// Called from CAsyncCDFilter::Load.
bool CAsyncCDStream::Load(std::wstring fileName, int iTrack)
{
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load entering - fileName = %s"), fileName.c_str() ));

	// Get drive letter for drivePath below..
	std::wstring driveLetter(1, (wchar_t)fileName[0]);
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load driveLetter = %s"), driveLetter.c_str() ));

	// Get drivePath for CreateFile().
	std::wstring drivePath;
	drivePath = (L"\\\\.\\");
	drivePath += driveLetter;
	drivePath += L':';			// "\\.\E:"
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load drivePath = %s"), drivePath.c_str() ));		

	// Ensure not in use.
	if(m_hDrive != INVALID_HANDLE_VALUE)
	{
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load m_hDrive in use - closing.")));		
		CloseHandle(m_hDrive);
		m_hDrive = INVALID_HANDLE_VALUE;
	}

	// Get CD Drive handle.
	m_hDrive = CreateFile(drivePath.c_str(),			//LPCTSTR
						  GENERIC_READ,
						  FILE_SHARE_READ,
						  NULL, 
						  OPEN_EXISTING, 
						  FILE_ATTRIBUTE_READONLY | FILE_FLAG_SEQUENTIAL_SCAN, 
						  (HANDLE)NULL );
	if(m_hDrive == INVALID_HANDLE_VALUE)
	{		
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load CreateFile failed.")));
		return false;
	}

	// Get track-table.
	ULONG nRead;
	CDROM_TOC toc;
	CDTRACK track;
	BOOL ret = FALSE;
	ret = DeviceIoControl(m_hDrive,				// handle to device
						  IOCTL_CDROM_READ_TOC, // operation
						  NULL,					// input data buffer
						  0,					// size of input data buffer
						  &toc,					// output data buffer
						  sizeof(toc),			// size of output data buffer
						  &nRead,				// byte count
						  NULL);				// overlapped information

	// DeviceIoControl failed.
	if(ret == 0)
	{
		CloseHandle(m_hDrive);
		m_hDrive = INVALID_HANDLE_VALUE;;
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load DeviceIoControl failed for file %s."),
					fileName.c_str() ));
		return false;
	}
	// Verify TOC bounds - all ones based.
	if(toc.FirstTrack < 1	   || 
	   toc.LastTrack  < 1	   ||
	   iTrack > toc.LastTrack  || 
	   iTrack < toc.FirstTrack )
	{
		CloseHandle(m_hDrive);
		m_hDrive = INVALID_HANDLE_VALUE;;
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load TOC bounds error for drive %s, track %d."), 
					driveLetter.c_str(), iTrack ));
		return false;
	}
	// Get CDTRACK data for each track from the toc TRACK_DATA.
	//
	// Bounds
	ULONG first = toc.FirstTrack - 1;	// zero based
	ULONG last  = toc.LastTrack;		// ones based

	// Set ar size.
	m_tracks.resize(last);

	// Get tracks.
	for(ULONG i = first; i < last; i++)
	{		
		track.Address_sector = AddressToSectors(toc.TrackData[i].Address);
		track.Length_sectors = AddressToSectors(toc.TrackData[i + 1].Address) - track.Address_sector;
		track.Length_bytes	 = track.Length_sectors * RAW_SECTOR_SIZE;
		track.TrackNumber	 = toc.TrackData[i].TrackNumber;	// ones based

		// Add to array.
		m_tracks[i] = track;
	}

	// Set current track.
	m_curTrack = m_tracks[iTrack - 1];	// zero based
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load - m_curTrack values:")));
	DbgLog((LOG_TRACE, 2, TEXT("  Address_sector = %u"),	m_curTrack.Address_sector));
	DbgLog((LOG_TRACE, 2, TEXT("  Length_sectors = %u"),	m_curTrack.Length_sectors));
	DbgLog((LOG_TRACE, 2, TEXT("  Length_bytes   = %I64d"), m_curTrack.Length_bytes));
	DbgLog((LOG_TRACE, 2, TEXT("  TrackNumber	 = %u"),	m_curTrack.TrackNumber));

	// Build wave header for iTrack.
	if(!CreateWaveHeader())
	{
		CloseHandle(m_hDrive);
		m_hDrive = INVALID_HANDLE_VALUE;;
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load CreateWaveHeader failed.")));
		return false;
	}

	// OK.
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Load file %s track %d successfully parsed."), 
		fileName.c_str(), iTrack));
	return true;
}

ULONG CAsyncCDStream::AddressToSectors(UCHAR Addr[4])
{
	//mpc - #define MSF2UINT(hgs) ((hgs[1] * 4500) + (hgs[2] * 75) + (hgs[3]))

	ULONG Sectors = (Addr[1] * (CD_BLOCKS_PER_SECOND * 60) ) +	// 75 x 60 = 4500
					(Addr[2] *  CD_BLOCKS_PER_SECOND)		 +	// 75
					 Addr[3] ;
	DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::AddressToSectors returning %u"), Sectors));
	return Sectors - 150;										// - 150
}

// Build a header for current track.
// Called from Load().
bool CAsyncCDStream::CreateWaveHeader()
{
	// Ensure.
	if(m_curTrack.TrackNumber < 1				|| 
	   m_curTrack.TrackNumber > m_tracks.size() )
	{
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::CreateWaveHeader m_curTrack %d bounds error."),
					m_curTrack.TrackNumber));
		return false;
	}

	// Create WAVE header in m_waveHeader.
	m_waveHeader.Set(44100, 16, 2, (ULONG)m_curTrack.Length_bytes); // todo - out ULONG cast
	m_waveHeaderSz = sizeof(m_waveHeader);
	return true;
}

// CAsyncStream override.
HRESULT CAsyncCDStream::SetPointer(LONGLONG llPos)
{
	if(llPos < 0 || llPos > m_curTrack.Length_bytes) 
	{
		return S_FALSE;
	}
	// Ok
	m_curReadPos = llPos;
	return S_OK;
}

// CAsyncStream override. See asyncio.h.
HRESULT CAsyncCDStream::Read(PBYTE	 pbBuffer,
							 DWORD	 dwBytesToRead,
							 BOOL	 bAlign,
							 LPDWORD pdwBytesRead)
{
	// pdwBytesToRead = 131072.
	//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  bytes requested = %d."), dwBytesToRead));

	// Ensure
	if(!m_hDrive)
	{
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  m_hDrive NULL.")));
		return S_FALSE;		
	}

	// From MPC mostly.
	//
	CAutoLock lck(&m_csLock);

	// Buffer.
	BYTE buff[RAW_SECTOR_SIZE]; // 2352 bytes

	// Current buf pos.
	PBYTE pbBufferOrg = pbBuffer;

	// Start at m_curReadPos - 0 at first.
	LONGLONG pos = m_curReadPos;
	//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  pos = %I64d."), pos));

	// Requested read size.
	size_t len = (size_t)dwBytesToRead;

	// Write a Wave header for current track at start.
	if(pos < m_waveHeaderSz && len > 0)
	{
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  writing wave header.")));

		// Note header for this track created in CreateWaveHeader().
		//
		// Write complete or partial header (size_t is UINT).
		size_t l = (size_t) min(len, m_waveHeaderSz - pos);
		DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  writing %u of %u wave header bytes."),
					l, m_waveHeaderSz));

		memcpy(pbBuffer, &((BYTE*)&m_waveHeader)[pos], l);
		pbBuffer += l;
		pos += l;
		len -= l;
	}

	// Back to 0 (or less?).
	pos -= m_waveHeaderSz;
	//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  pos after wave header read = %I64d."), pos));
	//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  m_curTrack.Length_bytes = %I64d."), 
	//			m_curTrack.Length_bytes));

	while(pos >= 0 && pos < m_curTrack.Length_bytes && len > 0)
	{
		RAW_READ_INFO rawreadinfo;
		rawreadinfo.SectorCount = 1;
		rawreadinfo.TrackMode = CDDA;

		UINT sector = m_curTrack.Address_sector + int(pos / RAW_SECTOR_SIZE); // int?
		//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  sector = %u."), sector));

		__int64 offset = pos % RAW_SECTOR_SIZE;

		rawreadinfo.DiskOffset.QuadPart = sector * 2048;
		//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  DiskOffset.QuadPart = %u."), 
		//			rawreadinfo.DiskOffset.QuadPart));

		DWORD BytesReturned = 0;
		BOOL b = DeviceIoControl(m_hDrive, 
								 IOCTL_CDROM_RAW_READ,
								 &rawreadinfo, 
								 sizeof(rawreadinfo),
								 buff, 
								 RAW_SECTOR_SIZE,
								 &BytesReturned, 
								 0 );
		// Can fail. 
		if(b == 0)
		{ 
			DWORD er = GetLastError();
			DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  DeviceIoControl failed error = %d."),
						er));

			// GetLastError returns ERROR_MORE_DATA if the
			// output buffer is too small 
			if(er == ERROR_MORE_DATA)  // 234L
			{
				DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  error = ERROR_MORE_DATA")));
			}
			// Fatal
			else if(er == ERROR_INVALID_HANDLE)  // 6L
			{
				DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  fatal error = ERROR_INVALID_HANDLE")));
				return S_FALSE;
			}
		}
		size_t l = (size_t) min( min(len, RAW_SECTOR_SIZE - offset), m_curTrack.Length_bytes - pos);
		//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  writing %u cd bytes."), l));

		memcpy(pbBuffer, &buff[offset], l);

		pbBuffer += l;
		pos += l;
		len -= l;
	}

	if(pdwBytesRead) 
		*pdwBytesRead = pbBuffer - pbBufferOrg;
	m_curReadPos += pbBuffer - pbBufferOrg;
	//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Read  m_curReadPos = %I64d."), m_curReadPos));

	return S_OK;
}

// CAsyncStream override.
LONGLONG CAsyncCDStream::Size(LONGLONG *pSizeAvailable)
{
	// From MPC
	//
	LONGLONG size = m_waveHeaderSz + m_curTrack.Length_bytes;
	if(pSizeAvailable) 
		*pSizeAvailable = size;
	//DbgLog((LOG_TRACE, 2, TEXT("CAsyncCDStream::Size returning size = %I64d"), size));

    return size;
}

// CAsyncStream override.
DWORD CAsyncCDStream::Alignment()
{
	return 1;
}
// CAsyncStream override.
void CAsyncCDStream::Lock()
{
	m_csLock.Lock();
}
// CAsyncStream override.
void CAsyncCDStream::Unlock()
{
	m_csLock.Unlock();
}

////////////////////////////////////////////////////////////
// Class CWaveFileHeader - for creating a wave-file header.
CWaveFileHeader::CWaveFileHeader()
{
	ZeroMemory(this, sizeof(*this) );
}

CWaveFileHeader::CWaveFileHeader(ULONG SamplingRate, USHORT BitsPerSample, 
								 USHORT Channels, ULONG DataSize)
{
	Set(SamplingRate, BitsPerSample, Channels, DataSize);
}

void CWaveFileHeader::Set(ULONG SamplingRate, USHORT BitsPerSample, 
						  USHORT Channels, ULONG DataSize)			// ULONG->LONGLONG DataSize?
{
	// Set Riff-Chunk
	CopyMemory( m_Riff_ID, "RIFF", 4 );
	m_Riff_Size = DataSize + 44;
	CopyMemory( m_Riff_Type, "WAVE", 4 );

	// Set Fmt-Chunk
	CopyMemory( m_Fmt_ID, "fmt ", 4 );
	m_Fmt_Length = 16;
	m_Fmt_Format = WAVE_FORMAT_PCM;
	m_Fmt_Channels = Channels;
	m_Fmt_SamplingRate = SamplingRate;
	m_Fmt_BlockAlign = Channels * BitsPerSample / 8;
	m_Fmt_DataRate = Channels * BitsPerSample * SamplingRate / 8;
	m_Fmt_BitsPerSample = BitsPerSample;

	// Set Data-Chunk
	CopyMemory( m_Data_ID, "data", 4 );
	m_Data_DataSize = DataSize;
}

