////////////////////////////////////////////////////////////////////////////
// CPlugin クラスのインプリメンテーション
//
////////////////////////////////////////////////////////////////////////////

#include "Plugin.h"
#include "TaEditShell.h"
#include "TaEditDraw.h"
#include "PluginFunc.h"
#include "CompoundStr.h"

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

CPlugin::CPlugin() :
	pPluginConf( NULL ),
	pCurPlugin( NULL ),
	pView( NULL ),
	Modified( false )
{

}

CPlugin::~CPlugin()
{
	confman_Close( pPluginConf );
	pPluginConf = NULL;
}

////////////////////////////////////////////////////////////////////////////
// メソッド
////////////////////////////////////////////////////////////////////////////

// プラグインの初期化
////////////////////////////////////////////////////////////////////////////

// プラグインを設定
// プラグインの情報の読み込み、メニューへの追加・必要に応じてメニューのマップを行う
// （利用可能なプラグインが無い場合は「ツール」メニューを表示しない）
CErrorBool CPlugin::ConfigurePlugin( const string &rConfigFile, Widget wgtCascade, Widget wgtPullDown, CTaEditDraw *argpView )
{
	CErrorBool r;
	pView = argpView;

	// 設定ファイル名が指定されていなければ、プラグインの読み込みは行わない
	if ( rConfigFile.empty() ) return true;

	// 作業用メモリ管理オブジェクトを初期化する
	m_WorkMemoryMgr.Initialize();

	// 情報を読み込む
	r = ReadPluginInfo( rConfigFile );
	if ( !r ) return r;

	// 読み込んだ情報の正当性を確認する。
	CheckPluginInfo();

	// 結果として、有効なプラグインが残らなければ、処理を終了する
	if ( vPluginInfo.empty() ) return true;

	// メニューを構築する
	return CreateMenu( wgtCascade, wgtPullDown );
}

// プラグインの情報を読み込む
CErrorBool CPlugin::ReadPluginInfo( const string &rConfigFile )
{
	assert( this && NULL == pPluginConf );
	const CCompoundStr *pErrMsg = NULL;
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();

	VPtr< wchar_t > wBuf;
	const wchar_t *wptr;
	void *h;
	int length;

	vPluginInfo.clear();

	// 設定情報の読み込み
	pPluginConf = confman_CreateConf( rConfigFile.c_str() );
	if ( NULL == pPluginConf )
		return CErrorBool( pConf->RefOutOfMemoryErrorMsg() );
	if ( !confman_Open( pPluginConf ) ) {
		pErrMsg = &( pConf->RefIOErrorMsg( errno ) );
		goto ERR_EXIT;
	}

	// セクション名を取得
	h = exconf_CreateInternalSectionList( pPluginConf, &length );
	if ( NULL == h ) {
		pErrMsg = &( pConf->RefOutOfMemoryErrorMsg() );
		goto ERR_EXIT;
	}
	if ( !wBuf.Malloc( length ) ) {
		pErrMsg = &( pConf->RefOutOfMemoryErrorMsg() );
		goto ERR_EXIT;
	}
	exconf_GetInternalList( h, wBuf.GetPtr(), length );

	// 各セクションの情報を読み込む
	wptr = wBuf.GetPtr();
	while( (*wptr)  ) {
		// 配列にオブジェクトを一つ追加
		PLUGIN_INFO workObj;
		vector< PLUGIN_INFO >::iterator itr;
		vPluginInfo.push_back( workObj );
		itr = vPluginInfo.begin() + vPluginInfo.size() - 1;
		const wchar_t *pInfo;

		// 必要な情報を読み込む
		itr->SectionName = wptr;
		pInfo = exconf_GetConstStr( pPluginConf, wptr, L"PluginName" );
		if ( pInfo ) JPEXT_WCStoMBS( pInfo, &( itr->PluginName ) );
		pInfo = exconf_GetConstStr( pPluginConf, wptr, L"IsSeparator" );
		if ( pInfo && wscasecmp( pInfo, L"True" ) == 0 ) {
				itr->IsSeparator = true;
		}
		else {
			itr->IsSeparator = false;
			pInfo = exconf_GetConstStr( pPluginConf, wptr, L"LibraryName" );
			if ( pInfo ) JPEXT_WCStoMBS( pInfo, &( itr->LibraryName ) );
			pInfo = exconf_GetConstStr( pPluginConf, wptr, L"FunctionName" );
			if ( pInfo ) JPEXT_WCStoMBS( pInfo, &( itr->FunctionName ) );
			itr->MenuLabel = "";
			pInfo = exconf_GetConstStr( pPluginConf, wptr, L"MenuLabel" );
			if ( pInfo ) JPEXT_WCStoMBS( pInfo, &( itr->MenuLabel ) );
		}

		CF_SKIPPTR( wptr );
	}

	return true;

ERR_EXIT:
	vPluginInfo.clear();
	if ( pPluginConf ) confman_Close( pPluginConf );
	pPluginConf = NULL;
	return CErrorBool( (*pErrMsg) );
}

