/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/

#define WIN32_LEAN_AND_MEAN

#ifdef _MSC_VER
#pragma warning(disable:4668 4917) // disable warnings for system headers
#endif

#include <windows.h>
#include <windowsx.h>
#include <shellapi.h>
#include <shlobj.h>
#include <tchar.h>

#include <stdio.h>

#define elementsof(buf) (sizeof(buf) / sizeof(buf[0]))

enum PathResult
{
    PATHRESULT_OK,
    PATHRESULT_API_NOT_SUPPORTED,
    PATHRESULT_EXE_NOT_FOUND
};

const int MAXCMDLINELEN = 32768;

static TCHAR       g_szSTInstallationPath[MAX_PATH] = TEXT("");
static TCHAR       g_szOperatingSystem[256]         = TEXT("");

static const TCHAR g_szSTExecutable[256]            = TEXT("stclient.exe");

//***************************************************************************

LONG RegReadValue( HKEY hBaseKey, LPCTSTR lpSubKey, LPCTSTR lpValueName, LPVOID lpData, DWORD cbData )
{
	HKEY	hKey = NULL;
	LONG	lResult( 0 );

	lResult = RegOpenKeyEx( hBaseKey, lpSubKey, 0, KEY_QUERY_VALUE, &hKey );

	if ( ERROR_SUCCESS == lResult )
	{
		lResult = RegQueryValueEx( hKey, lpValueName, NULL, NULL, (LPBYTE)lpData, &cbData );
		RegCloseKey( hKey );
	}

	return lResult;
}

//***************************************************************************

static LPTSTR *GetCommandArgs( int *pArgc )
{
#ifdef UNICODE
	return CommandLineToArgvW( GetCommandLineW(), pArgc );
#else
	*pArgc = __argc;
	return __argv;
#endif
}

//***************************************************************************

static bool IsSupportedPlatform()
{
    OSVERSIONINFO aOsVersion;

    ZeroMemory( &aOsVersion, sizeof( OSVERSIONINFO ));
    aOsVersion.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );

    // Try to determine OS version
    if ( GetVersionEx( &aOsVersion ))
    {
        switch ( aOsVersion.dwPlatformId )
        {
            case VER_PLATFORM_WIN32_NT:     // Windows NT based
                return true;
        
            case VER_PLATFORM_WIN32_WINDOWS: // Windows Me/98/95.
            case VER_PLATFORM_WIN32s:        // Win32s
                return false;

            default:
                return false;
        }
    }

    return false;
}

//***************************************************************************

static LPCTSTR GetOperatingSystemString()
{
    OSVERSIONINFO aOsVersion;

    ZeroMemory( &aOsVersion, sizeof( OSVERSIONINFO ));
    aOsVersion.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );

    _tcscpy( g_szOperatingSystem, TEXT( "Microsoft Windows" ));

    // Try to determine OS version
    if ( GetVersionEx( &aOsVersion ))
    {
        switch ( aOsVersion.dwPlatformId )
        {
            // Test for the Windows NT product family.
            case VER_PLATFORM_WIN32_NT:
            {
                if ( aOsVersion.dwMajorVersion == 3 )
                {
                    _tcscat( g_szOperatingSystem, TEXT( " NT 3." ));
                    if ( aOsVersion.dwMinorVersion == 0 )
                        _tcscat( g_szOperatingSystem, TEXT( "0" ));
                    else if ( aOsVersion.dwMinorVersion == 5 )
                        _tcscat( g_szOperatingSystem, TEXT( "5" ));
                    else if ( aOsVersion.dwMinorVersion == 51 )
                        _tcscat( g_szOperatingSystem, TEXT( "51" ));
                }
                else if ( aOsVersion.dwMajorVersion == 4 )
                    _tcscat( g_szOperatingSystem, TEXT( " NT 4.0" ));
                else if ( aOsVersion.dwMajorVersion == 5 )
                {
                    if ( aOsVersion.dwMinorVersion == 0 )
                        _tcscat( g_szOperatingSystem, TEXT( " 2000" ));
                    else if ( aOsVersion.dwMinorVersion == 1 )
                        _tcscat( g_szOperatingSystem, TEXT( " XP" ));
                    else if ( aOsVersion.dwMinorVersion == 2 )
                        _tcscat( g_szOperatingSystem, TEXT( " Server 2003" ));
                }
                else if ( aOsVersion.dwMajorVersion == 6 )
                {
                    if ( aOsVersion.dwMinorVersion == 0 )
                        _tcscat( g_szOperatingSystem, " Vista" );
                }
            }
            break;

            // Test for the Windows Me/98/95.
            case VER_PLATFORM_WIN32_WINDOWS:
            {
                if ( aOsVersion.dwMinorVersion == 0 )
                  _tcscat( g_szOperatingSystem, TEXT( " 95" ));
                else if ( aOsVersion.dwMinorVersion == 10 )
                  _tcscat( g_szOperatingSystem, TEXT( " 98" ));
                else if ( aOsVersion.dwMinorVersion == 90 )
                  _tcscat( g_szOperatingSystem, TEXT( " Me" ));
            }
            break;
        }
    }

    return g_szOperatingSystem;
}

