/*
 *	tcpip.c
 *
 *	TCP/IPプロトコルスイート
 *
 *	* Sat Sep 24 22:17:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sat Oct 01 18:26:00 JST 2005 Naoyuki Sawa
 *	- tcpip_start()にて、開始後に0.5秒間の待ち時間を追加しました。
 */
#include "app.h"

#ifdef USE_TCPIP

/****************************************************************************
 *	
 ****************************************************************************/

#define TCPIP_CONFIG_FNAME	"tcpip.cfg"	/* 設定ファイル名 */

/* TCP/IP設定ファイル */
typedef struct _TCPIPCONFIG {
	IPCONFIG ip_config;	/* IP設定 */
	int dns_server;		/* DNS設定 */
} TCPIPCONFIG;

/*--------------------------------------------------------------------------*/

static int tcpip_garp_timer;	/* Gratuitous ARP駆動タイマ (0:停止中) */

/*--------------------------------------------------------------------------*/

static int tcpip_load_config(TCPIPCONFIG* config);
static int tcpip_save_config(const TCPIPCONFIG* config);

static void tcpip_get_config(TCPIPCONFIG* config);
static void tcpip_set_config(const TCPIPCONFIG* config);

static int tcpip_do_config(TCPIPCONFIG* config, int cancelable);

/****************************************************************************
 *	
 ****************************************************************************/

void
tcpip_start(int txbuf_capacity)
{
	TCPIPCONFIG config;

	if(tcpip_garp_timer) {
		DIE(); /* 多重起動は禁止です */
	}

	/* TCP/IP設定をロードします。 */
	if(tcpip_load_config(&config) != 0) {

		/* 読み込み失敗ならば、仮の初期設定値を格納しておきます。 */
		config.ip_config.ip_address      = IP_ADDRESS(192,168,255,  1);
		config.ip_config.subnet_mask     = IP_ADDRESS(255,255,255,  0);
		config.ip_config.default_gateway = IP_ADDRESS(192,168,255,254);
		config.dns_server                = IP_ADDRESS(202,234,232,  6); /* プライマリDNS */
		//config.dns_server                = IP_ADDRESS(221,113,139,250); /* セカンダリDNS */

		/* TCP/IP設定を行います。 */
		tcpip_do_config(&config, 0/*キャンセル不可*/);
	}

	/* アプリケーション開始直後、最低0.1秒間は初期化を遅らせます。
	 * コマンドプロンプトからrunで実行した場合に、USBCOM応答を行うためです。
	 */
	while(now < SEC(0.1)) {
		schedule();
	}

	/* Ethernetドライバを開始します。 */
	ethernet_start(txbuf_capacity);

	/* IPドライバを開始します。 */
	ip_start();

	/* UDPドライバを開始します。 */
	udp_start();

	/* TCPドライバを開始します。 */
	tcp_start();

	/* Gratuitous ARPを開始します。(対症療法) */
	tcpip_garp_timer = timer_start(5000, (void(*)())arp_gratuitous, NULL);

	/* TCP/IP設定を反映します。 */
	tcpip_set_config(&config);

	/* * Windowsのデバイスドライバと、プロトコルバインディングが完了するまで、通信ができません。
	 *   所要時間が不明なので(そもそもPCと接続されているもかも不明)、0.5秒程度待つことにします。
	 * - もし、ここでの待ち時間が足りていなくても、実際の通信を開始するまでに充分な時間が有れば問題ありません。
	 *   また、通信開始時に待ち時間が足りていなくても、最初の方の通信が失敗するだけで、いずれ回復するはずです。
	 */
	delay(SEC(0.5));
}

void
tcpip_stop()
{
	TCPIPCONFIG config;

	if(!tcpip_garp_timer) {
		return; /* 起動していなければ、何もしません */
	}

	/* TCP/IP設定を取得します。 */
	tcpip_get_config(&config);

	/* Gratuitous ARPを停止します。(対症療法) */
	timer_stop(tcpip_garp_timer);
	tcpip_garp_timer = 0; /* 忘れないで!! */

	/* TCPドライバを停止します。 */
	tcp_stop();

	/* UDPドライバを停止します。 */
	udp_stop();

	/* IPドライバを停止します。 */
	ip_stop();

	/* Ethernetドライバを停止します。 */
	ethernet_stop();

	/* TCP/IP設定をセーブします。 */
	tcpip_save_config(&config);
}

void
tcpip_config()
{
	TCPIPCONFIG config;

	if(!tcpip_garp_timer) {
		DIE(); /* 起動中でなければ、設定できません */
	}

	/* 全てのボタンが離されるまで、待機します。 */
	while(joy & PAD_MASK) {
		schedule();
	}

	/* TCP/IP設定を取得します。 */
	tcpip_get_config(&config);

	/* TCP/IP設定を行い、OKならば、反映します。 */
	if(tcpip_do_config(&config, 1/*キャンセル可能*/) == 0) {
		tcpip_set_config(&config);
	}

	/* 全てのボタンが離されるまで、待機します。 */
	while(joy & PAD_MASK) {
		schedule();
	}
}

