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

#include "CommandID.h"
#include "Consruction.h"
#include "TaEditShell.h"
#include "ConfigInfo.h"
#include "TaEdit_glo.h"
#include "NegCmd.h"
#include "VPtr.h"
#include "lang/LangConfig.h"
#include "ProcStart.h"

#include <unistd.h>

using namespace std;
using namespace NNabikiVPtr;

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

CConsruction::CConsruction() :
	toSvrFD( -1 ),
	fromSvrFD( -1 ),
	EnableInputSource( false ),
	DetachConstruction( false )
{

}

CConsruction::~CConsruction()
{
	Clear();
}

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

// 初期化
bool CConsruction::Initialize()
{
	int tsPipe[2] = { -1, -1 };	// サーバ->主筆のパイプ
	int fsPipe[2] = { -1, -1 };	// 主筆->サーバのパイプ
	pid_t pid;

	pid = getpid();
	if ( pid == (pid_t)-1 ) return false;

	// 入出力用のパイプを構築する
	if ( pipe( tsPipe ) ) goto ERR_EXIT;
	if ( pipe( fsPipe ) ) goto ERR_EXIT;

	// ファイルにアタッチする
	toSvrFileName = AttachFile( pid, SCPX_FROM_SYUHITU, tsPipe[0] );
	fromSvrFName = AttachFile( pid, SCPX_TO_SYUHITU, fsPipe[1] );
	if ( toSvrFileName.empty() || fromSvrFName.empty() )
		goto ERR_EXIT;

	// doorを開く
	if ( !OpenDoor() ) goto ERR_EXIT;

	toSvrFD = tsPipe[1];
	fromSvrFD = fsPipe[0];

	// メッセージの受信関数を登録
	InputID = XtAppAddInput( app_context, fromSvrFD, (XtPointer)XtInputReadMask, ReceiveMsg_entry, this );
	EnableInputSource = true;

	DetachConstruction = false;
	return true;
	
ERR_EXIT:
	if ( tsPipe[0] >= 0 ) close( tsPipe[0] );
	if ( tsPipe[1] >= 0 ) close( tsPipe[1] );
	if ( fsPipe[0] >= 0 ) close( fsPipe[0] );
	if ( fsPipe[1] >= 0 ) close( fsPipe[1] );
	Clear();
	return false;
}

// 破棄
void CConsruction::Clear()
{
	// パイプを閉じる
	if ( toSvrFD >= 0 ) close( toSvrFD );
	if ( fromSvrFD >= 0 ) close( fromSvrFD );
	toSvrFD = -1;
	fromSvrFD = -1;

	// パイプをアタッチしたファイルを削除する
	if ( !toSvrFileName.empty() ) {
		fdetach( toSvrFileName.c_str() );
		unlink( toSvrFileName.c_str() );
		toSvrFileName = "";
	}
	if ( !fromSvrFName.empty() ) {
		fdetach( fromSvrFName.c_str() );
		unlink( fromSvrFName.c_str() );
		fromSvrFName = "";
	}

	// doorを閉じる
	Door.Close();

	// Xtに登録した入力ソースの登録を解除する
	if ( EnableInputSource ) {
		XtRemoveInput( InputID );
		EnableInputSource = false;
	}
}

// ファイルを開くことをサーバに通知する
CErrorBool CConsruction::OpenFile( const string &FName, bool *pComplateFlg )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	CNegCmd negcmd;
	int pid = getpid();

	assert( NULL != this && NULL != pComplateFlg );
	if ( pid < 0 ) return CErrorBool( pConf->RefFailToGetPID() );

	// メッセージを構築
	negcmd.AddData( SCI_S_COMMAND, SCID_CMD_OPENFILE_TS );
	negcmd.AddData( SCI_S_FILE_NAME, FName );
	negcmd.AddData( SCI_S_PID, pid );

	// メッセージを送信
	return SetEventToServer( negcmd, pComplateFlg );
}

// 新しくファイルを作成することをサーバに通知する
CErrorBool CConsruction::NewFile( bool *pComplateFlg )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	CNegCmd negcmd;
	int pid = getpid();

	assert( NULL != this && NULL != pComplateFlg );
	if ( pid < 0 ) return CErrorBool( pConf->RefFailToGetPID() );

	// メッセージを構築
	negcmd.AddData( SCI_S_COMMAND, SCID_CMD_NEWFILE_TS );
	negcmd.AddData( SCI_S_PID, pid );

	// メッセージを送信
	return SetEventToServer( negcmd, pComplateFlg );
}

