/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2019-2024 Baldur Karlsson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include <ctype.h>
#include <dlfcn.h>
#include <errno.h>
#include <iconv.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <set>
#include "api/app/renderdoc_app.h"
#include "api/replay/replay_enums.h"
#include "common/common.h"
#include "common/threading.h"
#include "os/os_specific.h"
#include "strings/string_utils.h"

#if ENABLED(RDOC_XLIB)
#include <X11/Xlib.h>
#include <X11/keysym.h>
#else
typedef struct _XDisplay Display;
#endif

#if ENABLED(RDOC_XCB)
#include <X11/keysym.h>
#include <xcb/xcb_keysyms.h>
#else
struct xcb_connection_t;
#endif

#if ENABLED(RDOC_WAYLAND)
#include <linux/input.h>
#include <wayland-client.h>
#include <map>
#else
struct wl_display;
struct wl_surface;
#endif

namespace Keyboard
{
void Init()
{
}

#if ENABLED(RDOC_XLIB)

Display *CurrentXDisplay = NULL;

void UseXlibDisplay(Display *dpy)
{
  if(CurrentXDisplay || dpy == NULL)
    return;

  CurrentXDisplay = XOpenDisplay(XDisplayString(dpy));
}

bool HasXlibInput()
{
  return CurrentXDisplay != NULL;
}

bool GetXlibKeyState(int key)
{
  if(CurrentXDisplay == NULL)
    return false;

  KeySym ks = 0;

  if(key >= eRENDERDOC_Key_A && key <= eRENDERDOC_Key_Z)
    ks = key;
  if(key >= eRENDERDOC_Key_0 && key <= eRENDERDOC_Key_9)
    ks = key;

  switch(key)
  {
    case eRENDERDOC_Key_Divide: ks = XK_KP_Divide; break;
    case eRENDERDOC_Key_Multiply: ks = XK_KP_Multiply; break;
    case eRENDERDOC_Key_Subtract: ks = XK_KP_Subtract; break;
    case eRENDERDOC_Key_Plus: ks = XK_KP_Add; break;
    case eRENDERDOC_Key_F1: ks = XK_F1; break;
    case eRENDERDOC_Key_F2: ks = XK_F2; break;
    case eRENDERDOC_Key_F3: ks = XK_F3; break;
    case eRENDERDOC_Key_F4: ks = XK_F4; break;
    case eRENDERDOC_Key_F5: ks = XK_F5; break;
    case eRENDERDOC_Key_F6: ks = XK_F6; break;
    case eRENDERDOC_Key_F7: ks = XK_F7; break;
    case eRENDERDOC_Key_F8: ks = XK_F8; break;
    case eRENDERDOC_Key_F9: ks = XK_F9; break;
    case eRENDERDOC_Key_F10: ks = XK_F10; break;
    case eRENDERDOC_Key_F11: ks = XK_F11; break;
    case eRENDERDOC_Key_F12: ks = XK_F12; break;
    case eRENDERDOC_Key_Home: ks = XK_Home; break;
    case eRENDERDOC_Key_End: ks = XK_End; break;
    case eRENDERDOC_Key_Insert: ks = XK_Insert; break;
    case eRENDERDOC_Key_Delete: ks = XK_Delete; break;
    case eRENDERDOC_Key_PageUp: ks = XK_Prior; break;
    case eRENDERDOC_Key_PageDn: ks = XK_Next; break;
    case eRENDERDOC_Key_Backspace: ks = XK_BackSpace; break;
    case eRENDERDOC_Key_Tab: ks = XK_Tab; break;
    case eRENDERDOC_Key_PrtScrn: ks = XK_Print; break;
    case eRENDERDOC_Key_Pause: ks = XK_Pause; break;
    default: break;
  }

  if(ks == 0)
    return false;

  KeyCode kc = XKeysymToKeycode(CurrentXDisplay, ks);

  char keyState[32];
  XQueryKeymap(CurrentXDisplay, keyState);

  int byteIdx = (kc / 8);
  int bitMask = 1 << (kc % 8);

  uint8_t keyByte = (uint8_t)keyState[byteIdx];

  return (keyByte & bitMask) != 0;
}

#else

// if RENDERDOC_WINDOWING_XLIB is not enabled

void UseXlibDisplay(Display *dpy)
{
}

bool HasXlibInput()
{
  return false;
}

bool GetXlibKeyState(int key)
{
  return false;
}

#endif

#if ENABLED(RDOC_XCB)

xcb_connection_t *connection;
xcb_key_symbols_t *symbols;

void UseXcbConnection(xcb_connection_t *conn)
{
  connection = conn;
  symbols = xcb_key_symbols_alloc(conn);
}

bool HasXCBInput()
{
  return symbols != NULL;
}

bool GetXCBKeyState(int key)
{
  if(symbols == NULL)
    return false;

  xcb_keysym_t ks = 0;

  if(key >= eRENDERDOC_Key_A && key <= eRENDERDOC_Key_Z)
    ks = key;
  if(key >= eRENDERDOC_Key_0 && key <= eRENDERDOC_Key_9)
    ks = key;

  switch(key)
  {
    case eRENDERDOC_Key_Divide: ks = XK_KP_Divide; break;
    case eRENDERDOC_Key_Multiply: ks = XK_KP_Multiply; break;
    case eRENDERDOC_Key_Subtract: ks = XK_KP_Subtract; break;
    case eRENDERDOC_Key_Plus: ks = XK_KP_Add; break;
    case eRENDERDOC_Key_F1: ks = XK_F1; break;
    case eRENDERDOC_Key_F2: ks = XK_F2; break;
    case eRENDERDOC_Key_F3: ks = XK_F3; break;
    case eRENDERDOC_Key_F4: ks = XK_F4; break;
    case eRENDERDOC_Key_F5: ks = XK_F5; break;
    case eRENDERDOC_Key_F6: ks = XK_F6; break;
    case eRENDERDOC_Key_F7: ks = XK_F7; break;
    case eRENDERDOC_Key_F8: ks = XK_F8; break;
    case eRENDERDOC_Key_F9: ks = XK_F9; break;
    case eRENDERDOC_Key_F10: ks = XK_F10; break;
    case eRENDERDOC_Key_F11: ks = XK_F11; break;
    case eRENDERDOC_Key_F12: ks = XK_F12; break;
    case eRENDERDOC_Key_Home: ks = XK_Home; break;
    case eRENDERDOC_Key_End: ks = XK_End; break;
    case eRENDERDOC_Key_Insert: ks = XK_Insert; break;
    case eRENDERDOC_Key_Delete: ks = XK_Delete; break;
    case eRENDERDOC_Key_PageUp: ks = XK_Prior; break;
    case eRENDERDOC_Key_PageDn: ks = XK_Next; break;
    case eRENDERDOC_Key_Backspace: ks = XK_BackSpace; break;
    case eRENDERDOC_Key_Tab: ks = XK_Tab; break;
    case eRENDERDOC_Key_PrtScrn: ks = XK_Print; break;
    case eRENDERDOC_Key_Pause: ks = XK_Pause; break;
    default: break;
  }

  if(ks == 0)
    return false;

  xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(symbols, ks);

  if(!keyCodes)
    return false;

  xcb_query_keymap_cookie_t keymapcookie = xcb_query_keymap(connection);
  xcb_query_keymap_reply_t *keys = xcb_query_keymap_reply(connection, keymapcookie, NULL);

  bool ret = false;

  if(keys && keyCodes[0] != XCB_NO_SYMBOL)
  {
    int byteIdx = (keyCodes[0] / 8);
    int bitMask = 1 << (keyCodes[0] % 8);

    ret = (keys->keys[byteIdx] & bitMask) != 0;
  }

  free(keyCodes);
  free(keys);

  return ret;
}

#else

// if RENDERDOC_WINDOWING_XCB is not enabled

void UseXcbConnection(xcb_connection_t *conn)
{
}

bool GetXCBKeyState(int key)
{
  return false;
}

bool HasXCBInput()
{
  return false;
}

#endif

#if ENABLED(RDOC_WAYLAND)

#include <wayland-client.h>

std::set<wl_display *> displays;
std::set<wl_surface *> surfaces;
std::map<rdcpair<wl_registry *, uint32_t>, wl_seat *> seatNames;
std::map<wl_seat *, wl_keyboard *> seatKeyboard;
bool inFocus = false;
Threading::CriticalSection waylandLock;

bool keyState[eRENDERDOC_Key_Max] = {};

void WaylandKeymapDummy(void *data, wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size)
{
}

void WaylandModifiersDummy(void *data, wl_keyboard *keyboard, uint32_t serial,
                           uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked,
                           uint32_t group)
{
}

void WaylandRepeatInfoDummy(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay)
{
}

void WaylandEnter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surf, wl_array *keys)
{
  SCOPED_LOCK(waylandLock);
  inFocus = surfaces.find(surf) != surfaces.end();
  RDCEraseEl(keyState);
}

