/* 名前：yavac
 * 概要：java プリプロセッサ
 *
 * 動作環境：Windows XP SP2
 * 詳細：
 *
 * 履歴：
 *	2013.04.18 Ver 1.01 jdk7u21対応
 *	2008.09.25 Ver 1.00
 *	2008.03.08 Ver 0.01
 */

/**
ant の javac タスクに追加
	fork="yes" executable="yavac.exe"
**/

#ifdef _DEBUG
#define DEBUG(x)	x
#else
#define DEBUG(x)
#endif

#if defined(__MINGW32__)
// c++ -s -o yavac.exe yavac.cpp
#define WINDOWS
#elif defined(__BORLANDC__) && __BORLANDC__ >= 0x0551
// bcc32 yavac.cpp
#define WINDOWS
#elif defined(_MSC_VER) && _MSC_VER >= 1400
// cl yavac.cpp
#define _CRT_SECURE_NO_DEPRECATE
	//#pragma warning(disable: 4996)
#define WINDOWS
#else
#error 不明なコンパイラです。
#endif

#ifdef _UNICODE
#error Unicode ではコンパイルできません。
#error コンパイルオプション「文字セット」を「マルチバイト」に変更してください。
#endif

#if defined(WINDOWS)
#include <windows.h>
#endif

#include <stdio.h>

char** GetCommandArgV(void);
int die(const char *format, ...);

void replace(const char *pattern, const char *str, char *buf);
char* newString(const char* t);

#define CONFIGFILE	"yavac.ini"
#define STDERRLOG	"__stderr__.log"
#define DEBUGLOG	"__yavac__.log"

#define MYBUFSIZE 16384

// メイン
int main(/*int argc, char *argv[]*/)
{
	/*
	 * コマンドラインパラメータの処理
	 */
	char **argv = GetCommandArgV();

	char *pszCommandLine = argv[1];

	if (pszCommandLine == NULL || *pszCommandLine == '\0')
		die("javac commandline not found");

	char *pszPreprocess = NULL;	// 「なんとかかんとか.pl」
	char *pszJavac = NULL;	//「C:\Program Files\Java\jdk1.6.0_04\bin\javac.exe」
	char *pszBuild = NULL;	//「build\source\」 -- コンパイル用ダミー
	char *pszSource = NULL;	//「java\」 -- 本物ソース

	FILE *fp;
	char *buf = new char[MYBUFSIZE + 1];	// 使い捨て何でもバッファ

	/*
	 * 設定ファイル yavac.ini を読み込む
	 */
	if ((fp = fopen(CONFIGFILE, "r")) == NULL)
		die(CONFIGFILE": Can't open");

	int lineNumber = 0;
	while (fgets(buf, MYBUFSIZE, fp) != NULL) {
		++lineNumber;
		*(strrchr(buf, '\n')) = '\0';	//行末の改行を消す
		if (*buf == '#') {
			continue;
		}
		else if (strncmp(buf, "preprocess=", 11) == 0) {
			char *p = buf+11;
			if (pszPreprocess != NULL) die(CONFIGFILE":%d: Error", lineNumber);
			pszPreprocess = newString(p);
		}
		else if (strncmp(buf, "javac=", 6) == 0) {
			char *p = buf+6;
			if (pszJavac != NULL) die(CONFIGFILE":%d: Error", lineNumber);
			pszJavac = newString(p);
		}
		else if (strncmp(buf, "source=", 7) == 0) {
			char *p = buf+7;
			if (pszSource != NULL) die(CONFIGFILE":%d: Error", lineNumber);
			pszSource = newString(p);
		}
		else if (strncmp(buf, "build=", 6) == 0) {
			char *p = buf+6;
			if (pszBuild != NULL) die(CONFIGFILE":%d: Error", lineNumber);
			pszBuild = newString(p);
		}
		else {
			die(CONFIGFILE":%d: Error", lineNumber);
		}
	}
	fclose(fp);

	// エラーチェック
	buf[0] = '\0';
	if (pszJavac      == NULL)	pszJavac = "javac";
	if (pszPreprocess == NULL)	strcat(buf, CONFIGFILE": preprocess=NULL\n");
	if (pszBuild      == NULL)	strcat(buf, CONFIGFILE": build=NULL\n");
	if (pszSource     == NULL)	strcat(buf, CONFIGFILE": source=NULL\n");
	if (buf[0] != '\0') die(buf);

	int iExitStatus;

	/*
	 * プリプロセス実行
	 */
#if defined(WINDOWS)
	sprintf(buf, "%s||EXIT 1", pszPreprocess);
	iExitStatus = system(buf);
#else
	iExitStatus = system(pszPreprocess) != 0;
#endif
	if (iExitStatus != 0)
		die("Preprocess error.");

	delete []pszPreprocess;

	/*
	 * コマンドラインを書き換える
	 */
	if (strstr(pszCommandLine, ".java") != NULL) {
		strcpy(buf, pszCommandLine);
		replace(pszSource, pszBuild, buf);
		pszCommandLine = newString(buf);
	}

	/*
	 * @コンパイルリストファイル の中身を書き換える
	 */
	char *p1, *p2;
	if ((p1 = strstr(pszCommandLine, " @")) != NULL) {
		p1 += 2;
		p2 = p1;
		while (*p2 != '\0' && *p2 != ' ') ++p2;
		int len = p2 - p1;

		char *filename1 = new char[len+1];
		char *filename2 = new char[len+1];

		strncpy(filename1, p1, len); filename1[len] = '\0';
		strncpy(filename2, p1, len); filename2[len] = '\0';
		filename2[len-1] = '_';

		FILE *fp1, *fp2;

		if ((fp1 = fopen(filename1, "r")) == NULL)
			die("%s: Can't open", filename1);

		if ((fp2 = fopen(filename2, "w")) == NULL)
			die("%s: Can't open", filename2);

		while (fgets(buf, MYBUFSIZE, fp1) != NULL) {
			replace(pszSource, pszBuild, buf);
			fputs(buf, fp2);
		}

		fclose(fp1);
		fclose(fp2);

		if (unlink(filename1) != 0)
			die("%s: Can't unlink", filename1);
		if (rename(filename2, filename1) != 0)
			die("%s: Cant rename", filename2);

		delete []filename1;
		delete []filename2;
	}

	/*
	 * system("javac .....") でコンパイルする
	 *
	 * ※ コンパイラの <stderr> は一時ファイルへリダイレクトする
	 */
#if defined(WINDOWS)
	sprintf(buf, "%s %s 2>"STDERRLOG"||EXIT 1"
				, pszJavac
				, pszCommandLine);
	iExitStatus = system(buf);
#else
	sprintf(buf, "%s %s 2>"STDERRLOG
				, pszJavac
				, pszCommandLine);
	iExitStatus = system(buf) != 0;
#endif
#ifdef DEBUGLOG
	fp = fopen(DEBUGLOG, "w");
	fputs(buf, fp);
	fclose(fp);
#endif
	delete []pszJavac;

	/*
	 * コンパイラが一時ファイルに吐いた <stderr> を画面に表示する
	 */
	if ((fp = fopen(STDERRLOG, "r")) == NULL)
		die(STDERRLOG": Can't open ");
	while (fgets(buf, MYBUFSIZE, fp) != NULL) {
		replace(pszBuild, pszSource, buf);
		fputs(buf, stderr);
	}
	fclose(fp);

	DEBUG(printf("ExitStatus=%ld\n", iExitStatus);)

//	/*
//	 * 一時ファイルの後始末
//	 */
//	unlink(STDERRLOG);

	exit(iExitStatus);
}

