/*************************************************************************************************/
/*!
   	@file		Asio.h
	@author 	Fanzo
*/
/*************************************************************************************************/
#pragma		once

///////////////////////////////////////////////////////////////////////////////////////////////////
//include files
#include	"iFace/iAsio.h"

#pragma pack( push , 8 )		//set align

namespace icubic_audio
{
using namespace icubic;
///////////////////////////////////////////////////////////////////////////////////////////////////
// preprocessor deifne

#ifdef cb_windows
	#define	asio_call __cdecl
#else
	#error	unknown
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
// type define

///////////////////////////////////////////////////////////////////////////////////////////////////
// shared functions define

///////////////////////////////////////////////////////////////////////////////////////////////////
// classes define

/**************************************************************************************************
"Asio" class 
**************************************************************************************************/
class Asio : 
	virtual public object_base , 
	public IAsio
{
	query_begin();
	iface_hook( IAsio , IAsio_IID )
	query_end( object_base );
	
	class ClockSourceEx : public ClockSource
	{
	public:
		long	index;
	};
	enum ThisPtrCmd
	{
		GetPtr , 
		SetPtr , 
		ClearPtr , 
	};
// variable member
private:
	IASIO*					m_asio;
	Array<ClockSourceEx>	m_clocks;
	ASIOCallbacks			m_asio_callbacks;
	State					m_state;
	
	// thread
	uint32					m_input_channel;
	uint32					m_output_channel;
	uint32					m_buffersamples;
	Array<ASIOBufferInfo>	m_buffers;
	Array<ASIOChannelInfo>	m_channels;
	IAsioCallback*			m_callback;
	
// audio thread functions
private:
//=================================================================================================
static
Asio* ThisPtr
		(
		ThisPtrCmd	cmd	= GetPtr , 
		Asio*		ptr	= 0
		)
{
	static
	CriticalSection			m_cs;
	static
	Asio*				m_this_ptr	= 0;
	
	if( cmd == GetPtr )
		return m_this_ptr;

	Autolock	lock( m_cs );
	if( cmd == ClearPtr )
	{
		m_this_ptr = 0;
		return 0;
	}
	else
	{
		if( m_this_ptr != 0 )
			return 0;
		m_this_ptr	= ptr;
		return ptr;
	}		 
}
//=================================================================================================
static
ASIOTime* asio_call BufferSwitchTimeInfoStatic
		(
		ASIOTime	*timeInfo, 
		long		index ,
		ASIOBool	processNow
		)
{
	return ThisPtr()->BufferSwitchTimeInfo( timeInfo , index , processNow );
}
//=================================================================================================
static
void asio_call BufferSwitchStatic
		(
		long		index, 
		ASIOBool	processNow
		)
{
	ThisPtr()->BufferSwitch( index , processNow );
}
//=================================================================================================
static
void asio_call SampleRateChangedStatic
		(
		ASIOSampleRate sRate
		)
{
	ThisPtr()->SampleRateChanged( sRate );
}
//=================================================================================================
static
long asio_call AsioMessagesStatic
		(
		long		selector , 
		long		value , 
		void*		msg , 
		double*		opt
		)
{
	return ThisPtr()->AsioMessages( selector , value , msg , opt );
}
//=================================================================================================
ASIOTime* BufferSwitchTimeInfo
		(
		ASIOTime	*timeinfo, 
		long		index ,
		ASIOBool	processnow
		)
{
	if( m_callback == 0 )
		return 0;
	return m_callback->BufferSwitchTimeInfo
			( 
			m_input_channel , 
			m_output_channel , 
			m_buffers.GetConstPtr() , 
			m_channels.GetConstPtr() , 
			m_buffersamples , 
			timeinfo , 
			index , 
			processnow 
			);
}
//=================================================================================================
void asio_call BufferSwitch
		(
		long		index, 
		ASIOBool	processnow
		)
{
	if( m_callback == 0 )
		return;
	m_callback->BufferSwitch
			( 
			m_input_channel , 
			m_output_channel , 
			m_buffers.GetConstPtr() , 
			m_channels.GetConstPtr() , 
			m_buffersamples , 
			index , 
			processnow 
			);
}
//=================================================================================================
void asio_call SampleRateChanged
		(
		ASIOSampleRate samplerate
		)
{
	if( m_callback == 0 )
		return;
	m_callback->SampleRateChanged( samplerate );
}
//=================================================================================================
long asio_call AsioMessages
		(
		long		selector , 
		long		value , 
		void*		msg , 
		double*		opt
		)
{
	if( m_callback == 0 )
		return 0;
	return m_callback->AsioMessages( selector , value , msg , opt );
}
// private functions
private:
//=================================================================================================
bool cb_call getBufferSize
		(
		long*		min_size , 
		long*		max_size , 
		long*		preferred_size , 
		long*		granularity_size
		)
{
	long	min , max , preferred , granularity;
	if( ASE_OK != m_asio->getBufferSize( &min , &max , &preferred , &granularity ) )
		return false;
	store( min_size , min );
	store( max_size , max );
	store( preferred_size , preferred );
	store( granularity_size , granularity );
	return true;
}
//=================================================================================================
bool createBuffers
		(
		ASIOBufferInfo*		buffer_info ,
		long				channel ,
		long				buffer_size, 
		ASIOCallbacks*		callbacks
		)
{
	if( ASE_OK != m_asio->createBuffers( buffer_info , channel , buffer_size , callbacks ) )
		return false;
	return true;
}
//=================================================================================================
bool getChannelInfo
		(
		ASIOChannelInfo*	info
		)
{
	if( ASE_OK != m_asio->getChannelInfo( info ) )
		return false;
	return true;
}
//=================================================================================================
bool disposeBuffers()
{
	if( ASE_OK != m_asio->disposeBuffers() )
		return false;
	return true;
}
//=================================================================================================
bool CreateBuffers
		(
		uint32			in_ch , 
		const uint32	in_align[] , 
		uint32			out_ch , 
		const uint32	out_align[] , 
		int				buffersize ,
		ASIOCallbacks*	callbacks
		)
{
	if( in_ch + out_ch == 0 )
		return false;
		
	// set buffers
	m_buffers.Resize( in_ch + out_ch );
	uint32	ch_off;
	for( ch_off = 0; ch_off < in_ch ; ch_off++ )
	{
		m_buffers[ ch_off ].isInput			= ASIOTrue;
		m_buffers[ ch_off ].channelNum		= in_align[ ch_off ];
		m_buffers[ ch_off ].buffers[ 0 ]	= 0;
		m_buffers[ ch_off ].buffers[ 1 ]	= 0;
	}
	for( ch_off = 0; ch_off < out_ch ; ch_off++ )
	{
		m_buffers[ ch_off + in_ch ].isInput			= ASIOFalse;
		m_buffers[ ch_off + in_ch ].channelNum		= out_align[ ch_off ];
		m_buffers[ ch_off + in_ch ].buffers[ 0 ]	= 0;
		m_buffers[ ch_off + in_ch ].buffers[ 1 ]	= 0;
	}
	if( ASE_OK != m_asio->createBuffers( m_buffers.GetPtr() , m_buffers.GetDatanum() , buffersize , callbacks ) )
	{
		m_buffers.Resize( 0 );
		return false;
	}
	// set channels
	m_channels.Resize( in_ch + out_ch );
	for ( ch_off = 0; ch_off < in_ch + out_ch ; ch_off++ )
	{
		m_channels[ ch_off ].channel	= m_buffers[ ch_off ].channelNum;
		m_channels[ ch_off ].isInput	= m_buffers[ ch_off ].isInput;
		if( false == getChannelInfo( &m_channels[ ch_off ] ) )
		{
			DestroyBuffers();
			return false;
		}
	}
	m_input_channel		= in_ch;
	m_output_channel	= out_ch;
	return true;
}
//=================================================================================================
void DestroyBuffers()
{
	disposeBuffers();
	m_buffers.Resize( 0 );
	m_channels.Resize( 0 );
	m_input_channel		= 0;
	m_output_channel	= 0;
}
//=================================================================================================
void UpdateClocks()
{
	long	num	= 1024;
	Array<ASIOClockSource>	clocks( num );
	if( ASE_OK != m_asio->getClockSources( clocks.GetPtr() , &num ) )
	{
		clocks.Resize( 0 );
	}
	clocks.ResizeHold( num );
	
	int		coff , cnum = clocks.GetDatanum();
	m_clocks.Resize( cnum );
	for( coff = 0 ; coff < cnum ; coff++ )
	{
		m_clocks[ coff ].associatedChannel	= clocks[ coff ].associatedChannel;
		m_clocks[ coff ].associatedGroup	= clocks[ coff ].associatedGroup;
		m_clocks[ coff ].isCurrentSource	= ( clocks[ coff ].isCurrentSource == ASIOFalse ) ? false : true;
		m_clocks[ coff ].index				= clocks[ coff ].index;
		m_clocks[ coff ].name				= MultiToWide( clocks[ coff ].name , strlen( clocks[ coff ].name ) , ShiftJis );
	}
 }
// "IAsio" interface functions
public:
//=================================================================================================
bool cb_call Initialize
		(
		const guid&	clsid , 
		void*		syshandle
		)
{
	if( m_asio != 0 )
		return false;
	if( 0 == ThisPtr( SetPtr , this ) )
		return false;

	// create asio		
	IASIO*		asio	= 0;
	GUID		t_clsid	= to_GUID( clsid );
	if( S_OK != ::CoCreateInstance( t_clsid , 0 , CLSCTX_INPROC_SERVER , t_clsid , ( void** )&asio ) )
	{
		ThisPtr( ClearPtr );
		return false;
	}

	// create
	if( ASIOFalse == asio->init( syshandle ) )
	{
		ThisPtr( ClearPtr );
		asio->Release();
		return false;
	}
	m_asio	= asio;
	
	// update clocksource
	UpdateClocks();
	
	return true;
}
//=================================================================================================
wstring cb_call GetDriverName()
{
	char name[ 32 ];
	m_asio->getDriverName( name );
	return MultiToWide( name , strlen( name ) , ShiftJis );
}
//=================================================================================================
long cb_call GetDriverVersion()
{
	return m_asio->getDriverVersion();
}
//=================================================================================================
wstring cb_call GetErrorMessage()
{
	char msg[ 124 ];
	m_asio->getErrorMessage( msg );
	return MultiToWide( msg , strlen( msg ) , ShiftJis );
}
//=================================================================================================
bool cb_call GetChannels
		(
		uint32*		in_channel , 
		uint32*		out_channel
		)
{
	long	in , out;
	if( ASE_OK != m_asio->getChannels( &in , &out ) )
		return false;
	store( in_channel , (uint32)in );
	store( out_channel , (uint32)out );
	return true;
}
//=================================================================================================
bool cb_call GetLatencies
		(
		long*		input_latency , 
		long*		output_latency
		)
{
	if( ASE_OK != m_asio->getLatencies( input_latency , output_latency ) )
		return false;
	return true;
}
//=================================================================================================
bool cb_call GetBufferSize
		(
		long*		size
		)
{
	if( false == getBufferSize( 0 , 0 , size , 0 ) )
		return false;
	return true;
}
//=================================================================================================
bool cb_call CanSampleRate
		(
		double	samplerate
		)
{
	if( ASE_OK != m_asio->canSampleRate( samplerate ) )
		return false;
	return true;
}
//=================================================================================================
bool cb_call GetSampleRate
		(
		double*		samplerate
		)
{
	if( ASE_OK != m_asio->getSampleRate( samplerate ) )
		return false;
	return true;
}
//=================================================================================================
bool cb_call SetSampleRate
		(
		double		samplerate
		)
{
	if( ASE_OK != m_asio->setSampleRate( samplerate ) )
		return false;
	return true;
}
//=================================================================================================
uint32 cb_call GetClockSourceNum()
{
	return m_clocks.GetDatanum();
}
//=================================================================================================
bool cb_call GetClockSource
		(
		uint32			clock_off , 
		ClockSource*	clock
		)
{
	if( clock_off >= (uint32)m_clocks.GetDatanum() )
		return false;
	if( clock != 0 )
		*clock = m_clocks[ clock_off ];
	return true;
}
//=================================================================================================
bool cb_call SetClockSource
		(
		uint32		clock_off
		)
{
	if( clock_off >= (uint32)m_clocks.GetDatanum() )
		return false;
	if( ASE_OK != m_asio->setClockSource( m_clocks[ clock_off ].index ) )
		return false;
	return true;
}
//=================================================================================================
bool cb_call GetSamplePosition
		(
		ASIOSamples*	spos, 
		ASIOTimeStamp*	tstamp
		)
{
	if( ASE_OK != m_asio->getSamplePosition( spos , tstamp ) )
		return false;
	return true;
}
//=================================================================================================
bool cb_call GetChannelInfo
		(
		long			channel , 
		bool			isInput , 
		ChannelInfo*	info
		)
{
	ASIOChannelInfo		ci;
	ci.channel	= channel;
	ci.isInput	= isInput == true ? ASIOTrue : ASIOFalse;
	if( ASE_OK != m_asio->getChannelInfo( &ci ) )
		return false;
	info->channelGroup	= ci.channelGroup;
	info->isActive		= ci.isActive == ASIOFalse ? false : true;
	info->type			= ci.type;
	info->name			= MultiToWide( ci.name , strlen( ci.name ) , ShiftJis );
	return true;
}
//=================================================================================================
bool cb_call ControlPanel()
{
	if( ASE_OK != m_asio->controlPanel() )
		return false;
	return true;
}
//=================================================================================================
bool cb_call Future
		(
		long	selector,
		void*	opt
		)
{
	if( ASE_OK != m_asio->future( selector , opt ) )
		return false;
	return true;
}
//=================================================================================================
bool cb_call OutputReady()
{
	if( ASE_OK != m_asio->outputReady() )
		return false;
	return true;
}
//=================================================================================================
bool cb_call Create
		(
		uint32			in_ch , 
		const uint32	in_align[] , 
		uint32			out_ch , 
		const uint32	out_align[] , 
		IAsioCallback*	callback , 
		uint32*			buffersample
		)
{
	Destroy();
	if( callback == 0 )
		return false;

	long	buffersize;
	if( false == GetBufferSize( &buffersize ) )
		return false;
	m_buffersamples	= buffersize;
	m_asio_callbacks.bufferSwitch			= &BufferSwitchStatic;
	m_asio_callbacks.sampleRateDidChange	= &SampleRateChangedStatic;
	m_asio_callbacks.asioMessage			= &AsioMessagesStatic;
	m_asio_callbacks.bufferSwitchTimeInfo	= &BufferSwitchTimeInfoStatic;
	if( false == CreateBuffers
			(
			in_ch , 
			in_align , 
			out_ch , 
			out_align , 
			m_buffersamples , 
			&m_asio_callbacks
			) )
			return false;
	store( buffersample , m_buffersamples );
	m_callback	= callback;
	m_state		= Stop_State;
	return true;
}
//=================================================================================================
bool cb_call Play()
{
	if( m_state == Play_State )
		return true;
	if( m_state != Stop_State )
		return false;
	if( ASE_OK != m_asio->start() )
		return false;
	m_state	= Play_State;
	return true;
}
//=================================================================================================
const ASIOBufferInfo* cb_call GetBufferInfos
		(
		uint32*		num
		)
{
	store( num , (uint32)m_buffers.GetDatanum() );
	return m_buffers.GetConstPtr();
}
//=================================================================================================
const ASIOChannelInfo* cb_call GetChannelInfos
		(
		uint32*		num
		)
{
	store( num , (uint32)m_channels.GetDatanum() );
	return m_channels.GetConstPtr();
}
//=================================================================================================
void cb_call Stop()
{
	if( m_state != Play_State )
		return;
	m_asio->stop();
	m_state	= Stop_State;
}
//=================================================================================================
void cb_call Destroy()
{
	if( m_state == Null_State )
		return;
	Stop();
	DestroyBuffers();
	m_callback	= 0;
	m_state	= Null_State;
}
// public functions
public:
//=================================================================================================
Asio() : 
		m_asio( 0 ) , 
		m_callback( 0 ) , 
		m_state( Null_State ) , 
		m_buffersamples( 0 ) , 
		m_input_channel( 0 ) , 
		m_output_channel( 0 )
{
}
//=================================================================================================
~Asio()
{
	Destroy();
	if( m_asio != 0 )
	{
		Stop();
		m_asio->Release();
		ThisPtr( ClearPtr );
	}
	m_asio	= 0;
}
};

/**************************************************************************************************
"EnumAsio" class 
**************************************************************************************************/
class EnumAsio : 
	virtual public object_base , 
	public IEnumAsio
{
	query_begin();
	iface_hook( IEnumAsio , IEnumAsio_IID )
	query_end( object_base );
	
	class DriverInfo
	{
	public:
		wstring		m_name;
		guid		m_clsid;
	};
// variable member
private:
	Straightdata<DriverInfo>	m_drivers;
	
// private functions
private:
//=================================================================================================
bool _RegQueryInfoKey
		(
		HKEY	hkey , 
		uint32*	rsubkeynum , 
		uint32*	rsubkeymaxlen
		)
{
	DWORD		subkeynum = 0 , subkeymaxlen = 0;
	if( ERROR_SUCCESS != ::RegQueryInfoKey
			(
			hkey , 
			0 , 0 , 0 , 
			&subkeynum , 
			&subkeymaxlen , 
			0 , 0 , 0 , 0 , 0 , 0
			) )
			return false;
	store( rsubkeynum , subkeynum );
	store( rsubkeymaxlen , subkeymaxlen );
	return true;
}
//=================================================================================================
bool _RegEnumKey
		(
		HKEY		hkey , 
		DWORD		index , 
		wstring*	name , 
		uint32		maxlen	
		)
{
	Array<wchar_t>	data( maxlen );
	DWORD			size = maxlen;
	if( ERROR_SUCCESS != ::RegEnumKeyExW( hkey , index , data.GetPtr() , &size , 0 , 0 , 0 , 0 ) )
		return false;
	*name	= ( const wchar_t* )data.GetConstPtr();
	return true;
}
//=================================================================================================
bool _RegQueryValue_reg_sz
		(
		HKEY			hkey , 
		const wchar_t*	name , 
		wstring*		value
		)
{
	DWORD	datatype = 0 , datasize = 0;
	if( ERROR_SUCCESS != RegQueryValueExW( hkey , name , 0 , &datatype , 0 , &datasize ) )
		return false;
	if( datatype != REG_SZ )
		return false;
	Array<uint8>	data( datasize );
	if( ERROR_SUCCESS != RegQueryValueExW( hkey , name , 0 , &datatype , data.GetPtr() , &datasize ) )
		return false;
	if( datasize == 0 )
		return false;
	*value	= (const wchar_t*)data.GetConstPtr();
	return true;
}
//=================================================================================================
void _RegCloseKey
		(
		HKEY&		hkey
		)
{
	if( hkey != NULL )
		RegCloseKey( hkey );
	hkey	= NULL;
}
//=================================================================================================
bool IsFileExist
		(
		const wchar_t*	path
		)
{
	FILE* fp = NULL;
	if( 0 != _wfopen_s( &fp , path , L"rb" ) )
		return false;
	if( fp == NULL )
		return false;
	fclose( fp );
	return true;
}
//=================================================================================================
bool GetDllPath
		(
		HKEY			hkey , 
		const wstring&	keyname , 
		wstring*		dllpath
		)
{
	HKEY	hksub	= NULL;
	if( ERROR_SUCCESS != RegOpenKeyExW( hkey , keyname.c_str() , 0 , KEY_READ , &hksub ) )
		return false;
	HKEY	hkpath	= NULL;
	if( ERROR_SUCCESS != RegOpenKeyExW( hksub , L"InprocServer32" , 0 , KEY_READ , &hkpath ) )
	{
		_RegCloseKey( hksub );
		_RegCloseKey( hkpath );
		return false;
	}
	wstring		path;
	if( false == _RegQueryValue_reg_sz( hkpath , 0 , &path ) )
	{
		_RegCloseKey( hksub );
		_RegCloseKey( hkpath );
		return false;
	}
	if( false == IsFileExist( path.c_str() ) )
	{
		_RegCloseKey( hksub );
		_RegCloseKey( hkpath );
		return false;
	}
	_RegCloseKey( hksub );
	_RegCloseKey( hkpath );
	store( dllpath , path );
	return true;
}
//=================================================================================================
bool SearchDllPath
		(
		const wstring&		t_clsid , 
		wstring*			dllpath
		)
{
	wstring		clsid	= t_clsid;
	std::transform( clsid.begin(), clsid.end(), clsid.begin() , std::tolower );

	HKEY		hkenum	= NULL;	
	if ( ERROR_SUCCESS != RegOpenKeyW( HKEY_CLASSES_ROOT , L"clsid" , &hkenum ) )
		return false;

	uint32	index , num , maxlen;
	if( false == _RegQueryInfoKey( hkenum , &num , &maxlen ) )
	{
		_RegCloseKey( hkenum );
		return false;
	}
	for( index = 0 ; index < num ; index++ )
	{
		wstring		keyname;
		if( false == _RegEnumKey( hkenum , index , &keyname , maxlen ) ) 
			continue;
		std::transform( keyname.begin(), keyname.end(), keyname.begin() , std::tolower );
		if( keyname != clsid )
			continue;
		if( true == GetDllPath( hkenum , keyname , dllpath ) )
		{
			_RegCloseKey( hkenum );
			return true;
		}
	}
	_RegCloseKey( hkenum );
	return false;
}
//=================================================================================================
void EnumDriver
		(
		HKEY			hkey ,
		const wstring&	keyname
		)
{
	HKEY	hksub	= NULL;
	if( ERROR_SUCCESS != RegOpenKeyExW( hkey , keyname.c_str() , 0 , KEY_READ , &hksub ) ) 
		return;

	wstring	clsid_str;
	if( false == _RegQueryValue_reg_sz( hksub , L"clsid" , &clsid_str ) )
	{
		_RegCloseKey( hksub );
		return;
	}
	// dllpath
/*
	wstring	dllpath;
	if( false == SearchDllPath( clsid_str , &dllpath ) )
	{
		_RegCloseKey( hksub );
		return;
	}
*/
	// clsid
	CLSID	clsid;
	if( S_OK != CLSIDFromString( ( LPOLESTR )clsid_str.c_str() , &clsid ) )
		return;
	
	// name
	wstring	name;
	if( false == _RegQueryValue_reg_sz( hksub , L"description" , &name ) )
		name	= keyname;
	
	// add info
	int	loff = m_drivers.Add();
	m_drivers[ loff ].m_clsid	= to_guid(clsid);
	m_drivers[ loff ].m_name	= name;

	_RegCloseKey( hksub );
}
// "IEnumAsio" interface functions
public:
//=================================================================================================
void cb_call Enum()
{
	Reset();

	HKEY	hkenum = NULL;
	if( ERROR_SUCCESS != RegOpenKeyW( HKEY_LOCAL_MACHINE , L"software\\asio" , &hkenum ) )
		return;

	uint32		index , num , maxlen;
	if( false == _RegQueryInfoKey( hkenum , &num , &maxlen ) )
	{
		RegCloseKey( hkenum );
		return;
	}
	for( index = 0 ; index < num ; index++ )
	{
		wstring		keyname;
		if( true == _RegEnumKey( hkenum , index , &keyname , maxlen ) ) 
			EnumDriver( hkenum , keyname );
	}
	RegCloseKey( hkenum );
}
//=================================================================================================
int32 cb_call GetDeviceNum()
{
	return m_drivers.GetDatanum();
}
//=================================================================================================
bool cb_call GetDeviceInfo
		(
		int32		devoff , 
		wstring*	name , 
		guid*		clsid
		)
{
	if( devoff < 0 || devoff >= m_drivers.GetDatanum() )
		return false;
	store( name , m_drivers[devoff].m_name );
	store( clsid , m_drivers[devoff].m_clsid );
	return true;
}
// public functions
public:
//=================================================================================================
EnumAsio()
{
}
//=================================================================================================
void Reset()
{
	m_drivers.Resize( 0 );
}
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// global variable define

///////////////////////////////////////////////////////////////////////////////////////////////////
// global functions define

};	//namespace

//using namespace icubic;		

#pragma pack( pop )			//release align