void WaylandLeave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surf)
{
  SCOPED_LOCK(waylandLock);
  inFocus = false;
  RDCEraseEl(keyState);
}

void WaylandKeypress(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time,
                     uint32_t key, uint32_t state)
{
  int keyIdx = -1;
  switch(key)
  {
    case KEY_0: keyIdx = eRENDERDOC_Key_0; break;
    case KEY_1: keyIdx = eRENDERDOC_Key_1; break;
    case KEY_2: keyIdx = eRENDERDOC_Key_2; break;
    case KEY_3: keyIdx = eRENDERDOC_Key_3; break;
    case KEY_4: keyIdx = eRENDERDOC_Key_4; break;
    case KEY_5: keyIdx = eRENDERDOC_Key_5; break;
    case KEY_6: keyIdx = eRENDERDOC_Key_6; break;
    case KEY_7: keyIdx = eRENDERDOC_Key_7; break;
    case KEY_8: keyIdx = eRENDERDOC_Key_8; break;
    case KEY_9: keyIdx = eRENDERDOC_Key_9; break;
    case KEY_A: keyIdx = eRENDERDOC_Key_A; break;
    case KEY_B: keyIdx = eRENDERDOC_Key_B; break;
    case KEY_C: keyIdx = eRENDERDOC_Key_C; break;
    case KEY_D: keyIdx = eRENDERDOC_Key_D; break;
    case KEY_E: keyIdx = eRENDERDOC_Key_E; break;
    case KEY_F: keyIdx = eRENDERDOC_Key_F; break;
    case KEY_G: keyIdx = eRENDERDOC_Key_G; break;
    case KEY_H: keyIdx = eRENDERDOC_Key_H; break;
    case KEY_I: keyIdx = eRENDERDOC_Key_I; break;
    case KEY_J: keyIdx = eRENDERDOC_Key_J; break;
    case KEY_K: keyIdx = eRENDERDOC_Key_K; break;
    case KEY_L: keyIdx = eRENDERDOC_Key_L; break;
    case KEY_M: keyIdx = eRENDERDOC_Key_M; break;
    case KEY_N: keyIdx = eRENDERDOC_Key_N; break;
    case KEY_O: keyIdx = eRENDERDOC_Key_O; break;
    case KEY_P: keyIdx = eRENDERDOC_Key_P; break;
    case KEY_Q: keyIdx = eRENDERDOC_Key_Q; break;
    case KEY_R: keyIdx = eRENDERDOC_Key_R; break;
    case KEY_S: keyIdx = eRENDERDOC_Key_S; break;
    case KEY_T: keyIdx = eRENDERDOC_Key_T; break;
    case KEY_U: keyIdx = eRENDERDOC_Key_U; break;
    case KEY_V: keyIdx = eRENDERDOC_Key_V; break;
    case KEY_W: keyIdx = eRENDERDOC_Key_W; break;
    case KEY_X: keyIdx = eRENDERDOC_Key_X; break;
    case KEY_Y: keyIdx = eRENDERDOC_Key_Y; break;
    case KEY_Z: keyIdx = eRENDERDOC_Key_Z; break;

    case KEY_KPSLASH: keyIdx = eRENDERDOC_Key_Divide; break;
    case KEY_KPASTERISK: keyIdx = eRENDERDOC_Key_Multiply; break;
    case KEY_KPMINUS: keyIdx = eRENDERDOC_Key_Subtract; break;
    case KEY_KPPLUS: keyIdx = eRENDERDOC_Key_Plus; break;

    case KEY_F1: keyIdx = eRENDERDOC_Key_F1; break;
    case KEY_F2: keyIdx = eRENDERDOC_Key_F2; break;
    case KEY_F3: keyIdx = eRENDERDOC_Key_F3; break;
    case KEY_F4: keyIdx = eRENDERDOC_Key_F4; break;
    case KEY_F5: keyIdx = eRENDERDOC_Key_F5; break;
    case KEY_F6: keyIdx = eRENDERDOC_Key_F6; break;
    case KEY_F7: keyIdx = eRENDERDOC_Key_F7; break;
    case KEY_F8: keyIdx = eRENDERDOC_Key_F8; break;
    case KEY_F9: keyIdx = eRENDERDOC_Key_F9; break;
    case KEY_F10: keyIdx = eRENDERDOC_Key_F10; break;
    case KEY_F11: keyIdx = eRENDERDOC_Key_F11; break;
    case KEY_F12: keyIdx = eRENDERDOC_Key_F12; break;

    case KEY_HOME: keyIdx = eRENDERDOC_Key_Home; break;
    case KEY_END: keyIdx = eRENDERDOC_Key_End; break;
    case KEY_INSERT: keyIdx = eRENDERDOC_Key_Insert; break;
    case KEY_DELETE: keyIdx = eRENDERDOC_Key_Delete; break;
    case KEY_PAGEUP: keyIdx = eRENDERDOC_Key_PageUp; break;
    case KEY_PAGEDOWN: keyIdx = eRENDERDOC_Key_PageDn; break;

    case KEY_BACKSPACE: keyIdx = eRENDERDOC_Key_Backspace; break;
    case KEY_TAB: keyIdx = eRENDERDOC_Key_Tab; break;
    case KEY_SYSRQ: keyIdx = eRENDERDOC_Key_PrtScrn; break;
    case KEY_PAUSE: keyIdx = eRENDERDOC_Key_Pause; break;
  }

  if(keyIdx < 0)
    return;

  {
    SCOPED_LOCK(waylandLock);
    keyState[keyIdx] = (state == WL_KEYBOARD_KEY_STATE_PRESSED);
  }
}