// ファイルに名前を付けて保存する事をサーバに通知する
CErrorBool CConsruction::WriteFile( const string &FName, bool *pComplateFlg )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	CNegCmd negcmd;
	int pid = getpid();

	assert( NULL != this && NULL != pComplateFlg );
	if ( pid < 0 ) return CErrorBool( pConf->RefFailToGetPID() );

	// メッセージを構築
	negcmd.AddData( SCI_S_COMMAND, SCID_CMD_WRITEFILE_TS );
	negcmd.AddData( SCI_S_FILE_NAME, FName );
	negcmd.AddData( SCI_S_PID, pid );

	// メッセージを送信
	return SetEventToServer( negcmd, pComplateFlg );
}

// 終了する事をサーバに通知する
CErrorBool CConsruction::OnExit()
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	CNegCmd negcmd;
	bool wCF;	// ダミー
	int pid = getpid();

	assert( NULL != this );
	if ( pid < 0 ) return CErrorBool( pConf->RefFailToGetPID() );

	// メッセージを構築
	negcmd.AddData( SCI_S_COMMAND, SCID_CMD_EXIT_TS );
	negcmd.AddData( SCI_S_PID, pid );

	// メッセージを送信
	return SetEventToServer( negcmd, &wCF );
}

// 新しく主筆を起動してファイルを開く
CErrorBool CConsruction::OpenFileWithNewWnd( const string &FName, int Line, bool *pComplateFlg )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	CNegCmd negcmd;
	int pid = getpid();

	assert( NULL != this );
	if ( pid < 0 ) return CErrorBool( pConf->RefFailToGetPID() );

	// メッセージを構築
	// コマンド種別を設定
	negcmd.AddData( SCI_S_COMMAND, SCID_CMD_OPENFILE_NW_TS );
	if ( !FName.empty() )
		negcmd.AddData( SCI_S_FILE_NAME, FName );
	if ( Line > 0 )
		negcmd.AddData( SCI_S_LINE_NUMBER, Line );
	negcmd.AddData( SCI_S_PID, pid );

	// メッセージを送信
	return SetEventToServer( negcmd, pComplateFlg );
}

// コンストラクション・モードへの参加を通知する
CErrorBool CConsruction::NoticeJoin( const string &FName )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	CNegCmd negcmd;
	bool wFLG;
	int pid = getpid();
	CErrorBool r;

	assert( NULL != this );
	if ( pid < 0 ) return CErrorBool( pConf->RefFailToGetPID() );

	negcmd.AddData( SCI_S_COMMAND, SCID_CMD_JOIN_TS );
	negcmd.AddData( SCI_S_PID, pid );
	if ( !FName.empty() ) negcmd.AddData( SCI_S_FILE_NAME, FName );
	return SetEventToServer( negcmd, &wFLG );
}

// コンストラクション・モードからの脱退を通知する
CErrorBool CConsruction::NoticeUnJoin()
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	CNegCmd negcmd;
	bool wFLG;
	int pid = getpid();

	// 切り離しの指示がでているのだから、わざわざ脱退の通知をする必要はない
	if ( DetachConstruction ) return true;

	assert( NULL != this );
	if ( pid < 0 ) return CErrorBool( pConf->RefFailToGetPID() );

	negcmd.AddData( SCI_S_COMMAND, SCID_CMD_UNJOIN_TS );
	negcmd.AddData( SCI_S_PID, pid );
	return SetEventToServer( negcmd, &wFLG );
}

// ファイルにアタッチする
string CConsruction::AttachFile( int pid, const char *prefix, int fd ) const
{
	char buf[15];
	char *p;
	string fname;
	int wd;

	p = getenv( SCEV_COMMUNITY_PATH );
	if ( NULL == p ) return "";

	// アタッチ対象のファイル名を合成
	snprintf( buf, sizeof( buf ) * sizeof( char ), "%s%d", prefix, pid );
	fname = p;
	if ( fname[ fname.length() - 1 ] != '/' )
		fname += '/';
	fname += buf;

	// ファイルを作成する
	wd = open( fname.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600 );
	if ( wd < 0 ) return "";
	close( wd );

	// 念のため
	fdetach( fname.c_str() );

	// アタッチする
	if ( !fattach( fd, fname.c_str() ) )
		return fname;	// 成功

	// 失敗したからファイルを削除する
	remove( fname.c_str() );
	return "";
}

