////////////////////////////////////////////////////////////////////////////
// TaEditJPEXT.cc グローバル関数の定義
//
////////////////////////////////////////////////////////////////////////////

#include "TaEditJPEXT.h"
#include "TaEditShell.h"
#include "CompoundStr.h"


#include <Xm/XmAll.h>
#include <limits.h>
#include <iconv.h>
#include <langinfo.h>

using namespace std;

static bool ChangeEncodeSpecifiedCD( iconv_t cd, const char *pSrc, string *pDst );
static CErrorBool UCStoClipboardEncode( const wchar_t *pSrc, string *pDest );
static CErrorBool ClipboardEncodetoCUS( const char *pSrc, wstring *pDest );

/*//////////////////////////////////////////////////////////
// フォント操作用の関数などの定義
//////////////////////////////////////////////////////////*/

// フォント名からフォントセットを構築する
// XLoadQueryFont関数の置き換え
XFontSet JPEXT_CreateFontSet( Display *pDisplay, const char *pFontName )
{
	char **missing_list;
	int missing_count;
	char *def_string;
	XFontSet fontSet;

	// 指定されたフォント名でフォントセットを構築する
	fontSet = XCreateFontSet( pDisplay, pFontName, &missing_list, &missing_count, &def_string );

	// 成功したら、そのフォントセットを返す。
	if ( fontSet ) return fontSet;

	// 失敗したら、とりあえず何らかのフォントセットを構築する。
	fontSet = XCreateFontSet( pDisplay, "*", &missing_list, &missing_count, &def_string );

	// 成功したら、とりあえずそのフォントセットを返す。
	if ( fontSet ) return fontSet;

	// 失敗したらアボートする。
	fprintf( stderr, "Can't create fontset." );
	abort();
}


// フォントセットから、文字の幅を取得する
// flg = 0　最大
// flg = 1　最小
short JPEXT_GetFontWidth( XFontSet fs, int flg )
{
	XFontStruct **pvFontStr;
	char **pvFontName;
	short Width;
	int cnt;
	int i;

	// フォント構造体とフォント名のリストを取得
	cnt = XFontsOfFontSet( fs, &pvFontStr, &pvFontName );

	Width = SHRT_MAX * flg;
	// 最大もしくは最小の幅の値を取得
	for ( i = 0; i < cnt; i++ ) {
		if ( 0 == flg ) {
			if ( Width < pvFontStr[ i ]->max_bounds.width )
				Width = pvFontStr[ i ]->max_bounds.width;
		}
		else {
			if ( Width > pvFontStr[ i ]->min_bounds.width )
				Width = pvFontStr[ i ]->min_bounds.width;
		}
	}

	return Width;
}

// フォントセットから、最大の文字の幅を取得する
// (CFontStruct)->max_bounds.widthの置き換え
short JPEXT_GetMaxWidth( XFontSet fs )
{
	return JPEXT_GetFontWidth( fs, 0 );
}

// フォントセットから、最大の文字の幅を取得する
// (CFontStruct)->min_bounds.widthの置き換え
short JPEXT_GetMinWidth( XFontSet fs )
{
	return JPEXT_GetFontWidth( fs, 1 );
}

// ascentを取得
short JPEXT_GetFont_ascent( XFontSet fs )
{
	XFontStruct **pvFontStr;
	char **pvFontName;
	long Max = 0;
	int cnt = XFontsOfFontSet( fs, &pvFontStr, &pvFontName );
	int i;
	for ( i = 0; i < cnt; i++ ) {
		if ( Max < pvFontStr[ i ]->ascent )
			Max = pvFontStr[ i ]->ascent;
	}
	return Max;
}

// descentを取得
short JPEXT_GetFont_descent( XFontSet fs )
{
	XFontStruct **pvFontStr;
	char **pvFontName;
	long Max = 0;
	int i;
	int cnt = XFontsOfFontSet( fs, &pvFontStr, &pvFontName );
	for ( i = 0; i < cnt; i++ ) {
		if ( Max < pvFontStr[ i ]->descent )
			Max = pvFontStr[ i ]->descent;
	}
	return Max;
}