void WaylandSeatCaps(void *data, wl_seat *seat, uint32_t capabilities)
{
  if(capabilities & WL_SEAT_CAPABILITY_KEYBOARD)
  {
    {
      SCOPED_LOCK(waylandLock);
      if(seatKeyboard[seat])
        return;
    }

    wl_keyboard *keyboard = wl_seat_get_keyboard(seat);
    static const wl_keyboard_listener listener = {
        WaylandKeymapDummy, WaylandEnter,          WaylandLeave,
        WaylandKeypress,    WaylandModifiersDummy, WaylandRepeatInfoDummy,
    };
    wl_keyboard_add_listener(keyboard, &listener, NULL);

    {
      SCOPED_LOCK(waylandLock);
      seatKeyboard[seat] = keyboard;
    }
  }
  else
  {
    wl_keyboard *keyboard = NULL;

    {
      SCOPED_LOCK(waylandLock);
      keyboard = seatKeyboard[seat];

      if(!keyboard)
        return;

      seatKeyboard[seat] = NULL;
    }

    wl_keyboard_destroy(keyboard);
  }
}

void WaylandRegistryAdd(void *data, wl_registry *reg, uint32_t name, const char *iface,
                        uint32_t version)
{
  if(!strcmp(iface, "wl_seat"))
  {
    wl_seat *seat = (wl_seat *)wl_registry_bind(reg, name, &wl_seat_interface, 1);
    static const wl_seat_listener listener = {&WaylandSeatCaps};
    wl_seat_add_listener(seat, &listener, NULL);

    {
      SCOPED_LOCK(waylandLock);
      seatNames[{reg, name}] = seat;
    }
  }
}

