/*/////////////////////////////////////////////////////////////////////////////
// confman 設定情報入出力ライブラリ
// ConfImp.h CConfImpクラス実体
*/

#include <errno.h>
#include "ConfImp.h"

///////////////////////////////////////////////////////////////////////////////
// 構築・破棄

CConfImp::CConfImp( )
{
	mapConfig.clear();		// とりあえず初期化
	strFileName = "";
	ModifyFlag = CF_FALSE;		// 変更フラグの初期化
	LastError = CF_NOERROR;		// エラー番号の初期化
}

CConfImp::~CConfImp( )
{

}

///////////////////////////////////////////////////////////////////////////////
// インタフェースの関数

// ファイル名の保存
void CConfImp::ConfImp_SetFileName( const char *FileName )
{
	strFileName = FileName;
}

// ファイルを読む
CF_BOOL CConfImp::ConfImp_Open()
{
	FILE *in_file;	// 入力ファイル
	CF_BOOL r;	// 戻り値
	SEC_TYPE::iterator p_sec;
	CONF_TYPE::iterator p_conf;

	in_file = fopen( strFileName.c_str(), "r" );
	if ( NULL == in_file ){
		LastError = CF_CANTOPENFILE;	// エラー番号の設定
		return CF_FALSE;		// オープンできない
	}

	r = ReadFile( in_file );	// ファイルを読む

	if ( fclose( in_file ) )		// ファイルを閉じる
		LastError = CF_CANTCLOSEFILE;	// ファイルのクローズに失敗

	return r;				// 結果を返す
}

// 文字列を取得する
CF_BOOL CConfImp::ConfImp_GetString( const wchar_t *SectionName, const wchar_t *KeyName, wchar_t *buf, int length )
{
	SEC_ITR p;	// 反復子
	if ( !FindKey( SectionName, KeyName, &p ) )	// 検索する
		return CF_FALSE;			// 見つからなければ偽を返す

	// 見つかったら文字列をコピーする
	wcsncpy( buf, p->second.c_str(), length - 1 );
	buf[ length - 1 ] = '\0';		// 最後にNULLを付けることを保証する
	return CF_TRUE;				// 真を返す

}

// 文字列を設定する
CF_BOOL CConfImp::ConfImp_SetString( const wchar_t *SectionName, const wchar_t *KeyName, const wchar_t *value )
{
	SEC_ITR p;	// 反復子

	if ( !FindKey( SectionName, KeyName, &p ) )	// 検索する
		return CF_FALSE;			// 見つからなければ偽を返す

	p->second = value;	// 文字列を記憶する
	ModifyFlag = CF_TRUE;	// 更新フラグを立てる
	return CF_TRUE;		// 真を返す

}

// 閉じる
CF_BOOL CConfImp::ConfImp_Close()
{
	// 更新フラグが真の場合ファイルへフラッシュする
	if ( ModifyFlag )
		return WriteFile();
	return CF_TRUE;
}

// 更新フラグを取得する
CF_BOOL CConfImp::ConfImp_GetModifyFlag( void )
{
	return ModifyFlag;
}

// 更新フラグを設定する
void CConfImp::ConfImp_SetModifyFlag( CF_BOOL b )
{
	ModifyFlag = b;
}

// セクション名のリストを取得
CF_BOOL CConfImp::ConfImp_GetSectionList( wstring &s, int Length )
{
	return MakeList( mapConfig.begin(), mapConfig.end(), s, Length );
}

// 上の関数同様キーのリストを返す
CF_BOOL CConfImp::ConfImp_GetKeyList( const wchar_t *SectionName, wstring &s, int Length )
{
	CONF_ITR conf_p;

	conf_p = mapConfig.find( (wstring)SectionName );// セクションを検索
	if ( conf_p == mapConfig.end() ){
		LastError = CF_NOSECTION;
		return CF_FALSE;		// セクションがなければエラー
	}

	return MakeList( conf_p->second.begin(), conf_p->second.end(), s, Length );
}