// フォントセットから、高さを取得
short JPEXT_GetFontHeight( XFontSet fs )
{
	return JPEXT_GetFont_ascent( fs ) + JPEXT_GetFont_descent( fs );
}

/*//////////////////////////////////////////////////////////
// 文字コードやエンコードの変換
//////////////////////////////////////////////////////////*/

// ワイドバイト文字列からマルチバイト文字列へ変換
bool JPEXT_WCStoMBS( const wchar_t *pWCS, std::string *pMBS )
{
	VPtr< char > pBuf;
	size_t len = wcstombs( NULL, pWCS, 0 ) + 1;
	if ( len == 0 ) return false;

	pMBS->clear();

	// メモリ領域を確保
	if ( !pBuf.Malloc( len * sizeof( wchar_t ) ) )
		return false;

	// マルチバイト文字列に変換
	if ( wcstombs( pBuf.GetPtr(), pWCS, len * sizeof( wchar_t ) ) == (size_t)-1 )
		return false;

	// 戻り値に設定
	(*pMBS) = pBuf.GetPtr();
	return true;
}

// マルチバイト文字列からワイドバイト文字列へ変換
bool JPEXT_MBStoWCS( const char *pMBS, std::wstring *pWCS )
{
	VPtr< wchar_t > pBuf;
	size_t len = mbstowcs( NULL, pMBS, 0 ) + 1;
	if ( len == 0 ) return false;
	pWCS->clear();

	// メモリ領域を確保
	if ( !pBuf.Malloc( len * sizeof( wchar_t ) ) )
		return false;

	// ワイドバイト文字列に変換
	if ( mbstowcs( pBuf.GetPtr(), pMBS, len * sizeof( wchar_t ) ) == (size_t)-1 )
		return false;

	// 戻り値に設定
	(*pWCS) = pBuf.GetPtr();
	return true;
}

// マルチバイト文字列間において、エンコードを変換する
bool JPEXT_ChangeEncode( const char *pSrc, string *pDst, const char *pSrcEncode, const char *pDestEncode )
{
	bool r;

	pDst->clear();
	iconv_t cd = iconv_open( pDestEncode, pSrcEncode );
	if ( cd == (iconv_t)-1 ) return false;
	r = ChangeEncodeSpecifiedCD( cd, pSrc, pDst );
	iconv_close( cd );
	return r;
}

// iconv_tを指定してのエンコード変換
bool ChangeEncodeSpecifiedCD( iconv_t cd, const char *pSrc, string *pDst )
{
	char OutBuf[128*8];	// iconvの出力バッファ
	size_t OutBufLen;	// OutBufのうちの使用できるバッファ長
	const char *ip;
	size_t InBufLen;
	char *op = OutBuf;
	int i;

	pDst->clear();
	InBufLen = strlen( pSrc ) * sizeof( char );
	ip = pSrc;
	op = OutBuf;
	OutBufLen = 128;
	while ( InBufLen > 0 ) {
		errno = 0;
		size_t r = iconv( cd, &ip, &InBufLen, &op, &OutBufLen );
		if ( r == (size_t)-1 ) {
			if ( errno == EILSEQ ) {
				// 無効なバイトが存在した場合は、無視する
				ip++;
				InBufLen--;
			}
		}
		OutBuf[ op - OutBuf ] = L'\0';
		pDst->insert( pDst->length(), OutBuf, op - OutBuf );
		op = OutBuf;
		OutBufLen = 128;
	}
	return true;
}

