/* Copyright (C) 2018 by Intevation GmbH <info@intevation.de>
 *
 * This file is free software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include <stdio.h>

#include "w32-util.h"

#include <unistd.h>

#ifdef _WIN32
# include <windows.h>
#endif

#include <QWindow>
#include <QDebug>

#define SLDIR "\\share\\locale"

namespace W32 {

std::string getGpg4winLocaleDir()
{
    const auto instdir = getGpg4winDir();
    if (instdir.empty()) {
        return std::string();
    }
    return instdir + SLDIR;
}

std::string getGpg4winDir()
{
    const auto tmp = readRegStr(nullptr, GPG4WIN_REGKEY_3,
                                "Install Directory");
    if (tmp.empty()) {
        return std::string();
    }

    if (!access(tmp.c_str(), R_OK)) {
        return tmp;
    } else {
        fprintf (stderr, "Failed to access: %s\n", tmp.c_str());
    }

  return std::string();
}

/* Helper for read_w32_registry_string(). */
#ifdef _WIN32
static HKEY
get_root_key(const char *root)
{
  HKEY root_key;

  if( !root )
    root_key = HKEY_CURRENT_USER;
  else if( !strcmp( root, "HKEY_CLASSES_ROOT" ) )
    root_key = HKEY_CLASSES_ROOT;
  else if( !strcmp( root, "HKEY_CURRENT_USER" ) )
    root_key = HKEY_CURRENT_USER;
  else if( !strcmp( root, "HKEY_LOCAL_MACHINE" ) )
    root_key = HKEY_LOCAL_MACHINE;
  else if( !strcmp( root, "HKEY_USERS" ) )
    root_key = HKEY_USERS;
  else if( !strcmp( root, "HKEY_PERFORMANCE_DATA" ) )
    root_key = HKEY_PERFORMANCE_DATA;
  else if( !strcmp( root, "HKEY_CURRENT_CONFIG" ) )
    root_key = HKEY_CURRENT_CONFIG;
  else
    return nullptr;
  return root_key;
}
#endif

#if defined(_WIN64)
#define CROSS_ACCESS KEY_WOW64_32KEY
#else
#define CROSS_ACCESS KEY_WOW64_64KEY
#endif

#ifdef _WIN32
std::string
_readRegStr (HKEY root_key, const char *dir,
             const char *name, bool alternate)
{
    HKEY key_handle;
    DWORD n1, nbytes, type;
    std::string ret;

    DWORD flags = KEY_READ;

    if (alternate) {
        flags |= CROSS_ACCESS;
    }

    if (RegOpenKeyExA(root_key, dir, 0, flags, &key_handle)) {
        return ret;
    }

    nbytes = 1;
    if (RegQueryValueExA(key_handle, name, 0, nullptr, nullptr, &nbytes)) {
        RegCloseKey (key_handle);
        return ret;
    }
    n1 = nbytes+1;
    char result[n1];
    if (RegQueryValueExA(key_handle, name, 0, &type, (LPBYTE)result, &n1)) {
        RegCloseKey(key_handle);
        return ret;
    }
    RegCloseKey(key_handle);
    result[nbytes] = 0; /* make sure it is really a string  */
    ret = result;
    if (type == REG_EXPAND_SZ && strchr (result, '%')) {
        n1 += 1000;
        char tmp[n1 +1];

        nbytes = ExpandEnvironmentStringsA(ret.c_str(), tmp, n1);
        if (nbytes && nbytes > n1) {
            n1 = nbytes;
            char tmp2[n1 +1];
            nbytes = ExpandEnvironmentStringsA(result, tmp2, n1);
            if (nbytes && nbytes > n1) {
                /* oops - truncated, better don't expand at all */
                return ret;
            }
            tmp2[nbytes] = 0;
            ret = tmp2;
        } else if (nbytes) { /* okay, reduce the length */
            tmp[nbytes] = 0;
            ret = tmp;
        }
    }
    return ret;
}
#endif

