/*	
 *	dstderr.c
 *
 *	dstderr - 標準エラー出力モニタ
 *	Copyright (C) 2002-2014 Naoyuki Sawa
 *
 *	* Sat Nov 30 14:06:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 *	* Sat Nov 30 17:27:00 JST 2002 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Dec 22 22:32:00 JST 2002 Naoyuki Sawa
 *	- 転送サイズ不正エラー時に、サイズも表示するよう変更。
 *	- ucs.txstatの検査時に、USBCOM_STAT_TXWAITのマスクをとるよう修正。
 *	  これをやらないと、間違ってTXING等のフラグも転送要求と見なしてしまい、転送フェーズがずれる。
 *	* Sun Mar 09 14:07:14 JST 2014 Naoyuki Sawa
 *	- clip/clipio.c の機能拡張に対応し、P/ECE標準入力への文字送信機能を追加しました。
 *	  当ツールは、これまではP/ECEからの標準エラー出力を表示するのみでしたが、今後は、P/ECE標準入力へ文字を送信できます。
 *	  プログラムの開発時に、PCからP/ECEへ、パラメータやテスト文字列を送信できるようになりました。
 *	* Mon Mar 10 17:10:30 JST 2014 Naoyuki Sawa
 *	- usage()が間違っていたので、修正しました。
 *	* Tue Mar 11 02:46:30 JST 2014 Naoyuki Sawa
 *	- ブロック無しのキーボード読み込みのために、これまでは_kbhit()と_getch()を使っていましたが、
 *	  GetNumberOfConsoleInputEvents()とReadConsoleInput()を使うように変更しました。
 *	  _kbhit()と_getch()では、シフトキー等を押した後に、入力が滞る不安定な挙動が発生したためです。(理由不明)
 *	  ちなみに、WaitForSingleObject()を使った場合も、同様に入力が滞る不安定な挙動が発生しました。(理由不明)
 *	  GetNumberOfConsoleInputEvents()とReadConsoleInput()を使えば、安定して動作するようです。
 */
#define STRICT
#include <windows.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <conio.h>

#include <pieceif.h>
#pragma comment(lib,"pieceif.lib")

#define VERSION		"20140311"

/****************************************************************************
 *	変数
 ****************************************************************************/

char* log_fname = NULL;	/* ログファイル名(NULL:なし) */
FILE* log_fp;		/* ログファイル(NULL:なし) */

char devno = 0;		/* P/ECEデバイス番号 */
int retry_interval = 0;	/* 再接続インターバル */
int terminate = 0;	/* Ctrl+Cによる終了要求 */

USBCOMS ucs;		/* USBCOM状態取得用 */
char rxbuf[4096];	/* 標準エラー出力からの受信バッファ */
			/* Windows用P/ECEドライバのバルクPIPEバッファサイズが4KBなので、 */
			/* 4KB以上の送受信は正しく動作しないようです。 */
char txbuf[4096];	/* 標準入力への送信バッファ */
			/* clip/clipio.cのstdin_read()のSTDIN_RXSIZE以上のサイズとすること。 */
			/* STDIN_RXSIZEより小さくても構わないが、一行で送れる文字数限界が少なくなる。 */
			/* STDIN_RXSIZEより大きくても構わないが、STDIN_RXSIZEを超える分は無駄である。 */
			/* 2014/3/9現在、STDIN_RXSIZE=128であるが、かなり余裕を持って4096と定義した。 */

/****************************************************************************
 *	usage()
 ****************************************************************************/