//***************************************************************************

static bool FileExists( LPCTSTR lpPathToFile )
{
    bool            bResult = false;
    HANDLE          hFind;
    WIN32_FIND_DATA FindFileData;

    hFind = FindFirstFile( lpPathToFile, &FindFileData );

    if ( hFind != INVALID_HANDLE_VALUE )
    {
        FindClose( hFind );
        bResult = true;
    }

    return bResult;
}

//***************************************************************************

static bool GetProgramFilesFolder( LPTSTR strPath )
{
    bool      bRet = false;
    HINSTANCE hLibrary;

    if (( hLibrary = LoadLibrary( "shell32.dll" )) != NULL )
    {
        BOOL (WINAPI *pSHGetSpecialFolderPathA)( HWND, LPSTR, int, BOOL );
        
        pSHGetSpecialFolderPathA = (BOOL (WINAPI *)(HWND, LPSTR, int, BOOL))GetProcAddress( hLibrary, "SHGetSpecialFolderPathA" );
        
        if ( pSHGetSpecialFolderPathA )
        {
            if ( pSHGetSpecialFolderPathA( NULL, strPath, CSIDL_PROGRAM_FILES, TRUE ))
                bRet = true;
        }
    }
	
    FreeLibrary( hLibrary );

    return ( bRet );
}

//***************************************************************************

static PathResult RetrieveExecutablePath( LPTSTR szExecutablePath )
{
    PathResult eRet = PATHRESULT_API_NOT_SUPPORTED;
    TCHAR szProgramFilesFolder[MAX_PATH];
    
    if ( GetProgramFilesFolder( szProgramFilesFolder ))
    {
        size_t nLen = _tcslen( szProgramFilesFolder );
        if ( nLen > 0 )
        {
            _tcscpy( szExecutablePath, szProgramFilesFolder );
            if ( szProgramFilesFolder[nLen-1] != '\\' )
                _tcscat( szExecutablePath, TEXT( "\\" ));
            _tcscat( szExecutablePath, TEXT( "Sun\\servicetag\\" ));
            _tcscat( szExecutablePath, g_szSTExecutable );
            eRet = FileExists( szExecutablePath ) ? PATHRESULT_OK : PATHRESULT_EXE_NOT_FOUND;
        }
    }

    return eRet;
}

//***************************************************************************

static void SafeCopy( LPTSTR lpTarget, LPCSTR lpSource, size_t nMaxLen )
{
    size_t nLen  = _tcslen( lpSource );
    size_t nCopy = ( nLen < size_t( nMaxLen-1 )) ? nLen : nMaxLen-1;
    _tcsncpy( lpTarget, lpSource, nMaxLen-1 );
    *(lpTarget+nCopy) = 0;
}

//***************************************************************************

