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

#if !defined( HIGHLIGHTMGR_VBS_H_INCLUDED_ )
#define HIGHLIGHTMGR_VBS_H_INCLUDED_

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

using namespace std;

namespace NLangExt {

namespace NLangExt_vbs {

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

// 現在ポイントしている文字の種類を示す
enum CURCHARTYPE {
	VBSCT_ALPHANUMERIC	= 0x01,
	VBSCT_KEYWORD		= 0x11,
	VBSCT_COM1_LEFT		= 0x02,	// rem によるコメント
	VBSCT_COM1_RIGHT	= 0x03,
	VBSCT_COM1_TEXT		= 0x04,
	VBSCT_COM2_LEFT		= 0x05,	// 'によるコメント
	VBSCT_COM2_RIGHT	= 0x06,
	VBSCT_COM2_TEXT		= 0x07,
	VBSCT_DQUOTE_LEFT	= 0x28,
	VBSCT_DQUOTE_RIGHT	= 0x29,
	VBSCT_DQUOTE_TEXT	= 0x2A,
	VBSCT_OTHER			= 0x0E,
	VBSCT_NULL			= 0x0F,

	VBSCTM_KEYWORD_MASK	= 0x10,	// キーワード用のマスク
	VBSCTM_DQUOTE_MASK	= 0x20
};

// cがアルファベットや数字か否かを判断する
template< typename T_Char >
bool IsAlphaNumeric( T_Char c )
{
	const char *p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789";
	while ( (*p) ) {
		if ( T_Char( (*p) ) == c ) return true;
		++p;
	}
	return false;
}

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( VBSCT_OTHER )
	{
		next();
	};

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

		// Remの文字数の処理
		if ( argType == VBSCT_COM1_LEFT ) {
			if ( c == T_Char( 'R' ) || c == T_Char( 'r' ) )
				CurCharCnt = 3;
			else {
				if ( c == T_Char( 'E' ) || c == T_Char( 'e' ) )
					CurCharCnt = 2;
				else
					CurCharCnt = 1;
			}
		}
*/
	};

	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;

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

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

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

		p2 = CurPtr;
		++p2;

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

		switch ( CurType & ~VBSCTM_KEYWORD_MASK ) {
		case VBSCT_ALPHANUMERIC:
		case VBSCT_OTHER:
		case VBSCT_COM1_RIGHT:
		case VBSCT_COM2_RIGHT:
		case VBSCT_DQUOTE_RIGHT:
			if ( (*CurPtr) == T_Char( '\"' ) && (*p2) == T_Char( '\"' ) ) {
				// 左側の"は文字列定数の開始のダブルクォーテーションである
				CurType = VBSCT_DQUOTE_LEFT;
				CurCharCnt = 1;
			}
			else {
				CurType = wType;
				CurCharCnt = wCnt;
			}
			break;

		case VBSCT_COM1_LEFT:
		case VBSCT_COM1_TEXT:
			CurCharCnt = 1;
			if ( (*CurPtr) == T_Char( '\n' ) )
				CurType = VBSCT_COM1_RIGHT;
			else
				CurType = VBSCT_COM1_TEXT;
			break;

		case VBSCT_COM2_LEFT:
		case VBSCT_COM2_TEXT:
			CurCharCnt = 1;
			if ( (*CurPtr) == T_Char( '\n' ) )
				CurType = VBSCT_COM2_RIGHT;
			else
				CurType = VBSCT_COM2_TEXT;
			break;

		case VBSCT_DQUOTE_LEFT:
		case VBSCT_DQUOTE_TEXT:
			if ( wType == VBSCT_DQUOTE_LEFT ) {
				CurType = VBSCT_DQUOTE_RIGHT;
				CurCharCnt = 1;
			}
			else {
				CurType = VBSCT_DQUOTE_TEXT;
				CurCharCnt = wCnt;
			}
			break;
		}
		return true;
	}

	// 文字の種別を判断
	void JdgCharType( T_Char c1, T_Char c2, CURCHARTYPE *pType, int *pCnt )
	{
		if ( c1 == T_Char( '\"' ) ) {
			if ( c2 == T_Char( '\"' ) ) {
				// 連続する二つの"は文字列中のダブルクォーテーション
				(*pType) = VBSCT_DQUOTE_TEXT;
				(*pCnt) = 2;
			}
			// ダブルクォーテーション
			(*pType) = VBSCT_DQUOTE_LEFT;
			(*pCnt) = 1;
			return ;
		}

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

		if ( IsAlphaNumeric( c1 ) ) {
			(*pType) = VBSCT_ALPHANUMERIC;
			(*pCnt) = 1;
		}
		else {
			(*pType) = VBSCT_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 );
	};

	// Remによるコメントに入ったことを通知する
	void SetCurType_RemComment()
	{
		// すでに末尾に達していた場合は特に何もしない
		if ( CurPtr == eptr ) return ;
		if ( (*CurPtr) == T_Char( '\n' ) )
			CurType = VBSCT_COM1_RIGHT;	// 現在の位置が改行コードであれば、コメントの終了である
		else
			CurType = VBSCT_COM1_TEXT;	// コメントである
	}
