using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace MidiInChecker2
{
	public class CInputManager : IDisposable
	{
		// 定数

		public static int n通常音量 = 110;


		// プロパティ

		public List<IInputDevice> list入力デバイス
		{
			get;
			private set;
		}

		public uint nInputMidiDevices;
		public List<string> listStrMidiDevices;

		// コンストラクタ

		//public CInputManager( IntPtr hWnd )
		public CInputManager()
		{
			this.list入力デバイス = new List<IInputDevice>( 10 );
			this.listStrMidiDevices = new List<string>();

			this.proc = new CWin32.MidiInProc( this.MidiInCallback );
			nInputMidiDevices = CWin32.midiInGetNumDevs();
			Trace.TraceInformation( "MIDI入力デバイス数: {0}", nInputMidiDevices );
			for ( uint i = 0; i < nInputMidiDevices; i++ )
			{
				CInputMIDI item = new CInputMIDI( i );
				this.list入力デバイス.Add( item );
				CWin32.MIDIINCAPS lpMidiInCaps = new CWin32.MIDIINCAPS();
				uint retcode = CWin32.midiInGetDevCaps( i, ref lpMidiInCaps, (uint) Marshal.SizeOf( lpMidiInCaps ) );
				if ( retcode != 0 )
				{
					Trace.TraceError( "MIDI In: Device{0}: midiInDevCaps(): {1:X2}: ", i, retcode );
				}
				else if ( ( CWin32.midiInOpen( ref item.hMidiIn, i, this.proc, 0, 0x30000 ) == 0 ) && ( item.hMidiIn != 0 ) )
				{
					CWin32.midiInStart( item.hMidiIn );
					Trace.TraceInformation( "MIDI In: [{0}] \"{1}\" の入力受付を開始しました。", i, lpMidiInCaps.szPname );
					this.listStrMidiDevices.Add( "MIDI In [" + i + "] = " + lpMidiInCaps.szPname );
				}
				else
				{
					Trace.TraceError( "MIDI In: [{0}] \"{1}\" の入力受付の開始に失敗しました。", i, lpMidiInCaps.szPname );
				}
			}
		}


		// メソッド

		public IInputDevice MidiIn( int ID )
		{
		    foreach ( IInputDevice device in this.list入力デバイス )
		    {
		        if ( ( device.e入力デバイス種別 == E入力デバイス種別.MidiIn ) && ( device.ID == ID ) )
		        {
		            return device;
		        }
		    }
		    return null;
		}
		public void tポーリング( bool bWindowがアクティブ中, bool bバッファ入力を使用する )
		{
			lock ( this.objMidiIn排他用 )
			{
				for ( int i = this.list入力デバイス.Count - 1; i >= 0; i-- )	// #24016 2011.1.6 yyagi: change not to use "foreach" to avoid InvalidOperation exception by Remove().
				{
					IInputDevice device = this.list入力デバイス[ i ];
					try
					{
						device.tポーリング( bWindowがアクティブ中, bバッファ入力を使用する );
					}
					catch ( Exception )							// #24016 2011.1.6 yyagi: catch exception for unplugging USB joystick, and remove the device object from the polling items.
					{
						this.list入力デバイス.Remove( device );
						device.Dispose();
						Trace.TraceError( "tポーリング時に対象deviceが抜かれており例外発生。同deviceをポーリング対象からRemoveしました。" );
					}
				}
			}
		}

		#region [ IDisposable＋α ]
		//-----------------
		public void Dispose()
		{
			this.Dispose( true );
		}
		public void Dispose( bool disposeManagedObjects )
		{
			if ( !this.bDisposed済み )
			{
				if ( disposeManagedObjects )
				{
					foreach ( IInputDevice device in this.list入力デバイス )
					{
						CInputMIDI tmidi = device as CInputMIDI;
						if ( tmidi != null )
						{
							CWin32.midiInStop( tmidi.hMidiIn );
							CWin32.midiInReset( tmidi.hMidiIn );
							CWin32.midiInClose( tmidi.hMidiIn );
							Trace.TraceInformation( "MIDI In: [{0}] を停止しました。", tmidi.ID );
						}
					}
					foreach ( IInputDevice device2 in this.list入力デバイス )
					{
						device2.Dispose();
					}
					lock ( this.objMidiIn排他用 )
					{
						this.list入力デバイス.Clear();
					}

					//this.directInput.Dispose();

					//if ( this.timer != null )
					//{
					//    this.timer.Dispose();
					//    this.timer = null;
					//}
				}
				this.bDisposed済み = true;
			}
		}
		~CInputManager()
		{
			this.Dispose( false );
			GC.KeepAlive( this );
		}
		//-----------------
		#endregion


		// その他

		#region [ private ]
		//-----------------
		private bool bDisposed済み;
		private List<uint> listHMIDIIN = new List<uint>( 8 );
		private object objMidiIn排他用 = new object();
		private CWin32.MidiInProc proc;

		private void MidiInCallback( uint hMidiIn, uint wMsg, int dwInstance, int dwParam1, int dwParam2 )
		{
			int p = dwParam1 & 0xF0;
			if ( wMsg != CWin32.MIM_DATA || ( p != 0x80 && p != 0x90 ) )
				return;

			//long time = this.timer.nシステム時刻;	// lock前に取得
			long time = System.DateTime.Now.Ticks;	// lock前に取得

			lock ( this.objMidiIn排他用 )
			{
				if ( ( this.list入力デバイス != null ) && ( this.list入力デバイス.Count != 0 ) )
				{
					foreach ( IInputDevice device in this.list入力デバイス )
					{
						CInputMIDI tmidi = device as CInputMIDI;
						if ( ( tmidi != null ) && ( tmidi.hMidiIn == hMidiIn ) )
						{
							tmidi.tメッセージからMIDI信号のみ受信( wMsg, dwInstance, dwParam1, dwParam2, time );
							break;
						}
					}
				}
			}
		}
		//-----------------
		#endregion
	}
}
