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

#if !defined( HIGHLIGHTMGR_CPP_H_INCLUDED_ )
#define HIGHLIGHTMGR_CPP_H_INCLUDED_

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

using namespace std;

namespace NLangExt {

namespace NLangExt_cpp {

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

// 現在ポイントしている文字の種類を示す
enum CURCHARTYPE {
	CCT_ALPHANUMERIC	= 0x01,
	CCT_KEYWORD			= 0x11,
	CCT_COM1_LEFT		= 0x02,
	CCT_COM1_RIGHT		= 0x03,
	CCT_COM1_TEXT		= 0x04,
	CCT_COM2_LEFT		= 0x05,
	CCT_COM2_RIGHT		= 0x06,
	CCT_COM2_TEXT		= 0x07,
	CCT_DQUOTE_LEFT		= 0x28,
	CCT_DQUOTE_RIGHT	= 0x29,
	CCT_DQUOTE_TEXT		= 0x2A,
	CCT_SQUOTE_LEFT		= 0x4B,
	CCT_SQUOTE_RIGHT	= 0x4C,
	CCT_SQUOTE_TEXT		= 0x4D,
	CCT_OTHER			= 0x0E,
	CCT_NULL			= 0x0F,

	CCTM_KEYWORD_MASK	= 0x10,	// キーワード用のマスク
	CCTM_DQUOTE_MASK	= 0x20,
	CCTM_SQUOTE_MASK	= 0x40
};

// 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
}

template < typename T_Char, typename T_Ptr >
class CProgPtr
{
public:
	// テキストの最初の位置に設定する場合
	CProgPtr( T_Ptr args, T_Ptr arge ) :
		sptr( args ), eptr( arge ), CurCharCnt( 0 ), CurPtr( args ), CurType( CCT_OTHER )
	{
		next();
	};

	// 更新の時に、テキストの途中に設定する場合
	CProgPtr( T_Ptr args, T_Ptr arge, CURCHARTYPE argType ) :
		sptr( args ), eptr( arge ), CurCharCnt( 1 ), CurPtr( args ), CurType( argType )
	{
		if ( argType == CCT_COM1_RIGHT && (*CurPtr) == T_Char( '*' ) )
			CurCharCnt = 2;
		if ( argType == CCT_COM1_LEFT && (*CurPtr) == T_Char( '/' ) )
			CurCharCnt = 2;
	};

	CProgPtr( const CProgPtr &r ) :
		sptr( r.sptr ), eptr( r.eptr ), CurCharCnt( r.CurCharCnt ), CurPtr( r.CurPtr ), CurType( r.CurType )
	{};

	// 一つ先に進める
	bool next()
	{
		CURCHARTYPE wType;
		int wCnt;
		T_Ptr p2;
		bool IsEsc;

		// すでに末尾をポイントしていたら、これ以上先には進めない
		if ( CurPtr == eptr ) return false;

		// ポインタを進める
		for ( int i = 0; i < CurCharCnt; i++ )
			++CurPtr;

		if ( CurPtr == eptr ) {
			// テキストの末尾に達した
			CurType = CCT_NULL;
			CurCharCnt = 0;
			return true;
		}

		p2 = CurPtr;
		++p2;

		// 移動先の文字種を判断
		JdgCharType( (*CurPtr), (*p2), &wType, &wCnt, &IsEsc );

		switch ( CurType & ~CCTM_KEYWORD_MASK ) {
		case CCT_ALPHANUMERIC:
		case CCT_OTHER:
		case CCT_COM1_RIGHT:
		case CCT_COM2_RIGHT:
		case CCT_DQUOTE_RIGHT:
		case CCT_SQUOTE_RIGHT:
			if ( wType != CCT_COM1_RIGHT ) {
				CurType = wType;
				CurCharCnt = wCnt;
			}
			else {
				// 突如*/が出現した
				CurType = CCT_OTHER;
				CurCharCnt = 1;
			}
			break;

		case CCT_COM1_LEFT:
		case CCT_COM1_TEXT:
			if ( wType != CCT_COM1_RIGHT ) {
				CurType = CCT_COM1_TEXT;
				CurCharCnt = 1;
			}
			else {
				CurType = CCT_COM1_RIGHT;
				CurCharCnt = 2;
			}
			break;

		case CCT_COM2_LEFT:
		case CCT_COM2_TEXT:
			CurCharCnt = 1;
			if ( (*CurPtr) != T_Char( '\n' ) )
				CurType = CCT_COM2_TEXT;
			else
				CurType = CCT_COM2_RIGHT;
			break;

		case CCT_DQUOTE_LEFT:
		case CCT_DQUOTE_TEXT:
			if ( wType == CCT_DQUOTE_LEFT ) {
				CurType = CCT_DQUOTE_RIGHT;
				CurCharCnt = 1;
			}
			else {
				CurType = CCT_DQUOTE_TEXT;
				CurCharCnt = ( IsEsc ? 2 : 1 );
			}
			break;

		case CCT_SQUOTE_LEFT:
		case CCT_SQUOTE_TEXT:
			if ( wType == CCT_SQUOTE_LEFT ) {
				CurType = CCT_DQUOTE_RIGHT;
				CurCharCnt = 1;
			}
			else {
				CurType = CCT_SQUOTE_TEXT;
				CurCharCnt = ( IsEsc ? 2 : 1 );
			}
			break;
		}
		return true;
	}

