/*-------------------------------------------------------------------------
 * drawElements Utility Library
 * ----------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed 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.
 *
 *//*!
 * \file
 * \brief Process abstraction.
 *//*--------------------------------------------------------------------*/

#include "deProcess.h"
#include "deMemory.h"
#include "deString.h"

#if (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_OSX) || (DE_OS == DE_OS_IOS) || (DE_OS == DE_OS_ANDROID) || (DE_OS == DE_OS_SYMBIAN)

#include "deCommandLine.h"

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>

typedef enum ProcessState_e
{
	PROCESSSTATE_NOT_STARTED = 0,
	PROCESSSTATE_RUNNING,
	PROCESSSTATE_FINISHED,

	PROCESSSTATE_LAST
} ProcessState;

struct deProcess_s
{
	ProcessState	state;
	int				exitCode;
	char*			lastError;

	pid_t			pid;
	deFile*			standardIn;
	deFile*			standardOut;
	deFile*			standardErr;
};

static void die (int statusPipe, const char* message)
{
	int msgLen = strlen(message);
	int res = 0;
	printf("Process launch failed: %s\n", message);
	res = (int)write(statusPipe, message, msgLen+1);
	DE_UNREF(res); /* No need to check result. */
	exit(-1);
}

static void dieLastError (int statusPipe, const char* message)
{
	char	msgBuf[256];
	int		lastErr	= errno;
	deSprintf(msgBuf, sizeof(msgBuf), "%s, error %d: %s", message, lastErr, strerror(lastErr));
	die(statusPipe, msgBuf);
}

DE_INLINE deBool beginsWithPath (const char* fileName, const char* pathPrefix)
{
	int pathLen = strlen(pathPrefix);

	/* Strip trailing / */
	while (pathLen > 0 && pathPrefix[pathLen-1] == '/')
		pathLen -= 1;

	return pathLen > 0 && deMemoryEqual(fileName, pathPrefix, pathLen) && fileName[pathLen] == '/';
}

static void stripLeadingPath (char* fileName, const char* pathPrefix)
{
	int pathLen		= strlen(pathPrefix);
	int fileNameLen	= strlen(fileName);

	DE_ASSERT(beginsWithPath(fileName, pathPrefix));

	/* Strip trailing / */
	while (pathLen > 0 && pathPrefix[pathLen-1] == '/')
		pathLen -= 1;

	DE_ASSERT(pathLen > 0);
	DE_ASSERT(fileName[pathLen] == '/');

	memmove(&fileName[0], &fileName[0]+pathLen+1, fileNameLen-pathLen);
}

/* Doesn't return on success. */
static void execProcess (const char* commandLine, const char* workingDirectory, int statusPipe)
{
	deCommandLine*	cmdLine		= deCommandLine_parse(commandLine);
	char**			argList		= cmdLine ? (char**)deCalloc(sizeof(char*)*(cmdLine->numArgs+1)) : DE_NULL;

	if (!cmdLine || !argList)
		die(statusPipe, "Command line parsing failed (out of memory)");

	if (workingDirectory && chdir(workingDirectory) != 0)
		dieLastError(statusPipe, "chdir() failed");

	{
		int argNdx;
		for (argNdx = 0; argNdx < cmdLine->numArgs; argNdx++)
			argList[argNdx] = cmdLine->args[argNdx];
		argList[argNdx] = DE_NULL; /* Terminate with 0. */
	}

	if (workingDirectory && beginsWithPath(argList[0], workingDirectory))
		stripLeadingPath(argList[0], workingDirectory);

	execv(argList[0], argList);

	/* Failed. */
	dieLastError(statusPipe, "execv() failed");
}

deProcess* deProcess_create (void)
{
	deProcess* process = (deProcess*)deCalloc(sizeof(deProcess));
	if (!process)
		return DE_FALSE;

	process->state = PROCESSSTATE_NOT_STARTED;

	return process;
}

