//******************************************************************************
//
// MIDITrail / MTMainView
//
// メインビュー制御クラス
//
// Copyright (C) 2010-2021 WADA Masashi. All Rights Reserved.
//
//******************************************************************************

#import <CoreAudio/CoreAudio.h>
#import "YNBaseLib.h"
#import "DIKeyDef.h"
#import "MTMainView.h"
#import "OGLRendererInfo.h"

@implementation MTMainView

//******************************************************************************
// 生成
//******************************************************************************
- (id)initWithFrame:(NSRect)frameRect rendererParam:(OGLRedererParam)rendererParam
{
	int result = 0;
	NSOpenGLPixelFormat *pPixelFormat = nil;
	NSOpenGLPixelFormatAttribute attrbSampleMode = 0;
	NSOpenGLPixelFormatAttribute attributes[16];
	OGLRendererInfo rendererInfo;
	BOOL isAccelerated = NO;
	int i = 0;
	
	m_CGLContext = NULL;
	m_pOpenGLContext = nil;
	m_DisplayLinkRef = nil;
	m_RendererParam = rendererParam;
	
	//レンダリング情報取得
	result = rendererInfo.Initialize();
	if (result != 0) goto EXIT;
	rendererInfo.GetAccelerationInfo(&isAccelerated);
	
	//ビュー生成時に設定するピクセルフォーマットを制御することで
	//アンチエイリアシングの有効／無効を切り替える
	
	//アンチエイリアシング：サンプルモード
	if (rendererParam.isEnableAntialiasing) {
		if (rendererParam.sampleMode == kCGLSupersampleBit) {
			attrbSampleMode = NSOpenGLPFASupersample;
		}
		else if (rendererParam.sampleMode == kCGLMultisampleBit) {
			attrbSampleMode = NSOpenGLPFAMultisample;
		}
	}
	
	//ピクセルフォーマット属性
	attributes[0]  = NSOpenGLPFAWindow;			//ウィンドウ表示
	attributes[1]  = NSOpenGLPFAColorSize;		//カラーバッファビット数
	attributes[2]  = 32;						//  設定値
	attributes[3]  = NSOpenGLPFAAlphaSize;		//アルファコンポーネントビット数
	attributes[4]  = 8;							//  設定値
	attributes[5]  = NSOpenGLPFADepthSize;		//深度バッファビット数
	attributes[6]  = 32;						//  設定値
	attributes[7]  = NSOpenGLPFADoubleBuffer;	//ダブルバッファピクセルフォーマット選択
	i = 8;
	if (isAccelerated) {
		attributes[i]  = NSOpenGLPFAAccelerated;		//ハードウェアレンダリング
		i++;
	}
	attributes[i]  = NSOpenGLPFANoRecovery;				//リカバリシステム無効
	attributes[i + 1] = 0;								//終端
	if (rendererParam.isEnableAntialiasing) {
		attributes[i + 1] = attrbSampleMode;			//アンチエイリアシング：サンプルモード
		attributes[i + 2] = NSOpenGLPFASampleBuffers;	//マルチサンプルバッファ
		attributes[i + 3] = 1;							//  設定値
		attributes[i + 4] = NSOpenGLPFASamples;			//マルチサンプルバッファごとのサンプル数
		attributes[i + 5] = rendererParam.sampleNum;	//  設定値
		attributes[i + 6] = 0;							//終端
	}
	
	//ピクセルフォーマット生成
	pPixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
	[pPixelFormat autorelease];
	
	//ビュー生成
	self = [super initWithFrame:frameRect pixelFormat:pPixelFormat];
	if (self) {
		//retina対応
		[self setWantsBestResolutionOpenGLSurface:YES];
	}
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return self;
}

//******************************************************************************
// 破棄
//******************************************************************************
- (void)dealloc
{
	CVDisplayLinkRelease(m_DisplayLinkRef);
	m_DisplayLinkRef = nil;
	
	[super dealloc];
}

//******************************************************************************
// ファーストレスポンダ受け入れ
//******************************************************************************
- (BOOL)acceptsFirstResponder
{
	//ファーストレスポンダを受け入れる
	//キー押下時にポンと音が鳴ってしまうためkeyDownをオーバライドする必要がある
	return YES;
}

