using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using Un4seen.Bass;
//using Un4seen.BassAsio;
//using Un4seen.BassWasapi;
//using Un4seen.Bass.AddOn.Mix;
using Un4seen.Bass.AddOn.Fx;

namespace FDK
{
	public class CBeatDetect : IDisposable
	{
		public struct stBeatPos
		{
			public float fBeatTime;
			public int n小節番号;
			public int nGrid;
			public int n小節内Grid;
			public bool b無効;					// 
			public bool bレーン表示する;		// 未使用

			public stBeatPos( float _fBeatTime, int _n小節番号, int _nGrid, int _n小節内Grid, bool _b無効, bool _bレーン表示する )
			{
				fBeatTime = _fBeatTime;
				n小節番号 = _n小節番号;
				nGrid = _nGrid;
				n小節内Grid = _n小節内Grid;
				b無効 = _b無効;
				bレーン表示する= _bレーン表示する;
			}
		}

		#region [ コンストラクタ ]
		public CBeatDetect()
		{
			Initialize();
		}
		public CBeatDetect( string _filename )
		{
			this.filename = _filename;
			Initialize();
		}
		#endregion
		#region [ 初期化(コンストラクタから呼び出される) ]
		private void Initialize()
		{
			if ( this.listBeatPositions == null )
			{
				this.listBeatPositions = new List<stBeatPos>();
			}

			#region [ BASS registration ]
			// BASS.NET ユーザ登録（BASSスプラッシュが非表示になる）。

			BassNet.Registration( "dtx2013@gmail.com", "2X9181017152222" );
			#endregion
			#region [ BASS Version Check ]
			// BASS のバージョンチェック。
			int nBASSVersion = Utils.HighWord( Bass.BASS_GetVersion() );
			if ( nBASSVersion != Bass.BASSVERSION )
				throw new DllNotFoundException( string.Format( "bass.dll のバージョンが異なります({0})。このプログラムはバージョン{1}で動作します。", nBASSVersion, Bass.BASSVERSION ) );

			int nBASSFXVersion = Utils.HighWord( BassFx.BASS_FX_GetVersion() );
			if ( nBASSFXVersion != BassFx.BASSFXVERSION )
				throw new DllNotFoundException( string.Format( "bass_fx.dll のバージョンが異なります({0})。このプログラムはバージョン{1}で動作します。", nBASSFXVersion, BassFx.BASSFXVERSION ) );
			#endregion

			#region [ BASS の設定。]
			//this.bIsBASSFree = true;
			//Debug.Assert( Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 ),		// 0:BASSストリームの自動更新を行わない。(サウンド出力しないため)
			//    string.Format( "BASS_SetConfig() に失敗しました。[{0}", Bass.BASS_ErrorGetCode() ) );
			#endregion
			#region [ BASS の初期化。]
			int nデバイス = 0;		// 0:"no sound" … BASS からはデバイスへアクセスさせない。
			//int nデバイス = -1;		// 0:"no sound" … BASS からはデバイスへアクセスさせない。
			int n周波数 = 44100;	// 仮決め。lデバイス（≠ドライバ）がネイティブに対応している周波数であれば何でもいい？ようだ。いずれにしろBASSMXで自動的にリサンプリングされる。
			if ( !Bass.BASS_Init( nデバイス, n周波数, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero ) )
				throw new Exception( string.Format( "BASS の初期化に失敗しました。(BASS_Init)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
			#endregion

			#region [ 指定されたサウンドファイルをBASSでオープンし、必要最小限の情報を取得する。]
			//this.hBassStream = Bass.BASS_StreamCreateFile( this.filename, 0, 0, BASSFlag.BASS_STREAM_PRESCAN | BASSFlag.BASS_STREAM_DECODE );
			this.hBassStream = Bass.BASS_StreamCreateFile( this.filename, 0, 0, BASSFlag.BASS_STREAM_DECODE );
			//this.hBassStream = Bass.BASS_StreamCreateFile( this.filename, 0, 0, BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_PRESCAN );
			if ( this.hBassStream == 0 )
				throw new Exception( string.Format( "{0}: サウンドストリームの生成に失敗しました。(BASS_StreamCreateFile)[{1}]", filename, Bass.BASS_ErrorGetCode().ToString() ) );

			this.nTotalBytes = Bass.BASS_ChannelGetLength( this.hBassStream );

			this.nTotalSeconds = Bass.BASS_ChannelBytes2Seconds( this.hBassStream, nTotalBytes );
			if ( !Bass.BASS_ChannelGetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ref fFreq ) )
			{
				string errmes = string.Format( "サウンドストリームの周波数取得に失敗しました。(BASS_ChannelGetAttribute)[{0}]", Bass.BASS_ErrorGetCode().ToString() );
				Bass.BASS_Free();
				throw new Exception( errmes );
			}



			int bs = Bass.BASS_ChannelSetFX( this.hBassStream, BASSFXType.BASS_FX_BFX_BQF, 1 );

			BASS_BFX_BQF param = new BASS_BFX_BQF(
				BASSBFXBQF.BASS_BFX_BQF_LOWPASS,	// filter
				800.0f,								// center	default=200
				15,									// gain deafult=0
				0,									// bandwidth
				1,	//0.67f,									// Q
				0,									// s
				BASSFXChan.BASS_BFX_CHANALL			//chans
			);

			bool b = Bass.BASS_FXSetParameters( bs, param );
			if ( b == false )
			{
				Debug.WriteLine( "effect set failed: " + Bass.BASS_ErrorGetCode().ToString() );
			}
			//bool b2 = Bass.BASS_ChannelPlay( this.hBassStream, false );
			//if ( b2 == false )
			//{
			//    Debug.WriteLine( "plyback failed: " + Bass.BASS_ErrorGetCode().ToString() );
			//}
			#endregion
		}
		#endregion

		/// <summary>
		/// 曲全体のテンポを取得する
		/// </summary>
		/// <returns>テンポ値</returns>
		/// <remarks>テンポ値の範囲は70-300</remarks>
		public float GetTempo()
		{
			fTempo = BassFx.BASS_FX_BPM_DecodeGet(
				this.hBassStream,
				0,
				nTotalSeconds,
				( 300 << 16 ) + 70,		// MAX BPM=320, MIN BPM=70
				//0,
				BASSFXBpm.BASS_FX_BPM_DEFAULT,		//BASSFXBpm.BASS_FX_BPM_MULT2,
				null,
				IntPtr.Zero );
			return fTempo;
		}
		/// <summary>
		/// 曲の一部分のテンポを取得する
		/// </summary>
		/// <param name="startSec">開始位置</param>
		/// <param name="endSec">終了位置</param>
		/// <returns>テンポ値</returns>
		/// <remarks>テンポ値の範囲は70-300</remarks>
		public float GetTempo( double startSec, double endSec )
		{
			fTempo = BassFx.BASS_FX_BPM_DecodeGet(
				this.hBassStream,
				startSec,
				endSec,
				( 300 << 16 ) + 70,		// MAX BPM=320, MIN BPM=70
				//0,
				BASSFXBpm.BASS_FX_BPM_DEFAULT,		//BASSFXBpm.BASS_FX_BPM_MULT2,
				null,
				IntPtr.Zero );
			return fTempo;
		}


		/// <summary>
		/// Beatの検出位置をListで返す
		/// </summary>
		/// <returns>Beat検出位置群</returns>
		public List<stBeatPos> GetBeatPositions()
		{
			#region [  BeatPosition格納リストの初期化 ]
			if ( this.listBeatPositions != null )
			{
				this.listBeatPositions.Clear();
			}
			else
			{
				this.listBeatPositions = new List<stBeatPos>();
			}
			#endregion

			BPMBEATPROC _beatProc = new BPMBEATPROC( GetBeat_ProgressCallback );

			//
			//
			// LPFを通してから、BeatDecodeGetしてみること。
			//
			//

			bool ret = BassFx.BASS_FX_BPM_BeatDecodeGet(
				this.hBassStream,
				0,
				nTotalSeconds,
				//0,
				BASSFXBpm.BASS_FX_BPM_DEFAULT,		//BASSFXBpm.BASS_FX_BPM_MULT2,
				_beatProc,
				IntPtr.Zero );

			return this.listBeatPositions;
		}

		private void GetBeat_ProgressCallback( int channel, double beatpos, IntPtr user )
		{
			stBeatPos sbp = new stBeatPos(
				(float) beatpos,
				0,
				0,
				0,
				false,
				true
			);				
				

			listBeatPositions.Add( sbp );
//			Debug.WriteLine( "Beat at: " + beatpos.ToString() );
		}


	
		
		public void Dispose()	// 使い終わったら必ずDispose()すること。BASSのリソースを握りっぱなしにすると、他の再生に不都合が生じるため。
		{
			BassFx.BASS_FX_BPM_Free( this.hBassStream );
			Bass.BASS_StreamFree( this.hBassStream );
			this.hBassStream = -1;
			Bass.BASS_Free();
		}
	
		// =============
		private string filename = "";
		private int hBassStream = -1;
		private long nTotalBytes = 0;
		private double nTotalSeconds = 0.0f;
		private float fFreq = 0.0f;
		private float fTempo;
		private List<stBeatPos> listBeatPositions = null;
	}
}
