#include <locale.h>
#include <stdio.h>
#include "../syuhitu/PluginFuncID.h"
#include <malloc.h>
#include <wchar.h>
#include <stdlib.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <string.h>

// 検索パスを取得
static const char* GetSearchPath( PFT_GetAPIFunction GetAPIFunction, const wchar_t *pWorkMemoryName, const wchar_t *pConfigName )
{
	PFT_AllocWorkMemory AllocWorkMemory = (PFT_AllocWorkMemory)GetAPIFunction( PFID_ALLOCWORKMEMORY );
	PFT_FindWorkMemory FindWorkMemory = (PFT_FindWorkMemory)GetAPIFunction( PFID_FINDWORKMEMORY );
	PFT_GetConfigValue GetConfigValue = (PFT_GetConfigValue)GetAPIFunction( PFID_GETCONFIGVALUE );
	PFT_ShowErrorMsgBox ShowErrorMsgBox = (PFT_ShowErrorMsgBox)GetAPIFunction( PFID_SHOWERRORMSGBOX );
	PFT_FreeWorkMemory FreeWorkMemory = (PFT_FreeWorkMemory)GetAPIFunction( PFID_FREEWORKMEMORY );

	void *vp = NULL;
	const wchar_t *wp = NULL;
	char *cp = NULL;
	char *cp2;
	wchar_t *pBuf = NULL;
	wchar_t *pBuf2;
	size_t len;
	int i;

	// 既存のデータを検索する
	vp = FindWorkMemory( pWorkMemoryName, PFT_FALSE );
	if ( vp ) return (const char*)vp;

	// 存在しなかった場合は、設定情報から値を取得する
	wp = GetConfigValue( pConfigName );
	if ( NULL == wp ){
		ShowErrorMsgBox( L"It fails to get configulation information." );
		return NULL;
	}

	// 作業用にメモリ領域を確保する
	len = wcslen( wp ) + 1;
	pBuf = (wchar_t*)malloc( sizeof( wchar_t ) * ( len + 1 ) );
	if ( NULL == pBuf ) return NULL;
	memcpy( pBuf, wp, len * sizeof( wchar_t ) );
	pBuf[len] = L'\0';	// pBufの末尾には\0を二つ格納しておく

	// マルチバイト文字列へ変換した後に必要となるバイト数を取得する
	len = wcstombs( NULL, wp, 0 ) + 10;
	if ( (size_t)-1 == len ) goto ERR_EXIT;

	// メモリ領域を確保する
	cp = (char*)AllocWorkMemory( len, pWorkMemoryName, PFT_FALSE );
	if ( NULL == cp ) goto ERR_EXIT;

	// 区切り記号の:を\0に置換、文字列の末尾に\0を二つ配置する
	cp2 = cp;	// 作業用書き込み先ポインタ
	pBuf2 = pBuf;	// 作業用読み込み元ポインタ
	while ( (*pBuf2) != L'\0' && cp2 < cp + len ) {
		const wchar_t *hpBuf2 = pBuf2;		// pBuf2を待避する
		for (; (*pBuf2) != L':' && (*pBuf2); pBuf2++ );	// :を検索する
		if ( pBuf2 == hpBuf2 ) {
			// :が二つ続いた場合は、無視する
			pBuf2++;
		}
		else {
			(*pBuf2) = L'\0';
			cp2 += wcstombs( cp2, hpBuf2, len - ( cp2 - cp ) );
			cp2++;
			pBuf2++;
		}
	}
	(*cp2) = '\0';	// 末尾に\0を二つ
	free( pBuf );
	return cp;

ERR_EXIT:
	free( pBuf );
	// FreeWorkMemory( L"INCLUDE_PATH", PFT_FALSE );
	return NULL;
}

// 選択範囲の文字列を取得する
static char *GetSelectionRangeStr( PFT_GetAPIFunction GetAPIFunction )
{
	PFT_GetSelectionRange GetSelectionRange = (PFT_GetSelectionRange)GetAPIFunction( PFID_GETSELECTIONRANGE );
	PFT_GetString GetString = (PFT_GetString)GetAPIFunction( PFID_GETSTRING );
	wchar_t *pSelRangeStrW = NULL;
	char *pSelRangeStr = NULL;
	int i;
	unsigned long SL, SC, EL, EC;

	// 選択範囲を取得
	GetSelectionRange( &SL, &SC, &EL, &EC );

	// 文字が選択されていない、もしくは選択範囲が複数行にまたがっている場合は、処理しない
	if ( SL == EL && SC == EC || SL != EL ) return NULL;

	// 選択範囲の文字列を取得
	i = EC - SC; // 現在選択されている文字列の長さ
	pSelRangeStrW = (wchar_t*)malloc( sizeof( wchar_t ) * ( i + 1 ) );
	if ( NULL == pSelRangeStrW ) return NULL;
	if ( !GetString( SL, SC, EL, EC, pSelRangeStrW, i + 1 ) ) goto ERR_EXIT;
	pSelRangeStrW[i] = L'\0';

	// 取得された文字列を、マルチバイト文字列に変換する
	i = wcstombs( NULL, pSelRangeStrW, 0 );
	pSelRangeStr = (char*)malloc( sizeof( char ) * ( i + 1 ) );
	if ( NULL == pSelRangeStr ) goto ERR_EXIT;
	wcstombs( pSelRangeStr, pSelRangeStrW, i );
	pSelRangeStr[i] = '\0';
	free( pSelRangeStrW );
	pSelRangeStrW = NULL;

	return pSelRangeStr;

ERR_EXIT:
	free( pSelRangeStrW );
	free( pSelRangeStr );
	return NULL;
}