/*--------------------------------------------------------------------------*/

/* 割り込み禁止の理由は、pceFileWriteSct()と、割り込み中のmalloc()/free()が衝突するのを防ぐためです。 */

static int
tcpip_load_config(TCPIPCONFIG* config)
{
	int result = -1;
	//
	FILEACC fa;

ENTER_CS;

	/* 設定ファイルをオープン、読み込み、クローズします。 */
	if(pceFileOpen(&fa, TCPIP_CONFIG_FNAME, FOMD_RD) == 0) {
		if(fa.fsize == sizeof(TCPIPCONFIG)) {
			if(pceFileReadSct(&fa, config, 0, sizeof(TCPIPCONFIG)) == sizeof(TCPIPCONFIG)) {
				result = 0; /* 成功 */
			}
		}
		pceFileClose(&fa);
	}

LEAVE_CS;

	return result;
}

static int
tcpip_save_config(const TCPIPCONFIG* config)
{
	int result = -1;
	//
	FILEACC fa;
	int old_turbo;
	TCPIPCONFIG old_config;

ENTER_CS;

	/* pceFileCreate()、pceFileWriteSct()のために、確実に24MHzに戻します。 */
	old_turbo = turbo(0);

	/* 旧い設定ファイルを読み込みます。 */
	if(tcpip_load_config(&old_config) == 0) {

		/* 新旧の設定ファイルが同じならば、何もせずに処理を返します。 */
		if(memcmp(config, &old_config, sizeof(TCPIPCONFIG)) == 0) {
			result = 0; /* 成功 */
			goto L_EXIT;
		}
	}

	/* 新しい設定ファイルを(再)作成、オープン、書き込み、クローズします。 */
	if(pceFileCreate(TCPIP_CONFIG_FNAME, sizeof(TCPIPCONFIG)) == 0) {
		if(pceFileOpen(&fa, TCPIP_CONFIG_FNAME, FOMD_WR) == 0) {
			if(pceFileWriteSct(&fa, config, 0, sizeof(TCPIPCONFIG)) == sizeof(TCPIPCONFIG)) {
				result = 0; /* 成功 */
			}
			pceFileClose(&fa);
		}
	}

L_EXIT:	/*------------------------------------------------------------------*/

	/* 必要に応じて、48MHzを復元します。 */
	turbo(old_turbo);

LEAVE_CS;

	return result;
}

/*--------------------------------------------------------------------------*/

static void
tcpip_get_config(TCPIPCONFIG* config)
{
	if(!tcpip_garp_timer) {
		DIE(); /* TCP/IP停止中は、設定を取得できません */
	}

	/* IP設定を取得します。 */
	ip_get_config(&config->ip_config);

	/* DNS設定を取得します。 */
	config->dns_server = dns_get_server();
}

static void
tcpip_set_config(const TCPIPCONFIG* config)
{
	if(!tcpip_garp_timer) {
		DIE(); /* TCP/IP停止中は、設定を反映できません */
	}

	/* IP設定を反映します。 */
	ip_set_config(&config->ip_config);

	/* DNS設定を反映します。 */
	dns_set_server(config->dns_server);
}

/*--------------------------------------------------------------------------*/