// ワイドバイト文字列を任意のエンコードのマルチバイト文字列へ変換する
bool JPEXT_WCStoSpecifiedEncodeMBS( const wchar_t *pWCS, string *pMBS, const char *pDestEncode )
{
	const char *pCurEncode = nl_langinfo( CODESET );	// 現在のロケールにおけるエンコード
	char wBuf[ 128 + MB_LEN_MAX + 1 ];
	size_t InBufLen;
	size_t WCSLen;
	int i, j;
	string wStr;

	pMBS->clear();
	iconv_t cd = iconv_open( pDestEncode, pCurEncode );
	if ( cd == (iconv_t)-1 ) return false;

	WCSLen = wcslen( pWCS );	// 入力文字列の長さを取得
	i = 0;
	while ( i < WCSLen ) {
		// ワイドバイト文字列を、一旦マルチバイト文字列に変換する
		j = 0;
		while ( j < 128 && i < WCSLen ) {
			j += wctomb( wBuf + j, pWCS[i] );
			i++;
		}
		wBuf[j] = '\0';

		// マルチバイト文字列のエンコードを変換する
		if ( !ChangeEncodeSpecifiedCD( cd, wBuf, &wStr ) ) {
			iconv_close( cd );
			return false;
		}

		// 戻り値用の文字列に連結する
		(*pMBS) += wStr;
	}
	iconv_close( cd );
	return true;
}

// 任意のエンコードのマルチバイト文字列をワイドバイト文字列へ変換する
bool JPEXT_SpecifiedEncodeMBStoWCS( const char *pMBS, wstring *pWCS, const char *pSrcEncode )
{
	const char *pCurEncode = nl_langinfo( CODESET );	// 現在のロケールにおけるエンコード
	string wStr;
	pWCS->clear();

	// エンコードを変換する
	if ( !JPEXT_ChangeEncode( pMBS, &wStr, pSrcEncode, pCurEncode ) )
		return false;

	// マルチバイト文字列をワイドバイト文字列に変換する
	return JPEXT_MBStoWCS( wStr.c_str(), pWCS );
}

// クリップボードにデータを設定する
CErrorBool JPEXT_SetClipboardData( Widget w, const std::wstring &rStr )
{
	Display *pDisplay = XtDisplay( w );
	Window window = XtWindow( w );
	CCompoundStr ClipLabel( "text" );
	int status;
	long item_id = 0;
	string wStr, wStr2;
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	CErrorBool r;

	// ワイドバイト文字列をマルチバイト文字列に変換する
	if ( !JPEXT_WCStoMBS( rStr.c_str(), &wStr ) )
		return pConf->RefOutOfMemoryErrorMsg();

/*	if ( !JPEXT_WCStoSpecifiedEncodeMBS( rStr.c_str(), &wStr, pConf->RefClipboardEncode().c_str() ) )
		return pConf->RefOutOfMemoryErrorMsg();
*/

	// クリップボードをロックする
	do {
		status = XmClipboardStartCopy(
			pDisplay, window, ClipLabel, CurrentTime, NULL, NULL, &item_id
		);
	} while ( status == ClipboardLocked );

	// データを登録する
	do {
		status = XmClipboardCopy (
			pDisplay, window, item_id, const_cast< char* >( "STRING" ),
			(char*)wStr.c_str(), ( wStr.length() + 1 ) * sizeof( char ), 0, NULL
		);
	} while ( status == ClipboardLocked );

	// クリップボードにデータを転送する。
	do {
		status = XmClipboardEndCopy ( pDisplay, window, item_id );
	} while ( status == ClipboardLocked );
	return true;
}