	// 文字の種別を判断
	void JdgCharType( T_Char c1, T_Char c2, CURCHARTYPE *pType, int *pCnt, bool *pIsEsc )
	{
		(*pIsEsc) = false;

		if ( IsAlphaNumeric( c1 ) ) {
			(*pType) = CCT_ALPHANUMERIC;
			(*pCnt) = 1;
			return ;
		}

		if ( c1 == T_Char( '\\' ) ) {
			(*pType) = CCT_OTHER;
			if ( c2 == T_Char( '\0' ) )
				(*pCnt) = 1;	// テキスト末尾の\記号
			else {
				(*pCnt) = 2;	// \でエスケープされた文字
				(*pIsEsc) = true;
			}
			return ;
		}

		if ( c1 == T_Char( '\"' ) ) {
			(*pType) = CCT_DQUOTE_LEFT;
			(*pCnt) = 1;
			return ;
		}

		if ( c1 == T_Char( '\'' ) ) {
			(*pType) = CCT_SQUOTE_LEFT;
			(*pCnt) = 1;
			return ;
		}

		if ( c1 == T_Char( '/' ) && c2 == T_Char( '/' ) ) {
			(*pType) = CCT_COM2_LEFT;
			(*pCnt) = 2;
			return ;
		}

		if ( c1 == T_Char( '/' ) && c2 == T_Char( '*' ) ) {
			(*pType) = CCT_COM1_LEFT;
			(*pCnt) = 2;
			return ;
		}

		if ( c1 == T_Char( '*' ) && c2 == T_Char( '/' ) ) {
			(*pType) = CCT_COM1_RIGHT;
			(*pCnt) = 2;
			return ;
		}

		(*pType) = CCT_OTHER;
		(*pCnt) = 1;
	}

	// 現在の文字の種別を示す
	CURCHARTYPE GetCurType() const
	{
		return CurType;
	}
	// ポインタを取得
	T_Ptr GetPtr() const
	{
		return CurPtr;
	};

	// 現在ポイントしている文字の文字数を取得
	int GetCurCharCnt() const
	{
		return CurCharCnt;
	};

	// 比較
	bool operator ==( const CProgPtr &r ) const {
		return ( sptr == r.sptr && eptr == r.eptr && CurPtr == r.CurPtr );
	};
	bool operator ==( const T_Ptr &r ) const {
		return ( CurPtr == r );
	};

private:
	T_Ptr sptr;
	T_Ptr eptr;
	T_Ptr CurPtr;
	CURCHARTYPE CurType;
	int CurCharCnt;	// 現在ポイントしている文字が何文字から構成されるか
};


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

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

	// 全ての色を更新する
	void FullUpdate( T_TxtPtr pSTxt, T_TxtPtr pETxt, T_CBPtr pSCB ) const
	{
		if ( pSTxt == pETxt ) return ;	// テキスト長が0ならばする事はない
		PartryUpdate( CProgPtr< T_Char, T_TxtPtr >( pSTxt, pETxt ), pSCB, true, pSTxt );
	};