void WaylandRegistryRemove(void *data, wl_registry *reg, uint32_t name)
{
  SCOPED_LOCK(waylandLock);
  auto it = seatNames.find({reg, name});
  if(it != seatNames.end())
  {
    wl_seat_destroy(it->second);
    seatNames.erase(it);
  }
}

void UseWaylandDisplay(wl_display *disp)
{
  // only listen to each display once at most
  {
    SCOPED_LOCK(waylandLock);
    if(displays.find(disp) != displays.end())
      return;
    displays.insert(disp);
  }

  static const wl_registry_listener listener = {&WaylandRegistryAdd, &WaylandRegistryRemove};

  // get the registry and listen to it. This will then let us find seats
  wl_registry_add_listener(wl_display_get_registry(disp), &listener, NULL);
}

void AddWaylandInputWindow(wl_surface *wnd)
{
  SCOPED_LOCK(waylandLock);
  surfaces.insert(wnd);
}

void RemoveWaylandInputWindow(wl_surface *wnd)
{
  SCOPED_LOCK(waylandLock);
  surfaces.erase(wnd);
}

bool HasWaylandInput()
{
  SCOPED_LOCK(waylandLock);
  return !displays.empty();
}

bool GetWaylandKeyState(int key)
{
  SCOPED_LOCK(waylandLock);
  return keyState[key];
}