static void deProcess_cleanupHandles (deProcess* process)
{
	if (process->standardIn)
		deFile_destroy(process->standardIn);

	if (process->standardOut)
		deFile_destroy(process->standardOut);

	if (process->standardErr)
		deFile_destroy(process->standardErr);

	process->pid			= 0;
	process->standardIn		= DE_NULL;
	process->standardOut	= DE_NULL;
	process->standardErr	= DE_NULL;
}

void deProcess_destroy (deProcess* process)
{
	/* Never leave child processes running. Otherwise we'll have zombies. */
	if (deProcess_isRunning(process))
	{
		deProcess_kill(process);
		deProcess_waitForFinish(process);
	}

	deProcess_cleanupHandles(process);
	deFree(process->lastError);
	deFree(process);
}

const char* deProcess_getLastError (const deProcess* process)
{
	return process->lastError ? process->lastError : "No error";
}

int deProcess_getExitCode (const deProcess* process)
{
	return process->exitCode;
}

static deBool deProcess_setError (deProcess* process, const char* error)
{
	if (process->lastError)
	{
		deFree(process->lastError);
		process->lastError = DE_NULL;
	}

	process->lastError = deStrdup(error);
	return process->lastError != DE_NULL;
}

static deBool deProcess_setErrorFromErrno (deProcess* process, const char* message)
{
	char	msgBuf[256];
	int		lastErr		= errno;
	deSprintf(msgBuf, sizeof(msgBuf), "%s, error %d: %s", message, lastErr, strerror(lastErr));
	return deProcess_setError(process, message);
}

static void closePipe (int p[2])
{
	if (p[0] >= 0)
		close(p[0]);
	if (p[1] >= 0)
		close(p[1]);
}

deBool deProcess_start (deProcess* process, const char* commandLine, const char* workingDirectory)
{
	pid_t		pid				= 0;
	int			pipeIn[2]		= { -1, -1 };
	int			pipeOut[2]		= { -1, -1 };
	int			pipeErr[2]		= { -1, -1 };
	int			statusPipe[2]	= { -1, -1 };

	if (process->state == PROCESSSTATE_RUNNING)
	{
		deProcess_setError(process, "Process already running");
		return DE_FALSE;
	}
	else if (process->state == PROCESSSTATE_FINISHED)
	{
		deProcess_cleanupHandles(process);
		process->state = PROCESSSTATE_NOT_STARTED;
	}

	if (pipe(pipeIn) < 0 || pipe(pipeOut) < 0 || pipe(pipeErr) < 0 || pipe(statusPipe) < 0)
	{
		deProcess_setErrorFromErrno(process, "pipe() failed");

		closePipe(pipeIn);
		closePipe(pipeOut);
		closePipe(pipeErr);
		closePipe(statusPipe);

		return DE_FALSE;
	}

	pid = fork();

	if (pid < 0)
	{
		deProcess_setErrorFromErrno(process, "fork() failed");

		closePipe(pipeIn);
		closePipe(pipeOut);
		closePipe(pipeErr);
		closePipe(statusPipe);

		return DE_FALSE;
	}

	if (pid == 0)
	{
		/* Child process. */

		/* Close unused endpoints. */
		close(pipeIn[1]);
		close(pipeOut[0]);
		close(pipeErr[0]);
		close(statusPipe[0]);

		/* Set status pipe to close on exec(). That way parent will know that exec() succeeded. */
		if (fcntl(statusPipe[1], F_SETFD, FD_CLOEXEC) != 0)
			dieLastError(statusPipe[1], "Failed to set FD_CLOEXEC");

		/* Map stdin. */
		if (pipeIn[0] != STDIN_FILENO &&
			dup2(pipeIn[0], STDIN_FILENO) != STDIN_FILENO)
			dieLastError(statusPipe[1], "dup2() failed");
		close(pipeIn[0]);

		/* Stdout. */
		if (pipeOut[1] != STDOUT_FILENO &&
			dup2(pipeOut[1], STDOUT_FILENO) != STDOUT_FILENO)
			dieLastError(statusPipe[1], "dup2() failed");
		close(pipeOut[1]);

		/* Stderr. */
		if (pipeErr[1] != STDERR_FILENO &&
			dup2(pipeErr[1], STDERR_FILENO) != STDERR_FILENO)
			dieLastError(statusPipe[1], "dup2() failed");
		close(pipeErr[1]);

		/* Doesn't return. */
		execProcess(commandLine, workingDirectory, statusPipe[1]);
	}
	else
	{
		/* Parent process. */

		/* Check status. */
		{
			char	errBuf[256];
			int		result	= 0;

			close(statusPipe[1]);
			while ((result = read(statusPipe[0], errBuf, 1)) == -1)
				if (errno != EAGAIN && errno != EINTR) break;

			if (result > 0)
			{
				/* Read full error msg. */
				int errPos = 1;
				while (errPos < DE_LENGTH_OF_ARRAY(errBuf))
				{
					result = read(statusPipe[0], errBuf+errPos, 1);
					if (result == -1)
						break; /* Done. */

					errPos += 1;
				}

				/* Make sure str is null-terminated. */
				errBuf[errPos] = 0;

				/* Close handles. */
				close(statusPipe[0]);
				closePipe(pipeIn);
				closePipe(pipeOut);
				closePipe(pipeErr);

				/* Run waitpid to clean up zombie. */
				waitpid(pid, &result, 0);

				deProcess_setError(process, errBuf);

				return DE_FALSE;
			}

			/* Status pipe is not needed. */
			close(statusPipe[0]);
		}

		/* Set running state. */
		process->pid		= pid;
		process->state		= PROCESSSTATE_RUNNING;

		/* Stdin, stdout. */
		close(pipeIn[0]);
		close(pipeOut[1]);
		close(pipeErr[1]);

		process->standardIn		= deFile_createFromHandle(pipeIn[1]);
		process->standardOut	= deFile_createFromHandle(pipeOut[0]);
		process->standardErr	= deFile_createFromHandle(pipeErr[0]);

		if (!process->standardIn)
			close(pipeIn[1]);

		if (!process->standardOut)
			close(pipeOut[0]);

		if (!process->standardErr)
			close(pipeErr[0]);
	}

	return DE_TRUE;
}