	// 変更に伴う更新
	void UpdateWidthChange(
		T_TxtPtr pSTxt,		// テキスト始端
		T_TxtPtr pETxt,		// テキスト末端
		T_TxtPtr pUpdateSPosTxt,	// 変更開始位置
		T_CBPtr pUpdateSPosCB,		// 変更開始位置のカラーバッファ
		T_TxtPtr pUpdateEPosTxt		// 変更終了位置
		) const
	{
		// 一旦前に戻ってから、進む
		// どこまで戻るか->変更箇所の直前の文字の文字種に依存
		// 英数字	英数字以外の文字まで
		// 空白等	空白等以外の文字まで
		// コメント	一文字戻る
		// 文字列	文字列以外もしくは\以外の記号
		// どこまで進むか->文字種の変更がなくなるまで

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

		T_TxtPtr wTxtPtr = pUpdateSPosTxt;
		T_CBPtr wCBPtr = pUpdateSPosCB;
		if ( !( wTxtPtr == pSTxt ) ) {
			// 前に戻る
			--wCBPtr;
			--wTxtPtr;
			switch ( (*wCBPtr) ) {
			case CCT_ALPHANUMERIC:
			case CCT_KEYWORD:
				while ( !( wTxtPtr == pSTxt ) && ( (*wCBPtr) & ~CCTM_KEYWORD_MASK ) == CCT_ALPHANUMERIC ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case CCT_COM1_LEFT:
			case CCT_COM1_RIGHT:
				// CCT_COM1_RIGHTで無いところまで戻る
				/*while ( !( wTxtPtr == pSTxt ) && ( (*wCBPtr) & ~CCTM_KEYWORD_MASK ) == CCT_COM1_RIGHT ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;*/
			case CCT_COM1_TEXT:
			case CCT_COM2_LEFT:
			case CCT_COM2_RIGHT:
			case CCT_COM2_TEXT:
				if ( !( wTxtPtr == pSTxt ) ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case CCT_DQUOTE_LEFT:
			case CCT_DQUOTE_RIGHT:
			case CCT_DQUOTE_TEXT:
				while ( !( wTxtPtr == pSTxt ) && ( (*wCBPtr) & CCTM_DQUOTE_MASK ) && (*wTxtPtr) == '\\' ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case CCT_SQUOTE_LEFT:
			case CCT_SQUOTE_RIGHT:
			case CCT_SQUOTE_TEXT:
				while ( !( wTxtPtr == pSTxt ) && ( (*wCBPtr) & CCTM_SQUOTE_MASK ) && (*wTxtPtr) == '\\' ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case CCT_OTHER:
				while ( !( wTxtPtr == pSTxt ) && (*wCBPtr) == CCT_OTHER ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case CCT_NULL:
				return ;
			}
		}

		// 進む
		unsigned char uc = (*wCBPtr);
		CProgPtr< T_Char, T_TxtPtr > ptr( wTxtPtr, pETxt, (CURCHARTYPE)uc );
		if ( wTxtPtr == pSTxt ) ptr = CProgPtr< T_Char, T_TxtPtr >( pSTxt, pETxt );	// 最初から更新する場合
		PartryUpdate( ptr, wCBPtr, false, pUpdateEPosTxt );	// 文字種が更新される間
	};

private:
	// 一部を更新する
	// flgに真を設定した場合は、pETxtまで更新する。
	// 偽を設定した場合は、pOvTxtより後ろで文字種の変更がなくなるまで更新する
	void PartryUpdate( CProgPtr< T_Char, T_TxtPtr > ptr, T_CBPtr pSCB, bool flg, T_TxtPtr pOvTxt ) const
	{
		T_CBPtr pColorBuf = pSCB;
		int wType;
		int OldType;
		int i;
		bool OverFlg = flg;

		OldType = (*pSCB);
		wType = ~OldType;	// とりあえず
		while ( ptr.GetCurType() != CCT_NULL && ( flg || OldType != wType || !OverFlg ) )
		{
			OldType = (*pColorBuf);	// 変更前の文字種を取得
// printf( "\'%c\' : %d 0x%02X \n", *(ptr.GetPtr()), ptr.GetCurCharCnt(), OldType );

			if ( ptr.GetCurType() == CCT_ALPHANUMERIC )
				wType = LoopKeyword( &ptr, &pColorBuf );	// 英字・数値の場合は、キーワードの可能性がある
			else {
				// pColorBufに設定する値を決定する
				wType = ptr.GetCurType();
				// pColorBufに値を設定する
				for ( i = 0 ; i < ptr.GetCurCharCnt(); i++ ) {
					OldType = (*pColorBuf);	// 変更前の文字種を取得
					(*pColorBuf) = wType;
					++pColorBuf;
				}
				ptr.next();
			}
			// ポインタがpOvTxtを越えたか否か
			if ( ptr.GetPtr() > pOvTxt ) OverFlg = true;
		}
	};

	// キーワードか否かを判断し、色を設定する
	CURCHARTYPE LoopKeyword( CProgPtr< T_Char, T_TxtPtr > *pPtr, T_CBPtr *pColorBuf ) const
	{
		CKeywordFinder< T_Char > Finder( &( KeywordList.GetKeywordList() ) );
		int i, j;
		T_CBPtr hpCB = (*pColorBuf);
		CURCHARTYPE wType;
		CURCHARTYPE SetType;

		// キーワードの候補を絞りつつ、色を設定する範囲を決定する
		i = 0;
		wType = pPtr->GetCurType();
		while ( wType != CCT_NULL && wType == CCT_ALPHANUMERIC ) {

			// 可能性の無いキーワードを除去する
			Finder.AddChar( *(pPtr->GetPtr()) );

			// 次の文字へ
			for ( j = 0; j < pPtr->GetCurCharCnt(); j++ )
				++(*pColorBuf);
			i++;
			pPtr->next();
			wType = pPtr->GetCurType();
		}

		// 設定する色を決定する
		if ( Finder.IsFound() )
			SetType = CCT_KEYWORD;	// キーワードである
		else
			SetType = CCT_ALPHANUMERIC;	// キーワードではない

		// 決定した範囲に色を設定する
		while ( !( hpCB == (*pColorBuf) ) ) {
			(*hpCB) = SetType;
			++hpCB;
		}
		return SetType;
	};

protected:
	T_KeywordList KeywordList;
};

} // namespace NLangExt_cpp

} // namespace NLangExt

#endif // HIGHLIGHTMGR_CPP_H_INCLUDED_
