/* arguments.cpp
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of Mysaifu JVM

Mysaifu JVM is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

Mysaifu JVM is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
*/

#include "StdAfx.h"
#include "arguments.h"
#include "common_funcs.h"
#include "jni_funcs.h"

/**
 * レジストリキー名
 */
#define REG_ROOT						_T("Software\\Mysaifu\\Mysaifu JVM")
#define REG_DEFAULT_BOOT_CLASSPATH		_T("DefaultBootClassPath")
#define REG_DEFAULT_CLASSPATH			_T("DefaultClassPath")
#define REG_DEFAULT_MAX_HEAP_SIZE		_T("DefaultMaxHeapSize")
#define REG_DEFAULT_CURRENT_DIRECTORY	_T("DefaultCurrentDirectory")

/**
 * デフォルトのブートクラスパス
 * %s の部分にはインストールパスが入る
 */
#define DEFAULT_BOOT_CLASSPATH_FORMAT	_T("%s\\jre\\lib\\rt.jar")
static _TCHAR g_default_boot_classpath[MAX_PATH + 1];

/**
 * デフォルトの最大ヒープサイズ
 */
#define DEFAULT_MAX_HEAP_SIZE_VALUE	(2 * 1024 * 1024)
static unsigned int g_default_max_heap_size = DEFAULT_MAX_HEAP_SIZE_VALUE;

/**
 * デフォルトのシステムクラスパス
 */
#define DEFAULT_CLASSPATH_VALUE	_T("\\;\\My Documents")
static _TCHAR g_default_classpath[MAX_PATH + 1] = DEFAULT_CLASSPATH_VALUE;

/**
 * デフォルトのカレントディレクトリ
 */
#define DEFAULT_CURRENT_DIRECTORY_VALUE	_T("\\");
static _TCHAR g_default_current_directory[MAX_PATH + 1] = DEFAULT_CURRENT_DIRECTORY_VALUE;

/**
 * デフォルトのJavaスタックサイズ
 */
#define DEFAULT_JAVA_STACK_SIZE	(32*1024)

/**
 * デフォルトのネイティブスタックサイズ
 */
#define DEFAULT_NATIVE_STACK_SIZE	163840

/**
 * 指定されたパラメタをバッファに読み込む
 */
static int read_parameter(const _TCHAR* str, _TCHAR* buff, int bufflen);

/**
 * デフォルト値をレジストリから読み込む
 */
static void load_default_values_from_registory();

/**
 * argumentsを初期化する
 */
static void init_arguments(arguments* config);

/**
 * メモリ量を読み込む
 */
static int parse_memory_size(const _TCHAR* opt);

/**
 * 引数情報に関する内部状態を初期化する
 */
void init_arguments_settings() {
	// レジストリから初期値を読み込んでおく
	load_default_values_from_registory();
}

/**
 * 引数情報を作成する
 */
arguments* arguments_create() {
	arguments* args = (arguments*) calloc(1, sizeof(arguments));
	if (! args) {
		fatal_error(FATAL_ERROR_NO_MEMORY);
	}
	init_arguments(args);
	return args;
}

/**
 * 引数情報に割り当てられたメモリを削除する
 */
void arguments_delete(arguments* args) {
	// プロパティを開放
	free(args->properties);
	args->properties = NULL;
	args->properties_count = 0;

	// 引数を開放
	free(args->argv);
	args->argv = NULL;

	free(args);
}

/**
 * メモリ量を読み込む
 */
static int parse_memory_size(_TCHAR* opt) {
	int result = 0;

	// 文字列末尾には単位（KまたはM)を指定可能
	int len = _tcslen(opt);
	_TCHAR unit = opt[len - 1];
	if (unit == _T('0') || unit == _T('1') || unit == _T('2') || unit == _T('3') || unit == _T('4')
			|| unit == _T('5') || unit == _T('6') || unit == _T('7') || unit == _T('8')
			|| unit == _T('9')) {
		// 末尾が数値の場合は何も加工しない
	} else if (unit == _T('K') || unit == _T('M')) {
		// 末尾が 'K' または 'M' の場合は数値部分のみにする
		opt[len - 1] = _T('\0');
	} else {
		// 引数指定がおかしい
		result = -1;
	}

	if (! result) {
		// 数値を読み取る
		result = _ttoi(opt);
		if (unit == _T('k') || unit == _T('K')) {
			result *= 1024;
		} else if (unit == _T('m') || unit == _T('M')) {
			result *= (1024 * 1024);
		}
	}
	return result;
}

