//******************************************************************************
//
// MIDITrail / MTMainView
//
// メインビュー制御クラス
//
// Copyright (C) 2010-2022 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;
	
	m_RendererParam = rendererParam;
	
	//Metalデバイス生成
	m_MTLDevice = MTLCreateSystemDefaultDevice();
	if (m_MTLDevice == nil) {
		result = YN_SET_ERR(@"The system does not support Metal.", 0, 0);
		goto EXIT;
	}
	
	//ビュー初期化
	self = [super initWithFrame:frameRect device:m_MTLDevice];
	self.delegate = self;
	
	//アンチエイリアシング：サンプルモード
	if ((rendererParam.isEnableAntialiasing) && (rendererParam.sampleMode == OGLMultisampleBit)) {
		self.sampleCount = rendererParam.sampleNum;
	}
	else {
		self.sampleCount = 1;
	}
	
	//ディスプレイリンク初期化
	[self setupCVDisplayLink];
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return self;
}

//******************************************************************************
// 破棄
//******************************************************************************
- (void)dealloc
{
	[m_MTLDevice release];
	[super dealloc];
}

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

//******************************************************************************
// ディスプレイリンク初期化
//******************************************************************************
- (void)setupCVDisplayLink
{
	int result = 0;
	NSNotification* pNotification = nil;
	NSNotificationCenter* pCenter = nil;
	
	//ディスプレイリンクによるメインスレッドでないスレッドからの描画処理は廃止したため
	//ここでは何もしない
	
	//通知オブジェクトを作成
	pNotification = [NSNotification notificationWithName:@"onPreparedDisplayLink"
												  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_Renderer.InitScene();
	
	//シーン生成
	result = pScene->Create(self, m_Renderer.GetDevice(), pSeqData);
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン開始
//******************************************************************************
- (int)startScene:(MTScene*)pScene
		isMonitor:(BOOL)isMonitor
{
	int result = 0;
	
	//シーンオブジェクト
	m_pScene = pScene;
	
	//モニタフラグ
	m_isMonitor = isMonitor;
	
	//シーンメッセージキューをクリア
	m_SceneMsgQueue.Clear();
	
	//ゲームコントローラー初期化
	[self initializeGameController];
	
	//FPS算出用パラメータ初期化
	m_PrevFPSUpdateTime = 0;
	m_DrawCount = -1;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン停止
//******************************************************************************
- (int)stopScene
{
	return 0;
}

//******************************************************************************
// シーン一時停止
//******************************************************************************
-(int)pauseScene
{
	return 0;
}

//******************************************************************************
// シーン再開
//******************************************************************************
-(int)resumeScene
{
	int result = 0;
	
	//FPS算出用パラメータ初期化
	m_PrevFPSUpdateTime = 0;
	m_DrawCount = -1;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：演奏開始
//******************************************************************************
- (int)scene_PlayStart
{
	int result = 0;
	
	//演奏開始
	result = m_pScene->OnPlayStart();
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：演奏終了
//******************************************************************************
- (int)scene_PlayEnd
{
	int result = 0;
	
	//演奏終了
	result = m_pScene->OnPlayEnd();
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：巻き戻し
//******************************************************************************
- (int)scene_Rewind
{
	int result = 0;
	
	//巻き戻し
	result = m_pScene->Rewind();
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：視点リセット
//******************************************************************************
- (int)scene_ResetViewpoint
{
	int result = 0;
	
	//視点リセット
	m_pScene->ResetViewpoint();
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：視点登録
//******************************************************************************
- (int)scene_SetViewpoint:(MTScene::MTViewParamMap*)pParamMap
{
	int result = 0;
	
	//視点登録
	m_pScene->SetViewParam(pParamMap);
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：視点取得
//******************************************************************************
- (int)scene_GetViewpoint:(MTScene::MTViewParamMap*)pParamMap
{
	int result = 0;
	
	//視点取得
	m_pScene->GetViewParam(pParamMap);
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：エフェクト設定
//******************************************************************************
- (int)scene_SetEffect:(MTScene::EffectType)type isEnable:(bool)isEnable
{
	int result = 0;
	
	//エフェクト設定
	m_pScene->SetEffect(type, isEnable);
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：マウスクリックイベント
//******************************************************************************
- (int)scene_OnMouseClick:(unsigned int)button
{
	int result = 0;
	
	//マウスクリックイベント
	result = m_pScene->OnWindowClicked(button, 0, 0);
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：マウスホイールイベント
//******************************************************************************
- (int)scene_OnMouseWheelWithDeltaX:(float)dX deltaY:(float)dY deltaZ:(float)dZ
{
	int result = 0;
	
	//マウスホイールイベント
	result = m_pScene->OnScrollWheel(dX, dY, dZ);
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン操作：ゲームコントローラー更新イベント
//******************************************************************************
- (int)scene_OnGameControllerChanged
{
	int result = 0;
	
	//ゲームコントローラー更新イベント
	result = m_pScene->OnGameControllerChanged();
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// 描画
//******************************************************************************
- (void)drawInMTKView:(nonnull MTKView *)view
{
	//NSLog(@"drawInMTKView");
	
	//シーン描画（メインスレッドによる描画処理）
	@autoreleasepool {
		[self thread_DrawScene];
	}
	
	return;
}

//******************************************************************************
// 描画サイズ変更
//******************************************************************************
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
{
	//何もしない
	return;
}

//******************************************************************************
// シーン描画
//******************************************************************************
- (void)thread_DrawScene
{
	int result = 0;
	
	//NSLog(@"thread_DrawScene");
	
	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:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 描画処理
//******************************************************************************
- (int)thread_DrawProc
{
	int result = 0;
	
	//レンダリング
	result = m_Renderer.RenderScene(self, (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:
			if  (m_DIKeyCtrl.IsKeyDown(DIK_OPTION) && m_DIKeyCtrl.IsKeyDown(DIK_COMMAND)) {
				//私の視点1保存
				[m_pMenuCtrl performActionSaveMyViewpoint1];
			}
			else if (m_DIKeyCtrl.IsKeyDown(DIK_COMMAND)) {
				//私の視点1移動
				[m_pMenuCtrl performActionMyViewpoint1];
			}
			else {
				//視点リセット
				[m_pMenuCtrl performActionResetViewpoint];
			}
			break;
		case DIK_8:
		case DIK_NUMPAD8:
			if  (m_DIKeyCtrl.IsKeyDown(DIK_OPTION) && m_DIKeyCtrl.IsKeyDown(DIK_COMMAND)) {
				//私の視点2保存
				[m_pMenuCtrl performActionSaveMyViewpoint2];
				//OPTION + COMMAND + 8 は「ズーム機能のオン/オフ」と重なるため
				//システム環境設定でショートカットを無効にする必要がある
			}
			else if (m_DIKeyCtrl.IsKeyDown(DIK_COMMAND)) {
				//私の視点2移動
				[m_pMenuCtrl performActionMyViewpoint2];
			}
			else {
				//静的視点2移動
				[m_pMenuCtrl performActionViewpoint2];
			}
			break;
		case DIK_9:
		case DIK_NUMPAD9:
			if  (m_DIKeyCtrl.IsKeyDown(DIK_OPTION) && m_DIKeyCtrl.IsKeyDown(DIK_COMMAND)) {
				//私の視点3保存
				[m_pMenuCtrl performActionSaveMyViewpoint3];
			}
			else if (m_DIKeyCtrl.IsKeyDown(DIK_COMMAND)) {
				//私の視点3移動
				[m_pMenuCtrl performActionMyViewpoint3];
			}
			else {
				//静的視点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.extendedGamepad.buttonA.valueChangedHandler
	= ^(GCControllerButtonInput* pButton, float value, BOOL isPressed) {
		m_GamePadCtrl.UpdateState();
		if (m_GamePadCtrl.DidPressNow_A()){
			[m_pMenuCtrl performActionPlay];
		}
	};
	//Bボタン：演奏停止
	pController.extendedGamepad.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.extendedGamepad.leftShoulder.valueChangedHandler
		= ^(GCControllerButtonInput* pButtonInput, float value, BOOL isPressed) {
			m_GamePadCtrl.UpdateState();
			if (m_GamePadCtrl.DidPressNow_LShoulder()){
				[self changeViewPoint:-1];
			}
		};
		//R ショルダーボタン：視点切り替え
		pController.extendedGamepad.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