// セクション追加
CF_BOOL CConfImp::ConfImp_AddSection( const wchar_t *SectionName )
{
	SEC_TYPE w;
	CONF_ITR conf_p;

	w.clear();	// 空のセクションを構築
	conf_p = mapConfig.find( (wstring)SectionName );	// 検索する
	if ( conf_p != mapConfig.end() ){
		// 見つかった場合は追加できない
		LastError = CF_FINDSECTION;
		return CF_FALSE;
	}

	// 見つからなければ追加する
	mapConfig.insert( pair< wstring, SEC_TYPE >( (wstring)SectionName, w ) );
	ModifyFlag = CF_TRUE;			// 更新フラグを立てる
	return CF_TRUE;

}

// キー追加
CF_BOOL CConfImp::ConfImp_AddKey( const wchar_t *SectionName, const wchar_t *KeyName, const wchar_t *Value )
{
	SEC_ITR sec_p;	// 反復子
	CONF_ITR conf_p;

	conf_p = mapConfig.find( (wstring)SectionName );	// セクションを検索
	if ( conf_p == mapConfig.end() ){
		LastError = CF_NOSECTION;
		return CF_FALSE;		// セクションがなければエラー
	}

	sec_p = ( conf_p->second ).find( (wstring)KeyName );	// キーで検索
	if ( sec_p != ( conf_p->second ).end() ){
		LastError = CF_FINDKEY;
		return CF_FALSE;		// キーがあればエラー
	}

	// セクションがあり、キーななければ追加
	(conf_p->second).insert( pair< wstring, wstring >( (wstring)KeyName, (wstring)Value ) );
	ModifyFlag = CF_TRUE;			// 更新フラグを立てる
	return CF_TRUE;
}

// セクション削除
CF_BOOL CConfImp::ConfImp_RemoveSection( const wchar_t *SectionName )
{
	CONF_ITR conf_p;

	conf_p = mapConfig.find( (wstring)SectionName );	// 検索
	if ( conf_p == mapConfig.end() ){
		LastError = CF_NOSECTION;
		return CF_FALSE;		// 既になければ偽を返す
	}

	mapConfig.erase( conf_p );	// 削除する
	ModifyFlag = CF_TRUE;		// 更新フラグを立てる

	return CF_TRUE;
}

// キー削除
CF_BOOL CConfImp::ConfImp_RemoveKey( const wchar_t *SectionName, const wchar_t *KeyName )
{
	CONF_ITR conf_p;
	SEC_ITR sec_p;

	conf_p = mapConfig.find( (wstring)SectionName );	// セクションを検索
	if ( conf_p == mapConfig.end() ){		// なければ偽を返す
		LastError = CF_NOSECTION;
		return CF_FALSE;
	}

	sec_p = ( conf_p->second ).find( (wstring)KeyName );	// キーで検索
	if ( sec_p == ( conf_p->second ).end() ){
		LastError = CF_NOKEY;
		return CF_FALSE;			// 無ければ偽を返す
	}

	(conf_p->second).erase( sec_p );	// 削除する
	ModifyFlag = CF_TRUE;			// 更新フラグを立てる

	return CF_TRUE;	
}

// 最後のエラーを取得
unsigned long CConfImp::ConfImp_GetLastError()
{
	return LastError;
}

// エラー値のクリア
void CConfImp::ConfImp_ClearLastError()
{
	LastError = CF_NOERROR;
}

// 値をstring型で取得
CF_BOOL CConfImp::ConfImp_GetSTLString( const wchar_t *SectionName, const wchar_t *KeyName, wstring &str )
{
	SEC_ITR p;
	
	if ( FindKey( SectionName, KeyName, &p ) ){
		str = p->second;
		return CF_TRUE;
	}
	return CF_FALSE;
}

// エラー値を設定
void CConfImp::ConfImp_SetError( unsigned long e )
{
	LastError = e;
}

// セクション名・キー名に対応する値の文字列へのポインタを取得する
const wchar_t* CConfImp::ConfImp_GetConstStr( const wchar_t *SectionName, const wchar_t *KeyName )
{
	SEC_ITR p;	// 反復子
	if ( !FindKey( SectionName, KeyName, &p ) )	// 検索する
		return NULL;
	return p->second.c_str();	// 値を返す
}

///////////////////////////////////////////////////////////////////////////////
// 内部関数
// ファイルの読込