#else

void UseWaylandDisplay(wl_display *disp)
{
}

void AddWaylandInputWindow(wl_surface *wnd)
{
}

void RemoveWaylandInputWindow(wl_surface *wnd)
{
}

bool HasWaylandInput()
{
  return false;
}

bool GetWaylandKeyState(int key)
{
  return false;
}

#endif

WindowingSystem UseUnknownDisplay(void *disp)
{
  if(disp == NULL)
    return WindowingSystem::Unknown;

  // could be wayland or xlib, try to detect.
  // both Display* and wl_display* are valid pointers, so dereference and read the first pointer
  // sized bytes
  void *firstPointer = NULL;
  memcpy(&firstPointer, disp, sizeof(void *));

  // in a Display* we don't know what this contains, but in a wl_display it should point to the
  // wl_display_interface exported symbol. Check with dladdr
  Dl_info info;
  if(dladdr(firstPointer, &info) && !strcmp(info.dli_sname, "wl_display_interface"))
  {
    UseWaylandDisplay((wl_display *)disp);
    return WindowingSystem::Wayland;
  }
  else
  {
    UseXlibDisplay((Display *)disp);
    return WindowingSystem::Xlib;
  }
}

void AddInputWindow(WindowingSystem windowSystem, void *wnd)
{
  if(windowSystem == WindowingSystem::Wayland)
  {
    AddWaylandInputWindow((wl_surface *)wnd);
  }
  else
  {
    // TODO check against this drawable & parent window being focused in GetKeyState
  }
}

void RemoveInputWindow(WindowingSystem windowSystem, void *wnd)
{
  if(windowSystem == WindowingSystem::Wayland)
  {
    RemoveWaylandInputWindow((wl_surface *)wnd);
  }
}

bool PlatformHasKeyInput()
{
  return HasXCBInput() || HasXlibInput() || HasWaylandInput();
}