int WINAPI _tWinMain( HINSTANCE /*hInstance*/, HINSTANCE, LPTSTR, int )
{
    const DWORD ERR_NO_RECORDS_FOUND = 225;
    const DWORD ERR_DUP_RECORD       = 226;
    
    DWORD dwExitCode = (DWORD)1;

    int     nArgs  = 0;
    LPTSTR* lpArgs = GetCommandArgs( &nArgs );
    
    if ( !IsSupportedPlatform() )
    {
        // Return 0 for a successful run on not supported platforms
        // We don't want that the Office tries to start us forever.
        return 0; 
    }
    
    if ( nArgs >= 11 )
    {
        TCHAR szTargetURN[1024]         = {0};
        TCHAR szProductName[1024]       = {0};
        TCHAR szProductVersion[1024]    = {0};
        TCHAR szParentProductName[1024] = {0};
        TCHAR szProductSource[1024]     = {0};
        TCHAR szInstanceURN[1024]       = {0};

//      -i)  INSTANCE_URN="$2"; shift;;
//      -t)  TARGET_URN="$2"; shift;;
//	    -p)  PRODUCT_NAME="$2"; shift;;
//	    -e)  PRODUCT_VERSION="$2"; shift;;
//	    -P)  PARENT_PRODUCT_NAME="$2"; shift;;
//	    -S)  PRODUCT_SOURCE="$2"; shift;;
//	    "usage: $0 [-i <instance urn>] -p <product name> -e <product version> -t <urn> -S <source> -P <parent product name>"

        int i = 1;
        while ( i < nArgs )
        {
            LPTSTR lpArg = lpArgs[i];
            if ( _tcslen( lpArg ) >= 2 )
            {
                if ( lpArg[0] == '-' )
                {
                    switch ( lpArg[1] )
                    {
                        case 'i':
                        {
                            if ( i < nArgs )
                                ++i;
                            SafeCopy( szInstanceURN, lpArgs[i], elementsof( szInstanceURN ));
                            break;
                        }
                    
                        case 't':
                        {
                            if ( i < nArgs ) 
                                ++i;
                            SafeCopy( szTargetURN, lpArgs[i], elementsof( szTargetURN ));
                            break;
                        }
                        case 'p':
                        {
                            if ( i < nArgs )
                                ++i;
                            SafeCopy( szProductName, lpArgs[i], elementsof( szProductName ));
                            break;
                        }
                        case 'e':
                        {
                            if ( i < nArgs )
                                ++i;
                            SafeCopy( szProductVersion, lpArgs[i], elementsof( szProductVersion ));
                            break;
                        }
                        case 'P':
                        {
                            if ( i < nArgs )
                                ++i;
                            SafeCopy( szParentProductName, lpArgs[i], elementsof( szParentProductName ));
                            break;
                        }
                        case 'S':
                        {
                            if ( i < nArgs )
                                ++i;
                            SafeCopy( szProductSource, lpArgs[i], elementsof( szProductSource ));
                            break;
                        }

                        default:
                            break;
                    } // switch
                }
            }
            
            ++i;
        }

        if ( RetrieveExecutablePath( g_szSTInstallationPath ) == PATHRESULT_OK )
        {
            BOOL bSuccess = TRUE;
            BOOL bProcessStarted = FALSE;
            
            STARTUPINFO         aStartupInfo;
            PROCESS_INFORMATION	aProcessInfo;
            LPTSTR              lpCommandLine = 0;
            
            ZeroMemory( &aStartupInfo, sizeof( aStartupInfo ));
            aStartupInfo.cb = sizeof( aStartupInfo );
            ZeroMemory( &aProcessInfo, sizeof( aProcessInfo ));
            
            if ( _tcslen( szInstanceURN ) == 0 )
            {
                // TEST=`${STCLIENT} -f -t ${TARGET_URN}`
                lpCommandLine = new TCHAR[MAXCMDLINELEN];

                _tcscpy( lpCommandLine, TEXT( "\"" ));
                _tcscat( lpCommandLine, g_szSTInstallationPath );
                _tcscat( lpCommandLine, TEXT( "\"" ));
                _tcscat( lpCommandLine, TEXT( " -f" ));
                _tcscat( lpCommandLine, TEXT( " -t "));
                _tcscat( lpCommandLine, TEXT( "\"" ));
                _tcscat( lpCommandLine, szTargetURN );
                _tcscat( lpCommandLine, TEXT( "\"" ));

	            bSuccess = CreateProcess(
                                   NULL,
		                           lpCommandLine,
		                           NULL,
		                           NULL,
		                           TRUE,
		                           CREATE_NO_WINDOW,
		                           NULL,
		                           NULL,
		                           &aStartupInfo,
		                           &aProcessInfo );
                
                bProcessStarted = TRUE;
                
                // wait until process ends to receive exit code
                WaitForSingleObject( aProcessInfo.hProcess, INFINITE );
                
                delete []lpCommandLine;
            }

            if ( bSuccess )
            {
                DWORD dwSTClientExitCode( ERR_NO_RECORDS_FOUND );
			    if ( bProcessStarted )
                {
                    GetExitCodeProcess( aProcessInfo.hProcess, &dwSTClientExitCode );
                    dwSTClientExitCode &= 0x000000ff;

			        CloseHandle( aProcessInfo.hProcess );
			        CloseHandle( aProcessInfo.hThread );
                }

                if ( dwSTClientExitCode == ERR_NO_RECORDS_FOUND )
                {
                    // output=`${STCLIENT} -a [-i "${INSTANCE_URN}"] -p "${PRODUCT_NAME}" -e "${PRODUCT_VERSION}" -t "${TARGET_URN}" -S "${PRODUCT_SOURCE}" -P "${PARENT_PRODUCT_NAME}" -m "Sun Microsystems, Inc." -A ${uname} -z global`
                    lpCommandLine = new TCHAR[MAXCMDLINELEN];

                    _tcscpy( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, g_szSTInstallationPath );
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, TEXT( " -a" ));
                    if ( _tcslen( szInstanceURN ) > 0 )
                    {
                        _tcscat( lpCommandLine, TEXT( " -i " ));
                        _tcscat( lpCommandLine, TEXT( "\"" ));
                        _tcscat( lpCommandLine, szInstanceURN );
                        _tcscat( lpCommandLine, TEXT( "\"" ));
                    }
                    _tcscat( lpCommandLine, TEXT( " -p " ));
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, szProductName );
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, TEXT( " -e " ));
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, szProductVersion );
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, TEXT( " -t " ));
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, szTargetURN );
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, TEXT( " -S " ));
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, szProductSource );
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, TEXT( " -P " ));
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, szParentProductName );
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, TEXT( " -m \"Sun Microsystems, Inc.\"" ));
                    _tcscat( lpCommandLine, TEXT( " -A " ));
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, GetOperatingSystemString() );
                    _tcscat( lpCommandLine, TEXT( "\"" ));
                    _tcscat( lpCommandLine, TEXT( " -z global" ));
    	            
                    ZeroMemory( &aStartupInfo, sizeof( aStartupInfo ));
	                aStartupInfo.cb = sizeof(aStartupInfo);
                    ZeroMemory( &aProcessInfo, sizeof( aProcessInfo ));
                    
                    bSuccess = CreateProcess(
                                       NULL,
		                               lpCommandLine,
		                               NULL,
		                               NULL,
		                               TRUE,
		                               CREATE_NO_WINDOW,
		                               NULL,
		                               NULL,
		                               &aStartupInfo,
		                               &aProcessInfo );
                    
                    delete []lpCommandLine;
                    
                    // wait until process ends to receive exit code
                    WaitForSingleObject( aProcessInfo.hProcess, INFINITE );
			        
                    dwSTClientExitCode = 0;
			        GetExitCodeProcess( aProcessInfo.hProcess, &dwSTClientExitCode );
                    dwSTClientExitCode &= 0x000000ff;

                    CloseHandle( aProcessInfo.hProcess );
			        CloseHandle( aProcessInfo.hThread );
                    
                    if ( !bSuccess )
                        dwExitCode = 1; // couldn't start stclient process
                    else
                    {
                        if ( _tcslen( szInstanceURN ) > 0 )
                        {
                            // don't register again if we registered in a previous run
                            // or we called stclient successfully.
                            if (( dwSTClientExitCode == ERR_DUP_RECORD ) ||
                                ( dwSTClientExitCode == 0 ))
                                dwExitCode = 0;
                            else
                                dwExitCode = 1; // other errors
                        }
                        else
                            dwExitCode = ( dwSTClientExitCode == 0 ) ? 0 : 1;
                    }
                }
                else if ( dwSTClientExitCode == 0 )
                    dwExitCode = 0; // already registered
                else
                    dwExitCode = 1; // other errors
            }
            else
                dwExitCode = 1; // couldn't start stclient
        }
        else
            dwExitCode = 1; // no executable found
    }
    else
        dwExitCode = 0; // wrong number of arguments
    
    return dwExitCode;
}