/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_desktop.hxx"
#define UNICODE
#define _UNICODE

#include <cstddef>

#define WIN32_LEAN_AND_MEAN
#if defined _MSC_VER
#pragma warning(push, 1)
#endif
#include <windows.h>
#include <shellapi.h>
#if defined _MSC_VER
#pragma warning(pop)
#endif

#include <tchar.h>

#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <systools/win32/uwinapi.h>

#include "rtl/string.h"

#include "../../../source/inc/exithelper.hxx"
#include "../extendloaderenvironment.hxx"

#define PIPE_PREFIX					TEXT("\\\\.\\pipe\\OSL_PIPE_")
#define	PIPE_POSTFIX				TEXT("_SingleOfficeIPC_")
#define PIPE_TERMINATION_SEQUENCE	"InternalIPC::ProcessingDone"

BOOL WINAPI ConvertSidToStringSid( PSID pSid, LPTSTR* StringSid )
{
    PSID_IDENTIFIER_AUTHORITY psia;
    DWORD dwSubAuthorities;
    DWORD dwSidRev=SID_REVISION;
    DWORD dwCounter;
    DWORD dwSidSize;

    // Validate the binary SID.

    if(!IsValidSid(pSid)) return FALSE;

    // Get the identifier authority value from the SID.

    psia = GetSidIdentifierAuthority(pSid);

    // Get the number of subauthorities in the SID.

    dwSubAuthorities = *GetSidSubAuthorityCount(pSid);

    // Compute the buffer length.
    // S-SID_REVISION- + IdentifierAuthority- + subauthorities- + NULL

    dwSidSize=(15 + 12 + (12 * dwSubAuthorities) + 1) * sizeof(TCHAR);

	*StringSid = (LPTSTR)LocalAlloc( LMEM_FIXED, dwSidSize );

    // Add 'S' prefix and revision number to the string.

    dwSidSize=wsprintf(*StringSid, TEXT("S-%lu-"), dwSidRev );

    // Add a SID identifier authority to the string.

    if ( (psia->Value[0] != 0) || (psia->Value[1] != 0) )
    {
        dwSidSize+=wsprintf(*StringSid + lstrlen(*StringSid),
                    TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"),
                    (USHORT)psia->Value[0],
                    (USHORT)psia->Value[1],
                    (USHORT)psia->Value[2],
                    (USHORT)psia->Value[3],
                    (USHORT)psia->Value[4],
                    (USHORT)psia->Value[5]);
    }
    else
    {
        dwSidSize+=wsprintf(*StringSid + lstrlen(*StringSid),
                    TEXT("%lu"),
                    (ULONG)(psia->Value[5]      )   +
                    (ULONG)(psia->Value[4] <<  8)   +
                    (ULONG)(psia->Value[3] << 16)   +
                    (ULONG)(psia->Value[2] << 24)   );
    }

    // Add SID subauthorities to the string.
    //
    for (dwCounter=0 ; dwCounter < dwSubAuthorities ; dwCounter++)
    {
        dwSidSize+=wsprintf(*StringSid + dwSidSize, TEXT("-%lu"),
                    *GetSidSubAuthority(pSid, dwCounter) );
    }

    return TRUE;
}


//---------------------------------------------------------------------------

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

//---------------------------------------------------------------------------

namespace {

bool writeArgument(HANDLE pipe, char prefix, WCHAR const * argument) {
    CHAR szBuffer[4096];
    int n = WideCharToMultiByte(
        CP_UTF8, 0, argument, -1, szBuffer, sizeof (szBuffer), NULL, NULL);
    char b[1 + 2 * ((sizeof szBuffer) - 1)]; // hopefully does not overflow
    b[0] = prefix;
    char * p = b + 1;
    for (int i = 0; i < n - 1; ++i) { // cannot underflow (n >= 0)
        char c = szBuffer[i];
        switch (c) {
        case '\0':
            *p++ = '\\';
            *p++ = '0';
            break;
        case ',':
            *p++ = '\\';
            *p++ = ',';
            break;
        case '\\':
            *p++ = '\\';
            *p++ = '\\';
            break;
        default:
            *p++ = c;
            break;
        }
    }
    DWORD w;
    return WriteFile(pipe, b, p - b, &w, NULL);
}

}

