#include <stdio.h>
#include <math.h>
#include <eggx.h>

// ============================ EGGX

int win;

#define WW (400)
#define HH (600)

// ========================== 二線分の交差チェック
/*
 http://www.hiramine.com/programming/graphics/2d_segmentintersection.html
 を参考に。
 線分 AB (ax,ay)-(bx,by) と CD (cx,cy)-(dx,dy) が交差しているかどうかを調べる
 r, s が共に 0-1 の間にあれば衝突と判定
 ただし、
 戻り値は衝突時は r そのもの。範囲は 0.0 <= r <= 1.0 である。
 非衝突時は 9.0 を返す。
 ただし、二線分が並行な場合は 99.0 であり、
 二線分が同一（重なっている）の場合は 999.0 を返す
 この r を用いて交差座標位置を求める
 */

#define NOCROSS  9.0
#define PARALLEL 99.0
#define DOUBLE   999.0

float colCheck(float ax, float ay,
			   float bx, float by,
			   float cx, float cy,
			   float dx, float dy)
{
	float denom; // 途中計算における分母(denominator)
	float acx, acy; // ベクター AC を表現する
	float r, s;
	
	denom = (bx - ax) * (dy - cy) - (by - ay) * (dx - cx);
	if( denom == 0.0 ) return PARALLEL; // 二線は並行である

	// ベクター AC を得る（C - A にあたる） 
	acx = cx - ax; acy = cy - ay;
	
	r=( ( dy - cy ) * acx - ( dx - cx ) * acy ) / denom;
	s=( ( by - ay ) * acx - ( bx - ax ) * acy ) / denom;
	
	if( (r == 0.0)&&(s == 0.0)) return DOUBLE; // 二線は同一である
	
	if( (0.0 <= r) && (r <= 1.0) && (0.0 <= s) && (s <= 1.0)) {
		return r; // 交差した
	} else {
		return NOCROSS; // 交差しない
	}
		
}

// ========================== 補助サブルーチン群

// 初期設定
void setup(void)
{
	win=gopen(WW, HH);
	winname(win, "Collision test");
	
}

// 終了処理
void closeall(void)
{
	// ggetch(); // quit 関数があるので外す
	gclose(win);
}

// ======================== 座標位置を指定して、交差点を計算させる

// 終了ボタンの位置、幅、高さ
#define QUITX (WW*0.3)
#define QUITY (40.0)
#define QUITW (35.0)
#define QUITH (20.0)

// 終了ボタンの表示
void showQuitButton(void)
{
	newpen(win, 4);
	fillrect(win, QUITX, QUITY, QUITW, QUITH);
	newpen(win, 1);
	drawstr(win, QUITX+2.0, QUITY+2.0, 16, 0.0, "QUIT");
}	

// 座標位置が終了ボタンの中かどうかをチェック
int inQuitButton(float x, float y)
{
	if ((QUITX<x)&&(x<(QUITX+QUITW))
		&&(QUITY<y)&&(y<(QUITY+QUITH))) {
		return 1; // quit ボタン内をクリックすれば 1 を返して return
	} else {
		return 0;
	}
}

#define CLIP 1000
// マウス入力：戻り値は x * 1000 + y 
int clickCheck(void)
{
	int w;
	float x, y;
	int code, type;

	do {
		w=ggetxpress(&type, &code, &x, &y);
	} while((w!=win)||(type!=ButtonPress));

	return (int)x * CLIP + (int)y; // 整数型で戻す
}

// 指定された位置、色にマークを入れる
void markPoint(float x, float y, int color)
{
	newpen(win, color);
	drawcirc(win, x, y, 3.0, 3.0);
}

// 指定された位置、色に線を引く
void drawLine(float ax, float ay, float bx, float by, int color)
{
	newpen(win, color);
	drawline(win, ax, ay, bx, by);
}

// 指定された位置、色に矢印つきの線を引く
void drawArrow(float ax, float ay, float bx, float by, int color)
{
	newpen(win, color);
	drawarrow(win, ax, ay, bx, by, 8.0, 6.0, 14);
}

// 線分 A, B から角を出す
float theta(float ax, float ay, float bx, float by)
{
	return atan2f(by-ay, bx-ax);
}

// 反射角を得る（壁CDに対してABが反射する場合）
float bounceRect(float ax, float ay,
				 float bx, float by,
				 float cx, float cy,
				 float dx, float dy)
{
	float th0, th1, th2;
	th1=theta(ax, ay, bx, by);
	th2=theta(cx, cy, dx, dy);
	th0=th1 - th2;
	return M_PI - th0 + th2 + M_PI; // 反射角
}

// A,B,C,D の各点を指定し、交差を判定
int crossPoint(void)
{
	int xy;
	float ax, ay, bx, by, cx, cy, dx, dy;
	float r; // 線分 AB 上にある交点の位置（A からスタートして線分全長を 1 とした位置）
	float isx, isy; // 交点 (isx, isy)
	float th, box, boy; // 交点を始点とした場合の反射角(th)、反射先位置 (box, boy)
	float l; // 反射してから後の長さ

	// 最初に終了ボタンを出しておく
	showQuitButton();

	// まず A, B, C, D の四点の座標位置を指定する
	// A を指定
	xy=clickCheck(); ax=xy/CLIP; ay=xy%CLIP;

	//	A ではなく QUIT ボタンを押したかどうかチェック
	if(inQuitButton(ax, ay)==1) return 1; // 当該枠内をクリックすれば 1 を返して return

	gclr(win); // 問題なければ画面を消去（終了ボタンを消す）
	markPoint(ax, ay, 1); // ポイントした位置にマルを描く
	
	// B を指定
	xy=clickCheck(); bx=xy/CLIP; by=xy%CLIP;
	drawArrow(ax, ay, bx, by, 1); // 線分 AB を描画（矢印つき）

	// C, D を指定して線分 CD を描画
	xy=clickCheck(); cx=xy/CLIP; cy=xy%CLIP;
	markPoint(cx, cy, 2); // ポイントした位置にマルを描く
	xy=clickCheck(); dx=xy/CLIP; dy=xy%CLIP;
	markPoint(dx, dy, 2); // ポイントした位置にマルを描く
	drawLine(cx, cy, dx, dy, 2); // 線分 CD を描画
	
	// ここまでで A,B,C,D の位置が決定できた
	
	// 交差チェック
	r=colCheck(ax, ay, bx, by, cx, cy, dx, dy);
	if(r >= NOCROSS) { // 交差しなかった
		drawstr(win, 10.0, 10.0, 16, 0.0, "NO CROSS");
	} else { // 交差した（r は単位時間に対して衝突するタイミング (0〜1) ）
		//交点を AB から求める（A から r ぶんだけ AB 方向に進んだ位置）
		isx = ax + r * ( bx - ax );
		isy = ay + r * ( by - ay );
		// 交点を描画
		markPoint(isx, isy, 3);
		
		// 反射角を得る（壁CDに対してABが反射する場合）
		th=bounceRect(ax, ay, bx, by, cx, cy, dx, dy);
		// 交点 (isx, isy) から r ぶんだけ角 th 方向に進んだ位置）
		l=(1.0-r)*hypotf(bx - ax, by - ay);
		box = isx + l * cosf(th);
		boy = isy + l * sinf(th);
		drawArrow(isx, isy, box, boy, 4);
	}
	return 0;
}

// ============================================ メインルーチン

int main (int argc, const char * argv[]) {
	setup();
	
	gclr(win);
	while(1) {
		if(crossPoint()==1) break;
	}
	
	closeall();
    return 0;
}