/*
 * buf 中の pattern を str に置き換える。
 * 正規表現には対応していない。
 * buf はじゅうぶんな大きさを確保しておくこと。
 */
void replace(const char *pattern, const char *str, char *buf)
{
	char *pos = buf;
	int n1 = strlen(pattern);
	int n2 = strlen(str);
	while ((pos = strstr(pos, pattern)) != NULL) {
		if (n1 != n2) { memmove(pos+n2, pos+n1, strlen(pos+n1)+1); }
		memmove(pos, str, n2);
	}
}
//|void replace(const char *pattern, const char *str, char *buf)
//|{
//|	char *pos;
//|	if ((pos = strstr(buf, pattern)) != NULL) {
//|		int n1 = strlen(pattern);
//|		int n2 = strlen(str);
//|		if (n1 != n2) { memmove(pos+n2, pos+n1, strlen(pos+n1)+1); }
//|		memmove(pos, str, n2);
//|	}
//|}

/*
 *
 */
char* newString(const char* t)
{
	return strcpy(new char [strlen(t)+1], t);
}

/*
 * コマンドライン文字列を得る
 */
char* nextCmd(char*cmd);

char** GetCommandArgV()
{
	static char *argv[3];

	char *cmd = newString(GetCommandLine());	// 生のコマンド文字列
	argv[0] = cmd; cmd = nextCmd(cmd);	// 最初 - このコマンド自身
	argv[1] = *cmd ? cmd : NULL;		// ２番目以後は何もせずに
	argv[2] = NULL;						// 終端
	return argv;
}

char* nextCmd(char*cmd)
{
	for (;;) {
		if (*cmd == '\0') return cmd;
		if (*cmd == '^') {
			strcpy(cmd, cmd + 1);
			++cmd;
			continue;
		}
		if (*cmd == '"') {
			strcpy(cmd, cmd + 1);
			for (;; ++cmd) {
				if (*cmd == '\0') return cmd;
				else if (*cmd == '"') {
					strcpy(cmd, cmd + 1);
					break;
				}
			}
			continue;
		}
		if (*cmd == ' ') {
			*cmd++ = '\0';
			while (*cmd == ' ') cmd++;
			return cmd;
		}
		++cmd;
	}
}

int die(const char *format, ...)
{
	char  buf[1024+1];
	va_list  args;
	va_start(args, format);
#ifdef _MSC_VER
	vsprintf_s(buf, sizeof(buf), format, args);	// Microsoft Visual C
#else
	wvsprintf(buf, format, args);	// WIN32 API
//	vsprintf(buf, format, args);	// C Runtime Function
#endif
	va_end(args);

	fputs(buf, stderr);
	exit(EXIT_FAILURE);
	return EXIT_FAILURE;
}