void
usage()
{
	fprintf(stderr, "dstderr - 標準エラー出力モニタ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2002-2014 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE: dstderr [options] [devno]\n");
	fprintf(stderr, "    devno     P/ECEデバイス番号\n");
	fprintf(stderr, "              省略時は、#0(一台目のP/ECE)に接続します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
	fprintf(stderr, "    -l<fname> ログファイルを作成します。\n");
	fprintf(stderr, "    -w<sec>   再接続インターバル[秒]\n");
	fprintf(stderr, "              省略するか0を指定すると、再接続しません。\n");
	exit(1);
}

/****************************************************************************
 *	disconnect
 ****************************************************************************/

void
disconnect()
{
	ismUCClose();	/* USBCOM切断(P/ECE独自のプロトコルレイヤでの切断) */
	ismExit();	/* バルクPIPE切断(OSレベルのUSB通信レイヤでの切断) */
}

/****************************************************************************
 *	sigint
 ****************************************************************************/

void
sigint(int sig)
{
	/* * ここではCtrl+Cが押されてことをマークしておき、メインループで判断します。
	 * * シグナルハンドラは非同期に呼ばれるので、この中で切断してはいけません。
	 *   P/ECEドライバがハングアップし、P/ECEをリセットするまで治らなくなります。
	 */
	retry_interval = 0;	/* もう再接続しない←※これ忘れると終わらないよ！*/
	terminate = 1;		/* 終了要求をマーク */
}

/****************************************************************************
 *	main
 ****************************************************************************/

int
main(int argc, char* argv[])
{
	int retval, i;
	char* p;
	char path[_MAX_PATH], drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT];

	HANDLE hStdIn;
	INPUT_RECORD InputRecord;
	int NumberOfEvents;

	hStdIn = GetStdHandle(STD_INPUT_HANDLE);
	if(hStdIn == INVALID_HANDLE_VALUE) { abort(); }

	/*-------------------- 初期化 --------------------*/

	/* Ctrl+Cをフックします。 */
	signal(SIGINT, sigint);

	/* コマンドライン解析。 */
	for(i = 1; i < argc; i++) {
		p = argv[i];
		if(p[0] == '-') {
			/* オプション指定 */
			switch(p[1]) {
			case 'l':
				if(log_fname != NULL) usage(); /* 二重指定 */
				log_fname = &p[2];
				if(strlen(log_fname) < 1) usage(); /* ファイル名指定なし */
				break;
			case 'w':
				retry_interval = (char)strtol(&p[2], &p, 10);
				if(*p != '\0') usage(); /* 数字以外の文字で終端した */
				break;
			default:
				usage();
			}
		} else {
			/* デバイス番号指定 */
			devno = (char)strtol(p, &p, 10);
			if(*p != '\0') usage(); /* 数字以外の文字で終端した */
		}
	}

	/* ログファイル名が指定されていたら、ログファイルを作成します。 */
	if(log_fname != NULL) {
		/* 拡張子が省略されていたら「.log」を付加。 */
		_splitpath(log_fname, drive, dir, fname, ext);
		if(strlen(ext) == 0) strcpy(ext, ".log");
		_makepath(path, drive, dir, fname, ext);

		/* ログファイルを作成します。 */
		log_fp = fopen(path, "wt");
		if(log_fp == NULL) {
			fprintf(stderr, "### ログファイル %s が作成できません。\n", path);
			exit(1);
		}
	}

	/*-------------------- メインループ --------------------*/
REDO:
	/* P/ECEに接続します。 */
	retval = ismInitEx(devno, PIECE_DEF_WAITN);
	if(retval != 0) {
		fprintf(stderr, "### P/ECEに接続できません。\n");
		goto NEXT;
	}

	/* USBCOMに接続します。 */
	retval = ismUCOpen(&ucs);
	if(retval != 0) {
		fprintf(stderr, "### USBCOMに接続できません。\n");
		goto NEXT;
	}
	if(!ucs.mystat || strcmp(ucs.signature, "stderr") != 0) {
		fprintf(stderr, "### 標準エラー出力に接続できません。\n");
		goto NEXT;
	}

	/* Ctrl+Cが押されるまで... */
	while(!terminate) {
		/* USBCOMの状態を取得します。 */
		retval = ismUCGetStat(&ucs); /* ismUCGetStat()はシグネチャ部分は読み込みません!!要注意 */
		if(retval != 0) {
			fprintf(stderr, "### USBCOMの状態取得に失敗しました。\n");
			goto NEXT;
		}

		/* P/ECE側は、単にpceUSBCOMStop()を呼び出しで切断する*のではなく*、
		 * 空文字シグネチャでpceUSBCOMSetup()を行い、すぐにpceUSBCOMStop()を呼んで切断してください。
		 * なぜかというと、PCとの接続が確立していると、pceUSBCOMStop()は切断処理を行わないからです。
		 * そのため、pceUSBCOMSetup()を使ってPCとの接続フラグおよびシグネチャをクリアしておき、
		 * あらためてpceUSBCOMStop()を呼び出して切断を行うのです。(BIOS 1.18のふるまいに依存)
		 */
		//if(!ucs.mystat || strcmp(ucs.signature, "stderr") != 0) {
		if(!ucs.mystat/* || strcmp(ucs.signature, "stderr") != 0*/) { /* ismUCGetStat()はシグネチャ部分を読まないので、検査しても無駄でした */
			fprintf(stderr, "### 標準エラー出力が切断されました。\n");
			goto NEXT;
		}

		/* P/ECEからの送信要求があれば... */
		if(ucs.txstat & USBCOM_STAT_TXWAIT/*TXING等のビットを無視するために、このマスク必須！*/) {
			if(ucs.txlen < 1 || sizeof rxbuf < ucs.txlen) {
				fprintf(stderr, "### 送信要求サイズが不正です。(%d)\n", ucs.txlen);
				goto NEXT;
			}

			/* P/ECEからの送信データを受信します。 */
			retval = ismUCRead(rxbuf, ucs.txlen);
			if(retval != 0) {
				fprintf(stderr, "### データ受信エラーです。\n");
				goto NEXT;
			}

			/* PC側の標準出力に出力します。 */
			fwrite(rxbuf, 1, ucs.txlen, stdout);

			/* ログファイルを開いていたら、ログファイルにも出力します。 */
			if(log_fp != NULL) fwrite(rxbuf, 1, ucs.txlen, log_fp);
		}

		/* キーボードが押されたら… */
		while(GetNumberOfConsoleInputEvents(hStdIn, &NumberOfEvents) && NumberOfEvents) {		/* 入力イベントが残ている間… */
			if(ReadConsoleInput(hStdIn, &InputRecord, 1, &NumberOfEvents) && NumberOfEvents) {	/* 入力イベントが読み出せて… */
				if(InputRecord.EventType == KEY_EVENT) {					/* それがキーイベントで… */
					if(InputRecord.Event.KeyEvent.bKeyDown) {				/* それがキー押下ならば… */
						int c = InputRecord.Event.KeyEvent.uChar.AsciiChar;
						/* リターンキーならば… */
						if(c == '\r') { /* リターンキーは'\n'でなく'\r'を返す。 */
							/* 送信バッファの文字が有れば… */
							if(txbuf[0]) {
								/* P/ECEからの受信要求があれば... */
								if(ucs.rxstat & USBCOM_STAT_RXWAIT/*RXING等のビットを無視するために、このマスク必須！*/) {
									if(ucs.rxlen < 1 || sizeof rxbuf < ucs.rxlen) {
										fprintf(stderr, "### 受信要求サイズが不正です。(%d)\n", ucs.rxlen);
										goto NEXT;
									}
									/* 文字列をP/ECEへ送信します。 */
									retval = ismUCWrite(txbuf, ucs.rxlen);
									if(retval != 0) {
										fprintf(stderr, "### データ送信エラーです。\n");
										goto NEXT;
									}
									/* 送信バッファをクリアする。 */
									memset(txbuf, 0, sizeof txbuf);
									/* コンソールの表示を改行する。 */
									_cputs("\r\n");
									/* ucs.rxstatは変化したはずなので、現在のucsに基づくサイクルはここまで。 */
									break;
								}
							}
						/* エスケープキーならば… */
						} else if(c == '\x1B') {
							sigint(SIGINT);	/* エスケープキーも、Ctrl+Cと同様に終了することにした */
							goto NEXT;
						/* バックスペースキーならば… */
						} else if(c == '\b') {
							/* 送信バッファの文字が有れば… */
							i = strlen(txbuf);
							if(i > 0) {
								/* 一文字削除する。 */
								txbuf[i - 1] = '\0';
								/* コンソールを一文字削除する。 */
								_cputs("\b \b"); /* _putch('\b')だけでは文字が消えないようだ。'\b'で位置を戻し、' 'で上書きし、再度'\b'で位置を戻す必要が有る。 */
							}
						/* 表示可能文字ならば… */
						} else if(isprint(c)) {
							/* 送信バッファに空きが有れば… */
							i = strlen(txbuf);
							if(i < sizeof txbuf - 2) {
								/* 一文字追加する。 */
								txbuf[i] = c;
								/* コンソールに表示する。 */
								_putch(c);
							}
						}
					}
				}
			}
		}
	}
NEXT:
	/* 確実に切断します。 */
	disconnect();

	/* 再接続インターバルが設定されていたら、少し待ってリトライします。 */
	if(retry_interval > 0) {
		fprintf(stderr, "### %d秒後に再接続します。\n", retry_interval);
		/* Sleep(retry_interval * 1000)で待つと、Ctrl+Cが割り込めません。 */
		i = GetTickCount();
		while((int)(GetTickCount() - i) < retry_interval * 1000) {
			Sleep(100); /* 100ミリ秒毎にCtrl+Cの割り込み機会を与えます */
			if(terminate) goto EXIT; /* 既に切断されているのでそのまま終了 */
		}
		goto REDO;
	}
EXIT:
	/* ログファイルを開いていたら、閉じます。 */
	if(log_fp != NULL) fclose(log_fp);

	return 0;
}

