//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//===========================================================================//
#include "cbase.h"
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "filesystem.h"
#include "mxtk/mx.h"
#include "mxStatusWindow.h"
#include "filesystem.h"
#include "StudioModel.h"
#include "ControlPanel.h"
#include "MDLViewer.h"
#include "mxExpressionTray.H"
#include "viewersettings.h"
#include "tier1/strtools.h"
#include "faceposer_models.h"
#include "expressions.h"
#include "choreoview.h"
#include "choreoscene.h"
#include "vstdlib/random.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "soundchars.h"
#include "sentence.h"
#include "PhonemeEditor.h"
#include <vgui/ILocalize.h>
#include "filesystem_init.h"
#include "tier2/p4helpers.h"


extern vgui::ILocalize *g_pLocalize;

StudioModel *FindAssociatedModel( CChoreoScene *scene, CChoreoActor *a );


//-----------------------------------------------------------------------------
// Purpose: Takes a full path and determines if the file exists on the disk
// Input  : *filename - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool FPFullpathFileExists( const char *filename )
{
	// Should be a full path
	Assert( strchr( filename, ':' ) );

	struct _stat buf;
	int result = _stat( filename, &buf );
	if ( result != -1 )
		return true;

	return false;
}

// Utility functions mostly
char *FacePoser_MakeWindowsSlashes( char *pname )
{
	static char returnString[ 4096 ];
	strcpy( returnString, pname );
	pname = returnString;

	while ( *pname ) 
	{
		if ( *pname == '/' )
		{
			*pname = '\\';
		}
		pname++;
	}

	return returnString;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
//-----------------------------------------------------------------------------
int GetCloseCaptionLanguageId()
{
	return g_viewerSettings.cclanguageid;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : id - 
//-----------------------------------------------------------------------------
void SetCloseCaptionLanguageId( int id, bool force /* = false */ )
{
	Assert( id >= 0 && id < CC_NUM_LANGUAGES );
	bool changed = g_viewerSettings.cclanguageid != id;
	g_viewerSettings.cclanguageid = id;
	if ( changed || force )
	{
		// Switch languages
		char const *suffix = CSentence::NameForLanguage( id );
		if ( Q_stricmp( suffix, "unknown_language" ) )
		{
			char fn[ MAX_PATH ];
			Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", suffix );

			g_pLocalize->RemoveAll();

			if ( Q_stricmp( suffix, "english" )&&
				filesystem->FileExists( "resource/closecaption_english.txt" ) )
			{
				g_pLocalize->AddFile( "resource/closecaption_english.txt", "GAME", true );
			}

			if ( filesystem->FileExists( fn ) )
			{
				g_pLocalize->AddFile( fn, "GAME", true );
			}
			else
			{
				Con_ErrorPrintf( "PhonemeEditor::SetCloseCaptionLanguageId  Warning, can't find localization file %s\n", fn );
			}

			// Need to redraw the choreoview at least
			if ( g_pChoreoView )
			{
				g_pChoreoView->InvalidateLayout();
			}
		}
	}

	if ( g_MDLViewer )
	{
		g_MDLViewer->UpdateLanguageMenu( id );
	}
}


char *va( const char *fmt, ... )
{
	va_list args;
	static char output[32][1024];
	static int outbuffer = 0;

	outbuffer++;
	va_start( args, fmt );
	vprintf( fmt, args );
	vsprintf( output[ outbuffer & 31 ], fmt, args );
	return output[ outbuffer & 31 ];
}

void Con_Printf( const char *fmt, ... )
{
	va_list args;
	static char output[1024];

	va_start( args, fmt );
	vprintf( fmt, args );
	vsprintf( output, fmt, args );

	if ( !g_pStatusWindow )
	{
		return;
	}

	g_pStatusWindow->StatusPrint( CONSOLE_COLOR, false, output );
}

void Con_ColorPrintf( COLORREF rgb, const char *fmt, ... )
{
	va_list args;
	static char output[1024];

	va_start( args, fmt );
	vprintf( fmt, args );
	vsprintf( output, fmt, args );

	if ( !g_pStatusWindow )
	{
		return;
	}

	g_pStatusWindow->StatusPrint( rgb, false, output );
}

void Con_ErrorPrintf( const char *fmt, ... )
{
	va_list args;
	static char output[1024];

	va_start( args, fmt );
	vprintf( fmt, args );
	vsprintf( output, fmt, args );

	if ( !g_pStatusWindow )
	{
		return;
	}

	g_pStatusWindow->StatusPrint( ERROR_COLOR, false, output );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *filename - 
//-----------------------------------------------------------------------------
void MakeFileWriteable( const char *filename )
{
	Assert( filesystem );
	char pFullPathBuf[ 512 ];
	char *pFullPath;
	if ( !Q_IsAbsolutePath( filename ) )
	{
		pFullPath = (char*)filesystem->RelativePathToFullPath( filename, NULL, pFullPathBuf, sizeof(pFullPathBuf) );
	}
	else
	{
		Q_strncpy( pFullPathBuf, filename, sizeof(pFullPathBuf) );
		pFullPath = pFullPathBuf;
	}

	if ( pFullPath )
	{
		Q_FixSlashes( pFullPath );
		SetFileAttributes( pFullPath, FILE_ATTRIBUTE_NORMAL );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *filename - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool IsFileWriteable( const char *filename )
{
	Assert( filesystem );
	char pFullPathBuf[ 512 ];
	char *pFullPath;
	if ( !Q_IsAbsolutePath( filename ) )
	{
		pFullPath = (char*)filesystem->RelativePathToFullPath( filename, NULL, pFullPathBuf, sizeof(pFullPathBuf) );
	}
	else
	{
		Q_strncpy( pFullPathBuf, filename, sizeof(pFullPathBuf) );
		pFullPath = pFullPathBuf;
	}

	if ( pFullPath )
	{
		Q_FixSlashes( pFullPath );
		DWORD attrib = GetFileAttributes( pFullPath );
		return ( ( attrib & FILE_ATTRIBUTE_READONLY ) == 0 );
	}

	// Doesn't seem to exist, so yeah, it's writable
	return true;
}

bool MakeFileWriteablePrompt( const char *filename, char const *promptTitle )
{
	if ( !IsFileWriteable( filename ) )
	{
		int retval = mxMessageBox( NULL, va( "File '%s' is Read-Only, make writable?", filename ),
			promptTitle, MX_MB_WARNING | MX_MB_YESNO );

		// Didn't pick yes, bail
		if ( retval != 0 )
			return false;

		MakeFileWriteable( filename );
	}

	return true;
}

void FPCopyFile( const char *source, const char *dest, bool bCheckOut )
{
	Assert( filesystem );
	char fullpaths[ MAX_PATH ];
	char fullpathd[ MAX_PATH ];

	if ( !Q_IsAbsolutePath( source ) )
	{
		filesystem->RelativePathToFullPath( source, NULL, fullpaths, sizeof(fullpaths) );
	}
	else
	{
		Q_strncpy( fullpaths, source, sizeof(fullpaths) );
	}

	Q_strncpy( fullpathd, fullpaths, MAX_PATH );
	char *pSubdir = Q_stristr( fullpathd, source );
	if ( pSubdir )
	{
		*pSubdir = 0;
	}
	Q_AppendSlash( fullpathd, MAX_PATH );
	Q_strncat( fullpathd, dest, MAX_PATH, MAX_PATH );

	Q_FixSlashes( fullpaths );
	Q_FixSlashes( fullpathd );

	if ( bCheckOut )
	{
		CP4AutoEditAddFile checkout( fullpathd );
		CopyFile( fullpaths, fullpathd, FALSE );
	}
	else
	{
		CopyFile( fullpaths, fullpathd, FALSE );
	}
}

bool FacePoser_HasWindowStyle( mxWindow *w, int bits )
{
	HWND wnd = (HWND)w->getHandle();
	DWORD style = GetWindowLong( wnd, GWL_STYLE );
	return ( style & bits ) ? true : false;
}

bool FacePoser_HasWindowExStyle( mxWindow *w, int bits )
{
	HWND wnd = (HWND)w->getHandle();
	DWORD style = GetWindowLong( wnd, GWL_EXSTYLE );
	return ( style & bits ) ? true : false;
}

void FacePoser_AddWindowStyle( mxWindow *w, int addbits )
{
	HWND wnd = (HWND)w->getHandle();
	DWORD style = GetWindowLong( wnd, GWL_STYLE );
	style |= addbits;
	SetWindowLong( wnd, GWL_STYLE, style );
}

void FacePoser_AddWindowExStyle( mxWindow *w, int addbits )
{
	HWND wnd = (HWND)w->getHandle();
	DWORD style = GetWindowLong( wnd, GWL_EXSTYLE );
	style |= addbits;
	SetWindowLong( wnd, GWL_EXSTYLE, style );
}

void FacePoser_RemoveWindowStyle( mxWindow *w, int removebits )
{
	HWND wnd = (HWND)w->getHandle();
	DWORD style = GetWindowLong( wnd, GWL_STYLE );
	style &= ~removebits;
	SetWindowLong( wnd, GWL_STYLE, style );
}

void FacePoser_RemoveWindowExStyle( mxWindow *w, int removebits )
{
	HWND wnd = (HWND)w->getHandle();
	DWORD style = GetWindowLong( wnd, GWL_EXSTYLE );
	style &= ~removebits;
	SetWindowLong( wnd, GWL_EXSTYLE, style );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *w - 
//-----------------------------------------------------------------------------
void FacePoser_MakeToolWindow( mxWindow *w, bool smallcaption )
{
	FacePoser_AddWindowStyle( w, WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS );
	if ( smallcaption )
	{
		FacePoser_AddWindowExStyle( w, WS_EX_OVERLAPPEDWINDOW );
		FacePoser_AddWindowExStyle( w, WS_EX_TOOLWINDOW );
	}
}

bool LoadViewerSettingsInt( char const *keyname, int *value );
bool SaveViewerSettingsInt ( const char *keyname, int value );

void FacePoser_LoadWindowPositions( char const *name, bool& visible, int& x, int& y, int& w, int& h, bool& locked, bool& zoomed )
{
	char subkey[ 512 ];
	int v;

	Q_snprintf( subkey, sizeof( subkey ), "%s - visible", name );
	LoadViewerSettingsInt( subkey, &v );
	visible = v ? true : false;
	
	Q_snprintf( subkey, sizeof( subkey ), "%s - locked", name );
	LoadViewerSettingsInt( subkey, &v );
	locked = v ? true : false;

	Q_snprintf( subkey, sizeof( subkey ), "%s - zoomed", name );
	LoadViewerSettingsInt( subkey, &v );
	zoomed = v ? true : false;

	Q_snprintf( subkey, sizeof( subkey ), "%s - x", name );
	LoadViewerSettingsInt( subkey, &x );
	Q_snprintf( subkey, sizeof( subkey ), "%s - y", name );
	LoadViewerSettingsInt( subkey, &y );
	Q_snprintf( subkey, sizeof( subkey ), "%s - width", name );
	LoadViewerSettingsInt( subkey, &w );
	Q_snprintf( subkey, sizeof( subkey ), "%s - height", name );
	LoadViewerSettingsInt( subkey, &h );
}

void FacePoser_SaveWindowPositions( char const *name, bool visible, int x, int y, int w, int h, bool locked, bool zoomed )
{
	char subkey[ 512 ];
	Q_snprintf( subkey, sizeof( subkey ), "%s - visible", name );
	SaveViewerSettingsInt( subkey, visible );
	Q_snprintf( subkey, sizeof( subkey ), "%s - locked", name );
	SaveViewerSettingsInt( subkey, locked );
	Q_snprintf( subkey, sizeof( subkey ), "%s - x", name );
	SaveViewerSettingsInt( subkey, x );
	Q_snprintf( subkey, sizeof( subkey ), "%s - y", name );
	SaveViewerSettingsInt( subkey, y );
	Q_snprintf( subkey, sizeof( subkey ), "%s - width", name );
	SaveViewerSettingsInt( subkey, w );
	Q_snprintf( subkey, sizeof( subkey ), "%s - height", name );
	SaveViewerSettingsInt( subkey, h );
	Q_snprintf( subkey, sizeof( subkey ), "%s - zoomed", name );
	SaveViewerSettingsInt( subkey, zoomed );
}

static char g_PhonemeRoot[ MAX_PATH ] = { 0 };
void FacePoser_SetPhonemeRootDir( char const *pchRootDir )
{
	Q_strncpy( g_PhonemeRoot, pchRootDir, sizeof( g_PhonemeRoot ) );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void FacePoser_EnsurePhonemesLoaded( void )
{
	// Don't bother unless a model is loaded, at least...
	CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr();
	if ( !hdr )
	{
		return;
	}

	char const *ext[] = 
	{
		"",
		"_strong",
		"_weak",
	};

	for ( int i = 0 ; i < ARRAYSIZE( ext ); ++i )
	{
		char clname[ 256 ];
		Q_snprintf( clname, sizeof( clname ), "%sphonemes%s", g_PhonemeRoot, ext[ i ] );
		Q_FixSlashes( clname );
		Q_strlower( clname );

		if ( !expressions->FindClass( clname, false ) )
		{
			char clfile[ MAX_PATH ];
			Q_snprintf( clfile, sizeof( clfile ), "expressions/%sphonemes%s.txt", g_PhonemeRoot, ext[ i ] );
			Q_FixSlashes( clfile );
			Q_strlower( clfile );

			if ( g_pFileSystem->FileExists( clfile ) )
				{
				expressions->LoadClass( clfile );
				CExpClass *cl = expressions->FindClass( clname, false );
				if ( !cl )
				{
					Con_Printf( "FacePoser_EnsurePhonemesLoaded:  %s missing!!!\n", clfile );
				}
			}
		}
	}
}

bool FacePoser_ShowFileNameDialog( bool openFile, char *relative, size_t bufsize, char const *subdir, char const *wildcard )
{
	Assert( relative );
	relative[ 0 ] = 0 ;
	Assert( subdir );
	Assert( wildcard );

	char workingdir[ 256 ];
	Q_getwd( workingdir, sizeof( workingdir ) );
	strlwr( workingdir );
	Q_FixSlashes( workingdir, '/' );

	// Show file io
	bool inWorkingDirectoryAlready = false;
	if ( Q_stristr_slash( workingdir, va( "%s%s", GetGameDirectory(), subdir ) ) )
	{
		inWorkingDirectoryAlready = true;
	}

// Show file io
	const char *fullpath = NULL;
	
	if ( openFile )
	{
		fullpath = mxGetOpenFileName( 
			0, 
			inWorkingDirectoryAlready ? "." : FacePoser_MakeWindowsSlashes( va( "%s%s/", GetGameDirectory(), subdir ) ), 
			wildcard );
	}
	else
	{
		fullpath = mxGetSaveFileName( 
			0, 
			inWorkingDirectoryAlready ? "." : FacePoser_MakeWindowsSlashes( va( "%s%s/", GetGameDirectory(), subdir ) ), 
			wildcard );
	}
	if ( !fullpath || !fullpath[ 0 ] )
		return false;

	Q_strncpy( relative, fullpath, bufsize );
	return true;
}

bool FacePoser_ShowOpenFileNameDialog( char *relative, size_t bufsize, char const *subdir, char const *wildcard )
{
	return FacePoser_ShowFileNameDialog( true, relative, bufsize, subdir, wildcard );
}

bool FacePoser_ShowSaveFileNameDialog( char *relative, size_t bufsize, char const *subdir, char const *wildcard )
{
	return FacePoser_ShowFileNameDialog( false, relative, bufsize, subdir, wildcard );
}

//-----------------------------------------------------------------------------
// Purpose: converts an english string to unicode
//-----------------------------------------------------------------------------
int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize)
{
	return ::MultiByteToWideChar(CP_ACP, 0, ansi, -1, unicode, unicodeBufferSize);
}

//-----------------------------------------------------------------------------
// Purpose: converts an unicode string to an english string
//-----------------------------------------------------------------------------
int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize)
{
	return ::WideCharToMultiByte(CP_ACP, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL);
}

//-----------------------------------------------------------------------------
// Purpose: If FPS is set and "using grid", snap to proper fractional time value
// Input  : t - 
// Output : float
//-----------------------------------------------------------------------------
float FacePoser_SnapTime( float t )
{
	if ( !g_pChoreoView )
		return t;

	CChoreoScene *scene = g_pChoreoView->GetScene();
	if ( !scene )
		return t;

	return scene->SnapTime( t );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : t - 
// Output : char const
//-----------------------------------------------------------------------------
char const *FacePoser_DescribeSnappedTime( float t )
{
	static char desc[ 128 ];
	Q_snprintf( desc, sizeof( desc ), "%.3f", t );

	if ( !g_pChoreoView )
		return desc;

	CChoreoScene *scene = g_pChoreoView->GetScene();
	if ( !scene )
		return desc;

	t = scene->SnapTime( t );

	int fps = scene->GetSceneFPS();

	int ipart = (int)t;
	int fracpart = (int)( ( t - (float)ipart ) * (float)fps + 0.5f );

	int frame = ipart * fps + fracpart;

	if ( fracpart == 0 )
	{
		Q_snprintf( desc, sizeof( desc ), "frame %i (time %i s.)", frame, ipart );
	}
	else
	{
		Q_snprintf( desc, sizeof( desc ), "frame %i (time %i + %i/%i s.)", 
			frame, ipart,fracpart, fps );
	}

	return desc;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
//-----------------------------------------------------------------------------
int FacePoser_GetSceneFPS( void )
{
	if ( !g_pChoreoView )
		return 1000;

	CChoreoScene *scene = g_pChoreoView->GetScene();
	if ( !scene )
		return 1000;

	return scene->GetSceneFPS();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool FacePoser_IsSnapping( void )
{
	if ( !g_pChoreoView )
		return false;

	CChoreoScene *scene = g_pChoreoView->GetScene();
	if ( !scene )
		return false;

	return scene->IsUsingFrameSnap();
}

char const *FacePoser_TranslateSoundNameGender( char const *soundname, gender_t gender )
{
	if ( Q_stristr( soundname, ".wav" ) )
		return PSkipSoundChars( soundname );

	return PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, gender ) );
}

char const *FacePoser_TranslateSoundName( char const *soundname, StudioModel *model /*= NULL*/ )
{
	if ( Q_stristr( soundname, ".wav" ) )
		return PSkipSoundChars( soundname );

	static char temp[ 256 ];

	if ( model )
	{
		Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, model->GetFileName() ) ), sizeof( temp ) );
	}
	else
	{
		Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, NULL ) ), sizeof( temp ) );
	}
	return temp;
}