deBool deProcess_isRunning (deProcess* process)
{
	if (process->state == PROCESSSTATE_RUNNING)
	{
		int status = 0;

		if (waitpid(process->pid, &status, WNOHANG) == 0)
			return DE_TRUE; /* No status available. */

		if (WIFEXITED(status) || WIFSIGNALED(status))
		{
			/* Child has finished. */
			process->state = PROCESSSTATE_FINISHED;
			return DE_FALSE;
		}
		else
			return DE_TRUE;
	}
	else
		return DE_FALSE;
}

deBool deProcess_waitForFinish (deProcess* process)
{
	int		status = 0;
	pid_t	waitResult;

	if (process->state != PROCESSSTATE_RUNNING)
	{
		deProcess_setError(process, "Process is not running");
		return DE_FALSE;
	}

	/* \note [pyry] Crazy hack for OS X Lion. Stupid Apple. */
	while ((waitResult = waitpid(process->pid, &status, 0)) != process->pid)
		if (errno != ENOENT) break;

	if (waitResult != process->pid)
	{
		deProcess_setErrorFromErrno(process, "waitpid() failed");
		return DE_FALSE; /* waitpid() failed. */
	}

	if (!WIFEXITED(status) && !WIFSIGNALED(status))
	{
		deProcess_setErrorFromErrno(process, "waitpid() failed");
		return DE_FALSE; /* Something strange happened. */
	}

	process->exitCode	= WEXITSTATUS(status);
	process->state		= PROCESSSTATE_FINISHED;
	return DE_TRUE;
}

static deBool deProcess_sendSignal (deProcess* process, int sigNum)
{
	if (process->state != PROCESSSTATE_RUNNING)
	{
		deProcess_setError(process, "Process is not running");
		return DE_FALSE;
	}

	if (kill(process->pid, sigNum) == 0)
		return DE_TRUE;
	else
	{
		deProcess_setErrorFromErrno(process, "kill() failed");
		return DE_FALSE;
	}
}