// サーバにイベントを通知する
CErrorBool CConsruction::SetEventToServer( const CNegCmd &negcmd, bool *pComplateFlg )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	VPtr< char > pRet;
	bool SvrDownFlg = false;
	int len;
	int i;

	assert( NULL != this && NULL != pComplateFlg );
	if ( !Door.Enable() )
		return CErrorBool( pConf->RefConstructionCommunicationErrorMsg() );

	// メッセージを送信
	pRet = Door.Call( &negcmd, &SvrDownFlg );
	if ( !pRet.GetPtr() && SvrDownFlg ) {
		// サーバが落ちていたら、サーバを起動する
		if ( StartServer() ) {
			// ユーザに同じ操作をもう一度行わせる
			(*pComplateFlg) = true;
			return CErrorBool( pConf->RefRebootServerMsg() );
		}
		else {
			// 諦める
			(*pComplateFlg) = false;
			return CErrorBool( pConf->RefFailedToRebootServerMsg() );
		}
	}

	// 戻り値を解釈
	CNegCmd rmsg( pRet.GetPtr(), pRet.GetLen() );

	// 処理の続行可否を取得
	if ( rmsg.GetData( SCI_R_STOPPED, SCID_RSTOP_STOP ) == SCID_RSTOP_RESUME )
		(*pComplateFlg) = false;	// 続行
	else
		(*pComplateFlg) = true;		// 処理中断

	// 定型的なエラーメッセージがあったら、それに応じたメッセージを表示させる
	switch( rmsg.GetData( SCI_R_MSG_NUMBER, SCID_RMSG_UNEXPENTED ) ) {
	case SCID_RMSG_OUTOFMEMORY:
		return CErrorBool( pConf->RefOutOfMemoryErrorMsg() );
	case SCID_RMSG_FILEISOPEND:
		return CErrorBool( pConf->RefFileIsOpenedByAnotherMsg() );
	case SCID_RMSG_SERVER_REVOKED:
		// サーバが停止されようとしている場合は、スタンドアロンモードへ移行する
		// （止まろうとしているのに再起動しても仕方がない）
		DetachConstruction = true;
		return CErrorBool( pConf->RefServerRevokedMsg() );
	}

	// 非定型なメッセージがあった場合は、念のため標準エラー出力に表示する
	const string &FreeMsg = rmsg.GetData( SCI_R_FREE_MSG, "" );
	if ( !FreeMsg.empty() )
		fprintf( stderr, "Syuhitu : It failed to communication to server : \"%s\".", FreeMsg.c_str() );

	// 正常終了で、かつ、メッセージがなければ真を返す
	if ( rmsg.GetData( SCI_R_STATUS, SCID_RSTAT_ERR ) == SCID_RSTAT_OK )
		return true;

	// その他のエラーが存在した場合
	fprintf( stderr, "Syuhitu : It failed to communication to server with unknown error." );
	return CErrorBool( pConf->RefConstructionCommunicationErrorMsg() );
}

// doorを開く
bool CConsruction::OpenDoor()
{
	string DoorFileName;
	const char *p;

	// doorのファイル名を取得
	p = getenv( SCEV_COMMUNITY_PATH );;
	if ( NULL == p ) return false;
	DoorFileName = p;
	if ( DoorFileName.length() == 0 ) return false;
	if ( DoorFileName[ DoorFileName.length() - 1 ] != '/' )
		DoorFileName += '/';
	DoorFileName += SCPX_DOOR;

	// doorを開く
	return Door.Open( DoorFileName.c_str() );
}

// doorサーバを起動させる
bool CConsruction::StartServer()
{
	int pid;
	int status;
	CProcStart procStart;
	const char *p = getenv( SCEV_CLIENT_PATH );
	if ( NULL == p ) return false;

	// クライアントを起動させるコマンドと引数を指定
	procStart.SpecifyCommand( string( p ) );
	procStart.AddArg( string( "-b" ) );

	// クライアントを起動
	pid = procStart.Start();
	if ( pid < 0 ) return false;

	// クライアントの終了ステータスを取得して、
	// サーバ起動に成功したか否かを確認するべきではあるが、
	// それをやるとデッドロックしてしまう。
	return true;
}

////////////////////////////////////////////////////////////////////////////
// サーバからのメッセージを処理する

