// SPDX-License-Identifier: GPL-3.0-or-later

#include <sys/utsname.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>

#include "specifier.h"
#include "util.h"

static const char *resolve_specifier(char spec, bool user_mode) {
  static char uid_buf[32], gid_buf[32];

  switch (spec) {

  case 'm':
    return get_machine_id();

  case 'b':
    return get_boot_id();

  case 'H': {
    static char host[256];
    static bool cached = false;
    if (!cached) {
      if (gethostname(host, sizeof(host)) < 0)
        return NULL;
      host[sizeof(host) - 1] = '\0';
      cached = true;
    }
    return host;
  }

  case 'v': {
    static struct utsname uts;
    static bool cached = false;
    if (!cached) {
      if (uname(&uts) < 0)
        return NULL;
      cached = true;
    }
    return uts.release;
  }

  case 'u': {
    struct passwd *pw = getpwuid(getuid());
    return pw ? pw->pw_name : NULL;
  }

  case 'U': {
    snprintf(uid_buf, sizeof(uid_buf), "%u", getuid());
    return uid_buf;
  }

  case 'g': {
    struct group *gr = getgrgid(getgid());
    return gr ? gr->gr_name : NULL;
  }

  case 'G': {
    snprintf(gid_buf, sizeof(gid_buf), "%u", getgid());
    return gid_buf;
  }

  case 'h': {
    struct passwd *pw = getpwuid(getuid());
    return pw ? pw->pw_dir : NULL;
  }

  case 't':
    if (user_mode) {
      const char *rt = getenv("XDG_RUNTIME_DIR");
      return rt ? rt : NULL;
    }
    return "/run";

  case 'S':
    if (user_mode) {
      static char *state = NULL;
      static bool cached = false;
      if (!cached) {
        const char *d = getenv("XDG_STATE_HOME");
        if (d)
          asprintf(&state, "%s", d);
        else {
          struct passwd *pw = getpwuid(getuid());
          if (!pw)
            return NULL;
          asprintf(&state, "%s/.local/state", pw->pw_dir);
        }
        cached = true;
      }
      return state;
    }
    return "/var/lib";

  case 'C':
    if (user_mode) {
      static char *cache = NULL;
      static bool cached = false;
      if (!cached) {
        const char *d = getenv("XDG_CACHE_HOME");
        if (d)
          asprintf(&cache, "%s", d);
        else {
          struct passwd *pw = getpwuid(getuid());
          if (!pw)
            return NULL;
          asprintf(&cache, "%s/.cache", pw->pw_dir);
        }
        cached = true;
      }
      return cache;
    }
    return "/var/cache";

  case 'L':
    if (user_mode) {
      static char *logs = NULL;
      static bool cached = false;
      if (!cached) {
        const char *d = getenv("XDG_STATE_HOME");
        if (d)
          asprintf(&logs, "%s/log", d);
        else {
          struct passwd *pw = getpwuid(getuid());
          if (!pw)
            return NULL;
          asprintf(&logs, "%s/.local/state/log", pw->pw_dir);
        }
        cached = true;
      }
      return logs;
    }
    return "/var/log";

  case 'T': {
    const char *t = getenv("TMPDIR");
    if (!t)
      t = getenv("TEMP");
    if (!t)
      t = getenv("TMP");
    return t ? t : "/tmp";
  }

  case 'V': {
    const char *t = getenv("TMPDIR");
    if (!t)
      t = getenv("TEMP");
    if (!t)
      t = getenv("TMP");
    return t ? t : "/var/tmp";
  }

  default:
    return NULL;
  }
}

int specifier_expand(const char *s, bool user_mode, char **ret) {
  if (!s) {
    *ret = NULL;
    return 0;
  }

  size_t len = 0;
  for (const char *p = s; *p;) {
    if (*p == '%' && p[1]) {
      if (p[1] == '%') {
        len++;
        p += 2;
        continue;
      }
      const char *val = resolve_specifier(p[1], user_mode);
      if (!val) {
        log_warning("Unknown or failed specifier '%%%c'", p[1]);
        len += 2;
        p += 2;
        continue;
      }
      len += strlen(val);
      p += 2;
    } else {
      len++;
      p++;
    }
  }

  char *out = malloc(len + 1);
  if (!out)
    return -ENOMEM;

  char *d = out;
  for (const char *p = s; *p;) {
    if (*p == '%' && p[1]) {
      if (p[1] == '%') {
        *d++ = '%';
        p += 2;
        continue;
      }
      const char *val = resolve_specifier(p[1], user_mode);
      if (!val) {
        *d++ = *p++;
        *d++ = *p++;
        continue;
      }
      size_t vl = strlen(val);
      memcpy(d, val, vl);
      d += vl;
      p += 2;
    } else {
      *d++ = *p++;
    }
  }
  *d = '\0';

  *ret = out;
  return 0;
}