deBool deProcess_terminate (deProcess* process)
{
	return deProcess_sendSignal(process, SIGTERM);
}

deBool deProcess_kill (deProcess* process)
{
	return deProcess_sendSignal(process, SIGKILL);
}

deFile* deProcess_getStdIn (deProcess* process)
{
	return process->standardIn;
}

deFile* deProcess_getStdOut (deProcess* process)
{
	return process->standardOut;
}

deFile* deProcess_getStdErr (deProcess* process)
{
	return process->standardErr;
}

deBool deProcess_closeStdIn (deProcess* process)
{
	if (process->standardIn)
	{
		deFile_destroy(process->standardIn);
		process->standardIn	= DE_NULL;
		return DE_TRUE;
	}
	else
		return DE_FALSE;
}

deBool deProcess_closeStdOut (deProcess* process)
{
	if (process->standardOut)
	{
		deFile_destroy(process->standardOut);
		process->standardOut = DE_NULL;
		return DE_TRUE;
	}
	else
		return DE_FALSE;
}

deBool deProcess_closeStdErr (deProcess* process)
{
	if (process->standardErr)
	{
		deFile_destroy(process->standardErr);
		process->standardErr = DE_NULL;
		return DE_TRUE;
	}
	else
		return DE_FALSE;
}

#elif (DE_OS == DE_OS_WIN32)

#define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <strsafe.h>

typedef enum ProcessState_e
{
	PROCESSSTATE_NOT_STARTED = 0,
	PROCESSSTATE_RUNNING,
	PROCESSSTATE_FINISHED,

	PROCESSSTATE_LAST
} ProcessState;

struct deProcess_s
{
	ProcessState			state;
	char*					lastError;
	int						exitCode;

	PROCESS_INFORMATION		procInfo;
	deFile*					standardIn;
	deFile*					standardOut;
	deFile*					standardErr;
};

static deBool deProcess_setError (deProcess* process, const char* error)
{
	if (process->lastError)
	{
		deFree(process->lastError);
		process->lastError = DE_NULL;
	}

	process->lastError = deStrdup(error);
	return process->lastError != DE_NULL;
}

static deBool deProcess_setErrorFromWin32 (deProcess* process, const char* msg)
{
	DWORD		error	= GetLastError();
	LPSTR		msgBuf;
	char		errBuf[256];

#if defined(UNICODE)
#	error Unicode not supported.
#endif

	if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
					  NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msgBuf, 0, DE_NULL) > 0)
	{
		deSprintf(errBuf, sizeof(errBuf), "%s, error %d: %s", msg, error, msgBuf);
		LocalFree(msgBuf);
		return deProcess_setError(process, errBuf);
	}
	else
	{
		/* Failed to get error str. */
		deSprintf(errBuf, sizeof(errBuf), "%s, error %d", msg, error);
		return deProcess_setError(process, errBuf);
	}
}

deProcess* deProcess_create (void)
{
	deProcess* process = (deProcess*)deCalloc(sizeof(deProcess));
	if (!process)
		return DE_NULL;

	process->state = PROCESSSTATE_NOT_STARTED;

	return process;
}

void deProcess_cleanupHandles (deProcess* process)
{
	DE_ASSERT(!deProcess_isRunning(process));

	if (process->standardErr)
		deFile_destroy(process->standardErr);

	if (process->standardOut)
		deFile_destroy(process->standardOut);

	if (process->standardIn)
		deFile_destroy(process->standardIn);

	if (process->procInfo.hProcess)
		CloseHandle(process->procInfo.hProcess);

	if (process->procInfo.hThread)
		CloseHandle(process->procInfo.hThread);

	process->standardErr		= DE_NULL;
	process->standardOut		= DE_NULL;
	process->standardIn			= DE_NULL;
	process->procInfo.hProcess	= DE_NULL;
	process->procInfo.hThread	= DE_NULL;
}

