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

#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <door.h>
#include <unistd.h>
#include <assert.h>
#include <alloca.h>

#include "Server.h"
#include "NegCmd.h"
#include "CommandID.h"

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

CServer::CServer( CSyInstance *pI ) :
	semaWait( 1 ),
	pInstanceMgr( pI ),
	semaAcsInstance( 1 ),
	IsRevoked( false )
{
	assert( pI );
}

CServer::~CServer()
{
	semaWait.V();
}

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

// doorとして公開される手続き
extern"C" void DoorEntry( void *cookie, char *argp, size_t arg_size, door_desc_t *dp, uint_t desc )
{
	CServer *p = reinterpret_cast< CServer* >( cookie );
	p->Door( argp, arg_size, dp, desc );
}

// doorサーバを起動する
bool CServer::StartServer( const char *pFName, int fd )
{
	int dd;
	struct stat wStat;
	int w;

	assert( this && pFName );

	// doorをアタッチするためのファイルを用意する
	if ( lstat( pFName, &wStat ) < 0 ) {
		int newfd;
		// ファイルを作成する
		newfd = creat( pFName, 0600 );
		if ( newfd < 0 ) {
			fprintf( stderr, "ERROR Syuhitu server : It failed to create %s file.\n", pFName );
			return false;
		}
		close( newfd );
	}
	else {
		// すでにdoorに関連づけられていて、
		// doorサーバが有効であれば、特にすることはない
		if ( wStat.st_mode & S_IFDOOR && EnableDoorServer( pFName ) )
			return true;
	}

	// doorを生成する
	dd = door_create( DoorEntry, this, 0 );
	if ( dd < 0 ) return false;

	fdetach( pFName );

	// 構築したdoorのファイル記述子をアタッチする
	if ( fattach( dd, pFName ) < 0 ) {
		fprintf( stderr, "ERROR Syuhitu server : It failed to fattach %s file.\n", pFName );
		return false;
	}

	DoorFileName = pFName;
	IsRevoked = false;

	// すでに起動している主筆の情報を取得
	pInstanceMgr->UpdateExistTaEditInfo();

	// 起動プロセスに、サーバが起動したことを通知してやる
	write( fd, "OK\n", 3 );

	// サーバの処理が終了するまで待ち合わせる
	semaWait.P();
	semaWait.P();	// 誰かがV操作をしてくれるまで待つ
	semaWait.V();
	IsRevoked = true;

	// doorサーバを破棄する
	if ( door_revoke( dd ) ) {
		fprintf( stderr, "ERROR Syuhitu server : It failed to revoke server.\n" );
	}
	fdetach( pFName );
	remove( pFName );

	return true;
}

// doorサーバの状態を確認
bool CServer::EnableDoorServer( const char *pFName )
{
	int d;
	bool r = false;
	struct door_info info;

	// doorとおぼしきファイルを開く
	d = open( pFName, O_RDWR );
	if ( d < 0 ) return false;

	// doorの情報を取得
	if ( !door_info( d, &info ) ) {
		if ( !( info.di_attributes & DOOR_REVOKED ) )
			r = true;
	}

	close( d );

	return r;
}

