#include <stdio.h>
/////////////////////////////////////////////////////////////
// TaEditClient.c
// 主筆クライアント

#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <door.h>
#include <string.h>
#include <sys/mman.h>
#include <memory.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <limits.h>

#include <string>
#include <list>
#include <map>

#include "VPtr.h"
#include "CmdOpt.h"
#include "CommandID.h"
#include "NegCmd.h"
#include "ProcStart.h"
#include "DoorClient.h"

using namespace NNabikiVPtr;
using namespace std;

static string AddYen( const string& rStr );
static string ResolvPath( const string &r );
static string AttachStdIO( int d );
static char *CopyString( const char* p );
static string GetDoorFilePath();
static bool StartServer();
static bool StartSyuhitu( const CCmdOpt &option );
static bool StartSyuhituByServer( const CCmdOpt &option );
static bool SendCommand( const CNegCmd &negcmd );
static void StopServer( bool ByForce );

// 末尾に/がついてることを保証する
string AddYen( const string& rStr )
{
	if ( rStr.length() == 0 ) return "/";
	if ( rStr[ rStr.length() - 1 ] != '/' )
		return rStr + '/';
	else
		return rStr;
}

// パス名を解決する
string ResolvPath( const string &r )
{
	char Buf[ PATH_MAX + 10 ];

	if ( r.empty() ) return "";
	if ( realpath( r.c_str(), Buf ) )
		return Buf;
	else
		return r;
}