CF_BOOL CConfImp::ReadFile( FILE *in_file )
{
	SEC_TYPE w_sec;
	wstring w_secname;
	tagCFChar ch;
	int stat = 1;

	// 初期状態
	do {
		ch = GetChar( in_file );
	} while( ch.type != CFCT_LS && !feof( in_file ) );

	// 初期状態のままファイル終端に達した場合はエラー
	if ( feof( in_file ) ){
		LastError = CF_CANTFINDSECTION;
		return CF_FALSE;
	}
	// 最上位状態遍移
	while( stat > 0 ){
		switch( stat ){
		case 1:	// セクション名の読み込み
			stat = ReadSectionName( in_file, &w_secname );
			break;
		case 2:	// セクションの内容を読み込む
			stat = CreateSection( in_file, &w_sec );
			mapConfig.insert( pair<wstring, SEC_TYPE >( w_secname, w_sec ) );
			break;
		}
	}
	if ( stat == -1 ) // 何らかの致命的エラーが発生した
		return CF_FALSE;

	return CF_TRUE;
}

// セクション名の読み込み
int CConfImp::ReadSectionName( FILE *in_file, wstring *name )
{
	tagCFChar ch;

	// 文字列の初期化
	name->erase( name->begin(), name->end() );

	// 文字を取得する
	ch = GetChar( in_file );
	while( !feof( in_file ) && ch.type != CFCT_RETURN && ch.type != CFCT_RS ){
		(*name) += ch.c;
		ch = GetChar( in_file );
	}

	// 閉じ中括弧でない場合はエラー
	if ( ch.type != CFCT_RS ){
		LastError = CF_SECTIONNAMEERROR;
		return -1;
	}
	// セクション名取得に成功した場合はセクション構築へ
	return 2;
}

// セクション構築状態変移
int CConfImp::CreateSection( FILE *in_file, SEC_TYPE *sec )
{
	tagCFChar ch;
	int CS_stat = 1;	// このレベルでの状態
	int EXIT_stat = 0;	// 戻り値（上位レベルでの次の状態）
				// 正常終了を初期値とする

	sec->clear();	// 初期化
	while( CS_stat > 0 ){
		switch( CS_stat ){
		case 1:	// 行末スキップ
			CS_stat = LineEndSkip( in_file );
			break;
		case 2:	// 行頭スキップ
			CS_stat = LineHeadSkip( in_file, &ch, &EXIT_stat );
			break;
		case 3:	// キー・値取得
			CS_stat = ReadKeyVal( in_file, ch, sec );
			break;
		}
	}
	return EXIT_stat;
}

// 行末スキップ
int CConfImp::LineEndSkip( FILE *in_file )
{
	tagCFChar ch;
	do {
		ch = GetChar( in_file );
	} while( !feof( in_file ) && ch.type != CFCT_RETURN );
	// 改行ならば行頭スキップへ
	if ( ch.type == CFCT_RETURN ) return 2;
	// ファイル終端ならば正常終了
	return 0;
}

// 行頭スキップ
int CConfImp::LineHeadSkip( FILE *in_file, tagCFChar *ch, int *EXIT_stat )
{
	do {
		(*ch) = GetChar( in_file );
	} while( !feof( in_file ) && ch->type != CFCT_COMMENT && ch->type != CFCT_LS && 
		( ch->type == CFCT_SPACE || ch->type == CFCT_TAB ) );

	// ファイル終端ならば正常終了
	if ( feof( in_file ) ) return 0;

	switch( ch->type ){
	case CFCT_COMMENT:
		return 1;	// 行末スキップへ
	case CFCT_LS:
		(*EXIT_stat) = 1;	// セクション名取得へ
		return 0;
	default :		// 英字につきキー・値取得へ
		return 3;
	}
}

