/////////////////////////////////////////////////////////////////////////////
// HighlightMgr_cbl.h CHighlightMgr_cblクラスのインプリメンテーション
// 
/////////////////////////////////////////////////////////////////////////////

#if !defined( HIGHLIGHTMGR_CBL_H_INCLUDED_ )
#define HIGHLIGHTMGR_CBL_H_INCLUDED_

#include <vector>
#include <algorithm>
#include "../KeywordFinder.h"

using namespace std;

namespace NLangExt {

namespace NLangExt_cbl {

///////////////////////////////////////////////////////////////////////////
// CProgPtr COBOLプログラムのソースコードのポイント、および構文要素の判断

/*
自由記法の場合
行頭の最大6文字までの数字は一連番号と見なす。
一連番号以降で、最初の空白でない文字が*もしくは/であった場合は注記行と見なす。
*>から改行までは行内注記と見なす。

引用符はダブルクォーテーションおよびシングルクォーテーションとする。
*/

// 現在ポイントしている文字の種類を示す
enum CURCHARTYPE {
	CCT_ALPHANUMERIC	= 0x01,
	CCT_KEYWORD			= 0x02,
	CCT_COMENT			= 0x03,
	CCT_SEQUENCE_NUMBER	= 0x04,
	CCT_STRING			= 0x05,
	CCT_OTHER			= 0x06,
	CCT_NULL			= 0x07
};

// cがアルファベットや数字か否かを判断する
template< typename T_Char >
bool IsAlphaNumeric( T_Char c )
{
#if LANGEXT_IS_ASCII
	return (
		c >= T_Char( 'A' ) && c <= T_Char( 'Z' ) ||
		c >= T_Char( 'a' ) && c <= T_Char( 'z' ) ||
		c >= T_Char( '0' ) && c <= T_Char( '9' ) ||
		c == T_Char( '-' )
	);
#else
	const char *p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-0123456789";
	while ( (*p) ) {
		if ( T_Char( (*p) ) == c ) return true;
		++p;
	}
	return false;
#endif
}

// cが数字か否か判断する
template < typename T_Char >
bool IsNumeric( T_Char c )
{
	const char *p = "0123456789";
	while ( (*p) ) {
		if ( T_Char( (*p) ) == c ) return true;
		++p;
	}
	return false;
}

///////////////////////////////////////////////////////////////////////////
// CHighlightMgr COBOLプログラムの構文強調表示処理を行う

template < typename T_Char, typename T_TxtPtr, typename T_CBPtr, typename T_KeywordList >
class CHighlightMgr_cbl
{
public:
	CHighlightMgr_cbl(){};
	virtual ~CHighlightMgr_cbl(){};

	// 全ての色を更新する
	// （pSTxtは行頭をポイントしており、pETxtは行末をポイントしているものと仮定する）
	void FullUpdate( T_TxtPtr pSTxt, T_TxtPtr pETxt, T_CBPtr pSCB ) const
	{
		if ( pSTxt == pETxt ) return ;	// テキスト長が0ならばする事はない
		PartryUpdate( pSTxt, pETxt, pSCB );
	};

	// 変更に伴う更新
	void UpdateWidthChange(
		T_TxtPtr pSTxt,		// テキスト始端
		T_TxtPtr pETxt,		// テキスト末端
		T_TxtPtr pUpdateSPosTxt,	// 変更開始位置
		T_CBPtr pUpdateSPosCB,		// 変更開始位置のカラーバッファ
		T_TxtPtr pUpdateEPosTxt		// 変更終了位置
		) const
	{
		// 一旦前に戻ってから、進む
		// 更新開始位置は、変更開始位置がある行の行頭
		// 更新終了位置は、変更数量位置のある行の行末

		if ( pSTxt == pETxt ) return ;	// テキスト長が0ならばする事はない

		// 変更開始位置を行頭まで移動する。
		if ( !( pUpdateSPosTxt == pSTxt ) && (*pUpdateSPosTxt) == T_Char( '\n' ) ) {
			--pUpdateSPosTxt;	// 更新開始位置が改行コード上にあった場合は、
			--pUpdateSPosCB;	// その行の行頭まで移動する必要がある
		}
		while ( !( pUpdateSPosTxt == pSTxt ) && !( (*pUpdateSPosTxt) == T_Char( '\n' ) ) ) {
			--pUpdateSPosTxt;	// 一つ前の改行コードまで移動する。
			--pUpdateSPosCB;
		}
		if ( (*pUpdateSPosTxt) == T_Char( '\n' ) && !( pUpdateSPosTxt == pETxt ) ) {
			++pUpdateSPosTxt;	// 発見した改行コードの次の文字へ移動する
			++pUpdateSPosCB;
		}
		if ( pUpdateSPosTxt == pETxt ) {
			--pUpdateSPosTxt;
			--pUpdateSPosCB;
		}

		// 変更終了位置を行末まで移動する
		while ( !( pUpdateEPosTxt == pETxt ) && !( (*pUpdateEPosTxt) == T_Char( '\n' ) ) ) {
			++pUpdateEPosTxt;
		}
		if ( !( pUpdateEPosTxt == pETxt ) )
			++pUpdateEPosTxt;

		// 更新する
		PartryUpdate( pUpdateSPosTxt, pUpdateEPosTxt, pUpdateSPosCB );
	};

private:
	// 指定された範囲を更新する
	void PartryUpdate( T_TxtPtr pSTxt, T_TxtPtr pETxt, T_CBPtr pSCB ) const
	{
		T_TxtPtr wpSTxt;
		while ( !( pSTxt == pETxt ) ) {
			wpSTxt = pSTxt;

			// 行末を求める
			while ( !( pSTxt == pETxt ) && !( (*pSTxt) == T_Char( '\n' ) ) )
				++pSTxt;
			// 一行を更新する
			UpdateLine( &wpSTxt, pSTxt, &pSCB );
			if ( !( pSTxt == pETxt ) ) {
				// 改行コードの色を設定する
				(*pSCB) = CCT_ALPHANUMERIC;
				++pSTxt;
				++pSCB;
			}
		}
	}