/**
 * 指定された文字列の内容に基づき、引数情報を生成する
 */
arguments* arguments_load(const _TCHAR* params) {
	arguments* args = arguments_create();

	bool error = false;
	bool option_end = false;
	while (*params && ! error) {
		// 空白は読み飛ばす
		while (*params && *params == _T(' ')) {
			params++;
		}
		if (! *params) {
			break;
		}
		
		if (*params == '-' && (! option_end)) {
			// オプションの処理
			if (_tcsncmp(params, _T("-cp "), 4) == 0
				|| _tcsncmp(params, _T("-classpath "), 11) == 0) {
				// -cp または -classpath オプション

				// 次の空白を探す
				while (*params != _T(' ')) {
					params++;
				}
				if (! *params) {
					break;
				}

				// パラメタを読み込む
				params += read_parameter(params,
										 args->classpath,
										 sizeof(args->classpath) / sizeof(*args->classpath));
			
			} else if (_tcsncmp(params, _T("-jar "), 5) == 0) {
				// jarオプション
				args->jar = true;
				params += 5;
				params += read_parameter(params,
										 args->jar_file_name,
										 sizeof(args->jar_file_name) / sizeof(*args->jar_file_name));
				
				// ここでオプションは終了
				option_end = true;

			} else if (_tcsncmp(params, _T("-Xbootclasspath:"), 16) == 0) {
				// -Xbootclasspath: オプション
				params += 16;
				params += read_parameter(params,
										 args->boot_classpath,
										 sizeof(args->boot_classpath) / sizeof(*args->boot_classpath));

			} else if (_tcsncmp(params, _T("-Xmx"), 4) == 0) {
				// -Xmxオプション
				params += 4;
				_TCHAR opt[256];
				params += read_parameter(params, opt, sizeof(opt) / sizeof(*opt));

				int value = parse_memory_size(opt);
				// 引数指定がおかしい
				if (value < 0) {
					error = true;
					break;
				}
				args->max_heap_size = value;

			} else if (_tcsncmp(params, _T("-Xoss"), 5) == 0) {
				// -Xossオプション(Javaスタックサイズ）
				params += 5;
				_TCHAR opt[256];
				params += read_parameter(params, opt, sizeof(opt) / sizeof(*opt));

				int value = parse_memory_size(opt);
				// 引数指定がおかしい
				if (value < 0) {
					error = true;
					break;
				}
				args->java_stack_size = value;

			} else if (_tcsncmp(params, _T("-Xss"), 4) == 0) {
				// -Xssオプション(ネイティブスタックサイズ）
				params += 4;
				_TCHAR opt[256];
				params += read_parameter(params, opt, sizeof(opt) / sizeof(*opt));

				int value = parse_memory_size(opt);
				// 引数指定がおかしい
				if (value < 0) {
					error = true;
					break;
				}
				args->native_stack_size = value;

			} else if (_tcsncmp(params, _T("-Xloggc:"), 8) == 0) {
				// -Xloggc: オプション
				params += 8;
				params += read_parameter(params, args->loggc, sizeof(args->loggc) / sizeof(*args->loggc));

			} else if (_tcsncmp(params, _T("-ea "), 4) == 0) {
				// -ea
				params += 4;
				args->enableAssertions = true;
			
			} else if (_tcsncmp(params, _T("-enableassertions "), 18) == 0) {
				// -enableassertions
				params += 18;
				args->enableAssertions = true;

			} else if (_tcsncmp(params, _T("-da "), 4) == 0) {
				// -da
				params += 4;
				args->enableAssertions = false;

			} else if (_tcsncmp(params, _T("-disableassertions "), 19) == 0) {
				// -disableassertions
				params += 19;
				args->enableAssertions = false;

			} else if (_tcsncmp(params, _T("-D"), 2) == 0) {
				// -Dkey=value
				params += 2;
				property prop;
				int index = 0;
				// 最初に = が現れる位置までが key
				while (*params && *params != _T('=')) {
					prop.key[index++] = *params++;
				}
				prop.key[index] = _T('\0');
				if (*params == _T('=')) {
					params++;
				}
				params += read_parameter(params, prop.value, sizeof(prop.value) / sizeof(*prop.value));
				arguments_put_property(args, prop.key, prop.value);

			} else if (_tcsncmp(params, _T("-verbose:gc "), 12) == 0) {
				// -verbose:gc オプション
				params += 12;
				args->verbose_gc = true;

			} else if (_tcsncmp(params, _T("-verify "), 8) == 0) {
				// -verify オプション
				params += 8;
				args->verify_mode = VERIFY_MODE_ALL;

			} else if (_tcsncmp(params, _T("-noverify "), 10) == 0) {
				// -noverify オプション
				params += 10;
				args->verify_mode = VERIFY_MODE_NONE;

			} else if (_tcsncmp(params, _T("-setcwd "), 8) == 0) {
				// -setcwdオプション
				params += 8;
				params += read_parameter(params,
										 args->current_directory,
										 sizeof(args->current_directory) / sizeof(args->current_directory[0]));

			} else if (_tcsncmp(params, _T("-Xhidevmwindow "), 15) == 0) {
				// -Xhidevmwindow オプション
				params += 15;
				args->hide_vm_window = true;

			} else if (_tcsncmp(params, _T("-Xconsole "), 10) == 0) {
				// -Xconsole オプション
				params += 10;
				args->show_console = true;

			} else if (_tcsncmp(params, _T("-Xlogfile:"), 10) == 0) {
				// -Xlogfile: オプション
				params += 10;
				params += read_parameter(params, args->logfile, sizeof(args->logfile) / sizeof(*args->logfile));

			} else if (_tcsncmp(params, _T("-Xusestdio "), 11) == 0) {
				// -Xusestdio オプション
				params += 11;
				args->use_stdio = true;

			} else {
				// 不明なオプション指定
				error = true;
				break;
			}
		} else {
			if (! args->jar) {
				// jarオプションが指定されていない場合は、
				// 実行クラス名を読み込む
				const _TCHAR* start = params;
				while (*params) {
					if (*params == _T(' ')) {
						break;
					}
					params++;
				}
				int len = (params - start) + 1;
				int buffsize = sizeof(args->class_name) / sizeof(*args->class_name); 
				len = (len < buffsize) ? len : buffsize;
				_tcsncpy(args->class_name, start, len - 1);
				args->class_name[len - 1] = _T('\0');
			}

			// 残りの文字列は Javaプログラムに渡す引数となる
			while (*params && *params == _T(' ')) {
				params++;
			}
			if (*params) {
				int len = _tcslen(params);
				args->argv = (_TCHAR*) malloc(sizeof(_TCHAR) * (len + 1));
				if (! args->argv) {
					fatal_error(FATAL_ERROR_NO_MEMORY);
				}
				_tcscpy(args->argv, params);
			}
			goto END;
		}
	}

END:
	if (error) {
		arguments_delete(args);
		args = NULL;
	}
	return args;
}