// 読み込んだプラグインの情報の正当性を確認する （エラーがあるデータを除去する）
void CPlugin::CheckPluginInfo()
{
	int i;
	int j;

	i = vPluginInfo.size() - 1;
	while ( i >= 0 ) {
		bool r = true;
		// 同じプラグイン名の文字が存在する場合はエラー
		j = 0;
		while( j < i ) {
			if ( vPluginInfo[i].PluginName == vPluginInfo[j].PluginName ) {
				vPluginInfo.erase( vPluginInfo.begin() + j );
				r = false;
				--i;
			}
			else
				++j;
		}
		if ( !r )
			vPluginInfo.erase( vPluginInfo.begin() + i );
		else {
			// 名前の正当性を確認する
			if ( !CheckNameStr( vPluginInfo[i].PluginName ) ||
				 ( !vPluginInfo[i].IsSeparator && !CheckNameStr( vPluginInfo[i].FunctionName ) ) )
				vPluginInfo.erase( vPluginInfo.begin() + i );
		}
		--i;
	}
}

// 名前に使用されている文字の正当性を確認
// （各名前にはA-Z、a-z、0-9及び_のみ使用可能）
bool CPlugin::CheckNameStr( const string& r ) const
{
	const char *wp = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_";
	int i;

	if ( r.empty() ) return false;	// 無名は不可

	// 使用不可な文字が含まれていないことを確認する
	for ( i = 0; i < r.length(); ++i )
		if ( strchr( wp, r[i] ) == NULL ) return false;
	return true;
}

// メニューを構築する
CErrorBool CPlugin::CreateMenu( Widget wgtCascade, Widget wgtPullDown )
{
	Arg al[3];
	int ac = 0;
	int i;
	const CCompoundStr *pMsg = NULL;
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();

	// 作業用に
	for ( i = 0; i < vPluginInfo.size(); ++i )
		vPluginInfo[i].wgtMenuBtn = NULL;

	// 各プラグインごと、メニューを構築する
	for ( i = 0; i < vPluginInfo.size(); ++i ) {
		CCompoundStr wCStr;

		// メニューのテキストを設定
		ac = 0;
		if ( !( vPluginInfo[i].MenuLabel.empty() ) ) {
			wCStr.SetStr( vPluginInfo[i].MenuLabel.c_str() );
			XtSetArg( al[ac], XmNlabelString, wCStr.GetStr() );
			ac++;
		}

		// ウィジット名を合成
		string wWgtName = string( "TEPI_") + vPluginInfo[i].PluginName;

		// ウィジットを構築
		if ( vPluginInfo[i].IsSeparator )
			vPluginInfo[i].wgtMenuBtn = XmCreateSeparator( wgtPullDown, const_cast< char* >( wWgtName.c_str() ), al, ac );
		else
			vPluginInfo[i].wgtMenuBtn = XmCreatePushButton( wgtPullDown, const_cast< char* >( wWgtName.c_str() ), al, ac );
		if ( NULL == vPluginInfo[i].wgtMenuBtn ) {
			pMsg = &( pConf->RefOutOfMemoryErrorMsg() );
			goto ERR_EXIT;
		}

		// コールバックを登録
		if ( !vPluginInfo[i].IsSeparator )
			XtAddCallback( vPluginInfo[i].wgtMenuBtn, XmNactivateCallback, PluginMenuCallback, this );

		XtManageChild( vPluginInfo[i].wgtMenuBtn );
	}

	// 「ツール」メニューを有効にする
	XtMapWidget( wgtCascade );

	return true;

ERR_EXIT:
	// 今まで生成したウィジットを破棄する
	for ( i = 0; i < vPluginInfo.size(); ++i ) {
		if ( vPluginInfo[i].wgtMenuBtn )
			XtDestroyWidget( vPluginInfo[i].wgtMenuBtn );
	}
	// プラグインの情報を全て破棄する
	vPluginInfo.clear();
	vPluginInfo.swap( vector< PLUGIN_INFO >() );
	return CErrorBool( (*pMsg) );
}