void deProcess_destroy (deProcess* process)
{
	if (deProcess_isRunning(process))
	{
		deProcess_kill(process);
		deProcess_waitForFinish(process);
	}

	deProcess_cleanupHandles(process);
	deFree(process->lastError);
	deFree(process);
}

const char* deProcess_getLastError (const deProcess* process)
{
	return process->lastError ? process->lastError : "No error";
}

int deProcess_getExitCode (const deProcess* process)
{
	return process->exitCode;
}

deBool deProcess_start (deProcess* process, const char* commandLine, const char* workingDirectory)
{
	SECURITY_ATTRIBUTES	securityAttr;
	STARTUPINFO			startInfo;

	/* Pipes. */
	HANDLE		stdInRead	= DE_NULL;
	HANDLE		stdInWrite	= DE_NULL;
	HANDLE		stdOutRead	= DE_NULL;
	HANDLE		stdOutWrite	= DE_NULL;
	HANDLE		stdErrRead	= DE_NULL;
	HANDLE		stdErrWrite	= DE_NULL;

	if (process->state == PROCESSSTATE_RUNNING)
	{
		deProcess_setError(process, "Process already running");
		return DE_FALSE;
	}
	else if (process->state == PROCESSSTATE_FINISHED)
	{
		/* Process finished, clean up old cruft. */
		deProcess_cleanupHandles(process);
		process->state = PROCESSSTATE_NOT_STARTED;
	}

	deMemset(&startInfo, 0, sizeof(startInfo));
	deMemset(&securityAttr, 0, sizeof(securityAttr));

	/* Security attributes for inheriting handle. */
	securityAttr.nLength				= sizeof(SECURITY_ATTRIBUTES);
	securityAttr.bInheritHandle			= TRUE;
	securityAttr.lpSecurityDescriptor	= DE_NULL;

	/* Create pipes. \todo [2011-10-03 pyry] Clean up handles on error! */
	if (!CreatePipe(&stdInRead, &stdInWrite, &securityAttr, 0) ||
		!SetHandleInformation(stdInWrite, HANDLE_FLAG_INHERIT, 0))
	{
		deProcess_setErrorFromWin32(process, "CreatePipe() failed");
		CloseHandle(stdInRead);
		CloseHandle(stdInWrite);
		return DE_FALSE;
	}

	if (!CreatePipe(&stdOutRead, &stdOutWrite, &securityAttr, 0) ||
		!SetHandleInformation(stdOutRead, HANDLE_FLAG_INHERIT, 0))
	{
		deProcess_setErrorFromWin32(process, "CreatePipe() failed");
		CloseHandle(stdInRead);
		CloseHandle(stdInWrite);
		CloseHandle(stdOutRead);
		CloseHandle(stdOutWrite);
		return DE_FALSE;
	}

	if (!CreatePipe(&stdErrRead, &stdErrWrite, &securityAttr, 0) ||
		!SetHandleInformation(stdErrRead, HANDLE_FLAG_INHERIT, 0))
	{
		deProcess_setErrorFromWin32(process, "CreatePipe() failed");
		CloseHandle(stdInRead);
		CloseHandle(stdInWrite);
		CloseHandle(stdOutRead);
		CloseHandle(stdOutWrite);
		CloseHandle(stdErrRead);
		CloseHandle(stdErrWrite);
		return DE_FALSE;
	}

	/* Setup startup info. */
	startInfo.cb = sizeof(startInfo);
	startInfo.hStdError		 = stdErrWrite;
	startInfo.hStdOutput	 = stdOutWrite;
	startInfo.hStdInput		 = stdInRead;
	startInfo.dwFlags		|= STARTF_USESTDHANDLES;

	if (!CreateProcess(DE_NULL, (LPTSTR)commandLine, DE_NULL, DE_NULL, TRUE /* inherit handles */, 0, DE_NULL, workingDirectory, &startInfo, &process->procInfo))
	{
		/* Store error info. */
		deProcess_setErrorFromWin32(process, "CreateProcess() failed");

		/* Close all handles. */
		CloseHandle(stdInRead);
		CloseHandle(stdInWrite);
		CloseHandle(stdOutRead);
		CloseHandle(stdOutWrite);
		CloseHandle(stdErrRead);
		CloseHandle(stdErrWrite);

		return DE_FALSE;
	}

	process->state = PROCESSSTATE_RUNNING;

	/* Close our ends of handles.*/
	CloseHandle(stdErrWrite);
	CloseHandle(stdOutWrite);
	CloseHandle(stdInRead);

	/* Construct stdio file objects \note May fail, not detected. */
	process->standardIn		= deFile_createFromHandle((deUintptr)stdInWrite);
	process->standardOut	= deFile_createFromHandle((deUintptr)stdOutRead);
	process->standardErr	= deFile_createFromHandle((deUintptr)stdErrRead);

	return DE_TRUE;
}