#ifdef __MINGW32__
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int )
#else
int WINAPI _tWinMain( HINSTANCE, HINSTANCE, LPTSTR, int )
#endif
{
	TCHAR				szTargetFileName[MAX_PATH] = TEXT("");
    TCHAR               szIniDirectory[MAX_PATH];
	TCHAR				szPerfTuneIniFile[MAX_PATH] = TEXT("");
	STARTUPINFO			aStartupInfo;

    desktop_win32::extendLoaderEnvironment(szTargetFileName, szIniDirectory);

	ZeroMemory( &aStartupInfo, sizeof(aStartupInfo) );
	aStartupInfo.cb = sizeof(aStartupInfo);

	GetStartupInfo( &aStartupInfo );
	// Get image path with same name but with .bin extension

	TCHAR				szModuleFileName[MAX_PATH];

	GetModuleFileName( NULL, szModuleFileName, MAX_PATH );
	_TCHAR	*lpLastSlash = _tcsrchr( szModuleFileName, '\\' );
	if ( lpLastSlash )
	{
		size_t len = lpLastSlash - szModuleFileName + 1;
		_tcsncpy( szPerfTuneIniFile, szModuleFileName, len );
		_tcsncpy( szPerfTuneIniFile + len, _T("perftune.ini"), sizeof(szPerfTuneIniFile)/sizeof(szPerfTuneIniFile[0]) - len );
	}

	// Create process with same command line, environment and stdio handles which
	// are directed to the created pipes

	DWORD	dwExitCode = (DWORD)-1;

	BOOL	fSuccess = FALSE;
	LPTSTR	lpCommandLine = NULL;
    int argc = 0;
    LPTSTR * argv = NULL;
    bool bFirst = true;
    WCHAR cwd[MAX_PATH];
    DWORD cwdLen = GetCurrentDirectoryW(MAX_PATH, cwd);
    if (cwdLen >= MAX_PATH) {
        cwdLen = 0;
    }

	do
	{
		TCHAR	szKey[32];

		GetPrivateProfileString(
			TEXT("PerformanceTuning"),
			TEXT("FastPipeCommunication"),
			TEXT("0"),
			szKey,
			elementsof(szKey),
			szPerfTuneIniFile
			);

		if ( 0 == _tcscmp( szKey, TEXT("1") ) )
		{
			HANDLE	hProcessToken;

			if ( OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hProcessToken ) )
			{
				TCHAR	szPipeName[4096];


				DWORD	dwTokenLength = 0;


				fSuccess = GetTokenInformation( hProcessToken, TokenUser, NULL, dwTokenLength, &dwTokenLength );

				PVOID	pTokenInfo = _alloca(dwTokenLength);
				fSuccess = GetTokenInformation( hProcessToken, TokenUser, pTokenInfo, dwTokenLength, &dwTokenLength );
				CloseHandle( hProcessToken );

				PSID pSid = ((PTOKEN_USER)pTokenInfo)->User.Sid;
				LPTSTR	szUserIdent = NULL;
				TCHAR	szSUPD[11] = TEXT("0");

				fSuccess = ConvertSidToStringSid( pSid, &szUserIdent );

				_tcsncpy( szPipeName, PIPE_PREFIX, elementsof(szPipeName) );
				_tcsncat( szPipeName, szUserIdent, elementsof(szPipeName) - _tcslen(szPipeName) - 1 );
				_tcsncat( szPipeName, PIPE_POSTFIX, elementsof(szPipeName) - _tcslen(szPipeName) - 1 );
				_tcsncat( szPipeName, _ultot( SUPD, szSUPD, 10), elementsof(szPipeName) - _tcslen(szPipeName) - 1 );

				LocalFree( szUserIdent );

				HANDLE	hPipe = CreateFile(
									szPipeName,
									GENERIC_READ|GENERIC_WRITE, 
									FILE_SHARE_READ | FILE_SHARE_WRITE,
									NULL,
									OPEN_EXISTING, 
									FILE_ATTRIBUTE_NORMAL, 
									NULL);

				if ( INVALID_HANDLE_VALUE != hPipe )
				{
					DWORD	dwBytesWritten;
					int	argc = 0;
					LPWSTR	*argv = CommandLineToArgvW( GetCommandLine(), &argc );

					fSuccess = WriteFile( hPipe, RTL_CONSTASCII_STRINGPARAM("InternalIPC::Arguments"), &dwBytesWritten, NULL );
                    if (fSuccess) {
                        if (cwdLen > 0) {
                            fSuccess = writeArgument(hPipe, '2', cwd);
                        } else {
                            fSuccess = WriteFile(
                                hPipe, RTL_CONSTASCII_STRINGPARAM("0"),
                                &dwBytesWritten, NULL);
                        }
                    }
					for ( int argn = 1; fSuccess && argn < argc; argn++ )
					{
                        fSuccess = writeArgument(hPipe, ',', argv[argn]);
					}

					if ( fSuccess )
					{
						fSuccess = WriteFile(  hPipe, "", 1, &dwBytesWritten, NULL );
						if ( fSuccess )
						{
							DWORD	dwBytesRead = 0;
							char	*pBuffer = (char *)_alloca( sizeof(PIPE_TERMINATION_SEQUENCE) );
							fSuccess = ReadFile( hPipe, pBuffer, sizeof(PIPE_TERMINATION_SEQUENCE) - 1, &dwBytesRead, NULL );
							if ( fSuccess )
							{
								pBuffer[dwBytesRead] = 0;
								if ( 0 != strcmp( PIPE_TERMINATION_SEQUENCE, pBuffer ) )
									fSuccess = FALSE;
							}
						}
					}

					CloseHandle( hPipe );

					return fSuccess ? 0 : -1;
				}

			}
		}

        if ( bFirst ) {
            argv = GetCommandArgs(&argc);
            std::size_t n = wcslen(argv[0]) + 2;
            for (int i = 1; i < argc; ++i) {
                n += wcslen(argv[i]) + 3;
            }
            n += MY_LENGTH(L" \"-env:OOO_CWD=2") + 4 * cwdLen +
                MY_LENGTH(L"\"") + 1;
                // 4 * cwdLen: each char preceded by backslash, each trailing
                // backslash doubled
            lpCommandLine = new WCHAR[n];
        }
        WCHAR * p = desktop_win32::commandLineAppend(
            lpCommandLine, MY_STRING(L"\""));
        p = desktop_win32::commandLineAppend(p, argv[0]);
        for (int i = 1; i < argc; ++i) {
            if (bFirst || ::desktop::ExitHelper::E_NORMAL_RESTART == dwExitCode || wcsncmp(argv[i], MY_STRING(L"-env:")) == 0) {
                p = desktop_win32::commandLineAppend(p, MY_STRING(L"\" \""));
                p = desktop_win32::commandLineAppend(p, argv[i]);
            }
        }

        p = desktop_win32::commandLineAppend(
            p, MY_STRING(L"\" \"-env:OOO_CWD="));
        if (cwdLen == 0) {
            p = desktop_win32::commandLineAppend(p, MY_STRING(L"0"));
        } else {
            p = desktop_win32::commandLineAppend(p, MY_STRING(L"2"));
            p = desktop_win32::commandLineAppendEncoded(p, cwd);
        }
        desktop_win32::commandLineAppend(p, MY_STRING(L"\""));
        bFirst = false;

		TCHAR	szParentProcessId[64]; // This is more than large enough for a 128 bit decimal value
		BOOL    bHeadlessMode( FALSE );

        {
            // Check command line arguments for "-headless" parameter. We only
            // set the environment variable "ATTACHED_PARENT_PROCESSID" for the headless
            // mode as self-destruction of the soffice.bin process can lead to
            // certain side-effects (log-off can result in data-loss, ".lock" is not deleted.
            // See 138244 for more information.
            int		argc;
		    LPTSTR	*argv = GetCommandArgs( &argc );

		    if ( argc > 1 )
		    {
			    int n;

			    for ( n = 1; n < argc; n++ )
			    {
		            if ( 0 == _tcsnicmp( argv[n], _T("-headless"), 9 ) )
                        bHeadlessMode = TRUE;
			    }
            }
        }
        
        if ( _ltot( (long)GetCurrentProcessId(),szParentProcessId, 10 ) && bHeadlessMode )
			SetEnvironmentVariable( TEXT("ATTACHED_PARENT_PROCESSID"), szParentProcessId );

		PROCESS_INFORMATION	aProcessInfo;
		
		fSuccess = CreateProcess(
			szTargetFileName,
			lpCommandLine,
			NULL,
			NULL,
			TRUE,
			0,
			NULL,
			szIniDirectory,
			&aStartupInfo,
			&aProcessInfo );

		if ( fSuccess )
		{
			DWORD	dwWaitResult;

			do
			{
				// On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWidth" so we have to do so
				// as if we where processing any messages

				dwWaitResult = MsgWaitForMultipleObjects( 1, &aProcessInfo.hProcess, FALSE, INFINITE, QS_ALLEVENTS );

				if (  WAIT_OBJECT_0 + 1 == dwWaitResult )
				{
					MSG	msg;

					PeekMessage( &msg, NULL, 0, 0, PM_REMOVE );
				}
			} while ( WAIT_OBJECT_0 + 1 == dwWaitResult );

			dwExitCode = 0;
			GetExitCodeProcess( aProcessInfo.hProcess, &dwExitCode );

			CloseHandle( aProcessInfo.hProcess );
			CloseHandle( aProcessInfo.hThread );
		}
	} while ( fSuccess
              && ( ::desktop::ExitHelper::E_CRASH_WITH_RESTART == dwExitCode || ::desktop::ExitHelper::E_NORMAL_RESTART == dwExitCode ));
    delete[] lpCommandLine;

	return fSuccess ? dwExitCode : -1;
}