bool GetKeyState(int key)
{
  return GetXCBKeyState(key) || GetXlibKeyState(key) || GetWaylandKeyState(key);
}
}

namespace FileIO
{
rdcstr GetTempRootPath()
{
  return "/tmp";
}

rdcstr GetAppFolderFilename(const rdcstr &filename)
{
  passwd *pw = getpwuid(getuid());
  rdcstr homedir = pw ? pw->pw_dir : "";

  if(homedir.empty())
    homedir = Process::GetEnvVariable("HOME");

  if(homedir.empty())
  {
    RDCERR("Can't get HOME directory, defaulting to '/' instead");
    homedir = "";
  }

  rdcstr ret = homedir + "/.renderdoc/";

  mkdir(ret.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);

  return ret + filename;
}

rdcstr DefaultFindFileInPath(const rdcstr &fileName);
rdcstr FindFileInPath(const rdcstr &fileName)
{
  return DefaultFindFileInPath(fileName);
}

void GetExecutableFilename(rdcstr &selfName)
{
  char path[512] = {0};
  readlink("/proc/self/exe", path, 511);

  selfName = rdcstr(path);
}

int LibraryLocator = 42;

void GetLibraryFilename(rdcstr &selfName)
{
  // this is a hack, but the only reliable way to find the absolute path to the library.
  // dladdr would be fine but it returns the wrong result for symbols in the library

  rdcstr librenderdoc_path;

  FILE *f = fopen("/proc/self/maps", FileIO::ReadText);

  if(f)
  {
    // read the whole thing in one go. There's no need to try and be tight with
    // this allocation, so just make sure we can read everything.
    char *map_string = new char[1024 * 1024];
    memset(map_string, 0, 1024 * 1024);

    ::fread(map_string, 1, 1024 * 1024, f);

    ::fclose(f);

    char *c = strstr(map_string, "/lib" STRINGIZE(RDOC_BASE_NAME) ".so");

    if(c)
    {
      // walk backwards until we hit the start of the line
      while(c > map_string)
      {
        c--;

        if(c[0] == '\n')
        {
          c++;
          break;
        }
      }

      // walk forwards across the address range (00400000-0040c000)
      while(isalnum(c[0]) || c[0] == '-')
        c++;

      // whitespace
      while(c[0] == ' ')
        c++;

      // permissions (r-xp)
      while(isalpha(c[0]) || c[0] == '-')
        c++;

      // whitespace
      while(c[0] == ' ')
        c++;

      // offset (0000b000)
      while(isalnum(c[0]) || c[0] == '-')
        c++;

      // whitespace
      while(c[0] == ' ')
        c++;

      // dev
      while(isalnum(c[0]) || c[0] == ':')
        c++;

      // whitespace
      while(c[0] == ' ')
        c++;

      // inode
      while(isdigit(c[0]))
        c++;

      // whitespace
      while(c[0] == ' ')
        c++;

      // FINALLY we are at the start of the actual path
      char *end = strchr(c, '\n');

      if(end)
        librenderdoc_path = rdcstr(c, end - c);
    }

    delete[] map_string;
  }

  if(librenderdoc_path.empty())
  {
    RDCWARN("Couldn't get lib" STRINGIZE(
        RDOC_BASE_NAME) ".so path from /proc/self/maps, falling back to dladdr");

    Dl_info info;
    if(dladdr(&LibraryLocator, &info))
      librenderdoc_path = info.dli_fname;
  }

  selfName = librenderdoc_path;
}
};