deBool deProcess_isRunning (deProcess* process)
{
	if (process->state == PROCESSSTATE_RUNNING)
	{
		int exitCode;
		BOOL result = GetExitCodeProcess(process->procInfo.hProcess, (LPDWORD)&exitCode);

		if (result != TRUE)
		{
			deProcess_setErrorFromWin32(process, "GetExitCodeProcess() failed");
			return DE_FALSE;
		}

		if (exitCode == STILL_ACTIVE)
			return DE_TRUE;
		else
		{
			/* Done. */
			process->exitCode	= exitCode;
			process->state		= PROCESSSTATE_FINISHED;
			return DE_FALSE;
		}
	}
	else
		return DE_FALSE;
}

deBool deProcess_waitForFinish (deProcess* process)
{
	if (process->state == PROCESSSTATE_RUNNING)
	{
		if (WaitForSingleObject(process->procInfo.hProcess, INFINITE) != WAIT_OBJECT_0)
		{
			deProcess_setErrorFromWin32(process, "WaitForSingleObject() failed");
			return DE_FALSE;
		}
		return !deProcess_isRunning(process);
	}
	else
	{
		deProcess_setError(process, "Process is not running");
		return DE_FALSE;
	}
}

static deBool stopProcess (deProcess* process, deBool kill)
{
	if (process->state == PROCESSSTATE_RUNNING)
	{
		if (!TerminateProcess(process->procInfo.hProcess, kill ? -1 : 0))
		{
			deProcess_setErrorFromWin32(process, "TerminateProcess() failed");
			return DE_FALSE;
		}
		else
			return DE_TRUE;
	}
	else
	{
		deProcess_setError(process, "Process is not running");
		return DE_FALSE;
	}
}

deBool deProcess_terminate (deProcess* process)
{
	return stopProcess(process, DE_FALSE);
}

deBool deProcess_kill (deProcess* process)
{
	return stopProcess(process, DE_TRUE);
}

deFile* deProcess_getStdIn (deProcess* process)
{
	return process->standardIn;
}

deFile* deProcess_getStdOut (deProcess* process)
{
	return process->standardOut;
}

deFile* deProcess_getStdErr (deProcess* process)
{
	return process->standardErr;
}

deBool deProcess_closeStdIn (deProcess* process)
{
	if (process->standardIn)
	{
		deFile_destroy(process->standardIn);
		process->standardIn	= DE_NULL;
		return DE_TRUE;
	}
	else
		return DE_FALSE;
}

deBool deProcess_closeStdOut (deProcess* process)
{
	if (process->standardOut)
	{
		deFile_destroy(process->standardOut);
		process->standardOut = DE_NULL;
		return DE_TRUE;
	}
	else
		return DE_FALSE;
}

deBool deProcess_closeStdErr (deProcess* process)
{
	if (process->standardErr)
	{
		deFile_destroy(process->standardErr);
		process->standardErr = DE_NULL;
		return DE_TRUE;
	}
	else
		return DE_FALSE;
}

#else
#	error Implement deProcess for your OS.
#endif