/**
 * 指定されたファイルに、引数情報を保存する
 */
int arguments_store(arguments* args, FILE* file) {
	// ブートクラスパスを出力する
	if (_tcscmp(args->boot_classpath, g_default_boot_classpath)) {
		// デフォルト値と異なる場合にのみ出力
		_fputts(_T(" -Xbootclasspath:\""), file);
		_fputts(args->boot_classpath, file);
		_fputts(_T("\""), file);
	}
	
	// 最大ヒープサイズを出力する
	if (args->max_heap_size != g_default_max_heap_size) {
		// デフォルト値と異なる場合にのみ出力
		_ftprintf(file, _T(" -Xmx%dK"), args->max_heap_size / 1024);
	}

	// Javaスタックサイズを出力する
	if (args->java_stack_size != DEFAULT_JAVA_STACK_SIZE) {
		// デフォルト値と異なる場合にのみ出力
		_ftprintf(file, _T(" -Xoss%d"), args->java_stack_size);
	}

	// ネイティブスタックサイズを出力する
	if (args->native_stack_size != DEFAULT_NATIVE_STACK_SIZE) {
		// デフォルト値と異なる場合にのみ出力
		_ftprintf(file, _T(" -Xss%d"), args->native_stack_size);
	}


	// アサーションの有効／無効を出力する
	if (args->enableAssertions) {
		_fputts(_T(" -ea"), file);
	} else {
		// デフォルトは無効
		// _fputts(_T(" -da"), file);
	}

	// -verbose:gc
	if (args->verbose_gc) {
		_fputts(_T(" -verbose:gc "), file);
	}
	
	// ベリファイモード
	switch (args->verify_mode) {
	case VERIFY_MODE_ALL:
		_fputts(_T(" -verify "), file);
		break;

	case VERIFY_MODE_NONE:
		// デフォルト値
		break;

	}

	// カレントディレクトリを設定する
	if (_tcscmp(args->current_directory, g_default_current_directory) != 0) {
		// デフォルト値と異なる場合にのみ出力
		_ftprintf(file, _T(" -setcwd \"%s\" "), args->current_directory);
	}

	// VMウインドウを非表示にする
	if (args->hide_vm_window) {
		_fputts(_T(" -Xhidevmwindow "), file);
	}

	// コンソール表示／非表示
	if (args->show_console) {
		_fputts(_T(" -Xconsole "), file);
	}

	if (_tcslen(args->logfile)) {
		_ftprintf(file, _T(" -Xlogfile:\"%s\""), args->logfile);
	}

	// 標準入出力の使用
	if (args->use_stdio) {
		_fputts(_T(" -Xusestdio "), file);
	}

	// システムプロパティを出力する
	for (int i = 0; i < args->properties_count; ++i) {
		_fputts(_T(" -D"), file);
		_fputts(args->properties[i].key, file);
		_fputts(_T("=\""), file);
		_fputts(args->properties[i].value, file);
		_fputts(_T("\" "), file);
	}
	
	if (args->jar) {
		// jarオプションが指定されている場合
		_ftprintf(file, _T(" -jar \"%s\""), args->jar_file_name);
	} else {
		// jarオプションが指定されていない場合
		// クラスパスを出力する
		if (_tcscmp(args->classpath, g_default_classpath)) {
			_fputts(_T(" -cp \""), file);
			_fputts(args->classpath, file);
			_fputts(_T("\""), file);
		}
		// クラス名を出力する
		_fputts(_T(" "), file);
		_fputts(args->class_name, file);
	}

	// Javaプログラムへの引数を出力する
	if (args->argv) {
		_ftprintf(file, _T(" %s"), args->argv);
	}
	return 1;
}