// ファイル名から、当該のファイルのパスの部分を取り出す
char* CreateFileNamePath( const char *pFileName )
{
	int i;
	char *rp = NULL;

	// メモリ領域を確保
	i = strlen( pFileName );
	rp = (char*)malloc( sizeof( char ) * ( i + 1 ) );
	if ( NULL == rp ) return NULL;

	// 値をコピー・ファイル名の部分を取り除く
	memcpy( rp, pFileName, sizeof( char ) * ( i + 1 ) );
	for (; i >= 0 && rp[i] != '/'; --i );
	if ( i >= 0 ) rp[i] = '\0';

	return rp;
}


// pBufに開く対象となるファイルの名前を生成する
// pBufは最大PATH_MAXまで
static PFT_BOOL ResolvPathName( char *pBuf, const char *pIcludePath, const char *pBase, const char *pFileName )
{
	// InckudePathが絶対パスだった場合はpIcludePath+pFileName
	// 相対パスだった場合は、pBase+pIcludePath+pFileName

	int IP, B, FN;
	int i;

	assert( pBuf && pIcludePath && pBase && pFileName );

	if ( '/' == pIcludePath[0] ) {
		IP = strlen( pIcludePath );
		FN = strlen( pFileName );
		if ( IP + FN + 1 >= PATH_MAX ) return PFT_FALSE;
		strncpy( pBuf, pIcludePath, PATH_MAX );
		i = IP;
		if ( 0 == i || i > 0 && '/' != pBuf[ i - 1 ] ) {
			pBuf[i] = '/';
			i++;
		}
		strncpy( pBuf + i, pFileName, PATH_MAX - i );
	}
	else {
		IP = strlen( pIcludePath );
		FN = strlen( pFileName );
		B = strlen( pBase );
		if ( IP + B + FN + 2 >= PATH_MAX ) return PFT_FALSE;
		strncpy( pBuf, pBase, PATH_MAX );
		i = B;
		if ( 0 == i ) {
			// pBaseが与えられなかった場合は、プロセスのカレントディレクトリと見なす
			pBuf[i] = '.';
			i++;
		}
		if ( '/' != pBuf[ i - 1 ] ) {
			pBuf[i] = '/';
			i++;
		}
		strncpy( pBuf + i , pIcludePath, PATH_MAX - i );
		i += IP;
		if ( 0 == i || '/' != pBuf[ i - 1 ] ) {
			pBuf[i] = '/';
			i++;
		}
		strncpy( pBuf + i, pFileName, PATH_MAX - i );
	}
	return PFT_TRUE;
}

static PFT_BOOL OpenSpecifiedFile( const char *pFileName, PFT_GetAPIFunction GetAPIFunction )
{
	struct stat wStat;
	PFT_OpenFile OpenFile = (PFT_OpenFile)GetAPIFunction( PFID_OPENFILE );
	PFT_OpenFileNewWindow OpenFileNewWindow = (PFT_OpenFileNewWindow)GetAPIFunction( PFID_OPENFILENEWWINDOW );

	assert( pFileName );

	if ( pFileName[0] == '\0' ) return PFT_FALSE;

	// 指定されたファイルの状態を確認する
	if ( stat( pFileName, &wStat ) )
		return PFT_FALSE;

	return OpenFileNewWindow( pFileName );
}