// メニューが押下されたときのコールバック
void CPlugin::PluginMenuCallback( Widget wgt, XtPointer client_data, XtPointer call_data )
{
	CPlugin *p = reinterpret_cast< CPlugin* >( client_data );
	p->OnMenu( wgt );
}

// メニューが選択された時のイベント
void CPlugin::OnMenu( Widget wgt )
{
	int i;

	// 押されたメニューがいずれか、を検索する。
	for ( i = 0; i < vPluginInfo.size() && wgt != vPluginInfo[i].wgtMenuBtn; i++ );
	if ( i >= vPluginInfo.size() ) {
		VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
		m_TaEditShell->ProcErrorMsg( pConf->RefUnexpectedErrorMsg() );
		return ;
	}

	// 該当するプラグインを呼び出す
	pCurPlugin = vPluginInfo.begin() + i;
	Modified = false;
	CallPlugin( pCurPlugin->LibraryName, pCurPlugin->FunctionName );
	pCurPlugin = NULL;

	// プラグインの実行により変更された場合は、テキスト編集終了を通知する
	if ( Modified ) {
		m_TaEditShell->GetDocument()->EndEdit();
	}
}

// プラグインの実行
////////////////////////////////////////////////////////////////////////////

// プラグインの関数を呼び出す
void CPlugin::CallPlugin( const string &rLibraryName, const string &rFunctionName )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	void* handle = NULL;
	void (*pFunc)( void* (*)( unsigned int ) ) = NULL;
	CCompoundStr CS;

	// ライブラリをロードする
	handle = dlopen( rLibraryName.c_str(), RTLD_LOCAL | RTLD_FIRST | LM_ID_LDSO );
	if ( NULL == handle ) {
		pConf->GetFaultLoadPluginLibraryMsg( &CS, rLibraryName, dlerror() );
		m_TaEditShell->ProcErrorMsg( CS );
		return ;
	}

	// 関数のアドレスを取得する
	pFunc = reinterpret_cast< void(*)( void* (*)( unsigned int ) ) >( dlsym( handle, rFunctionName.c_str() ) );
	if ( NULL == pFunc ) {
		pConf->GetFaultGetPluginFunctionMsg( &CS, rLibraryName, rFunctionName, dlerror() );
		m_TaEditShell->ProcErrorMsg( CS );
		goto ERR_EXIT;
	}

	// 関数を呼び出す
	pFunc( GetFunctionAddress );

ERR_EXIT:
	if ( handle ) dlclose( handle );
}

// プラグインの機能の実装
////////////////////////////////////////////////////////////////////////////

// カーソル位置の取得
const T_CurPos& CPlugin::GetCurPos() const
{
	assert( NULL != this && NULL != pView );
	return pView->GetCursorInfo()->GetCurPos();
}

// 選択範囲の取得
const T_SelRange& CPlugin::GetSelRange() const
{
	assert( NULL != this && NULL != pView );
	return pView->GetCursorInfo()->GetSelRange();
}