//******************************************************************************
// OpenGL初期化
//******************************************************************************
- (void)prepareOpenGL
{
	int result = 0;
	CVReturn cvreturn = 0;
	CGLPixelFormatObj pixelFormat;
	NSNotification* pNotification = nil;
	NSNotificationCenter* pCenter = nil;
	
	[super prepareOpenGL];
	
	//コンテキスト取得
	m_CGLContext = (CGLContextObj)[[self openGLContext] CGLContextObj];
	m_pOpenGLContext = [self openGLContext];
	
	//ディスプレイリンク生成
	cvreturn = CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLinkRef);
	if (cvreturn != kCVReturnSuccess) {
		result = YN_SET_ERR(@"CVDisplayLink API error.", cvreturn, 0);
		goto EXIT;
	}
	
	//ディスプレイリンクコールバック登録
	cvreturn = CVDisplayLinkSetOutputCallback(m_DisplayLinkRef, &DisplayLinkCallback, (void*)(self));
	if (cvreturn != kCVReturnSuccess) {
		result = YN_SET_ERR(@"CVDisplayLink API error.", cvreturn, 0);
		goto EXIT;
	}
	
	//ディスプレイリンクディスプレイ設定
	pixelFormat = [[self pixelFormat] CGLPixelFormatObj];
	cvreturn = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(m_DisplayLinkRef, m_CGLContext, pixelFormat);
	if (cvreturn != kCVReturnSuccess) {
		result = YN_SET_ERR(@"CVDisplayLink API error.", cvreturn, 0);
		goto EXIT;
	}
	
	//通知オブジェクトを作成
	pNotification = [NSNotification notificationWithName:@"onPreparedOpenGL"
												  object:self
												userInfo:nil];
	
	//通知に対応する処理をメインスレッドに処理させる
	pCenter = [NSNotificationCenter defaultCenter];
	[pCenter performSelectorOnMainThread:@selector(postNotification:)
							  withObject:pNotification
						   waitUntilDone:NO];
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
}