static int
tcpip_do_config(TCPIPCONFIG* config, int cancelable)
{
	int T0  = pceTimerGetCount();
	int col = 11;
	int row =  0;
	int ok  =  1;
	//
	int a;
	int b;
	int c;
	int d;
	int i;
	int j;
	int x;
	int y;
	int fore;
	int back;
	int ip_address_changed;
	unsigned char digit[4][12];

	/* 設定値を展開します。 */
	for(i = 0; i < 4; i++) {
		switch(i) {
		case 0: a = config->ip_config.ip_address;      break;
		case 1: a = config->ip_config.subnet_mask;     break;
		case 2: a = config->ip_config.default_gateway; break;
		case 3: a = config->dns_server;                break;
		default: DIE();
		}
		for(j = 0; j < 12; j += 3) {
			b = (a >> 24) & 255;
			digit[i][j + 0] = (b / 100) % 10;
			digit[i][j + 1] = (b /  10) % 10;
			digit[i][j + 2] = (b /   1) % 10;
			a <<= 8;
		}
	}

	for(;;) {
		schedule();

		/* ボタンが押されている間、カーソル点滅を停止します。 */
		if(joy & PAD_MASK) {
			T0 = pceTimerGetCount();
		}

		/* SELECTボタンが押されたら、即座にキャンセルします。 */
		if(cancelable) {
			if(joy & TRG_SELECT) {
				ok = 0;
				break;
			}
		}

		/* 方向ボタンが押されたら、項目を選択します。 */
		if(joy & TRG_LF) {
			if(row == 4) {
				if(cancelable) {
					ok = !ok;
				}
			} else {
				if(col == 0) {
					col = 11;
				} else {
					col--;
				}
			}
		}
		if(joy & TRG_RI) {
			if(row == 4) {
				if(cancelable) {
					ok = !ok;
				}
			} else {
				if(col == 11) {
					col = 0;
				} else {
					col++;
				}
			}
		}
		if(joy & TRG_UP) {
			if(row == 0) {
				row = 4;
			} else {
				row--;
			}
		}
		if(joy & TRG_DN) {
			if(row == 4) {
				row = 0;
			} else {
				row++;
			}
		}

		ip_address_changed = 0;

		/* Aボタンが押されたら... */
		if(joy & TRG_A) {

			/* OK、または、キャンセルが選択されていたら、抜けます。 */
			if(row == 4) {
				break;

			/* 数値項目が選択されていたら... */
			} else {

				/* 選択されている桁を増やします。 */
				if(digit[row][col] == 9) {
					digit[row][col] = 0;
				} else {
					digit[row][col]++;
				}

				/* 255を超えたら、回り込みます。 */
				switch(col % 3) {
				case 0: /* 100の位 */
					if(digit[row][col + 0] * 100 +
					   digit[row][col + 1] *  10 +
					   digit[row][col + 2] *   1 > 255) {
						digit[row][col] = 0;
					}
					break;
				case 1: /*  10の位 */
					if(digit[row][col - 1] * 100 +
					   digit[row][col + 0] *  10 +
					   digit[row][col + 1] *   1 > 255) {
						digit[row][col] = 0;
					}
					break;
				case 2: /*   1の位 */
					if(digit[row][col - 2] * 100 +
					   digit[row][col - 1] *  10 +
					   digit[row][col + 0] *   1 > 255) {
						digit[row][col] = 0;
					}
					break;
				default:
					DIE();
				}

				/* IPアドレスが変更されたことをマークします。 */
				if(row == 0) {
					ip_address_changed = 1;
				}
			}
		}

		/* Bボタンが押されたら... */
		if(joy & TRG_B) {

			/* OK、または、キャンセルが選択されていたら、抜けます。 */
			if(row == 4) {
				break;

			/* 数値項目が選択されていたら... */
			} else {

				/* 選択されている桁を減らします。 */
				if(digit[row][col] == 0) {
					digit[row][col] = 9;
				} else {
					digit[row][col]--;
				}

				/* 255を超えたら、回り込みます。 */
				switch(col % 3) {
				case 0: /* 100の位 */
					if(digit[row][col + 0] * 100 +
					   digit[row][col + 1] *  10 +
					   digit[row][col + 2] *   1 > 255) {
						digit[row][col] = 2;
					}
					break;
				case 1: /*  10の位 */
					if(digit[row][col - 1] * 100 +
					   digit[row][col + 0] *  10 +
					   digit[row][col + 1] *   1 > 255) {
						digit[row][col] = 5;
					}
					break;
				case 2: /*   1の位 */
					if(digit[row][col - 2] * 100 +
					   digit[row][col - 1] *  10 +
					   digit[row][col + 0] *   1 > 255) {
						digit[row][col] = 5;
					}
					break;
				default:
					DIE();
				}

				/* IPアドレスが変更されたことをマークします。 */
				if(row == 0) {
					ip_address_changed = 1;
				}
			}
		}

		/* 画面をクリアします。 */
		surface_clear(&surface, 0);

		/* タイトルを表示します。 */
		render_rectangle_fill(&render, 0, 0, 128, 10, 3);
		render_printf(&render, 0, 0, 0x80, 0, "■TCP/IP設定");

		/* 数値項目を表示します。 */
		y = 11;
		for(i = 0; i < 4; i++) {
			x = 5;

			switch(i) {
			case 0: render_printf(&render, x, y, 0x80, 3, "IPｱﾄﾞﾚｽ  "); break;
			case 1: render_printf(&render, x, y, 0x80, 3, "ｻﾌﾞﾈｯﾄﾏｽｸ"); break;
			case 2: render_printf(&render, x, y, 0x80, 3, "ｹﾞｰﾄｳｪｲ  "); break;
			case 3: render_printf(&render, x, y, 0x80, 3, "DNSｻｰﾊﾞ  "); break;
			default: DIE();
			}

			x += 5 * 9;

			render_line(&render, x + 2, y + 2, x + 4, y + 2, 3);
			render_line(&render, x + 2, y + 6, x + 4, y + 6, 3);

			x += 5;

			for(j = 0; j < 12; j++) {
				if((i == row) && (j == col) && ((pceTimerGetCount() - T0) % 500 < 300)) {
					fore =  0;
					back =  2;
				} else {
					fore =  3;
					back = -1;
				}
				if(back >= 0) {
					render_rectangle_fill(&render, x, y, 6, 9, back);
				}

				switch(j % 3) {
				case 0: /* 100の位 */
					if(!digit[i][j]) {
						fore = 1;
					}
					break;
				case 1: /*  10の位 */
					if(!digit[i][j] && !digit[i][j - 1]) {
						fore = 1;
					}
					break;
				}
				render_printf(&render, x, y, 0x80, fore, "%d", digit[i][j]);

				x += 5;

				if((j == 2) || (j == 5) || (j == 8)) {
					render_point(&render, x + 1, y + 7, 3);
					x += 2;
				}
			}

			y += 10;
		}

		/* IPアドレスが変更されたら... */
		if(ip_address_changed) {

			/* 変更後のIPアドレスを取得します。 */
			a = 0;
			for(i = 0; i < 12; i += 3) {
				x = digit[0][i + 0] * 100
				  + digit[0][i + 1] *  10
				  + digit[0][i + 2] *   1;
				a = a << 8 | x;
			}

			/* 変更前のサブネットマスクを取得します。 */
			b = 0;
			for(i = 0; i < 12; i += 3) {
				x = digit[1][i + 0] * 100
				  + digit[1][i + 1] *  10
				  + digit[1][i + 2] *   1;
				b = b << 8 | x;
			}

			/* 変更前のデフォルトゲートウェイを取得します。 */
			c = 0;
			for(i = 0; i < 12; i += 3) {
				x = digit[2][i + 0] * 100
				  + digit[2][i + 1] *  10
				  + digit[2][i + 2] *   1;
				c = c << 8 | x;
			}

			/* 変更後のサブネットマスクを作成します。 */
			d = ip_address_class_mask(a);

			/* サブネットマスクが変化したら、自動設定します。 */
			if(b != d) {
				x = d;
				for(i = 0; i < 12; i += 3) {
					y = (x >> 24) & 255;
					digit[1][i + 0] = (y / 100) % 10;
					digit[1][i + 1] = (y /  10) % 10;
					digit[1][i + 2] = (y /   1) % 10;
					x <<= 8;
				}
			}

			/* 変更後のIPアドレスのネットワークと、変更前のデフォルトゲートウェイのネットワークが違っていたら、
			 * デフォルトゲートウェイのネットワーク部分を、変更後のIPアドレスのネットワーク部分に合わせます。
			 */
			if((a & d) != (c & d)) {
				x = (a & d) | (c & ~d);
				for(i = 0; i < 12; i += 3) {
					y = (x >> 24) & 255;
					digit[2][i + 0] = (y / 100) % 10;
					digit[2][i + 1] = (y /  10) % 10;
					digit[2][i + 2] = (y /   1) % 10;
					x <<= 8;
				}
			}
		}

		/* OK、キャンセルを表示します。 */
		y =  77;
		x = 100;

		if(cancelable) {
			if((row == 4) && !ok && ((pceTimerGetCount() - T0) % 500 < 300)) {
				fore =  0;
				back =  2;
			} else {
				fore =  3;
				back = -1;
			}
			if(back >= 0) {
				render_rectangle_fill(&render, x, y, 26, 9, back);
			}
			render_rectangle(&render, x - 1, y - 1, 28, 11, 1);
			render_printf(&render, x, y, 0x80, fore, "ｷｬﾝｾﾙ");

			x -= 14;
		} else {
			x += 15;
		}

		if((row == 4) && ok && ((pceTimerGetCount() - T0) % 500 < 300)) {
			fore =  0;
			back =  2;
		} else {
			fore =  3;
			back = -1;
		}
		if(back >= 0) {
			render_rectangle_fill(&render, x, y, 11, 9, back);
		}
		render_rectangle(&render, x - 1, y - 1, 13, 11, 1);
		render_printf(&render, x, y, 0x80, fore, "OK");

	}

	/* 設定値を格納します。 */
	if(ok) {
		for(i = 0; i < 4; i++) {
			a = 0;
			for(j = 0; j < 12; j += 3) {
				b = digit[i][j + 0] * 100
				  + digit[i][j + 1] *  10
				  + digit[i][j + 2] *   1;
				a = a << 8 | b;
			}
			switch(i) {
			case 0: config->ip_config.ip_address      = a; break;
			case 1: config->ip_config.subnet_mask     = a; break;
			case 2: config->ip_config.default_gateway = a; break;
			case 3: config->dns_server                = a; break;
			default: DIE();
			}
		}
	}

	return ok ? 0/*OK*/ : -1/*キャンセル*/;
}

/****************************************************************************
 *	
 ****************************************************************************/

#endif /*USE_TCPIP*/
