#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#  include <process.h>
#else
#  include <sys/types.h>
#  include <sys/wait.h>
#endif

#define ARRAY_LEN(ARRAY) (sizeof(ARRAY) / sizeof *(ARRAY))

#define CONCAT_ARRAYS(TYPE, ARRAY_A, ARRAY_B) \
  (TYPE*)concatArrays((const void** const)(ARRAY_A), \
  ARRAY_LEN(ARRAY_A) * sizeof(TYPE), (const void** const)(ARRAY_B), \
  ARRAY_LEN(ARRAY_B) * sizeof(TYPE), sizeof(TYPE))

void*
concatArrays(const void* const arrayA, const size_t sizeA,
  const void* const arrayB, const size_t sizeB, const size_t sizeType) {
    char* const concatenatedArray = malloc(sizeType * (sizeA + sizeB));
    memcpy(concatenatedArray, arrayA, sizeA * sizeType);
    memcpy(concatenatedArray + sizeA * sizeType, arrayB, sizeB * sizeType);
    return concatenatedArray;
}

char*
joinStr(const char* const strA, const char* const strB,
  const char* const separator) {
    const int lenA = strlen(strA);
    const int lenB = strlen(strB);
    const int lenSeparator = strlen(separator);
    char* const concatenatedStr = malloc(lenA + lenB + lenSeparator + 1);
    memcpy(concatenatedStr, strA, lenA);
    memcpy(concatenatedStr + lenA, separator, lenSeparator);
    memcpy(concatenatedStr + lenA + lenSeparator, strB, lenB + 1);
    return concatenatedStr;
}

int
spawnParpgProcess(char* const pythonExecutable, char* const configDir,
  const int argc, char* const argv[]) {
    const int sizeCharPtr = sizeof(char*);
    const int nBaseArgs = 4;
    char** const baseArgs = malloc(nBaseArgs * sizeCharPtr);
    const int nAdditionalArgs = argc - 1;
    char** const additionalArgs =
      (char**)malloc(nAdditionalArgs * sizeCharPtr);
    const int nArgs = nBaseArgs + nAdditionalArgs;
    char* const oldPythonPath = getenv("PYTHONPATH");
    char* newPythonPath;
    // Use calloc so that the last element is NULL.
    char** const env = calloc(2, sizeCharPtr);
    int exitStatus;
    // Use calloc so that the last element is NULL.
    char** const args = (char**)calloc(nArgs + 1,  sizeCharPtr);
    int i;
    
    baseArgs[0] = pythonExecutable;
    baseArgs[1] = "-m";
    baseArgs[2] = "parpg.main";
    baseArgs[3] = configDir;
    if (nAdditionalArgs > 0) {
        for (i = 1; i < argc; i++) {
            additionalArgs[i - 1] = argv[i];
        }
        memcpy(args, CONCAT_ARRAYS(char*, baseArgs, additionalArgs),
          nArgs * sizeCharPtr);
    } else {
        // Use memcpy so that the last NULL element in args is preserved.
        memcpy(args, baseArgs, nArgs * sizeCharPtr);
    }
    if (oldPythonPath != 0) {
        newPythonPath = joinStr(oldPythonPath, "@PY_PACKAGES_DIR@", ":");
    } else {
        newPythonPath = "@PY_PACKAGES_DIR@";
    }
    env[0] = joinStr("PYTHONPATH=", newPythonPath, "");
#ifdef _WIN32
#  ifdef _MSC_VER // MSVC deprecated POSIX spawn functions.
    exitStatus = _spawnve(_P_WAIT, pythonExecutable, args, env);
#  else
    exitStatus = spawnve(P_WAIT, pythonExecutable, args, env);
#  endif
#else
    exitStatus = execve(pythonExecutable, args, env);
#endif
    free(baseArgs);
    free(additionalArgs);
    free(env);
    free(args);
    return exitStatus;
}

int
main(int argc, char* argv[]) {
    const int exitStatus = spawnParpgProcess("@PYTHON@", "@SYS_CONF_DIR@",
      argc, argv);
    if (exitStatus < 0) {
        perror("failed to execute subprocess");
        exit(EXIT_FAILURE);
    } else {
        exit(EXIT_SUCCESS);
    }
}