void OpenInclude( PFT_GetAPIFunction GetAPIFunction )
{
	PFT_ShowInformationMsgBox ShowInformationMsgBox = (PFT_ShowInformationMsgBox)GetAPIFunction( PFID_SHOWINFORMATIONMSGBOX );
	PFT_GetFileName GetFileName = (PFT_GetFileName)GetAPIFunction( PFID_GETFILENAME );
	const char *pIncludePath;
	const char *cp;
	const char *pFileName = NULL;
	char *pBasePath = NULL;
	char *pSelRangeStr = NULL;
	int i;
	char FileNameBuf[ PATH_MAX ];

	// 選択された範囲の文字列を取得
	pSelRangeStr = GetSelectionRangeStr( GetAPIFunction );
	if ( NULL == pSelRangeStr ) return ;

	// 現在開かれているファイルの名前を取得
	pFileName = GetFileName();

	// 相対パスを解決するための、基準となるパス名を生成
	pBasePath = CreateFileNamePath( pFileName );
	if ( NULL == pBasePath ) goto ERR_EXIT;

	// includeファイルの検索パスを取得
	pIncludePath = GetSearchPath( GetAPIFunction, L"INCLUDE_PATH", L"IncludePath" );
	if ( NULL == pIncludePath ) goto ERR_EXIT;

	cp = pIncludePath;
	while ( (*cp) ) {
		// 開くファイルの名前を合成する
		if ( ResolvPathName( FileNameBuf, cp, pBasePath, pSelRangeStr ) ) {
			// ファイルを開く
			if ( OpenSpecifiedFile( FileNameBuf, GetAPIFunction ) )
				goto ERR_EXIT;	// エラーではないが
		}
		for (; (*cp); cp++ );
		cp++;
	}
	// ファイルが見つからなかったことを通知する
	ShowInformationMsgBox( L"Can't find specified file." );
ERR_EXIT:
	free( pBasePath );
	free( pSelRangeStr );
}

// ファイル名を比較する
// 二つのファイル名が同じファイルを指していた場は真を返す
PFT_BOOL CompareFileName( const char *f1, const char *f2 )
{
	char Buf1[ PATH_MAX ];
	char Buf2[ PATH_MAX ];

	realpath( f1, Buf1 );
	realpath( f2, Buf2 );
	return ( strcmp( Buf1, Buf2 ) == 0 );
}

void OpenSiblings( PFT_GetAPIFunction GetAPIFunction )
{
	PFT_ShowInformationMsgBox ShowInformationMsgBox = (PFT_ShowInformationMsgBox)GetAPIFunction( PFID_SHOWINFORMATIONMSGBOX );
	PFT_GetFileName GetFileName = (PFT_GetFileName)GetAPIFunction( PFID_GETFILENAME );
	const char *pSearchPath;
	const char *pSearchExt;
	const char *cp1, *cp2;
	const char *pFileName = NULL;
	char *pBasePath = NULL;
	char *pFileNameBody = NULL;
	int i, j;
	char FileNameBuf[ PATH_MAX ];

	// 現在開かれているファイルの名前を取得
	pFileName = GetFileName();

	// 相対パスを解決するための、基準となるパス名を生成
	pBasePath = CreateFileNamePath( pFileName );
	if ( NULL == pBasePath ) goto ERR_EXIT;

	// ファイル名の拡張子を取り除いた部分を作成
	i = strlen( pFileName );
	pFileNameBody = (char*)malloc( sizeof( char ) * ( i + 1 ) );
	if ( NULL == pFileNameBody ) goto ERR_EXIT;
	j = strlen( pBasePath );
	memcpy( pFileNameBody, pFileName + j + 1, i - j );
	pFileNameBody[ i - j ] = '\0';
	for ( i = i - j - 1; i >= 0 && pFileNameBody[i] != '.'; i-- );
	if ( i >= 0 ) pFileNameBody[ i + 1 ] = '\0';

	// ファイルの検索パスを取得
	pSearchPath = GetSearchPath( GetAPIFunction, L"SEARCH_PATH", L"SearchPath" );
	if ( NULL == pSearchPath ) goto ERR_EXIT;

	// 検索対象の拡張子を取得
	pSearchExt = GetSearchPath( GetAPIFunction, L"SEARCH_EXT", L"SearchExt" );
	if ( NULL == pSearchExt ) goto ERR_EXIT;

	cp1 = pSearchPath;
	while ( (*cp1) ) {
		cp2 = pSearchExt;
		while ( (*cp2) ) {
			// 開くファイルの名前を合成する
			if ( ResolvPathName( FileNameBuf, cp1, pBasePath, pFileNameBody ) ) {
				if ( strlen( FileNameBuf ) + strlen( cp2 ) + 1 <= PATH_MAX ) {
					// 拡張子を連結する
					strcat( FileNameBuf, cp2 );
					if ( !CompareFileName( FileNameBuf, pFileName ) ) {
						// ファイルを開く
						if ( OpenSpecifiedFile( FileNameBuf, GetAPIFunction ) )
							goto ERR_EXIT;	// エラーではないが
					}
				}
			}
			for (; (*cp2); cp2++ );
			cp2++;
		}
		for (; (*cp1); cp1++ );
		cp1++;
	}
	// ファイルが見つからなかったことを通知する
	ShowInformationMsgBox( L"Can't find specified file." );

ERR_EXIT:
	free( pBasePath );
	free( pFileNameBody );
}