namespace StringFormat
{
// cache iconv_t descriptor to save on iconv_open/iconv_close each time
iconv_t iconvWide2UTF8 = (iconv_t)-1;
iconv_t iconvUTF82Wide = (iconv_t)-1;

// iconv is not thread safe when sharing an iconv_t descriptor
// I don't expect much contention but if it happens we could TryLock
// before creating a temporary iconv_t, or hold two iconv_ts, or something.
Threading::CriticalSection iconvLock;

void Shutdown()
{
  SCOPED_LOCK(iconvLock);

  if(iconvWide2UTF8 != (iconv_t)-1)
    iconv_close(iconvWide2UTF8);
  iconvWide2UTF8 = (iconv_t)-1;

  if(iconvUTF82Wide != (iconv_t)-1)
    iconv_close(iconvUTF82Wide);
  iconvUTF82Wide = (iconv_t)-1;
}

rdcstr Wide2UTF8(const rdcwstr &s)
{
  // include room for null terminator, assuming unicode input (not ucs)
  // utf-8 characters can be max 4 bytes.
  size_t len = (s.length() + 1) * 4;

  rdcarray<char> charBuffer;
  charBuffer.resize(len);

  size_t ret;

  {
    SCOPED_LOCK(iconvLock);

    if(iconvWide2UTF8 == (iconv_t)-1)
      iconvWide2UTF8 = iconv_open("UTF-8", "WCHAR_T");

    if(iconvWide2UTF8 == (iconv_t)-1)
    {
      RDCERR("Couldn't open iconv for WCHAR_T to UTF-8: %d", errno);
      return "";
    }

    char *inbuf = (char *)s.c_str();
    size_t insize = (s.length() + 1) * sizeof(wchar_t);    // include null terminator
    char *outbuf = &charBuffer[0];
    size_t outsize = len;

    ret = iconv(iconvWide2UTF8, &inbuf, &insize, &outbuf, &outsize);
  }

  if(ret == (size_t)-1)
  {
#if ENABLED(RDOC_DEVEL)
    RDCWARN("Failed to convert wstring");
#endif
    return "";
  }

  // convert to string from null-terminated string - utf-8 never contains
  // 0 bytes before the null terminator, and this way we don't care if
  // charBuffer is larger than the string
  return rdcstr(&charBuffer[0]);
}

rdcwstr UTF82Wide(const rdcstr &s)
{
  // include room for null terminator, for ascii input we need at least as many output chars as
  // input.
  size_t len = s.length() + 1;

  rdcarray<wchar_t> wcharBuffer;
  wcharBuffer.resize(len);

  size_t ret;

  {
    SCOPED_LOCK(iconvLock);

    if(iconvUTF82Wide == (iconv_t)-1)
      iconvUTF82Wide = iconv_open("WCHAR_T", "UTF-8");

    if(iconvUTF82Wide == (iconv_t)-1)
    {
      RDCERR("Couldn't open iconv for UTF-8 to WCHAR_T: %d", errno);
      return L"";
    }

    char *inbuf = (char *)s.c_str();
    size_t insize = s.length() + 1;    // include null terminator
    char *outbuf = (char *)&wcharBuffer[0];
    size_t outsize = len * sizeof(wchar_t);

    ret = iconv(iconvUTF82Wide, &inbuf, &insize, &outbuf, &outsize);
  }

  if(ret == (size_t)-1)
  {
#if ENABLED(RDOC_DEVEL)
    RDCWARN("Failed to convert wstring");
#endif
    return L"";
  }

  // convert to string from null-terminated string
  return rdcwstr(&wcharBuffer[0]);
}
};

namespace OSUtility
{
void WriteOutput(int channel, const char *str)
{
  if(channel == OSUtility::Output_StdOut)
  {
    fprintf(stdout, "%s", str);
    fflush(stdout);
  }
  else if(channel == OSUtility::Output_StdErr)
  {
    fprintf(stderr, "%s", str);
    fflush(stderr);
  }
}

uint64_t GetMachineIdent()
{
  uint64_t ret = MachineIdent_Linux;

#if defined(_M_ARM) || defined(__arm__)
  ret |= MachineIdent_Arch_ARM;
#else
  ret |= MachineIdent_Arch_x86;
#endif

#if ENABLED(RDOC_X64)
  ret |= MachineIdent_64bit;
#else
  ret |= MachineIdent_32bit;
#endif

  return ret;
}
};