/**
 * プロパティを設定する
 */
void arguments_put_property(arguments* args, const _TCHAR* key, const _TCHAR* value) {
	for (int i = 0; i < args->properties_count; ++i) {
		if (! _tcscmp(args->properties[i].key, key)) {
			// 一致するキーを持つ内容を置換する
			_tcscpy(args->properties[i].value, value);
			return;
		}
	}

	// 新規にバッファを作成して追加する
	args->properties_count++;
	args->properties = (property*) realloc(args->properties,
											 sizeof(property) * args->properties_count);
	if (! args->properties) {
		fatal_error(FATAL_ERROR_NO_MEMORY);
	}
	_tcscpy(args->properties[args->properties_count - 1].key, key);
	_tcscpy(args->properties[args->properties_count - 1].value, value);
}

/**
 * すべてのプロパティをクリアする
 */
void arguments_clear_all_properties(arguments* args) {
	free(args->properties);
	args->properties_count = 0;
	args->properties = NULL;
}

/**
 * プロパティのキーを返す
 */
const _TCHAR* arguments_get_property_key(arguments* args, int index) {
	assert(index >= 0 && index < args->properties_count);
	return args->properties[index].key;
}

/**
 * 指定されたキーに対応するプロパティ値を返す
 */
const _TCHAR* arguments_get_property(arguments* args, const _TCHAR* key) {
	for (int i = 0; i < args->properties_count; ++i) {
		if (! _tcscmp(args->properties[i].key, key)) {
			return args->properties[i].value;
		}
	}
	return NULL;
}