// キー・値取得
int CConfImp::ReadKeyVal( FILE *in_file, tagCFChar ch, SEC_TYPE *sec )
{
	// chは行頭スキップより渡される最初の一文字
	tagCFChar ws = ch;
	wstring strKey;
	wstring strVal;

	strKey.erase( strKey.begin(), strKey.end() );	// ’キー’を初期化
	strVal.erase( strVal.begin(), strVal.end() );	// ’値’を初期化

	// キー取得
	while( ws.type != CFCT_EQUAL && ws.type != CFCT_RETURN && 
		ws.type != CFCT_SPACE && ws.type != CFCT_TAB && !feof( in_file ) ){
		strKey += ws.c;
		ws = GetChar( in_file );
	}

	// 改行・ファイル終端ならば行破棄
	if ( feof( in_file ) || ws.type == CFCT_RETURN ) goto BREAK_LINE;

	// キー・イコール間スキップ（キー取得をイコールで抜けたらなにもしない）
	while ( !feof( in_file ) && ( ws.type == CFCT_SPACE || ws.type == CFCT_TAB ) ) {	// 空白をスキップ
		ws = GetChar( in_file );
	}
	// イコール以外ならば行破棄
	if ( ws.type != CFCT_EQUAL || feof( in_file ) ){
		if ( ws.type == CFCT_COMMENT ) ws.type = CFCT_ERR; // ここでのセミコロンはエラー
		goto BREAK_LINE;
	}

	// イコール・値間スキップ
	do {
		ws = GetChar( in_file );
	} while ( ( ws.type == CFCT_SPACE || ws.type == CFCT_TAB ) && !feof( in_file ) );	// 空白をスキップ
	// ファイル終端・改行ならば行破棄
	if ( feof( in_file ) || ws.type == CFCT_RETURN ) goto BREAK_LINE;

	// ’値’読み込み（イコール・値間スキップで読んだ最後の一文字をstrValに加える）
	do {
		strVal += ws.c;
		ws = GetChar( in_file );
	} while ( !feof( in_file ) && ws.type != CFCT_RETURN && ws.type != CFCT_COMMENT );

	// キー・値をセット
	sec->insert( pair< wstring, wstring >( strKey, strVal ) );

	// 次の状態へ遍移する
	switch( ws.type ){
		case CFCT_ELSE : return 0;
		case CFCT_RETURN : return 2;
		default : return 1;
	}

BREAK_LINE:	// 行が破棄された場合の処理
	switch( ws.type ){
	case CFCT_ELSE :
		LastError = CF_BREAKLINE_EOF;
		return 0;	// 正常終了へ
	case CFCT_RETURN:
		LastError = CF_BREAKLINE_RTN;
		return 2;	// 行頭スキップへ
	case CFCT_COMMENT :	// セミコロンの場合はエラーではない
		return 1;	// 行末スキップへ
	default :
		LastError = CF_BREAKLINE_ETC;
		return 1;	// 行末スキップへ
	}
}

///////////////////////////////////////////////////////////////////////////////
// その他下請けルーチン

// セクション名とキーで検索する
CF_BOOL CConfImp::FindKey( const wchar_t *SectionName, const wchar_t *KeyName, SEC_ITR *p )
{
	// セクションを格納するmapを検索
	CONF_ITR conf_p = mapConfig.find( (wstring)SectionName );
	if ( conf_p == mapConfig.end() ){
		// セクションが見つからない
		LastError = CF_NOSECTION;
		return CF_FALSE;
	}

	// セクションが見つかればキーで検索
	(*p) = ( conf_p->second ).find( (wstring)KeyName );
	if ( (*p) == ( conf_p->second ).end() ){
		// キーが見つからない
		LastError = CF_NOKEY;
		return CF_FALSE;
	}

	return CF_TRUE;				// 見つかった
}

// ファイルへフラッシュする
CF_BOOL CConfImp::WriteFile( void )
{
	FILE *out_file;
	CONF_ITR p_conf;
	SEC_ITR p_sec;
	wstring w_str;

	// ファイルをオープン
	if ( ( out_file = fopen( strFileName.c_str(), "w" ) ) == NULL ){
		LastError = CF_CANTOPENFILE;
		return CF_FALSE;	// ファイルオープンに失敗した場合
	}

	for( p_conf = mapConfig.begin(); p_conf != mapConfig.end(); p_conf++ ){
		// セクション名の出力文字列を合成
		w_str = (wstring)L"\n[" + p_conf->first + (wstring)L"]\n";
		// 文字列を出力・エラーの場合は中止
		if ( !WriteLine( out_file, &w_str ) ) goto ERR_EXIT;
		// セクション中のキーと値を出力
		for ( p_sec = p_conf->second.begin(); p_sec != p_conf->second.end(); p_sec++ ){
			// キーと値の文字列を合成
			w_str = (wstring)L" " + p_sec->first + (wstring)L" = " + p_sec->second + (wstring)L"\n";
			// 出力
			if ( !WriteLine( out_file, &w_str ) ) goto ERR_EXIT;
		}
	}
	if ( fclose( out_file ) )
		LastError = CF_CANTCLOSEFILE;
	return CF_TRUE;

ERR_EXIT:
	if ( fclose( out_file ) )
		LastError = CF_CANTCLOSEFILE;
	return CF_FALSE;
}