	// 指定された行を更新する
	// （(*ppSTxt)からpETxtまでの間には改行コードは存在しないものとする）
	void UpdateLine( T_TxtPtr *ppSTxt, T_TxtPtr pETxt, T_CBPtr *ppSCB ) const
	{
		int i;
		T_TxtPtr wpSTxt;

		// 指定された行が0文字ならば、する事はない
		if ( (*ppSTxt) == pETxt ) return ;

		// 行頭の空白はスキップする
		// SkipSpace( ppSTxt, pETxt, ppSCB );

		// 行頭から始まる最大6文字の数字は、一連番号と見なす
		i = 0;
		while ( !( (*ppSTxt) == pETxt ) && IsNumeric( (**ppSTxt) ) && i < 6 ) {
			(**ppSCB) = CCT_SEQUENCE_NUMBER;
			++(*ppSCB);
			++(*ppSTxt);
			i++;
		}
		if ( (*ppSTxt) == pETxt ) return ;

		// 空白をスキップする
		SkipSpace( ppSTxt, pETxt, ppSCB );
		if ( (*ppSTxt) == pETxt ) return ;

		// 一連番号以降、最初に出現した空白以外の文字が*もしくは/であった場合は、当該の行全てをコメントとする
		if ( (**ppSTxt) == T_Char( '*' ) || (**ppSTxt) == T_Char( '/' ) ) {
			while ( !( (*ppSTxt) == pETxt ) ) {
				(**ppSCB) = CCT_COMENT;
				++(*ppSCB);
				++(*ppSTxt);
			}
			return ;
		}

		while ( !( (*ppSTxt) == pETxt ) ) {
			// アルファベットの処理
			if ( IsAlphaNumeric( (**ppSTxt) ) ) {
				LookupKeyword( ppSTxt, pETxt, ppSCB );
				continue;
			}

			// 文字列の処理
			if ( (**ppSTxt) == T_Char( '\"' ) || (**ppSTxt) == T_Char( '\'' ) ) {
				SkipString( ppSTxt, pETxt, ppSCB );
				continue;
			}

			// 空白の処理
			if ( (**ppSTxt) == T_Char( ' ' ) || (**ppSTxt) == T_Char( '\t' ) ) {
				SkipSpace( ppSTxt, pETxt, ppSCB );
				continue;
			}

			// それ以外の文字
			wpSTxt = (*ppSTxt);
			++wpSTxt;
			if ( !( wpSTxt == pETxt ) ) {
				if ( (**ppSTxt) == T_Char( '*' ) && (*wpSTxt) == T_Char( '>' ) ) {
					// *>以降は、全てコメントとする。
					while ( !( (*ppSTxt) == pETxt ) ) {
						(**ppSCB) = CCT_COMENT;
						++(*ppSCB);
						++(*ppSTxt);
					}
					return ;
				}
			}

			// 通常のテキスト
			(**ppSCB) = CCT_ALPHANUMERIC;
			++(*ppSCB);
			++(*ppSTxt);
		}
	}

	// 空白を処理する
	void SkipSpace( T_TxtPtr *ppSTxt, T_TxtPtr pETxt, T_CBPtr *ppSCB ) const
	{
		// 空白をスキップする
		while ( !( (*ppSTxt) == pETxt ) && ( (**ppSTxt) == T_Char( ' ' ) || (**ppSTxt) == T_Char( '\t' ) ) ) {
			(**ppSCB) = CCT_ALPHANUMERIC;
			++(*ppSCB);
			++(*ppSTxt);
		}
	}

	// ダブルクォーテーション及びシングルクォーテーションを処理する
	void SkipString( T_TxtPtr *ppSTxt, T_TxtPtr pETxt, T_CBPtr *ppSCB ) const
	{
		T_Char ch = (**ppSTxt);
		do {
			(**ppSCB) = CCT_STRING;
			++(*ppSCB);
			++(*ppSTxt);
		} while ( !( (*ppSTxt) == pETxt ) && !( (**ppSTxt) == ch ) );
		if ( (*ppSTxt) == pETxt ) return ;

		// 終端側のクォーテーションの処理
		(**ppSCB) = CCT_STRING;
		++(*ppSCB);
		++(*ppSTxt);
	}

	// アルファベットを処理する
	void LookupKeyword( T_TxtPtr *ppSTxt, T_TxtPtr pETxt, T_CBPtr *ppSCB ) const
	{
		char c;
		CKeywordList_cbl Keyword;
		CKeywordFinder< T_Char, false > Finder( &Keyword.GetKeywordList() );
		T_CBPtr wpCB = (*ppSCB);

		// 英数字を処理する
		while ( !( (*ppSTxt) == pETxt ) && IsAlphaNumeric( (**ppSTxt) ) ) {
			Finder.AddChar( (**ppSTxt) );
			++(*ppSTxt);
			++(*ppSCB);
		}

		// 設定する色を決定する
		if ( Finder.IsFound() )
			c = CCT_KEYWORD;
		else
			c = CCT_ALPHANUMERIC;

		// 色を設定する
		while ( !( wpCB == (*ppSCB) ) ) {
			(*wpCB) = c;
			++wpCB;
		}
	}

protected:
	T_KeywordList KeywordList;
};

} // namespace NLangExt_cbl

} // namespace NLangExt

#endif // HIGHLIGHTMGR_CBL_H_INCLUDED_