/**
 * 指定されたパラメタをバッファに読み込む
 */
static int read_parameter(const _TCHAR* str, _TCHAR* buff, int bufflen) {
	const _TCHAR* start = str;

	// 空白を読み飛ばす
	while (*str && *str == _T(' ')) {
		str++;
	}

	// クオート文字を調べる
	bool quote = (*str == _T('"')) ? true : false;
	if (quote) {
		str++;
	}
	int copylen = 0;
	while (*str && copylen < bufflen) {
		if (quote) {
			if (*str == _T('"')) {
				str++;
				break;
			}
		} else if (*str == _T(' ')) {
			str++;
			break;
		}
		*buff++ = *str++;
		copylen++;
	}
	*buff = _T('\0');
	return str - start;
}

static void load_default_values_from_registory() {
	// デフォルトのブートクラスパスを代入する
	// （JAVA_HOMEが入っているため、固定値にできない）
	_stprintf(g_default_boot_classpath,
		      DEFAULT_BOOT_CLASSPATH_FORMAT,
			  get_java_home()); 

	// デフォルト値をレジストリから取得する
	// レジストリからデフォルト値をロードする
	HKEY hKey;
	long error = RegOpenKeyEx(HKEY_CURRENT_USER,
							  REG_ROOT,
							  0,
							  0,
							  &hKey);
    if (error == ERROR_SUCCESS) {
		// キーが存在している場合は、キーの値を読み込む
		DWORD dwType;
		DWORD cbData = MAX_PATH + 1;
		_TCHAR buff[MAX_PATH + 1];
		
		// ブートクラスパス
		error = RegQueryValueEx(hKey,
						REG_DEFAULT_BOOT_CLASSPATH,
						NULL,
						&dwType,
						(LPBYTE) buff,
						&cbData);
		if (error == ERROR_SUCCESS && dwType == REG_SZ) {
			_tcscpy(g_default_boot_classpath, buff);
		}

		// クラスパス
		error = RegQueryValueEx(hKey,
							   REG_DEFAULT_CLASSPATH,
							   NULL,
							   &dwType,
							   (LPBYTE) buff,
							   &cbData);
		if (error == ERROR_SUCCESS && dwType == REG_SZ) {
			_tcscpy(g_default_classpath, buff);
		}

		// カレントディレクトリ
		error = RegQueryValueEx(hKey,
							   REG_DEFAULT_CURRENT_DIRECTORY,
							   NULL,
							   &dwType,
							   (LPBYTE) buff,
							   &cbData);
		if (error == ERROR_SUCCESS && dwType == REG_SZ) {
			_tcscpy(g_default_current_directory, buff);
		}

		// 最大ヒープサイズ
		DWORD dwHeapSize;
		cbData = sizeof(dwHeapSize);
		error = RegQueryValueEx(hKey,
						REG_DEFAULT_MAX_HEAP_SIZE,
						NULL,
						&dwType,
						(LPBYTE) &dwHeapSize,
						&cbData);
		if (error == ERROR_SUCCESS && dwType == REG_DWORD) {
			// 単位はKBで指定されている
			g_default_max_heap_size = dwHeapSize * 1024;
		}
		RegCloseKey(hKey);
	}
}

/**
 * argumentsを初期化する
 */
static void init_arguments(arguments* config) {
	memset(config, 0, sizeof(arguments));

	// クラスパス
	_tcscpy(config->classpath, g_default_classpath);

	// 最大ヒープサイズ
	config->max_heap_size = g_default_max_heap_size;

	// Javaスタックサイズ
	config->java_stack_size = DEFAULT_JAVA_STACK_SIZE;

	// ネイティブスタックサイズ
	config->native_stack_size = DEFAULT_NATIVE_STACK_SIZE;

	// ブートクラスパス
	_tcscpy(config->boot_classpath, g_default_boot_classpath);
    
	// ベリファイモード
	config->verify_mode = VERIFY_MODE_NONE;	// -noverify

	// カレントディレクトリ
	_tcscpy(config->current_directory, g_default_current_directory);
}