private:
	T_Ptr sptr;
	T_Ptr eptr;
	T_Ptr CurPtr;
	CURCHARTYPE CurType;
	int CurCharCnt;	// 現在ポイントしている文字が何文字から構成されるか
};


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

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

	// 全ての色を更新する
	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
	{
		// 一旦前に戻ってから、進む
		// どこまで戻るか->変更箇所の直前の文字の文字種に依存
		// 英数字	英数字以外の文字まで
		// 空白等	空白等以外の文字まで
		// コメント1左　コメント1左以外の文字まで
		// 上記以外のコメント　一文字
		// 文字列	文字列以外もしくは"以外の記号
		// どこまで進むか->文字種の変更がなくなるまで

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

		T_TxtPtr wTxtPtr = pUpdateSPosTxt;
		T_CBPtr wCBPtr = pUpdateSPosCB;
		if ( !( wTxtPtr == pSTxt ) ) {
			// 前に戻る
			--wCBPtr;
			--wTxtPtr;
			switch ( (*wCBPtr) ) {
			case VBSCT_ALPHANUMERIC:
			case VBSCT_KEYWORD:
				while ( !( wTxtPtr == pSTxt ) && ( (*wCBPtr) & ~VBSCTM_KEYWORD_MASK ) == VBSCT_ALPHANUMERIC ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case VBSCT_COM1_LEFT:
				while ( !( wTxtPtr == pSTxt ) && (*wCBPtr) == VBSCT_COM1_LEFT ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case VBSCT_COM1_RIGHT:
			case VBSCT_COM1_TEXT:
			case VBSCT_COM2_LEFT:
			case VBSCT_COM2_RIGHT:
			case VBSCT_COM2_TEXT:
				if ( !( wTxtPtr == pSTxt ) ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case VBSCT_DQUOTE_LEFT:
			case VBSCT_DQUOTE_RIGHT:
			case VBSCT_DQUOTE_TEXT:
				while ( !( wTxtPtr == pSTxt ) && ( (*wCBPtr) & VBSCTM_DQUOTE_MASK ) && (*wTxtPtr) == '\"' ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case VBSCT_OTHER:
				while ( !( wTxtPtr == pSTxt ) && (*wCBPtr) == VBSCT_OTHER ) {
					--wCBPtr;
					--wTxtPtr;
				}
				break;
			case VBSCT_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() != VBSCT_NULL && ( flg || OldType != wType || !OverFlg ) )
		{
			OldType = (*pColorBuf);	// 変更前の文字種を取得

			if ( ptr.GetCurType() == VBSCT_ALPHANUMERIC )
				wType = LoopKeyword( &ptr, &pColorBuf );	// 英字・数値の場合は、キーワードの可能性がある
			else {
				// pColorBufに設定する値を決定する
				wType = ptr.GetCurType();
				// pColorBufに値を設定する
				for ( i = 0 ; i < ptr.GetCurCharCnt(); i++ ) {
					(*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, false > Finder( &( KeywordList.GetKeywordList() ) );
		int j;
		T_CBPtr hpCB = (*pColorBuf);
		CURCHARTYPE wType;
		CURCHARTYPE SetType;

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

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

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

		// 設定する色を決定する
		if ( Finder.IsFound() ) {
			if ( Finder.GetFoundStr() != L"Rem" )
				SetType = VBSCT_KEYWORD;	// キーワードである
			else {
				SetType = VBSCT_COM1_LEFT;	// Remキーワードによるコメントである
				pPtr->SetCurType_RemComment();
			}
		}
		else
			SetType = VBSCT_ALPHANUMERIC;	// キーワードではない

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

protected:
	T_KeywordList KeywordList;
};

} // namespace NLangExt_vbs

} // namespace NLangExt

#endif // HIGHLIGHTMGR_VBS_H_INCLUDED_