//******************************************************************************
// 初期化処理
//******************************************************************************
- (int)initialize:(SMMsgQueue*)pMsgQueue
		 menuCtrl:(MTMenuCtrl*)pMenuCtrl
{
	int result = 0;
	NSNotificationCenter* pCenter = nil;
	
	//メッセージキュー
	m_pMsgQueue = pMsgQueue;
	
	//メインメニュー制御
	m_pMenuCtrl = pMenuCtrl;
	
	//レンダラ初期化
	result = m_Renderer.Initialize(self, m_RendererParam);
	if (result != 0) goto EXIT;
	
	//時間初期化
	result = m_MachTime.Initialize();
	if (result != 0) goto EXIT;
	
	//ドロップを許可するデータタイプを設定：ファイルパス
	[self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
	
	//ドラッグ状態フラグ
	m_isDragAcceptable = NO;
	m_isDragging = NO;
	
	//キー入力制御初期化
	result = m_DIKeyCtrl.Initialize(nil);
	if (result != 0) goto EXIT;
	
	//モニタ状態
	m_isMonitor = NO;

	//ゲームパッド制御初期化
	result = m_GamePadCtrl.Initialize(0);
	if (result != 0) goto EXIT;
	
	//ゲームパッド用視点番号
	m_GamePadViewPointNo = 0;
	
	//ゲームコントローラー接続/切断通知先登録
	pCenter = [NSNotificationCenter defaultCenter];
	[pCenter addObserver:self
				selector:@selector(onGameControllerDidConnect)
					name:GCControllerDidConnectNotification
				  object:nil];
	[pCenter addObserver:self
				selector:@selector(onGameControllerDidDisconnect)
					name:GCControllerDidDisconnectNotification
				  object:nil];
	
EXIT:;
	return result;
}

//******************************************************************************
// 終了処理
//******************************************************************************
- (void)terminate
{
	//レンダラ終了
	m_Renderer.Terminate();
	
	//キー入力制御終了
	m_DIKeyCtrl.Terminate();
}

//******************************************************************************
// シーン生成
//******************************************************************************
- (int)createScene:(MTScene*)pScene
		   seqData:(SMSeqData*)pSeqData
{
	int result = 0;
	
	//コンテキストロック
	[m_pOpenGLContext lock];
	[m_pOpenGLContext makeCurrentContext];
	
	result = pScene->Create(self, m_Renderer.GetDevice(), pSeqData);
	if (result != 0) goto EXIT;
	
EXIT:;
	//コンテキストロック解除
	[m_pOpenGLContext flushBuffer];
	[m_pOpenGLContext unlock];
	return result;
}

//******************************************************************************
// シーン開始
//******************************************************************************
- (int)startScene:(MTScene*)pScene
		isMonitor:(BOOL)isMonitor
{
	int result = 0;
	CVReturn cvreturn = 0;
	
	//シーンオブジェクト
	m_pScene = pScene;
	
	//モニタフラグ
	m_isMonitor = isMonitor;
	
	//シーンメッセージキューをクリア
	m_SceneMsgQueue.Clear();
	
	//ゲームコントローラー初期化
	[self initializeGameController];
	
	//ディスプレイリンク処理開始
	//  ディスプレイリンク用スレッドでコールバックの呼び出しが繰り返される
	cvreturn = CVDisplayLinkStart(m_DisplayLinkRef);
	if (cvreturn != kCVReturnSuccess) {
		result = YN_SET_ERR(@"CVDisplayLink API error.", cvreturn, 0);
		goto EXIT;
	}
	
	//FPS算出用パラメータ初期化
	m_PrevFPSUpdateTime = 0;
	m_DrawCount = -1;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン停止
//******************************************************************************
- (int)stopScene
{
	int result = 0;
	CVReturn cvreturn = 0;
	
	//ディスプレイリンク処理停止
	//  ディスプレイリンク用スレッドでコールバックの呼び出しが停止される
	cvreturn = CVDisplayLinkStop(m_DisplayLinkRef);
	if (cvreturn != kCVReturnSuccess) {
		result = YN_SET_ERR(@"CVDisplayLink API error.", cvreturn, 0);
		goto EXIT;
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン一時停止
//******************************************************************************
-(int)pauseScene
{
	int result = 0;
	CVReturn cvreturn = 0;
	
	//ディスプレイリンク処理停止
	//  ディスプレイリンク用スレッドでコールバックの呼び出しが停止される
	cvreturn = CVDisplayLinkStop(m_DisplayLinkRef);
	if (cvreturn != kCVReturnSuccess) {
		result = YN_SET_ERR(@"CVDisplayLink API error.", cvreturn, 0);
		goto EXIT;
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン再開
//******************************************************************************
-(int)resumeScene
{
	int result = 0;
	CVReturn cvreturn = 0;
	
	//ディスプレイリンク処理開始
	//  ディスプレイリンク用スレッドでコールバックの呼び出しが繰り返される
	cvreturn = CVDisplayLinkStart(m_DisplayLinkRef);
	if (cvreturn != kCVReturnSuccess) {
		result = YN_SET_ERR(@"CVDisplayLink API error.", cvreturn, 0);
		goto EXIT;
	}
	
	//FPS算出用パラメータ初期化
	m_PrevFPSUpdateTime = 0;
	m_DrawCount = -1;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：演奏開始
//******************************************************************************
- (int)scene_PlayStart
{
	int result = 0;
	MTSceneMsgPlayStart* pMsg = NULL;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgPlayStart();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	//メッセージ通知：非同期
	result = m_SceneMsgQueue.PostMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	return result;
}

//******************************************************************************
// シーン操作：演奏終了
//******************************************************************************
- (int)scene_PlayEnd
{
	int result = 0;
	MTSceneMsgPlayEnd* pMsg = NULL;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgPlayEnd();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	//メッセージ通知：非同期
	result = m_SceneMsgQueue.PostMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	return result;
}

//******************************************************************************
// シーン操作：巻き戻し
//******************************************************************************
- (int)scene_Rewind
{
	int result = 0;
	MTSceneMsgRewind* pMsg = NULL;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgRewind();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	//メッセージ通知：非同期
	result = m_SceneMsgQueue.PostMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	return result;
}

//******************************************************************************
// シーン操作：視点リセット
//******************************************************************************
- (int)scene_ResetViewpoint
{
	int result = 0;
	MTSceneMsgResetViewpoint* pMsg = NULL;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgResetViewpoint();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	//メッセージ通知：非同期
	result = m_SceneMsgQueue.PostMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	return result;
}

//******************************************************************************
// シーン操作：視点登録
//******************************************************************************
- (int)scene_SetViewpoint:(MTScene::MTViewParamMap*)pParamMap
{
	int result = 0;
	MTSceneMsgSetViewpoint* pMsg = NULL;
	MTScene::MTViewParamMap* pDestPramMap = NULL;
	MTScene::MTViewParamMap::iterator itr;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgSetViewpoint();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	//指定された視点情報をメッセージにコピーする
	pDestPramMap = pMsg->GetViewParamMapPtr();
	for (itr = pParamMap->begin(); itr != pParamMap->end(); itr++) {
		pDestPramMap->insert(MTScene::MTViewParamMapPair((itr->first).c_str(), itr->second));
	}
	
	//メッセージ通知：同期
	result = m_SceneMsgQueue.SendMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//同期の場合メッセージの破棄は送信側で行う
	delete pMsg;
	return result;
}

//******************************************************************************
// シーン操作：視点取得
//******************************************************************************
- (int)scene_GetViewpoint:(MTScene::MTViewParamMap*)pParamMap
{
	int result = 0;
	MTSceneMsgGetViewpoint* pMsg = NULL;
	MTScene::MTViewParamMap* pSrcPramMap = NULL;
	MTScene::MTViewParamMap::iterator itr;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgGetViewpoint();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	//メッセージ通知：同期
	result = m_SceneMsgQueue.SendMessage(pMsg);
	if (result != 0) goto EXIT;
	
	//取得した視点情報をコピーして返す
	pParamMap->clear();
	pSrcPramMap = pMsg->GetViewParamMapPtr();
	for (itr = pSrcPramMap->begin(); itr != pSrcPramMap->end(); itr++) {
		pParamMap->insert(MTScene::MTViewParamMapPair((itr->first).c_str(), itr->second));
	}
	
EXIT:;
	//同期の場合メッセージの破棄は送信側で行う
	delete pMsg;
	return result;
}

//******************************************************************************
// シーン操作：エフェクト設定
//******************************************************************************
- (int)scene_SetEffect:(MTScene::EffectType)type isEnable:(bool)isEnable
{
	int result = 0;
	MTSceneMsgSetEffect* pMsg = NULL;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgSetEffect();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	pMsg->SetEffect(type, isEnable);
	
	//メッセージ通知：非同期
	result = m_SceneMsgQueue.PostMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	return result;
}

//******************************************************************************
// シーン操作：マウスクリックイベント
//******************************************************************************
- (int)scene_OnMouseClick:(unsigned int)button
{
	int result = 0;
	MTSceneMsgOnMouseClick* pMsg = NULL;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgOnMouseClick();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	pMsg->SetClickButton(button);
	
	//メッセージ通知：非同期
	result = m_SceneMsgQueue.PostMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	return result;
}

//******************************************************************************
// シーン操作：マウスホイールイベント
//******************************************************************************
- (int)scene_OnMouseWheelWithDeltaX:(float)dX deltaY:(float)dY deltaZ:(float)dZ
{
	int result = 0;
	MTSceneMsgOnMouseWheel* pMsg = NULL;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgOnMouseWheel();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	pMsg->SetWheelDelta(dX, dY, dZ);
	
	//メッセージ通知：非同期
	result = m_SceneMsgQueue.PostMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	return result;
}

//******************************************************************************
// シーン操作：ゲームコントローラー更新イベント
//******************************************************************************
- (int)scene_OnGameControllerChanged
{
	int result = 0;
	MTSceneMsgOnGameControllerChanged* pMsg = NULL;
	
	//メッセージ生成
	try {
		pMsg = new MTSceneMsgOnGameControllerChanged();
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", 0, 0);
		goto EXIT;
	}
	
	//メッセージ通知：非同期
	result = m_SceneMsgQueue.PostMessage(pMsg);
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	return result;
}

//******************************************************************************
// ディスプレイリンクコールバック
//******************************************************************************
static CVReturn DisplayLinkCallback(
		CVDisplayLinkRef displayLink,
		const CVTimeStamp* pNow,
		const CVTimeStamp* pOutputTime,
		CVOptionFlags flagsIn,
		CVOptionFlags* pFlagsOut,
		void* pDisplayLinkContext
	)
{
	MTMainView* pMainView = nil;
	
	pMainView = (MTMainView*)pDisplayLinkContext;
	
	@autoreleasepool {
		//シーン描画
		[pMainView thread_DrawScene];
	}
	
	return kCVReturnSuccess;
}

//******************************************************************************
// シーン描画
//******************************************************************************
- (void)thread_DrawScene
{
	int result = 0;
	
	//コンテキストロック
	[m_pOpenGLContext lock];
	[m_pOpenGLContext makeCurrentContext];
	
	if (m_pScene == NULL) {
		result = YN_SET_ERR(@"Program error.", 0, 0);
		goto EXIT;
	}
	
	//シーケンサメッセージ処理
	result = [self thread_SequencerMsgProc];
	if (result != 0) goto EXIT;
	
	//シーンメッセージ処理
	result = [self thread_SceneMsgProc];
	if (result != 0) goto EXIT;
	
	//描画
	result = [self thread_DrawProc];
	if (result != 0) goto EXIT;
	
	//FPS更新
	[self thread_UpdateFPS];
	
EXIT:;
	//コンテキストロック解除
	[m_pOpenGLContext flushBuffer];
	[m_pOpenGLContext unlock];
	
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 描画処理
//******************************************************************************
- (int)thread_DrawProc
{
	int result = 0;
	
	//レンダリング
	result = m_Renderer.RenderScene((OGLScene*)m_pScene);
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーケンサメッセージ処理
//******************************************************************************
- (int)thread_SequencerMsgProc
{
	int result = 0;
	bool isExist = false;
	unsigned int wParam = 0;
	unsigned int lParam = 0;
	SMMsgParser parser;
	
	while (YES) {
		//メッセージ取り出し
		result = m_pMsgQueue->GetMessage(&isExist, &wParam, &lParam);
		if (result != 0) goto EXIT;
		
		//メッセージがなければ終了
		if (!isExist) break;
		
		//メッセージ通知
		result = m_pScene->OnRecvSequencerMsg(wParam, lParam);
		if (result != 0) goto EXIT;	
		
		//演奏状態変更通知への対応
		parser.Parse(wParam, lParam);
		if (parser.GetMsg() == SMMsgParser::MsgPlayStatus) {
			//一時停止
			if (parser.GetPlayStatus() == SMMsgParser::StatusPause) {
				[self thread_PostPlayStatus:@"onChangePlayStatusPause"];
			}
			//停止（演奏終了）
			else if (parser.GetPlayStatus() == SMMsgParser::StatusStop) {
				[self thread_PostPlayStatus:@"onChangePlayStatusStop"];
			}
		}
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// シーンメッセージ処理
//******************************************************************************
- (int)thread_SceneMsgProc
{
	int result = 0;
	bool isExist = false;
	MTSceneMsg* pMsg = NULL;
	
	//描画スレッド実行中はシーンオブジェクトを描画スレッドが占有する
	//他のスレッドからシーンオブジェクトを直接操作してはならない
	//描画スレッドは描画前にキューに登録されたメッセージをすべて処理する
	
	while (YES) {
		//メッセージ取り出し
		result = m_SceneMsgQueue.GetMessage(&isExist, &pMsg);
		if (result != 0) goto EXIT;
		
		//メッセージがなければ終了
		if (!isExist) break;
		
		//メッセージに対応する処理を実行
		result = [self thread_ExecSceneMsg:pMsg];
		if (result != 0) goto EXIT;
		
		//メッセージ処理応答
		if (pMsg->IsSyncMode()) {
			//同期モード：応答する
			//  メッセージ破棄は呼び出し側で行う
			pMsg->WakeUp();
		}
		else {
			//非同期モード
			//  メッセージ破棄は受け取り側で行う
			delete pMsg;
			pMsg = NULL;
		}
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// シーンメッセージ実行
//******************************************************************************
- (int)thread_ExecSceneMsg:(MTSceneMsg*)pMsg
{
	int result = 0;
	MTSceneMsgSetViewpoint* pMsgSetViewpoint = NULL;
	MTSceneMsgGetViewpoint* pMsgGetViewpoint = NULL;
	MTSceneMsgSetEffect* pMsgSetEffect = NULL;
	MTSceneMsgOnMouseClick* pMsgOnMouseClick = NULL;
	MTSceneMsgOnMouseWheel* pMsgOnMouseWheel = NULL;
	MTScene::EffectType type;
	bool isEnable = false;
	float deltaWheelX, deltaWheelY, deltaWheelZ = 0.0f;
	
	//オブジェクト指向的には残念な実装であるが
	//シーンオブジェクトへの操作が見通せるようにする
	
	switch (pMsg->GetMsgId()) {
		//演奏開始
		case MTSCENEMSG_PLAY_START:
			result = m_pScene->OnPlayStart();
			break;
		//演奏終了
		case MTSCENEMSG_PLAY_END:
			result = m_pScene->OnPlayEnd();
			break;
		//巻き戻し
		case MTSCENEMSG_REWIND:
			result = m_pScene->Rewind();
			break;
		//視点リセット
		case MTSCENEMSG_RESET_VIEWPOINT:
			m_pScene->ResetViewpoint();
			break;
		//視点登録
		case MTSCENEMSG_SET_VIEWPOINT:
			pMsgSetViewpoint = (MTSceneMsgSetViewpoint*)pMsg;
			m_pScene->SetViewParam(pMsgSetViewpoint->GetViewParamMapPtr());
			break;
		//視点取得
		case MTSCENEMSG_GET_VIEWPOINT:
			pMsgGetViewpoint = (MTSceneMsgGetViewpoint*)pMsg;
			m_pScene->GetViewParam(pMsgGetViewpoint->GetViewParamMapPtr());
			break;
		//エフェクト設定
		case MTSCENEMSG_SET_EFFECT:
			pMsgSetEffect = (MTSceneMsgSetEffect*)pMsg;
			pMsgSetEffect->GetEffect(&type, &isEnable);
			m_pScene->SetEffect(type, isEnable);
			break;
		//マウスクリックイベント
		case MTSCENEMSG_ON_MOUSE_CLICK:
			pMsgOnMouseClick = (MTSceneMsgOnMouseClick*)pMsg;
			m_pScene->OnWindowClicked(pMsgOnMouseClick->GetClickButton(), 0, 0);
			break; 
		//マウスホイールイベント
		case MTSCENEMSG_ON_MOUSE_WHEEL:
			pMsgOnMouseWheel = (MTSceneMsgOnMouseWheel*)pMsg;
			pMsgOnMouseWheel->GetWheelDelta(&deltaWheelX, &deltaWheelY, &deltaWheelZ);
			m_pScene->OnScrollWheel(deltaWheelX, deltaWheelY, deltaWheelZ);
			break;
		//ゲームコントローラー更新
		case MTSCENEMSG_ON_GAMECONTROLLER_CHANGED:
			result = m_pScene->OnGameControllerChanged();
			if (result != 0) goto EXIT;
			break;
		default:
			break;
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// 演奏状態変更通知受信処理
//******************************************************************************
- (int)thread_PostPlayStatus:(NSString*)pNotificationName
{
	int result = 0;
	NSNotification* pNotification = nil;
	NSNotificationCenter* pCenter = nil;
	
	//通知オブジェクトを作成
	pNotification = [NSNotification notificationWithName:pNotificationName
												  object:self
											    userInfo:nil];
	//通知する
	pCenter = [NSNotificationCenter defaultCenter];
	
	//通知に対応する処理を演奏スレッドで処理させる場合
	//[pCenter postNotification:pNotification];
	
	//通知に対応する処理をメインスレッドに処理させる場合
	[pCenter performSelectorOnMainThread:@selector(postNotification:)
							  withObject:pNotification
						   waitUntilDone:NO];
	
	return result;
}

//******************************************************************************
// FPS更新
//******************************************************************************
- (void)thread_UpdateFPS
{
	uint64_t curTime = 0;
	uint64_t diffTime = 0;
	
	//現在時刻
	curTime = m_MachTime.GetCurTimeInNanosec();
	
	//前回FPS更新時刻を初期化
	if (m_DrawCount < 0) {
		m_PrevFPSUpdateTime = curTime;
	}
	
	//描画回数カウント
	m_DrawCount++;
	
	//描画100回ごとにFPSを更新
	if (m_DrawCount >= 100) {
		//描画周期（ナノ秒）
		diffTime = curTime - m_PrevFPSUpdateTime;
		
		//FPS算出
		m_FPS = (float)((double)(1000 * 1000000) * (double)(m_DrawCount) / (double)diffTime);
		
		//描画回数リセット
		m_DrawCount = 0;
		
		//前回FPS更新時刻を記録
		m_PrevFPSUpdateTime = curTime;
	}
}

//******************************************************************************
// FPS取得
//******************************************************************************
- (float)FPS
{
	return m_FPS;
}

//******************************************************************************
// アプリケーションアクティブ状態設定
//******************************************************************************
- (void)setActiveState:(BOOL)isActive
{
	bool isActiveState = (isActive)? true : false;
	
	//シーンオブジェクトを直接操作しているが描画スレッドへの影響はない
	if (m_pScene != NULL) {
		m_pScene->SetActiveState(isActiveState);
	}
	m_DIKeyCtrl.SetActiveState(isActiveState);
}

//******************************************************************************
//ドラッグ許可設定
//******************************************************************************
- (void)setDragAcceptable:(BOOL)isAcceptable
{
	m_isDragAcceptable = isAcceptable;
}

//******************************************************************************
// キー押下イベント
//******************************************************************************
- (void)keyDown:(NSEvent*)theEvent
{
	unsigned short keycode = 0;
	
	//タイプされたキーのコード
	keycode = [theEvent keyCode];
	
	//NSLog(@"keyDown %d", keycode);
	
	//メインメニューの当該機能を呼び出す
	//メインメニュー選択操作と同一にする
	switch (keycode) {
		case DIK_SPACE:
		case DIK_NUMPAD0:
			if  (m_DIKeyCtrl.IsKeyDown(DIK_SHIFT)) {
				//モニタ開始
				[m_pMenuCtrl performActionStartMonitoring];
			}
			else {
				//演奏開始／一時停止
				[m_pMenuCtrl performActionPlay];
			}
			break;
		case DIK_ESCAPE:
		case DIK_NUMPADENTER:
			if (m_isMonitor) {
				//モニタ停止
				[m_pMenuCtrl performActionStopMonitoring];		
			}
			else {
				//演奏停止
				[m_pMenuCtrl performActionStop];
			}
			break;
		case DIK_1:
		case DIK_NUMPAD1:
			//再生リワインド
			[m_pMenuCtrl performActionSkipBack];
			break;
		case DIK_2:
		case DIK_NUMPAD2:
			//再生スキップ
			[m_pMenuCtrl performActionSkipForward];
			break;
		case DIK_4:
		case DIK_NUMPAD4:
			//再生スピードダウン
			[m_pMenuCtrl performActionPlaySpeedDown];
			break;
		case DIK_5:
		case DIK_NUMPAD5:
			//再生スピードアップ
			[m_pMenuCtrl performActionPlaySpeedUp];
			break;
		case DIK_7:
		case DIK_NUMPAD7:
			//視点リセット
			[m_pMenuCtrl performActionResetViewpoint];
			break;
		case DIK_8:
		case DIK_NUMPAD8:
			//静的視点2移動
			[m_pMenuCtrl performActionViewpoint2];
			break;
		case DIK_9:
		case DIK_NUMPAD9:
			//静的視点3移動
			[m_pMenuCtrl performActionViewpoint3];
			break;
	}
}

//******************************************************************************
// マウス左クリックイベント
//******************************************************************************
- (void)mouseDown:(NSEvent*)theEvent
{
	int result = 0;
	NSPoint point;
	NSRect rect;
	
	//NSLog(@"mouseDown");
	
	//マウスカーソルがスクリーン上端に近い場合はイベントを無視する
	//  フルスクリーン状態のとき、視線移動モードに入りマウスカーソルを無効化するタイミングと
	//  フルスクリーンで隠れていたメニューバーが表示されるタイミングが重なると、
	//  メニューバー上にカーソルが表示されたまま移動できなくなる事象が発生する。
	//  何も操作できなくなるため、マウスカーソルが画面上端に近い場合はイベントを無視する。
	//  メモ：メニューバーのサイズは22ピクセル（非retina環境）
	rect = [[NSScreen mainScreen] frame];
	point = [NSEvent mouseLocation];
	if ((rect.size.height - point.y) < 22) goto EXIT;
	
	result = [self scene_OnMouseClick:WM_LBUTTONDOWN];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
}

//******************************************************************************
// マウス右クリックイベント
//******************************************************************************
- (void)rightMouseDown:(NSEvent*)theEvent
{
	int result = 0;
	
	//NSLog(@"rightMouseDown");
	
	result = [self scene_OnMouseClick:WM_RBUTTONDOWN];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
}

//******************************************************************************
// マウス中ボタン押下イベント
//******************************************************************************
- (void)otherMouseDown:(NSEvent*)theEvent
{
	int result = 0;
	
	//NSLog(@"otherMouseDown");
	
	result = [self scene_OnMouseClick:WM_MBUTTONDOWN];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
}

//******************************************************************************
// マウスホイールイベント
//******************************************************************************
- (void)scrollWheel:(NSEvent*)theEvent;
{
	int result = 0;
	
	//NSLog(@"scrollWheel");
	
	result = [self scene_OnMouseWheelWithDeltaX:[theEvent deltaX]
										 deltaY:[theEvent deltaY]
										 deltaZ:[theEvent deltaZ]];
	if (result != 0) goto EXIT;
	
EXIT:;
	//メッセージの破棄は受信側で行う
	if (result != 0) YN_SHOW_ERR();
}

//******************************************************************************
//ファイルドロップ対応：ドラッグ中のマウスカーソルが領域内に入った
//******************************************************************************
- (NSDragOperation)draggingEntered:(id)sender
{
	BOOL isAcceptableObject = NO;
	BOOL isDir = NO;
	NSDragOperation operation;
	NSString* pPath = nil;
	
	//受付可能か確認
	isAcceptableObject = [self isAcceptableObject:sender path:&pPath isDir:&isDir];
	
	//ドラッグ状態更新
	//ドラッグ許可状態でかつドラッグ可能なパスである場合のみ受け入れる
	if (m_isDragAcceptable && isAcceptableObject) {
		m_isDragging = YES;
		operation = NSDragOperationGeneric;
	}
	else {
		m_isDragging = NO;
		operation = NSDragOperationNone;
	}
	
	return operation;
}

//******************************************************************************
//ファイルドロップ対応：ドラッグ中のマウスカーソルが領域内で移動した
//******************************************************************************
- (NSDragOperation)draggingUpdated:(id)sender
{
	NSDragOperation operation;
	
	if (m_isDragging) {
		operation = NSDragOperationGeneric;
	}
	else {
		operation = NSDragOperationNone;
	}
	
	return operation;
}

//******************************************************************************
//ファイルドロップ対応：ドラッグ中のマウスカーソルが領域から外れた
//******************************************************************************
- (void)draggingExited:(id)sender
{
	m_isDragging = NO;
}

//******************************************************************************
//ファイルドロップ対応：ドロップされた
//******************************************************************************
- (BOOL)prepareForDragOperation:(id)sender
{
	return m_isDragging;
}

//******************************************************************************
//ファイルドロップ対応：ドロップ処理実行
//******************************************************************************
- (BOOL)performDragOperation:(id)sender
{
	BOOL isDropped = NO;
	BOOL isAcceptableObject = NO;
	BOOL isDir = NO;
	NSString* pPath = nil;
	
	//受付可能か確認
	isAcceptableObject = [self isAcceptableObject:sender path:&pPath isDir:&isDir];
	
	//ドラッグ許可状態でかつドラッグ可能なパスである場合のみ受け入れる
	if (m_isDragAcceptable && isAcceptableObject) {
		if (isDir) {
			//フォルダドロップイベント通知
			[m_pMenuCtrl onDropFolder:pPath];
		}
		else {
			//ファイルドロップイベント通知
			[m_pMenuCtrl onDropFile:pPath];
		}
		isDropped = YES;
	}
	
EXIT:;
	return isDropped;
}

//******************************************************************************
//ファイルドロップ対応：ドロップ処理完了通知
//******************************************************************************
- (void)concludeDragOperation:(id)sender
{
	//何もしない
}

//******************************************************************************
//ファイルドロップ対応：受け入れ可能判定
//******************************************************************************
- (BOOL)isAcceptableObject:(id)sender path:(NSString**)pPathPtr isDir:(BOOL*)pIsDir
{
	NSPasteboard *pPasteboard = nil;
	NSArray *pFileNameArray = nil;
	NSString* pPath = nil;
	BOOL isAcceptable = NO;
	BOOL isExist = NO;
	BOOL isDir = NO;
	
	//ペーストボードからパス配列を取得
	pPasteboard = [sender draggingPasteboard];
	pFileNameArray = [pPasteboard propertyListForType: NSFilenamesPboardType];
	
	//複数ファイルのドロップは無視する
	if ([pFileNameArray count] != 1) goto EXIT;
	
	//パスの取得
	pPath = [pFileNameArray objectAtIndex:0];
	
	//パスの存在確認
	isExist = [[NSFileManager defaultManager] fileExistsAtPath:pPath isDirectory:&isDir];
	if (!isExist) goto EXIT;
	
	isAcceptable = YES;
	*pPathPtr = pPath;
	*pIsDir = isDir;
	
EXIT:;
	return isAcceptable;
}

//******************************************************************************
// ゲームコントローラー接続通知
//******************************************************************************
- (void)onGameControllerDidConnect
{
	int result = 0;
	
	NSLog(@"Game Controller connected");
	
	//ゲームコントローラー初期化
	result = [self initializeGameController];
	if (result != 0) goto EXIT;
	
	//ゲームコントローラー更新をシーンに通知
	result = [self scene_OnGameControllerChanged];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// ゲームコントローラー切断通知
//******************************************************************************
- (void)onGameControllerDidDisconnect
{
	int result = 0;
	
	NSLog(@"Game Controller disconnected");
	
	//ゲームコントローラー初期化
	result = [self initializeGameController];
	if (result != 0) goto EXIT;
	
	//ゲームコントローラー更新をシーンに通知
	result = [self scene_OnGameControllerChanged];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// ゲームコントローラー初期化
//******************************************************************************
- (int)initializeGameController
{
	int result = 0;
	GCController* pController = nil;
	
	//ゲームパッド制御初期化
	result = m_GamePadCtrl.Initialize(0);
	if (result != 0) goto EXIT;
	
	//ゲームコントローラーが存在しなければ何もせず終了
	pController = m_GamePadCtrl.GetController();
	if (pController == nil) goto EXIT;
	
	//ポーズボタン：奏開始／一時停止
	pController.controllerPausedHandler
	= ^(GCController* controller) {
		[m_pMenuCtrl performActionPlay];
	};
	//Aボタン：演奏開始／一時停止
	pController.gamepad.buttonA.valueChangedHandler
	= ^(GCControllerButtonInput* pButton, float value, BOOL isPressed) {
		m_GamePadCtrl.UpdateState();
		if (m_GamePadCtrl.DidPressNow_A()){
			[m_pMenuCtrl performActionPlay];
		}
	};
	//Bボタン：演奏停止
	pController.gamepad.buttonB.valueChangedHandler
	= ^(GCControllerButtonInput* pButton, float value, BOOL isPressed) {
		m_GamePadCtrl.UpdateState();
		if (m_GamePadCtrl.DidPressNow_B()){
			[m_pMenuCtrl performActionStop];
		}
	};
	//拡張ゲームパッドでない場合
	if (pController.extendedGamepad == nil) {
		//L ショルダーボタン：視点切り替え
		pController.gamepad.leftShoulder.valueChangedHandler
		= ^(GCControllerButtonInput* pButtonInput, float value, BOOL isPressed) {
			m_GamePadCtrl.UpdateState();
			if (m_GamePadCtrl.DidPressNow_LShoulder()){
				[self changeViewPoint:-1];
			}
		};
		//R ショルダーボタン：視点切り替え
		pController.gamepad.rightShoulder.valueChangedHandler
		= ^(GCControllerButtonInput* pButtonInput, float value, BOOL isPressed) {
			m_GamePadCtrl.UpdateState();
			if (m_GamePadCtrl.DidPressNow_RShoulder()){
				[self changeViewPoint:+1];
			}
		};
	}
	//拡張ゲームパッドの場合
	else {
		//L1 ショルダーボタン：視点切り替え
		pController.extendedGamepad.leftShoulder.valueChangedHandler
		= ^(GCControllerButtonInput* pButtonInput, float value, BOOL isPressed) {
			m_GamePadCtrl.UpdateState();
			if (m_GamePadCtrl.DidPressNow_LShoulder()){
				[self changeViewPoint:-1];
			}
		};
		//R1 ショルダーボタン：視点切り替え
		pController.extendedGamepad.rightShoulder.valueChangedHandler
		= ^(GCControllerButtonInput* pButtonInput, float value, BOOL isPressed) {
			m_GamePadCtrl.UpdateState();
			if (m_GamePadCtrl.DidPressNow_RShoulder()){
				[self changeViewPoint:+1];
			}
		};
		//L2 トリガーボタン：再生リワインド
		pController.extendedGamepad.leftTrigger.valueChangedHandler
		= ^(GCControllerButtonInput* pButtonInput, float value, BOOL isPressed) {
			m_GamePadCtrl.UpdateState();
			if (m_GamePadCtrl.DidPressNow_LTrigger()){
				[m_pMenuCtrl performActionSkipBack];
			}
		};
		//R2 トリガーボタン：再生スキップ
		pController.extendedGamepad.rightTrigger.valueChangedHandler
		= ^(GCControllerButtonInput* pButtonInput, float value, BOOL isPressed) {
			m_GamePadCtrl.UpdateState();
			if (m_GamePadCtrl.DidPressNow_RTrigger()){
				[m_pMenuCtrl performActionSkipForward];
			}
		};
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// ゲームコントローラーボタンイベントに関するメモ
//******************************************************************************
// ボタンを1回押すだけでもvalueの異なる複数の通知が連続発生する
// ボタンを半押しで押し続けるとvalueが異なる通知が発生し続ける
// ボタンを全押しするとvalueが1.0となり通知が止まる
// ボタンを離すとvalueが0.0の通知が発生し、次にボタンを押すまで通知が発生しない

//******************************************************************************
// 視点切り替え
//******************************************************************************
- (void)changeViewPoint:(int)step
{
	//ゲームパッド用視点番号更新
	m_GamePadViewPointNo += step;
	
	if (m_GamePadViewPointNo < 0) {
		m_GamePadViewPointNo = 2;
	}
	else if (m_GamePadViewPointNo > 2) {
		m_GamePadViewPointNo = 0;
	}
	
	//視点切り替え
	switch (m_GamePadViewPointNo) {
		case 0:
			[m_pMenuCtrl performActionResetViewpoint];
			break;
		case 1:
			[m_pMenuCtrl performActionViewpoint2];
			break;
		case 2:
			[m_pMenuCtrl performActionViewpoint3];
			break;
		default:
			break;
	}
	
	return;
}

//******************************************************************************
// フルスクリーン開始
//******************************************************************************
- (int)enterFullScreen
{
	int result = 0;
	
	//ビューポートリセット
	result = m_Renderer.ResetViewPort(self);
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// フルスクリーン終了
//******************************************************************************
- (int)exitFullScreen
{
	int result = 0;
	
	//ビューポートリセット
	result = m_Renderer.ResetViewPort(self);
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

@end