// 一行を出力
bool CConfImp::WriteLine( FILE *out_file, wstring *str )
{
	int length = str->size();	// 文字列長を取得

	// 出力する・出力したサイズが文字列長に満たない場合はエラー
	if ( fwrite( str->c_str(), sizeof( wchar_t ), length, out_file ) < length ){
		LastError = CF_WRITEERROR;
		return false;
	}
	return true;	// 正常に出力したため真を返す
}

// セクション名・キー名のリストを作成する
// バッファにNULL文字で区切られたセクション・キー名を格納。最後はNULLが２つ入る
template <class TYPE>
CF_BOOL CConfImp::MakeList( TYPE s, TYPE e, wstring &buf, int Length )
{
	while ( s != e ){			// すべての要素を走査
		if ( Length != -1 && s->first.size() + buf.size() + 2 >= Length ){
			buf.insert( buf.end(), L'\0' );		// NULL文字を追加する
			return CF_FALSE;	// 最大文字列長を超える場合は偽を返し終了
		}
		buf += s->first;		// 文字列をコピー
		buf.insert( buf.end(), L'\0' );		// NULL文字を追加する
		s++;					// 次の要素へ
	}
	buf.insert( buf.end(), L'\0' );		// NULL文字を追加する
	return CF_TRUE;				// 成功したため真を返す
}

// 通称：１文字読み込む
// 機能：エスケープシーケンスを解釈し文字を読み込む
tagCFChar CConfImp::GetChar( FILE *infile )
{
	tagCFChar wt;

	wt.c = NULL;
	wt.type = CFCT_ERR;
	if ( feof( infile ) ) return wt;	// 読めない場合はエラーを返す
	wt.c = fgetwc( infile );		// とりあえず一文字

	if ( wt.c != L'\\' ) {
		// \で無い場合はエスケープシーケンスではない
		// 文字に対応する種類を設定する
		switch ( wt.c ) {
		case L';' :
			wt.type = CFCT_COMMENT;
			break;
		case L'\n' :
			wt.type = CFCT_RETURN;
			break;
		case L'=' :
			wt.type = CFCT_EQUAL;
			break;
		case L' ' :
			wt.type = CFCT_SPACE;
			break;
		case L'\t' :
			wt.type = CFCT_TAB;
			break;
		case L'[' :
			wt.type = CFCT_LS;
			break;
		case L']' :
			wt.type = CFCT_RS;
			break;
		case EOF :
			wt.type = CFCT_ERR;
			break;
		default :
			wt.type = CFCT_ELSE;
		}
	}
	else {
		// \だったばあいはエスケープシーケンスにつき、
		// 次の文字を読み込み、判断する。
		// 読み込めなければエラーを返す
		if ( feof( infile ) ) return wt;
		wt.c = fgetwc( infile );
		switch ( wt.c ) {
		case L';' :	// \;は文字としての;
		case L'=' :	// \=は文字としての=
		case L' ' :	// \ は文字としてのスペース
		case L'\\' :	// \\は\
		case L'[' :	// \[は[
		case L']' :	// \]は]
			wt.type = CFCT_ELSE;
			break;
		case L'n' :	// \nは文字としての改行コード
			wt.type = CFCT_ELSE;
			wt.c = L'\n';
			break;
		case L'\n' :	// 行の末尾に\がある場合は、次の行へ継続
			return GetChar( infile );	// 次の文字を読んで返す
			break;
		case L't' :	// \tはタブ
			wt.type = CFCT_ELSE;
			wt.c = L'\t';
			break;
		default :
			wt.type = CFCT_ERR;
		}
	}
	return wt;
}