// サーバからのメッセージ受信のエントリポイント
void CConsruction::ReceiveMsg_entry( XtPointer ptr, int *source, XtInputId *id )
{
	CConsruction *p = reinterpret_cast< CConsruction* >( ptr );

	// メッセージを処理する
	p->ReceiveMsg( (*source) );

	if ( p->GetDetachConstruction() ) {
		// スタンドアロン・モードへ移行する
		m_TaEditShell->ProcErrorMsg( m_TaEditShell->SetStandaloneMode() );
	}
}

// サーバからのメッセージの受信
void CConsruction::ReceiveMsg( int src )
{
	CNegCmd negret;	// 戻り値
	string RetStr;
	string fname;

	if ( toSvrFD < 0 ) {
		// サーバへ結果が返せない
		fprintf( stderr, "Syuhitu : Unexpected error." );
		return ;
	}

	// コマンドを解釈する
	CNegCmd negcmd( src );

	switch ( negcmd.GetData( SCI_S_COMMAND, SC_NOT_AVAILABLE ) ) {
	case SCID_CMD_ACTIVATE_ST:
		// アクティブ化する
		if ( Activatewindow( negcmd ) )
			negret.AddData( SCI_R_STATUS, SCID_RSTAT_OK );
		else
			negret.AddData( SCI_R_STATUS, SCID_RSTAT_ERR );
		break;

	case SCID_CMD_RQ_FNAME_ST:
		// ファイル名を要求する
		fname = m_TaEditShell->GetDocument()->GetFileName();
		negret.AddData( SCI_S_FILE_NAME, fname.c_str() );
		break;

	case SCID_CMD_SVR_RESTART_ST:
		// サーバが再起動された
		Door.Close();
		if ( OpenDoor() )
			negret.AddData( SCI_R_STATUS, SCID_RSTAT_OK );
		else
			negret.AddData( SCI_R_STATUS, SCID_RSTAT_ERR );
		break;

	case SCID_CMD_SET_STANDALONE_ST:
		// スタンドアロンモードにする
		negret.AddData( SCI_R_STATUS, SCID_RSTAT_OK );
		// フラグを設定し、切り離させる
		DetachConstruction = true;
		break;

	default:
		// 認識できないコマンド
		negret.AddData( SCI_R_STATUS, SCID_RSTAT_ERR );
		negret.AddData( SCI_R_FREE_MSG, "Unknown command." );
	}

	// 応答を返す
	RetStr = negret.GetSendData();
	write( toSvrFD, RetStr.c_str(), RetStr.length() );
}

// ウインドウをアクティブ化する
bool CConsruction::Activatewindow( const CNegCmd &rCmd )
{
	int LineNumber;
	string Syntax;

	// 行番号と構文種別を取得
	LineNumber = rCmd.GetData( SCI_S_LINE_NUMBER, 0 );
	Syntax = rCmd.GetData( SCI_S_SYNTAX, "" );

	// 構文種別を設定する
	if ( !Syntax.empty() ) {
		enumLANGTYPE LangType = NLangExt::GetLangTypeID_ByLangName( Syntax.c_str() );
		switch ( LangType ) {
		case LANGTYPE_TEXT:
			m_TaEditShell->SetFileTypeTXT( NULL, NULL );
			break;
		case LANGTYPE_CPP:
			m_TaEditShell->SetFileTypeCPP( NULL, NULL );
			break;
		case LANGTYPE_JAVA:
			m_TaEditShell->SetFileTypeJAVA( NULL, NULL );
			break;
		case LANGTYPE_CBL:
			m_TaEditShell->SetFileTypeCBL( NULL, NULL );
			break;
		case LANGTYPE_JS:
			m_TaEditShell->SetFileTypeVBS( NULL, NULL );
			break;
		case LANGTYPE_VBS:
			m_TaEditShell->SetFileTypeJS( NULL, NULL );
			break;
		case LANGTYPE_CS:
			m_TaEditShell->SetFileTypeCS( NULL, NULL );
			break;
		}
	}

	// ウインドウをアクティブにする
	m_TaEditShell->RaiseWindow();

	// カーソルの位置を設定する
	if ( LineNumber > 0 )
		m_TaEditShell->MoveTo( LineNumber );

	return true;
}

// コンストラクションモードから切り離すか否か
bool CConsruction::GetDetachConstruction() const
{
	return DetachConstruction;
}


