/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8                               :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/intrin/getenv.h"
#include "libc/mem/alloca.h"
#include "libc/proc/ntspawn.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"

#define ToUpper(c) ((c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 'A' : (c))

struct EnvBuilder {
  char *buf;
  char **var;
  int bufi;
  int vari;
};

static inline int IsAlpha(int c) {
  return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}

static textwindows int Compare(const char *l, const char *r) {
  int a, b;
  size_t i = 0;
  for (;;) {
    a = l[i] & 255;
    b = r[i] & 255;
    if (a == '=')
      a = 0;
    if (b == '=')
      b = 0;
    if (a != b || !b)
      break;
    ++i;
  }
  return a - b;
}

static textwindows int InsertString(struct EnvBuilder *env, const char *str) {
  int c, i, cmp;
  char *var, *path = 0;

  if (!str)
    return 0;

  // copy key=val to buf
  var = env->buf + env->bufi;
  do {
    c = *str++;
    if (env->bufi + 2 > 32767)
      return e2big();
    env->buf[env->bufi++] = c;
    if (c == '=' && str[0] == '/' && IsAlpha(str[1]) && str[2] == '/') {
      path = env->buf + env->bufi;
    }
  } while (c);

  // fixup key=/c/... → key=c:\...
  if (path)
    mungentpath(path);

  // append key=val to sorted list using insertion sort technique
  for (i = env->vari;; --i) {
    if (!i || (cmp = Compare(var, env->var[i - 1])) > 0) {
      // insert entry for new key
      env->var[i] = var;
      env->vari++;
      break;
    }
    if (!cmp) {
      // deduplicate preferring latter
      env->var[i - 1] = var;
      for (; i < env->vari; ++i) {
        env->var[i] = env->var[i + 1];
      }
      break;
    }
    // sift items right to create empty slot at insertion point
    env->var[i] = env->var[i - 1];
  }
  return 0;
}

static textwindows int InsertStrings(struct EnvBuilder *env,
                                     char *const strs[]) {
  if (strs) {
    for (int i = 0; strs[i]; ++i) {
      if (InsertString(env, strs[i]) == -1) {
        return -1;
      }
    }
  }
  return 0;
}

static textwindows int CountStrings(char *const strs[]) {
  int n = 0;
  if (strs) {
    while (*strs++) {
      ++n;
    }
  }
  return n;
}

/**
 * Copies sorted environment variable block for Windows.
 *
 * This is designed to meet the requirements of CreateProcess().
 *
 * @param envblock receives sorted double-NUL terminated string list
 * @param envp is an a NULL-terminated array of UTF-8 strings
 * @param extravar is a VAR=val string we consider part of envp or NULL
 * @return 0 on success, or -1 w/ errno
 * @error E2BIG if total number of shorts (including nul) exceeded 32767
 * @asyncsignalsafe
 */
textwindows int mkntenvblock(char16_t envblock[32767], char *const envp[],
                             char *const extravars[], char buf[32767]) {
  int i, k, n;
  struct Env e;
  struct EnvBuilder env = {buf};

  // allocate string pointer array for sorting purposes
  n = (CountStrings(envp) + CountStrings(extravars) + 1) * sizeof(char *);
#pragma GCC push_options
#pragma GCC diagnostic ignored "-Walloca-larger-than="
#pragma GCC diagnostic ignored "-Wanalyzer-out-of-bounds"
  env.var = alloca(n);
  CheckLargeStackAllocation(env.var, n);
#pragma GCC pop_options

  // load new environment into string pointer array and fix file paths
  if (InsertStrings(&env, envp) == -1)
    return -1;
  if (InsertStrings(&env, extravars) == -1)
    return -1;
  if (environ) {
    // https://jpassing.com/2009/12/28/the-hidden-danger-of-forgetting-to-specify-systemroot-in-a-custom-environment-block/
    e = __getenv(environ, "SYSTEMROOT");
    if (e.s && InsertString(&env, environ[e.i]) == -1) {
      return -1;
    }
  }

  // copy utf-8 sorted string pointer array into contiguous utf-16 block
  // in other words, we're creating a double-nul terminated string list!
  for (k = i = 0; i < env.vari; ++i) {
    k += tprecode8to16(envblock + k, -1, env.var[i]).ax + 1;
  }
  unassert(k <= env.bufi);
  envblock[k] = 0;

  return 0;
}