std::string
readRegStr (const char *root, const char *dir, const char *name)
{
#ifndef _WIN32
    (void)root; (void)dir; (void)name;
    return std::string();
#else
    HKEY root_key;
    std::string ret;
    if (!(root_key = get_root_key(root))) {
        return ret;
    }

    if (root == nullptr)
    {
        /* Nullptr so we first look into HKLM if we have
           an override */
        ret = _readRegStr (HKEY_LOCAL_MACHINE, dir, name, false);
        if (ret.empty()) {
            // Try alternate as fallback
            ret = _readRegStr (HKEY_LOCAL_MACHINE, dir, name, true);
        }
        if (ret.size() && ret[ret.size() - 1] == '!')
        {
            // Using override reg value
            qDebug() << "Using override for %s" << name;
            return ret;
        }
    }
    ret = _readRegStr (root_key, dir, name, false);

    if (ret.empty()) {
        // Try local machine as fallback.
        qDebug() << "Fallback to HKLM for" << dir << name;
        ret = _readRegStr (HKEY_LOCAL_MACHINE, dir, name, false);
        if (ret.empty()) {
            // Try alternative registry view as fallback
            qDebug() << "Fallback to HKLM alternative for" << dir << name;
            ret = _readRegStr (HKEY_LOCAL_MACHINE, dir, name, true);
        }
    }
    qDebug() << "Returning:" << (ret.empty() ? "empty" : ret.c_str());
    return ret;
#endif
}

bool writeRegStr(const char *root, const char *path, const char *key,
                 const char *val)
{
#ifndef _WIN32
  (void) root; (void) path; (void) key; (void) val;
  return false;
#else
  HKEY h, hk;
  int type;
  int ec;

  hk = get_root_key (root);

  if (!hk) {
      fprintf(stderr, "Failed to find root key.\n");
  }

  DWORD flags = KEY_ALL_ACCESS;

  ec = RegCreateKeyExA(hk, path, 0, NULL, REG_OPTION_NON_VOLATILE,
                       flags, NULL, &h, NULL);
  if (ec != ERROR_SUCCESS)
    {
      fprintf (stderr, "creating/opening registry key `%s' failed\n", path);
      return false;
    }
  type = strchr (val, '%')? REG_EXPAND_SZ : REG_SZ;
  ec = RegSetValueExA(h, key, 0, type, (const BYTE*)val, strlen (val));
  if (ec != ERROR_SUCCESS)
    {
      fprintf (stderr, "saving registry key `%s'->`%s' failed\n", path, key);
      RegCloseKey(h);
      return false;
    }
  RegCloseKey(h);
  return true;
#endif
}


#ifdef _WIN32
static int
has_high_integrity(HANDLE hToken)
{
  PTOKEN_MANDATORY_LABEL integrity_label = NULL;
  DWORD integrity_level = 0,
        size = 0;


  if (hToken == NULL || hToken == INVALID_HANDLE_VALUE)
    {
      return 0;
    }

  /* Get the required size */
  if (!GetTokenInformation (hToken, TokenIntegrityLevel,
                            NULL, 0, &size))
    {
      if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
          return 0;
        }
    }
  integrity_label = (PTOKEN_MANDATORY_LABEL) LocalAlloc(0, size);
  if (integrity_label == NULL)
    {
      return 0;
    }

  if (!GetTokenInformation (hToken, TokenIntegrityLevel,
                            integrity_label, size, &size))
    {
      LocalFree(integrity_label);
      return 0;
    }

  /* Get the last integrity level */
  integrity_level = *GetSidSubAuthority(integrity_label->Label.Sid,
                     (DWORD)(UCHAR)(*GetSidSubAuthorityCount(
                        integrity_label->Label.Sid) - 1));

  LocalFree (integrity_label);

  return integrity_level >= SECURITY_MANDATORY_HIGH_RID;
}
#endif


bool isElevated()
{
#ifdef _WIN32
  int ret = 0;
  HANDLE hToken = NULL;
  if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken))
    {
      DWORD elevation;
      DWORD cbSize = sizeof (DWORD);
      if (GetTokenInformation (hToken, TokenElevation, &elevation,
                               sizeof (TokenElevation), &cbSize))
        {
          ret = elevation;
        }
    }
  /* Elevation will be true and ElevationType TokenElevationTypeFull even
     if the token is a user token created by SAFER so we additionally
     check the integrity level of the token which will only be high in
     the real elevated process and medium otherwise. */
  ret = ret && has_high_integrity (hToken);

  if (hToken)
    CloseHandle (hToken);

  return ret;
#else
  return false;
#endif
}

}// namespace W32

bool strToBool(const std::string &str, bool defaultVal, bool &forced)
{
    forced = false;
    if (str.empty()) {
      return defaultVal;
    }

    if (str.size() && str[str.size() - 1] == '!')
    {
        forced = true;
    }

    if (str[0] == '1') {
      return true;
    }
    if (str[0] == '0') {
      return false;
    }

    qDebug() << "Unknown bool val" << str.c_str();
    return defaultVal;
}