// doorとして公開される手続き
void CServer::Door( char *argp, size_t arg_size, door_desc_t *dp, uint_t desc )
{
	CNegCmd ToClient;
	string ToClientStr;
	char *pRetBuf = NULL;
	int i;
	int RetryCnt = 2;

	// コマンドを解釈する
	CNegCmd negcmd( argp, arg_size );

	// コマンドの種別を取得する
	int CmdType = negcmd.GetData( SCI_S_COMMAND, SC_NOT_AVAILABLE );
	if ( CmdType == SC_NOT_AVAILABLE ) {
		ToClient.AddData( SCI_R_STATUS, SCID_RSTAT_ERR );
		ToClient.AddData( SCI_R_STOPPED, SCID_RSTOP_STOP );
		ToClient.AddData( SCI_R_FREE_MSG, "Unexpected error. Command is empty." );
		RetryCnt = 0;
	}

	while ( RetryCnt > 0 ) {

		// 主筆のインスタンスにアクセスできるようになるまで待ち合わせる
		semaAcsInstance.P();

		// 待ち合わせている間に、サーバが停止させられたか否かを確認する
		if ( IsRevoked ) {
			semaAcsInstance.V();
			ToClient.AddData( SCI_R_STATUS, SCID_RSTAT_ERR );
			ToClient.AddData( SCI_R_STOPPED, SCID_RSTOP_RESUME );
			ToClient.AddData( SCI_R_MSG_NUMBER, SCID_RMSG_SERVER_REVOKED );
			RetryCnt = 0;
			break;
		}

		// コマンドの種類別に処理を行う
		switch ( CmdType ) {
		case SCID_CMD_OPENFILE_CS:
			// ファイルを開く
			pInstanceMgr->OpenFile_CS( negcmd, &ToClient );
			break;
		case SCID_CMD_OPENFILE_TS:
			// 主筆でファイルが開かれる
			pInstanceMgr->OpenFile_TS( negcmd, &ToClient );
			break;
		case SCID_CMD_NEWFILE_TS:
			// 主筆でファイルが新規に作成される
			pInstanceMgr->NewFile_TS( negcmd, &ToClient );
			break;
		case SCID_CMD_WRITEFILE_TS:
			// 主筆で名前を付けて保存が行われる
			pInstanceMgr->WriteFile_TS( negcmd, &ToClient );
			break;
		case SCID_CMD_EXIT_TS:
			// 主筆が終了する
			pInstanceMgr->Exit_TS( negcmd, &ToClient );
			break;
		case SCID_CMD_STOP_CS:
			// サーバを停止する
			if ( pInstanceMgr->CanBeStopServer() ) {
				// 停止できる場合は停止する
				IsRevoked = true;
				semaWait.V();
				ToClient.AddData( SCI_R_STATUS, SCID_RSTAT_OK );
				ToClient.AddData( SCI_R_STOPPED, SCID_RSTOP_RESUME );
			}
			else {
				// 主筆のインスタンスが残っているため、停止できない
				ToClient.AddData( SCI_R_STATUS, SCID_RSTAT_ERR );
				ToClient.AddData( SCI_R_STOPPED, SCID_RSTOP_RESUME );
				ToClient.AddData( SCI_R_MSG_NUMBER, SCID_RMSG_TAEDIT_IS_EXIST );
			}
			break;
		case SCID_CMD_STOP_FORCE_CS:
			// サーバを強制的に停止する
			pInstanceMgr->StopServerByForce_CS();
			ToClient.AddData( SCI_R_STATUS, SCID_RSTAT_OK );
			ToClient.AddData( SCI_R_STOPPED, SCID_RSTOP_RESUME );
			IsRevoked = true;
			semaWait.V();
			break;
		case SCID_CMD_OPENFILE_NW_TS:
			// 新しいウインドウでファイルを開く
			pInstanceMgr->OpenFileNW_TS( negcmd, &ToClient );
			break;
		case SCID_CMD_JOIN_TS:
			// 主筆が新しくコンストラクション・モードに参加する
			pInstanceMgr->Join_TS( negcmd, &ToClient );
			break;
		case SCID_CMD_UNJOIN_TS:
			// 主筆がコンストラクション・モードから脱退する
			pInstanceMgr->Exit_TS( negcmd, &ToClient );
			break;
		}

		// エラーを返した場合は、起動している主筆の情報を更新してから、
		// もう一度処理する。（クライアントからのコマンドの場合のみ）
		if ( ToClient.GetData( SCI_R_STATUS, SCID_RSTAT_ERR ) == SCID_RSTAT_ERR &&
				RetryCnt > 1 &&
				CmdType & SCID_CMD_CS_MASK ) {

			// 起動中の主筆の情報を更新
			pInstanceMgr->UpdateExistTaEditInfo();
			// 戻り値のメッセージを初期化
			ToClient.Initialize();
			RetryCnt--;
		}
		else
			RetryCnt = 0;

		semaAcsInstance.V();
	}

	// 呼び出し元に結果を返す
	ToClientStr = ToClient.GetSendData();
	pRetBuf = (char*)alloca( sizeof( char ) * ToClientStr.length() );
	memcpy( pRetBuf, ToClientStr.c_str(), ToClientStr.length() );

	door_return( pRetBuf, ToClientStr.length(), NULL, 0 );
}

