//--------------------------------------------------------------------------------------
//
// X-BASIC'
//	外部関数：iOS用 motion
//
//--------------------------------------------------------------------------------------
// 外部関数の構造を持っている

#import "Motion.h"
#import "Compass.h"
#import "libs.h"
#include "fdef.h"
#include "support.h"
#import "myString.h"
#import "XBiOSt.h"	// isLandscape(),waitSub()
#import "XBiOSmotion.h"

//---------------------------------------------------------------------------------------------
// 共通ワーク

static Motion 	*motion;	// これは内部で設定
static Compass 	*compass; 	// 同上
static int fuseMotion;		// モーションを何に使っているか

// stick()用
static int devDirec;			// stickStart()呼び出し時のデバイス方向
static double stickThreshold;	// しきい値;0〜100%=0.0〜1.0;割合であるが、軸の長さを１としているので即長さでもある
static double stickBaseX,stickBaseY;	// 自動補正用
static int stick0;				// 前回の入力値

//---------------------------------------------------------------------------------------------

static RET_FUNC _F_version(SWord numFAC,FAC fac[])
// この外部関数のバージョンを返す
{
	printFunc();
	fac[0].dl.cp[LL]=VERSION_XBIOSFUNCM;
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------

static RET_FUNC _F_motionStart(SWord numFAC,FAC fac[])
{
	printFunc();
	
	if (fuseMotion==FUSE_MOTION_JOYSTICK) {
		// すでにJoystickで使っている
		fac[0].dl.l[LL]=YES;
	} else {
		int i=0;
		int dataSecond=(int)fac[i++].dl.l[LL];
		
		int dno=MOTION_DEGREE;
		if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
			dno=(int)fac[i].dl.l[LL];
		}
		i++;
		
		fac[0].dl.l[LL]=c2basicYESNO([motion start:dataSecond dno:dno]);
	}
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_motionClear(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	[motion clear];
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_motionGetRotation(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	CMRotationRate xyz;
	[motion getRotation:&xyz];
	nslog(@"xyz=%2f,%2f,%2f",xyz.x,xyz.y,xyz.z);
	int i=0;
	// FLOAT_VPの受け方は↓こうする
	double *yp=fac[i++].dl.dp[LL];
	double *zp=fac[i++].dl.dp[LL];
//	nslog(@"yp=%p,zp=%p",yp,zp);
#if AARM>=2
	storeDoubleToDoublePointer(yp,xyz.y);
	storeDoubleToDoublePointer(zp,xyz.z);
#else
	*yp=xyz.y;
	*zp=xyz.z;
#endif
	fac[0].dl.d=xyz.x;
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_motionCount(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	fac[0].dl.l[LL]=[motion count];
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_motionPause(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	int fpause=basic2cYESNO(fac[0].dl.l[LL]);
	[motion pause:fpause];
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_motionSelectData(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	int dno=(int)fac[0].dl.l[LL];
	[motion selectData:dno];
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_motionEnd(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	[motion end];
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	fuseMotion=FUSE_MOTION_NONE; // 使ってない
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------
// v1.3 : 方位取得関数
// yawが方位ではないとわかったので、その代わりに実装
//---------------------------------------------------------------------------------------------

static RET_FUNC _F_compassStart(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	int i=0;
	int datas=(int)fac[i++].dl.l[LL];
	fac[0].dl.l[LL]=c2basicYESNO([compass start:datas]);
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_compassClear(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	[compass clear];
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_compassGetData(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	CLHeading *data=[compass getData];
	nslog(@"data=%f,%f",data.trueHeading,data.magneticHeading);
	int i=0;
	if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
		// FLOAT_VPの受け方は↓こうする
		double *mh=fac[i].dl.dp[LL];
#if AARM>=2
		storeDoubleToDoublePointer(mh,data.magneticHeading);
#else
		*mh=data.magneticHeading;
#endif
	}
	i++;
	fac[0].dl.d=data.trueHeading;
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_compassCount(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	fac[0].dl.l[LL]=[compass count];
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_compassPause(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	int fpause=basic2cYESNO(fac[0].dl.l[LL]);
	[compass pause:fpause];
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

static RET_FUNC _F_compassEnd(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	[compass end];
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------
// V3.50
// Joystick関数

// int stick([mode;int])
static RET_FUNC _F_stick(SWord numFAC,FAC fac[])
{
	printFunc();

	int mode=NO;
	if (fac[0].facType!=VOID_PARAM) { // 引数が省略されていない時
		mode=fac[0].dl.l[LL];
	}
	
	//
	int cnt=[motion count];
	// cnt=0は以下の場合にあり得る
	// <100msの間隔で呼び出した；前回の読み出しから今回の読み出しの間に新たなデータを受信出来ていない
	// その時はまだ更新データがないので、前のデータを返す
	int ky;
	if (cnt==0) {
		ky=stick0;
	} else {
		nslog(@"cnt=%d",cnt);
		
		CMRotationRate xyz;
		while (cnt-->0) {
			// 複数データが来た時には最後の1つを使う
			[motion getRotation:&xyz];
		}
		double x=xyz.x,y=xyz.y;
//		nslog(@"x=%2f,y=%2f,z=%2.f",x,y,xyz.z);
		
		// xy平面への投影軸長さを計算する
		// 軸の長さを1とする
		// 角度は時計回りになっている。
		// 本来投影軸はcos()でとるべきだが、伸びるほうが計算しやすいので、sin()で取る
		// 角度をラジアンに直す
		x=degree2radian(x-stickBaseX);
		y=degree2radian(y-stickBaseY);
		double rx,ry;
		// まず方向を決定する
		// BIT0=右
		// BIT1=左	当然左右は同時には入らない
		// BIT2=上
		// BIT3=下	当然上下は同時には入らない
		// かつてX68ではそれを使ってボタン数を増やしていたが、遠い思い出。
		int Bright=BIT0,Bleft=BIT1,Bup=BIT2,Bdown=BIT3;
		switch (devDirec) {
			case UIDeviceOrientationPortrait:// 縦
				nslog(@"縦");
				rx=sin(x);
				ry=sin(y);
				break;
			case UIDeviceOrientationPortraitUpsideDown:// 上下逆
				nslog(@"上下逆");
				// x,yとも方向が逆になる
				rx=sin(x);
				ry=sin(y);
				Bright=BIT1;Bleft=BIT0;Bup=BIT3;Bdown=BIT2;
				break;
			case UIDeviceOrientationLandscapeLeft:// ホームボタンは右
				nslog(@"ホームボタンは右");
				// x,y/左右が入れ替わる
				rx=sin(y);
				ry=sin(x);
				Bright=BIT1;Bleft=BIT0;
				break;
			case UIDeviceOrientationLandscapeRight:// ホームボタンは左
				nslog(@"ホームボタンは左");
				// x,y/上下が入れ替わる
				rx=sin(y);
				ry=sin(x);
				Bup=BIT3;Bdown=BIT2;
				break;
		}
		double arx=fabs(rx);
		double ary=fabs(ry);
		nslog(@"|rx|=%f,|ry|=%f",arx,ary);
		int direcX=0;
		int direcY=0;
		if (arx>=stickThreshold) {
			// 左右方向がある
			direcX=Bleft;
			if (rx>0) direcX=Bright;
		}
		if (ary>=stickThreshold) {
			// 上下方向がある
			direcY=Bdown;
			if (ry>0) direcY=Bup;
		}
		if (mode) {
			// 上下左右だけの時
			if (direcX!=0 && direcY!=0) {
				// 斜め方向である
				// より値が大きい方にする
				if (arx>ary) direcY=0; else direcX=0;
			}
		}
		int direc=direcX|direcY;
		ky=STICK_DIREC_NONE; // どの方向にも傾いてない
		switch (direc) {
			case BIT0:ky=STICK_DIREC_RIGHT;	break;
			case BIT1:ky=STICK_DIREC_LEFT;	break;
			case BIT2:ky=STICK_DIREC_UP;	break;
			case BIT3:ky=STICK_DIREC_DOWN;	break;
			//
			case BIT0|BIT2:ky=STICK_DIREC_RIGHT_UP;		break;
			case BIT0|BIT3:ky=STICK_DIREC_RIGHT_DOWN;	break;
			case BIT1|BIT2:ky=STICK_DIREC_LEFT_UP;		break;
			case BIT1|BIT3:ky=STICK_DIREC_LEFT_DOWN;	break;
		}
		stick0=ky; // 今回の値を保存
	}
	fac[0].dl.l[LL]=ky;
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int stickStart([threshold;float][,fcalibrate;int])
static RET_FUNC _F_stickStart(SWord numFAC,FAC fac[])
{
	printFunc();
	BOOL ret;
	if (fuseMotion==FUSE_MOTION_MOTION) {
		// すでにMotionで使っている
		ret=YES;
	} else {
		// モーションの初期化
		// 使うのはMOTION_DEGREEで1秒もあれば十分
		ret=[motion start:1 dno:MOTION_DEGREE];
		if (!ret) { // モーションは使える
			// Joystickの状態設定
			int i=0;
			stickThreshold=STAICK_DEFAULT_THRESHOLD;
			if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
				// FLOAT_VPの受け方は↓こうする
				stickThreshold=fac[i].dl.d; // 値の指定は%で10〜90
				if (stickThreshold<10) stickThreshold=10; // 念のため
				if (stickThreshold>90) stickThreshold=90; // 念のため
				stickThreshold/=100.0; // %
			}
			nslog(@"stickThreshold=%f%%",stickThreshold*100.0);
			i++;
			
			// デバイスの向きは初期化時に固定；途中は上下左右動かすからデバイス向きで判定を変えてはいけない
			devDirec=[UIDevice currentDevice].orientation;
			switch (devDirec) {
				case UIDeviceOrientationFaceUp:  // 上向け
				case UIDeviceOrientationFaceDown:// 下向け
				{
					FAC fac2[2];
					fac2[0].facType=VOID_PARAM;
					_F_isLandscape(1,fac2);	// この中ではエラーは発生しない
					if (fac2[0].dl.l[LL]==BASIC_YES) {
						devDirec=UIDeviceOrientationLandscapeRight;
					} else {
						devDirec=UIDeviceOrientationPortrait;
					}
				}
					break;
			}
			nslog(@"devDirec=%@",strDeviceOrientation(devDirec));

			BOOL fcalibrate=NO;
			stickBaseX=stickBaseY=0;
			if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
				// 自動補正するかどうか
				fcalibrate=basic2cYESNO(fac[i].dl.l[LL]);
				// 自由角度設定にすると間違った値の時の排除が難しいので自動のみ
				// また、自動補正するとななめ入力は入らなくなる(実験の結果、現在の判定方法では入力が極めて困難なので〜３次元的に)
			}
			if (fcalibrate) { // 調整
				nslog(@"調整(補正)");
				waitSub(0.5); // 少し待つ(少なくとも１回のサンプリングが行われるまで);0.3では時間切れになった
				CMRotationRate xyz;
				int cnt=[motion count];
				if (cnt==0) {
					// 時間切れ
					nslog(@"時間切れ");
					ret=YES;
					goto JOYSTICK_ERROR;
				}
				while (cnt-->0) {
					[motion getRotation:&xyz];
				}
//				switch (devDirec) {
//					case UIDeviceOrientationPortrait:
//					case UIDeviceOrientationPortraitUpsideDown:
//						// 縦画面
						stickBaseX=xyz.x;
						stickBaseY=xyz.y;
//						break;
//					case UIDeviceOrientationLandscapeLeft:
//					case UIDeviceOrientationLandscapeRight:
//						// 横画面
//						stickBaseDegree=xyz.x;
//						break;
//				}
				nslog(@"x=%2f,y=%2f",xyz.x,xyz.y);
			}
			i++;
			nslog(@"stickBaseX/Y=%f,%f",stickBaseX,stickBaseY);
			
			stick0=STICK_DIREC_CENTER;
		} else {
			// モーションでエラーが発生しているときはbaseDegreeを設定しても無駄なので何もしない
			nslog(@"モーションは利用できない");
		}
	}
JOYSTICK_ERROR:;
	fac[0].dl.l[LL]=c2basicYESNO(ret);
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// stickEnd()
// motionEnd()と同じ
static RET_FUNC _F_stickEnd(SWord numFAC,FAC fac[])
{
	printFunc();
	//
	[motion end];
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	fuseMotion=FUSE_MOTION_NONE; // 使ってない
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------
// インフォメーションテーブル (5.2.1章)
//---------------------------------------------------------------------------------------------

// 関数名テーブル(トークンテーブル)
static const char *iOSMTokens[]={
	"motionStart",
	"motionClear",
	"motionGetRotation",
	"motionCount",
	"motionPause",
	"motionSelectData",
	"motionEnd",
	// v1.3
	"compassStart",
	"compassClear",
	"compassGetData",
	"compassCount",
	"compassPause",
	"compassEnd",
	// v3.5
	"stick",
	"stickStart",
	"stickEnd",
	//
	// バージョン
	"versionXBiOSMotion$",
	// end of table
	NULL,
};

// 実行アドレステーブル
static FUNC_EXEC iOSMFuncExec[]={
	// Cの標準関数名と重複させないよう_F_を付ける
	{_F_motionStart},
	{_F_motionClear},
	{_F_motionGetRotation},
	{_F_motionCount},
	{_F_motionPause},
	{_F_motionSelectData},
	{_F_motionEnd},
	// v1.2
	{_F_compassStart},
	{_F_compassClear},
	{_F_compassGetData},
	{_F_compassCount},
	{_F_compassPause},
	{_F_compassEnd},
	// V3.5
	{_F_stick},
	{_F_stickStart},
	{_F_stickEnd},
	//
	// バージョン
	{_F_version},
	// end of table
	{NULL},
};

//------------------------------
// パラメーターテーブル
// (1)種類別定義
// 1関数の引数は最大MAX_PARAM個

static const uwPARAMTYPE p_motionStart[]={
	INT_VAL,
	INT_OMT,
	INT_RET,
};

static const uwPARAMTYPE p_motionPause[]={
	INT_VAL,
	VOID_RET,
};
#define p_motionSelectData	p_motionPause
#define p_compassPause		p_motionPause

static const uwPARAMTYPE p_motionClear[]={
	VOID_RET,
};
#define p_compassClear 	p_motionClear
#define p_motionEnd		p_motionClear
#define p_compassEnd 	p_motionEnd
#define p_stickEnd	 	p_motionEnd

static const uwPARAMTYPE p_motionGetRotation[]={
	FLOAT_VP,
	FLOAT_VP,
	FLOAT_RET,
};

static const uwPARAMTYPE p_motionCount[]={
	INT_RET,
};
#define p_compassCount	p_motionCount

static const uwPARAMTYPE p_compassStart[]={
	INT_VAL,
	INT_RET,
};

static const uwPARAMTYPE p_compassGetData[]={
	FLOAT_VP|PARAM_OMIT,
	FLOAT_RET,
};

static const uwPARAMTYPE p_stick[]={
	INT_OMT,	// mode
	INT_RET
};
static const uwPARAMTYPE p_stickStart[]={
	FLOAT_OMT,	// 感度（しきい値)＝傾いたと判断する角度
	INT_OMT,	// 基本角度自動設定モード
//	INT_OMT,	// モード;今はデジタルモードしかないので省略
	INT_RET
};

static const uwPARAMTYPE p_version[]={
	STR_RET,
};

//------------------------------
// パラメーターテーブル
// (2)関数別定義

static
const
uwPARAMTYPE *iOSMParams[]={
	p_motionStart,
	p_motionClear,
	p_motionGetRotation,
	p_motionCount,
	p_motionPause,
	p_motionSelectData,
	p_motionEnd,
	// V1.2
	p_compassStart,
	p_compassClear,
	p_compassGetData,
	p_compassCount,
	p_compassPause,
	p_compassEnd,
	// v3.5
	p_stick,
	p_stickStart,
	p_stickEnd,
	//
	// バージョン
	p_version	,
	// end of table
	NULL,
};

//-------------

// この中の関数の初期化処理
static void _Frun(void)
{
	printFunc();
	motion =[[Motion  alloc]init];
	compass=[[Compass alloc]init];
	//
	devDirec=UIDeviceOrientationPortrait;
	stickThreshold=STAICK_DEFAULT_THRESHOLD;
	stickBaseX=stickBaseY=0;
	stick0=STICK_DIREC_CENTER;
	fuseMotion=FUSE_MOTION_NONE;
}

// この中の関数の終了処理
static void _Fend(void)
{
	printFunc();
	// 停止
	[motion  end];
	[compass end];
	// 開放
	motion =nil;
	compass=nil;
}

// インフォメーションテーブル
const
INFORMATION_TABLE	InfoIosMFuncs={
	#ifdef FUNC_DEBUG
	"iOS Motion functions",
	#endif
	returnOnly,	// Finit
	_Frun,		// Frun
	_Fend,		// Fend
	returnOnly,	// Fexit
	returnOnly,	// Fbreak
	NULL,		// FctrlD;未使用
	NULL,		// 予約1
	NULL,		// 予約2
	//
	iOSMTokens,
	iOSMParams,
	iOSMFuncExec
};

//---------------------------------------------------------------------------------------------
