#include <stdio.h>
#include <limits.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <wchar.h>
#include <locale.h>
#include <stdlib.h>

#define PYTHON3_STDLIB_REL_PATH "stdlib.zip"
#define PYTHON3_MODULES_REL_PATH "modules"
#define PYTHON3_DLL_REL_PATH "libpython3.7m.so"

#define SYS_PATH_BUFFER_SIZE (2*(PATH_MAX + 1))

static char NULL_PTR_STR[] = "NULL";

static void GetExecutablePath(char* path)
{
  int size = readlink("/proc/self/exe", path, PATH_MAX);
  if (size < 0)
    size = 0;
  path[size] = 0;
}

static void GetRelativePathFormat(char* base, char* fmt)
{
  unsigned idx;
  char *p, *end;
  end = strrchr(base, '/');
  for (idx = 0, p = base; *p; ++p, ++idx)
  {
    fmt[idx] = *p;
    if (p == end)
      break;
  }
  fmt[++idx] = '%';
  fmt[++idx] = 's';
  fmt[++idx] = 0;
}

typedef void (*Py_SetProgramNamePtr)(wchar_t*);
typedef void (*Py_SetPathPtr)(const wchar_t*);
typedef int (*Py_MainPtr)(int, wchar_t**);
typedef void* (*PyMem_RawMallocPtr)(size_t);
typedef void (*PyMem_RawFreePtr)(void*);
typedef wchar_t* (*Py_DecodeLocalePtr)(const char*, size_t*);


int main(int argc, char** argv)
{
  char executable[PATH_MAX + 1] = {0};
  char pthfmt[PATH_MAX + 1]     = {0};
  char corepath[PATH_MAX + 1]   = {0};
  char stdlibpath[PATH_MAX + 1] = {0};
  char modpath[PATH_MAX + 1]    = {0};
  char syspath[SYS_PATH_BUFFER_SIZE] = {0};
  void* core = 0;
  int retcode = 126;
  int i;

  Py_SetProgramNamePtr Py_SetProgramName = 0;
  Py_SetPathPtr Py_SetPath = 0;
  Py_MainPtr Py_Main = 0;
  PyMem_RawMallocPtr PyMem_RawMalloc = 0;
  PyMem_RawFreePtr PyMem_RawFree = 0;
  Py_DecodeLocalePtr Py_DecodeLocale = 0;

  GetExecutablePath(executable);
  GetRelativePathFormat(executable, pthfmt);

  snprintf(corepath, PATH_MAX, pthfmt, PYTHON3_DLL_REL_PATH);
  snprintf(stdlibpath, PATH_MAX, pthfmt, PYTHON3_STDLIB_REL_PATH);
  snprintf(modpath, PATH_MAX, pthfmt, PYTHON3_MODULES_REL_PATH);
  snprintf(syspath, SYS_PATH_BUFFER_SIZE-1, "%s:%s", stdlibpath, modpath);

  core = dlopen(corepath, RTLD_LAZY);
  if (core == 0)
  {
    const char* lasterr = dlerror();
    if (lasterr == 0)
      lasterr = NULL_PTR_STR;
    fprintf(stderr, "Fatal Python error: cannot load library: '%s', dlerror: %s\n", corepath, lasterr);
    goto exit;
  }

  Py_SetProgramName = (Py_SetProgramNamePtr)dlsym(core, "Py_SetProgramName");
  if (Py_SetProgramName == 0)
  {
    const char* lasterr = dlerror();
    if (lasterr == 0)
      lasterr = NULL_PTR_STR;
    fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "Py_SetProgramName", corepath, lasterr);
    goto exit;
  }

  Py_SetPath = (Py_SetPathPtr)dlsym(core, "Py_SetPath");
  if (Py_SetPath == 0)
  {
    const char* lasterr = dlerror();
    if (lasterr == 0)
      lasterr = NULL_PTR_STR;
    fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "Py_SetPath", corepath, lasterr);
    goto exit;
  }

  Py_Main = (Py_MainPtr)dlsym(core, "Py_Main");
  if (Py_Main == 0)
  {
    const char* lasterr = dlerror();
    if (lasterr == 0)
      lasterr = NULL_PTR_STR;
    fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "Py_Main", corepath, lasterr);
    goto exit;
  }

  PyMem_RawMalloc = (PyMem_RawMallocPtr)dlsym(core, "PyMem_RawMalloc");
  if (PyMem_RawMalloc == 0)
  {
    const char* lasterr = dlerror();
    if (lasterr == 0)
      lasterr = NULL_PTR_STR;
    fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "PyMem_RawMalloc", corepath, lasterr);
    goto exit;
  }

  PyMem_RawFree = (PyMem_RawFreePtr)dlsym(core, "PyMem_RawFree");
  if (PyMem_RawFree == 0)
  {
    const char* lasterr = dlerror();
    if (lasterr == 0)
      lasterr = NULL_PTR_STR;
    fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "PyMem_RawFree", corepath, lasterr);
    goto exit;
  }

  Py_DecodeLocale = (Py_DecodeLocalePtr)dlsym(core, "Py_DecodeLocale");
  if (Py_DecodeLocale == 0)
  {
    const char* lasterr = dlerror();
    if (lasterr == 0)
      lasterr = NULL_PTR_STR;
    fprintf(stderr, "Fatal Python error: cannot load symbol: '%s' from library '%s', dlerror: %s\n", "Py_DecodeLocale", corepath, lasterr);
    goto exit;
  }

  wchar_t* executable_w = Py_DecodeLocale(executable, 0);
  if (executable_w == 0)
  {
    fprintf(stderr, "Fatal Python error: unable to decode executable path: '%s'\n", executable);
    goto exit;
  }

  wchar_t* syspath_w = Py_DecodeLocale(syspath, 0);
  if (syspath_w == 0)
  {
    fprintf(stderr, "Fatal Python error: unable to decode syspath: '%s'\n", syspath);
    goto exit;
  }

  wchar_t** argv_copy = (wchar_t **)PyMem_RawMalloc(sizeof(wchar_t*)*(argc+1));
  wchar_t** argv_copy2 = (wchar_t **)PyMem_RawMalloc(sizeof(wchar_t*)*(argc+1));

  char* oldloc = strdup(setlocale(LC_ALL, 0));
  setlocale(LC_ALL, "");
  for (i = 0; i < argc; ++i)
  {
    argv_copy[i] = Py_DecodeLocale(argv[i], 0);
    if (argv_copy[i] == 0)
    {
      free(oldloc);
      fprintf(stderr, "Fatal Python error: unable to decode the command line argument #%i\n", i + 1);
      goto exit;
    }
    argv_copy2[i] = argv_copy[i];
  }
  argv_copy2[argc] = argv_copy[argc] = 0;
  setlocale(LC_ALL, oldloc);
  free(oldloc);

  Py_SetProgramName(executable_w);
  Py_SetPath(syspath_w);
  retcode = Py_Main(argc, argv_copy);

  PyMem_RawFree(executable_w);
  PyMem_RawFree(syspath_w);
  for (i = 0; i < argc; i++)
  {
    PyMem_RawFree(argv_copy2[i]);
  }
  PyMem_RawFree(argv_copy);
  PyMem_RawFree(argv_copy2);

exit:
  if (core != 0)
    dlclose(core);

  return retcode;
}