// クリップボードからデータを取得する
CErrorBool JPEXT_GetClipboardData( Widget w, std::wstring *pStr )
{
	Display *pDisplay = XtDisplay( w );
	Window window = XtWindow( w );
	int status;
	long item_id = 0;
	unsigned long DataLength = 0;
	VPtr< char > pBuf;
	const char FormatString[3][10] = { "TEXT", "C_STRING", "STRING" };
	int type;	// FormatStringのインデクスを保持
	int i, j;
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();

	// クリップボードに記録されているデータ長を取得する
	type = 0;
	do {
		do {
			status = XmClipboardInquireLength(
				pDisplay, window, const_cast< char* >( FormatString[type] ), &DataLength );
		} while ( status == ClipboardLocked );
		if ( ClipboardNoData == status || 0 == DataLength )
			type++;
	} while ( type < 3 && ClipboardNoData == status );
	if ( type == 3 ) {
		(*pStr) = L"";	// 返すべきものはない
		return true;
	}

	// メモリ領域を確保する
	if ( !pBuf.Malloc( DataLength + 1 ) )
		return pConf->RefOutOfMemoryErrorMsg();

	// クリップボードからデータを取得する
	do {
		status = XmClipboardRetrieve(
			pDisplay, window, const_cast< char* >( FormatString[type] ), pBuf.GetPtr(),
                        DataLength, NULL, NULL );
	} while ( status == ClipboardLocked );

/*
for ( i = 0; i < DataLength; i++ ) {
	printf( "%d : %c : 0x%02X\n", i, pBuf[i], 0xFF & pBuf[i] );
}
printf( "\n\n" );
*/

	if ( type == 0 ) {
		// ISO 2022でエンコードされているらしい
		// 制御用の符号を取り除いてしまえ
		i = 0;
		j = 0;
		while ( i < DataLength ) {
			if ( pBuf[i] != 0x1b ) {
				pBuf[j] = pBuf[i];
				i++;
				j++;
			}
			else {
				if ( i + 1 < DataLength ) {
					if ( pBuf[ i + 1 ] == '$' )
						i += 4;
					else
						i += 3;
				}
				else
					i++;
			}
		}
		DataLength = j;
	}

	pBuf[DataLength] = '\0';
	JPEXT_MBStoWCS( pBuf.GetPtr(), pStr );
	return true;
}

// 内部表現の文字列をクリップボード用の文字列に変換する
CErrorBool UCStoClipboardEncode( const wchar_t *pSrc, string *pDest )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	const string &rEncode = pConf->RefClipboardEncode();
	iconv_t cd;
	char OutBuf[1024];
	size_t OutBufLen;
	size_t InBufLen;
	const char *pInBuf;
	char *pOutBuf;
	size_t r;

	// 変換ディスクリプタを割り当てる
	cd = iconv_open( rEncode.c_str(), "UTF-8" );
	if ( cd == (iconv_t)-1 )
		return pConf->RefFailToConvertCharSet();

	// 変換を行う
	OutBufLen = 128;
	InBufLen = wcslen( pSrc ) * sizeof( wchar_t );
	pInBuf = reinterpret_cast< const char* >( pSrc );
	pOutBuf = OutBuf;
	r = 0;
	while( InBufLen > 0 ) {
		errno = 0;
		r = iconv( cd, &pInBuf, &InBufLen, &pOutBuf, &OutBufLen );
		if ( r == (size_t)-1 ) {
			switch ( errno ) {
			case EINVAL:
				InBufLen = 0;
				break;
			case EILSEQ:
				InBufLen--;
				break;
			}
		}
		pOutBuf[0] = '\0';
		pDest->insert( pDest->length(), OutBuf, pOutBuf - OutBuf );
		OutBufLen = 128;
		pOutBuf = OutBuf;
	}
	
	// 変換ディスクリプタを解放する
	iconv_close( cd );

	return true;
}

// クリップボード用の文字列を内部表現の文字列に変換する
CErrorBool ClipboardEncodetoCUS( const char *pSrc, wstring *pDest )
{
	VClsPtr< CConfigInfo > pConf = m_TaEditShell->GetConfigInfo();
	const string &rEncode = pConf->RefClipboardEncode();
	return true;
}

// パス名の操作
string JPEXT_ResolvPath( const string &rPath )
{
	char buf[ PATH_MAX + 1 ];

	if ( !realpath( rPath.c_str(), buf ) )
		return rPath;
	buf[ PATH_MAX ] = '\0';
	return buf;
}