char const *FacePoser_TranslateSoundName( CChoreoEvent *event )
{
	char const *soundname = event->GetParameters();
	if ( Q_stristr( soundname, ".wav" ) )
		return PSkipSoundChars( soundname );

	// See if we can figure out the .mdl associated to this event's actor
	static char temp[ 256 ];
	temp[ 0 ] = 0;
	StudioModel *model = NULL;

	CChoreoActor *a = event->GetActor();
	CChoreoScene *s = event->GetScene();

	if ( a != NULL && 
		 s != NULL )
	{
		model = FindAssociatedModel( s, a );
	}

	Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, model ? model->GetFileName() : NULL ) ), sizeof( temp ) );
	return temp;
}


#if defined( _WIN32 ) || defined( WIN32 )
#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/')
#else	//_WIN32
#define PATHSEPARATOR(c) ((c) == '/')
#endif	//_WIN32

static bool charsmatch( char c1, char c2 )
{
	if ( tolower( c1 ) == tolower( c2 ) )
		return true;
	if ( PATHSEPARATOR( c1 ) && PATHSEPARATOR( c2 ) )
		return true;
	return false;
}


char *Q_stristr_slash( char const *pStr, char const *pSearch )
{
	AssertValidStringPtr(pStr);
	AssertValidStringPtr(pSearch);

	if (!pStr || !pSearch) 
		return 0;

	char const* pLetter = pStr;

	// Check the entire string
	while (*pLetter != 0)
	{
		// Skip over non-matches
		if ( charsmatch( *pLetter, *pSearch ) )
		{
			// Check for match
			char const* pMatch = pLetter + 1;
			char const* pTest = pSearch + 1;
			while (*pTest != 0)
			{
				// We've run off the end; don't bother.
				if (*pMatch == 0)
					return 0;

				if ( !charsmatch( *pMatch, *pTest ) )
					break;

				++pMatch;
				++pTest;
			}

			// Found a match!
			if (*pTest == 0)
				return (char *)pLetter;
		}

		++pLetter;
	}

	return 0;
}

static CUniformRandomStream g_Random;
IUniformRandomStream *random = &g_Random;