// カーソル位置と選択範囲の設定
bool CPlugin::SetCurPos_SelRange( const T_CurPos &rCurPos, const T_SelRange& rSelRange )
{
	assert( NULL != this && NULL != pView );
	VClsPtr< CCursor > pCursor = pView->GetCursorInfo();
	ScrollInfoPacket scrPacket;
	T_CurPos vc[3] = { rCurPos, rSelRange.GetSPos(), rSelRange.GetEPos() };
	int i;

	// 引数を確認する
	const CTaEditDoc *pDoc = m_TaEditShell->GetDocument();
	if ( NULL == pDoc ) return false;
	const T_LineData& rData = pDoc->RefData();

	for ( i = 0; i < 3; i++ ) {
		if ( vc[i].GetLine() < 0 || vc[i].GetLine() >= rData.size() )
			return false;
		if ( vc[i].GetCPos() < 0 || vc[i].GetCPos() > rData[ vc[i].GetLine() ].length() )
			return false;
	}

	// カーソル位置を設定
	pCursor->SetCurPos( rCurPos, &scrPacket, rSelRange );

	// 再描画
	if ( scrPacket.NeedXScroll || scrPacket.NeedYScroll )
		pView->AfterEditRedraw( scrPacket );
	else
		pView->AfterSelRangeRedraw( scrPacket.ChangeSLine, scrPacket.ChangeELine );
	return true;
}

// 文字列の置換
bool CPlugin::Replace( const T_SelRange &rRange, const wchar_t *pStr )
{
	CTaEditDoc *pDoc = m_TaEditShell->GetDocument();
	T_CurPos NextPos;
	ScrollInfoPacket ScrPacket;
	bool r = true;

	// 指定された範囲の文字列を置換
	if ( !m_TaEditShell->ProcErrorMsg( pDoc->Insert( pStr, rRange.GetSPos(), rRange, &NextPos ) ) )
		r = false;

	// カーソルを設定
	pView->GetCursorInfo()->SetCurPos( NextPos, &ScrPacket, T_SelRange( NextPos, NextPos ) );

	// 再描画
	pView->AfterEditRedraw( ScrPacket );

	// テキストが変更されたことを示す
	Modified = true;

	return r;
}

// 現在実行中のプラグインの設定情報を取得する
const wchar_t* CPlugin::GetCurPluginConfig( const wchar_t *pName ) const
{
	assert( NULL != this );
	// 必要な情報が取得できなかった場合はNULLを返す
	if ( NULL == pCurPlugin || NULL == pPluginConf ) return NULL;
	// 設定情報を検索する
	return exconf_GetConstStr( pPluginConf, pCurPlugin->SectionName.c_str(), pName );
}

// 拡張情報を設定する
bool CPlugin::SetExtendInfo( unsigned long Line, int idx, bool flg )
{
	CTaEditDoc *pDoc = m_TaEditShell->GetDocument();
	ScrollInfoPacket ScrPacket;
	bool r = true;

	if ( !m_TaEditShell->ProcErrorMsg( pDoc->SetExtendInfo( Line, idx, flg ) ) )
		return false;

	// 再描画（選択範囲の変化もなく、スクロールもしない）
	ScrPacket.NeedXScroll = false;
	ScrPacket.NeedYScroll = false;
	ScrPacket.IsSelCange = false;
	pView->AfterEditRedraw( ScrPacket );

	return true;
}

// 現在実行しているプラグインを識別するIDを取得
const wchar_t* CPlugin::GetPluginID() const
{
	// プラグイン設定ファイルのセクション名で識別する
	if ( NULL == pCurPlugin ) return NULL;
	return pCurPlugin->SectionName.c_str();
}

// 作業用メモリ管理オブジェクトを取得する
CWorkMemoryMgr& CPlugin::GetWorkMemoryMgr()
{
	return m_WorkMemoryMgr;
}

// テキスト編集フラグをクリアする
void CPlugin::ClearModifiedFlg()
{
	// ファイルの保存やオープンを行ったときは、テキスト編集フラグをおろす必要がある
	Modified = false;
}