// 標準入出力などをファイルに関連づける
string AttachStdIO( int d )
{
	char Buf[ L_tmpnam + 1];
	int fd;

	// テンポラリのファイル名を構築
	tmpnam( Buf );

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

	// 念のため
	fdetach( Buf );

	// アタッチする
	if ( !fattach( d, Buf ) )
		return Buf;	// 成功

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

// 文字列を複製
char *CopyString( const char* p )
{
	int len;
	char *r;
	assert( p );

	len = strlen( p );
	r = (char*)malloc( sizeof( char ) * ( len + 1 ) );
	if ( !r ) {
		fprintf( stderr, "ERROR Syuhitu client : Out of memory.\n" );
		return NULL;
	}
	strcpy( r, p );
	return r;
}

// doorのファイル名を取得する
string GetDoorFilePath()
{
	string r;
	const char *p = getenv( SCEV_COMMUNITY_PATH );
	if ( !p ) {
		fprintf( stderr, "ERROR Syuhitu client : %s environment valuable is empty.\n", SCEV_COMMUNITY_PATH );
		return "";
	}

	r = AddYen( string( p ) ) + SCPX_DOOR;

	return r;
}

// サーバを起動する
bool StartServer()
{
	string DoorFileName = GetDoorFilePath();
	const char *pServerPath = getenv( SCEV_SERVER_PATH );
	char wBuf[256];
	int fd[2] = { -1, -1 };
	bool r;
	int i;
	CProcStart procStart;

	// doorが有効か否か確認する
	// 使用可能であれば、特にすることはない
	if ( CDoorClient::CheckDoor( DoorFileName.c_str() ) )
		return true;

	if ( DoorFileName.empty() ) goto ERR_EXIT;

	// サーバとの通信用のパイプを構築
	if ( pipe( fd ) ) goto ERR_EXIT;

	// サーバを起動
	snprintf( wBuf, 256, "%d", fd[1] );
	procStart.SpecifyCommand( string( pServerPath ) );
	procStart.AddArg( DoorFileName );
	procStart.AddArg( string( wBuf ) );
	r = procStart.Start();
	if ( r < 0 ) goto ERR_EXIT;

	// サーバからの応答を待つ
	i = read( fd[0], wBuf, 256 );
	if ( i > 0 && i < 256 ) {
		wBuf[i] = '\0';
		if ( strncmp( wBuf,"OK\n", i ) )
			r = false;
	}

ERR_EXIT:
	// パイプを閉じる
	if ( fd[0] >= 0 ) close( fd[0] );
	if ( fd[1] >= 0 ) close( fd[1] );

	// 必要に応じてエラーメッセージを表示する
	if ( !r )
		fprintf( stderr, "ERROR Syuhitu client : It failed to start server.\n" );

	return r;
}


// 主筆を起動
bool StartSyuhitu( const CCmdOpt &option )
{
	const char *p;
	CProcStart procStart;

	p = getenv( SCEV_TAEDIT_PATH );
	if ( NULL == p ) {
		fprintf( stderr, "ERROR Syuhitu client : %s environment valuable is empty.\n", SCEV_TAEDIT_PATH );
		return false;
	}

	procStart.SpecifyCommand( string( p ) );

	if ( option.GetFileName().length() > 0 )
		procStart.AddArg( string( "/file:" ) + option.GetFileName() );

	if ( option.GetLineNumber() > 0 ) {
		char wBuf[128];
		snprintf( wBuf, 128, " /line:%d", option.GetLineNumber() );
		procStart.AddArg( string( wBuf ) );
	}

	if ( option.GetLangType().length() > 0 )
		procStart.AddArg( string( " /syntax:" ) + option.GetLangType() );

	if ( option.GetStandAlone() )
		procStart.AddArg( string( " /standalone" ) );

	if ( procStart.Start() < 0 ) {
		fprintf( stderr, "ERROR Syuhitu client : It failed to start Syuhitu." );
		return false;
	}
	return true;
}

// サーバ経由で主筆を起動する
bool StartSyuhituByServer( const CCmdOpt &option )
{
	string StdOutName;	// 標準出力を関連づけたファイルのパス名
	string StdErrName;	// 同標準エラー出力
	string FileName;	// 開くファイル名
	int LineNumber;		// 行番号
	string LangType;	// 構文種別
	CNegCmd negcmd;
	bool r = false;
	int len;
	int i;

	// 標準出力と標準エラー出力をファイルに関連づける
	StdOutName = AttachStdIO( 1 );
	if ( StdOutName.length() == 0 )
		fprintf( stderr, "WARNIG Syuhitu client : It failed to attach stdout to file.\n" );

	StdErrName = AttachStdIO( 2 );
	if ( StdErrName.length() == 0 )
		fprintf( stderr, "WARNIG Syuhitu client : It failed to attach stderr to file.\n" );

	// オプションの値を取得
	FileName = ResolvPath( option.GetFileName() );
	LineNumber = option.GetLineNumber();
	LangType = option.GetLangType();

	// コマンド種別を設定
	negcmd.AddData( SCI_S_COMMAND, SCID_CMD_OPENFILE_CS );

	// ファイル名
	if ( !FileName.empty() )
		negcmd.AddData( SCI_S_FILE_NAME, FileName );

	// 行番号
	if ( LineNumber > 0 )
		negcmd.AddData( SCI_S_LINE_NUMBER, LineNumber );

	// 構文種別
	if ( !LangType.empty() )
		negcmd.AddData( SCI_S_SYNTAX, LangType );

	// 標準出力のファイル名
	if ( !StdOutName.empty() )
			negcmd.AddData( SCI_S_STDOUT, StdOutName );

	// 標準エラー出力のファイル名
	if ( !StdErrName.empty() )
		negcmd.AddData( SCI_S_STDERR, StdErrName );

	// サーバにコマンドを送信する
	r = SendCommand( negcmd );

	// 標準出力などにアタッチされたファイルを削除する
	if ( StdOutName.length() > 0 ) {
		fdetach( StdOutName.c_str() );
		remove( StdOutName.c_str() );
	}
	if ( StdErrName.length() > 0 ) {
		fdetach( StdErrName.c_str() );
		remove( StdErrName.c_str() );
	}

	return r;
}

// サーバにコマンドを送信する
bool SendCommand( const CNegCmd &negcmd )
{
	string DoorFileName;
	VPtr< char > pRetBuf;
	CDoorClient doorClient;
	string ServerErrorMsg;

	// doorのファイル名を取得
	DoorFileName = GetDoorFilePath();
	if ( DoorFileName.length() <= 0 ) return false;

	// doorを開く
	if ( !doorClient.Open( DoorFileName.c_str() ) ) return false;

	// doorを呼び出す
	pRetBuf = doorClient.Call( &negcmd );
	if ( !pRetBuf.GetPtr() ) {
		// 呼び出しに失敗した
		fprintf( stderr, "ERROR Syuhitu client : It failed to call server.\n" );
		return false;
	}

	// 結果を解釈する
	CNegCmd rmsg( pRetBuf.GetPtr(), pRetBuf.GetLen() );

	// OKなら終了
	if ( rmsg.GetData( SCI_R_STATUS, SCID_RSTAT_ERR ) == SCID_RSTAT_OK )
		return true;

	// エラーメッセージを決定
	switch ( rmsg.GetData( SCI_R_MSG_NUMBER, SCID_RMSG_UNEXPENTED ) ) {
	case SCID_RMSG_OUTOFMEMORY:
		ServerErrorMsg = "Out of memory";
		break;
	case SCID_RMSG_FILEISOPEND:
		ServerErrorMsg = "Specified file has been opened by another syuhitu.";
		break;
	case SCID_RMSG_TAEDIT_IS_EXIST:
		ServerErrorMsg = "Syuhitu instance is exist.";
		break;
	case SCID_RMSG_SERVER_REVOKED:
		ServerErrorMsg = "Server will be revoked.";
		break;
	default:
		ServerErrorMsg = rmsg.GetData( SCI_R_FREE_MSG, "" );
		if ( ServerErrorMsg.empty() )
			ServerErrorMsg = "Unexpected error";
	}

	// エラーメッセージを表示する
	fprintf( stderr,
		"ERROR Syuhitu client : Error was occurred in server - %s\n",
		ServerErrorMsg.c_str()
	);

	return false;
}

// サーバを停止させる
void StopServer( bool ByForce )
{
	CNegCmd negcmd;

	if ( ByForce )
		negcmd.AddData( SCI_S_COMMAND, SCID_CMD_STOP_FORCE_CS );
	else
		negcmd.AddData( SCI_S_COMMAND, SCID_CMD_STOP_CS );

	if ( SendCommand( negcmd ) ) {
		// 成功したら一応メッセージを表示しておく
		fprintf( stderr, "Syuhitu client : Succeeded to stop server.\n" );
	}
}

int main( int argc, char *argv[] )
{
	string DoorFileName;
	CCmdOpt option;

	// コマンドのオプションを解釈
	if ( !option.Initialize( argc, argv ) ) return 0;

	switch ( option.GetCmdType() ) {
	case CT_FILE_OPEN:
		// ファイルのオープン

		if ( option.GetStandAlone() ) {
			// スタンドアロンの場合は、主筆を起動して終了
			if ( StartSyuhitu( option ) )
				return 0;
			else
				return 1;
		}

		// サーバを起動
		if ( !StartServer() ) return 1;

		// 主筆を起動する
		if ( !StartSyuhituByServer( option ) ) return 1;

		break;

	case CT_BOOT_SERVER:
		// サーバの起動のみ
		if ( !StartServer() ) return 1;
		break;

	case CT_STOP_SERVER:
		// サーバの停止
		StopServer( option.GetStopSvrByForce() );
		break;
	}

	return 0;
}
