/** \file wutil.c
  Wide character equivalents of various standard unix
  functions.
*/
#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <wchar.h>
#include <wctype.h>
#include <string.h>
#include <dirent.h>
#include <stdarg.h>
#include <limits.h>
#include <libgen.h>
#include <pthread.h>
#include <string>
#include <map>


#if HAVE_LIBINTL_H
#include <libintl.h>
#endif

#include "fallback.h"
#include "util.h"

#include "common.h"
#include "wutil.h"

typedef std::string cstring;

const file_id_t kInvalidFileID = {(dev_t)-1LL, (ino_t)-1LL, (uint64_t)-1LL, -1, -1, (uint32_t)-1};

/**
   Minimum length of the internal covnersion buffers
*/
#define TMP_LEN_MIN 256

#ifndef PATH_MAX
#ifdef MAXPATHLEN
#define PATH_MAX MAXPATHLEN
#else
/**
   Fallback length of MAXPATHLEN. Just a hopefully sane value...
*/
#define PATH_MAX 4096
#endif
#endif

/* Lock to protect wgettext */
static pthread_mutex_t wgettext_lock;

/* Maps string keys to (immortal) pointers to string values */
typedef std::map<wcstring, wcstring *> wgettext_map_t;
static std::map<wcstring, wcstring *> wgettext_map;

void wutil_init()
{
}

void wutil_destroy()
{
}

bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, bool *out_is_dir)
{
    struct dirent *d = readdir(dir);
    if (!d) return false;

    out_name = str2wcstring(d->d_name);
    if (out_is_dir)
    {
        /* The caller cares if this is a directory, so check */
        bool is_dir = false;
        
        /* We may be able to skip stat, if the readdir can tell us the file type directly */
        bool check_with_stat = true;
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
        if (d->d_type == DT_DIR)
        {
            /* Known directory */
            is_dir = true;
            check_with_stat = false;
        }
        else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN)
        {
            /* We want to treat symlinks to directories as directories. Use stat to resolve it. */
            check_with_stat = true;
        }
        else
        {
            /* Regular file */
            is_dir = false;
            check_with_stat = false;
        }
#endif // HAVE_STRUCT_DIRENT_D_TYPE
        if (check_with_stat)
        {
            /* We couldn't determine the file type from the dirent; check by stat'ing it */
            cstring fullpath = wcs2string(dir_path);
            fullpath.push_back('/');
            fullpath.append(d->d_name);
            struct stat buf;
            if (stat(fullpath.c_str(), &buf) != 0)
            {
                is_dir = false;
            }
            else
            {
                is_dir = !!(S_ISDIR(buf.st_mode));
            }
        }
        *out_is_dir = is_dir;
    }
    return true;
}

bool wreaddir(DIR *dir, std::wstring &out_name)
{
    struct dirent *d = readdir(dir);
    if (!d) return false;

    out_name = str2wcstring(d->d_name);
    return true;
}


wchar_t *wgetcwd(wchar_t *buff, size_t sz)
{
    char *buffc = (char *)malloc(sz*MAX_UTF8_BYTES);
    char *res;
    wchar_t *ret = 0;

    if (!buffc)
    {
        errno = ENOMEM;
        return 0;
    }

    res = getcwd(buffc, sz*MAX_UTF8_BYTES);
    if (res)
    {
        if ((size_t)-1 != mbstowcs(buff, buffc, sizeof(wchar_t) * sz))
        {
            ret = buff;
        }
    }

    free(buffc);

    return ret;
}

int wchdir(const wcstring &dir)
{
    cstring tmp = wcs2string(dir);
    return chdir(tmp.c_str());
}

FILE *wfopen(const wcstring &path, const char *mode)
{
    int permissions = 0, options = 0;
    size_t idx = 0;
    switch (mode[idx++])
    {
        case 'r':
            permissions = O_RDONLY;
            break;
        case 'w':
            permissions = O_WRONLY;
            options = O_CREAT | O_TRUNC;
            break;
        case 'a':
            permissions = O_WRONLY;
            options = O_CREAT | O_APPEND;
            break;
        default:
            errno = EINVAL;
            return NULL;
            break;
    }
    /* Skip binary */
    if (mode[idx] == 'b')
        idx++;

    /* Consider append option */
    if (mode[idx] == '+')
        permissions = O_RDWR;

    int fd = wopen_cloexec(path, permissions | options, 0666);
    if (fd < 0)
        return NULL;
    FILE *result = fdopen(fd, mode);
    if (result == NULL)
        close(fd);
    return result;
}

bool set_cloexec(int fd)
{
    int flags = fcntl(fd, F_GETFD, 0);
    if (flags < 0)
    {
        return false;
    }
    else if (flags & FD_CLOEXEC)
    {
        return true;
    }
    else
    {
        return fcntl(fd, F_SETFD, flags | FD_CLOEXEC) >= 0;
    }
}

static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool cloexec)
{
    ASSERT_IS_NOT_FORKED_CHILD();
    cstring tmp = wcs2string(pathname);
    /* Prefer to use O_CLOEXEC. It has to both be defined and nonzero */
#ifdef O_CLOEXEC
    if (cloexec && O_CLOEXEC)
    {
        flags |= O_CLOEXEC;
        cloexec = false;
    }
#endif
    int fd = ::open(tmp.c_str(), flags, mode);
    if (cloexec && fd >= 0 && ! set_cloexec(fd))
    {
        close(fd);
        fd = -1;
    }
    return fd;

}

int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode)
{
    return wopen_internal(pathname, flags, mode, true);
}

DIR *wopendir(const wcstring &name)
{
    const cstring tmp = wcs2string(name);
    return opendir(tmp.c_str());
}

int wstat(const wcstring &file_name, struct stat *buf)
{
    const cstring tmp = wcs2string(file_name);
    return stat(tmp.c_str(), buf);
}

int lwstat(const wcstring &file_name, struct stat *buf)
{
    const cstring tmp = wcs2string(file_name);
    return lstat(tmp.c_str(), buf);
}

int waccess(const wcstring &file_name, int mode)
{
    const cstring tmp = wcs2string(file_name);
    return access(tmp.c_str(), mode);
}

int wunlink(const wcstring &file_name)
{
    const cstring tmp = wcs2string(file_name);
    return unlink(tmp.c_str());
}

void wperror(const wchar_t *s)
{
    int e = errno;
    if (s[0] != L'\0')
    {
        fwprintf(stderr, L"%ls: ", s);
    }
    fwprintf(stderr, L"%s\n", strerror(e));
}

int make_fd_nonblocking(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    int err = 0;
    if (!(flags & O_NONBLOCK))
    {
        err = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    }
    return err == -1 ? errno : 0;
}

int make_fd_blocking(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    int err = 0;
    if (flags & O_NONBLOCK)
    {
        err = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
    }
    return err == -1 ? errno : 0;
}

static inline void safe_append(char *buffer, const char *s, size_t buffsize)
{
    strncat(buffer, s, buffsize - strlen(buffer) - 1);
}

// In general, strerror is not async-safe, and therefore we cannot use it directly
// So instead we have to grub through sys_nerr and sys_errlist directly
// On GNU toolchain, this will produce a deprecation warning from the linker (!!),
// which appears impossible to suppress!
const char *safe_strerror(int err)
{
#if defined(__UCLIBC__)
    // uClibc does not have sys_errlist, however, its strerror is believed to be async-safe
    // See #808
    return strerror(err);
#elif defined(HAVE__SYS__ERRS) || defined(HAVE_SYS_ERRLIST)
#ifdef HAVE_SYS_ERRLIST
    if (err >= 0 && err < sys_nerr && sys_errlist[err] != NULL)
    {
        return sys_errlist[err];
    }
#elif defined(HAVE__SYS__ERRS)
    extern const char _sys_errs[];
    extern const int _sys_index[];
    extern int _sys_num_err;

    if (err >= 0 && err < _sys_num_err) {
		return &_sys_errs[_sys_index[err]];
    }
#endif // either HAVE__SYS__ERRS or HAVE_SYS_ERRLIST
    else
#endif // defined(HAVE__SYS__ERRS) || defined(HAVE_SYS_ERRLIST)
    {
        int saved_err = errno;

        /* Use a shared buffer for this case */
        static char buff[384];
        char errnum_buff[64];
        format_long_safe(errnum_buff, err);

        buff[0] = '\0';
        safe_append(buff, "unknown error (errno was ", sizeof buff);
        safe_append(buff, errnum_buff, sizeof buff);
        safe_append(buff, ")", sizeof buff);

        errno = saved_err;
        return buff;
    }
}

void safe_perror(const char *message)
{
    // Note we cannot use strerror, because on Linux it uses gettext, which is not safe
    int err = errno;

    char buff[384];
    buff[0] = '\0';

    if (message)
    {
        safe_append(buff, message, sizeof buff);
        safe_append(buff, ": ", sizeof buff);
    }
    safe_append(buff, safe_strerror(err), sizeof buff);
    safe_append(buff, "\n", sizeof buff);

    write_ignore(STDERR_FILENO, buff, strlen(buff));
    errno = err;
}

#ifdef HAVE_REALPATH_NULL

wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path)
{
    cstring narrow_path = wcs2string(pathname);
    char *narrow_res = realpath(narrow_path.c_str(), NULL);

    if (!narrow_res)
        return NULL;

    wchar_t *res;
    wcstring wide_res = str2wcstring(narrow_res);
    if (resolved_path)
    {
        wcslcpy(resolved_path, wide_res.c_str(), PATH_MAX);
        res = resolved_path;
    }
    else
    {
        res = wcsdup(wide_res.c_str());
    }

    free(narrow_res);

    return res;
}

#else

wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path)
{
    cstring tmp = wcs2string(pathname);
    char narrow_buff[PATH_MAX];
    char *narrow_res = realpath(tmp.c_str(), narrow_buff);
    wchar_t *res;

    if (!narrow_res)
        return 0;

    const wcstring wide_res = str2wcstring(narrow_res);
    if (resolved_path)
    {
        wcslcpy(resolved_path, wide_res.c_str(), PATH_MAX);
        res = resolved_path;
    }
    else
    {
        res = wcsdup(wide_res.c_str());
    }
    return res;
}

#endif


wcstring wdirname(const wcstring &path)
{
    char *tmp = wcs2str(path.c_str());
    char *narrow_res = dirname(tmp);
    wcstring result = format_string(L"%s", narrow_res);
    free(tmp);
    return result;
}

wcstring wbasename(const wcstring &path)
{
    char *tmp = wcs2str(path.c_str());
    char *narrow_res = basename(tmp);
    wcstring result = format_string(L"%s", narrow_res);
    free(tmp);
    return result;
}

/* Really init wgettext */
static void wgettext_really_init()
{
    pthread_mutex_init(&wgettext_lock, NULL);
    fish_bindtextdomain(PACKAGE_NAME, LOCALEDIR);
    fish_textdomain(PACKAGE_NAME);
}

/**
   For wgettext: Internal init function. Automatically called when a translation is first requested.
*/
static void wgettext_init_if_necessary()
{
    static pthread_once_t once = PTHREAD_ONCE_INIT;
    pthread_once(&once, wgettext_really_init);
}

const wchar_t *wgettext(const wchar_t *in)
{
    if (!in)
        return in;

    // preserve errno across this since this is often used in printing error messages
    int err = errno;

    wgettext_init_if_necessary();

    wcstring key = in;
    scoped_lock lock(wgettext_lock);

    wcstring *& val = wgettext_map[key];
    if (val == NULL)
    {
        cstring mbs_in = wcs2string(key);
        char *out = fish_gettext(mbs_in.c_str());
        val = new wcstring(format_string(L"%s", out)); //note that this writes into the map!
    }
    errno = err;
    return val->c_str(); //looks dangerous but is safe, since the string is stored in the map
}

int wmkdir(const wcstring &name, int mode)
{
    cstring name_narrow = wcs2string(name);
    return mkdir(name_narrow.c_str(), mode);
}

int wrename(const wcstring &old, const wcstring &newv)
{
    cstring old_narrow = wcs2string(old);
    cstring new_narrow =wcs2string(newv);
    return rename(old_narrow.c_str(), new_narrow.c_str());
}

int fish_wcstoi(const wchar_t *str, wchar_t ** endptr, int base)
{
    long ret = wcstol(str, endptr, base);
    if (ret > INT_MAX)
    {
        ret = INT_MAX;
        errno = ERANGE;
    }
    else if (ret < INT_MIN)
    {
        ret = INT_MIN;
        errno = ERANGE;
    }
    return (int)ret;
}

file_id_t file_id_t::file_id_from_stat(const struct stat *buf)
{
    assert(buf != NULL);
    
    file_id_t result = {};
    result.device = buf->st_dev;
    result.inode = buf->st_ino;
    result.size = buf->st_size;
    result.change_seconds = buf->st_ctime;
    
#if STAT_HAVE_NSEC
    result.change_nanoseconds = buf->st_ctime_nsec;
#elif defined(__APPLE__)
    result.change_nanoseconds = buf->st_ctimespec.tv_nsec;
#elif defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_XOPEN_SOURCE)
    result.change_nanoseconds = buf->st_ctim.tv_nsec;
#else
    result.change_nanoseconds = 0;
#endif

#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) ||  defined(__OpenBSD__) || defined(__NetBSD__)
    result.generation = buf->st_gen;
#else
    result.generation = 0;
#endif
    return result;
}


file_id_t file_id_for_fd(int fd)
{
    file_id_t result = kInvalidFileID;
    struct stat buf = {};
    if (0 == fstat(fd, &buf))
    {
        result = file_id_t::file_id_from_stat(&buf);
    }
    return result;
}

file_id_t file_id_for_path(const wcstring &path)
{
    file_id_t result = kInvalidFileID;
    struct stat buf = {};
    if (0 == wstat(path, &buf))
    {
        result = file_id_t::file_id_from_stat(&buf);
    }
    return result;

}

bool file_id_t::operator==(const file_id_t &rhs) const
{
    return device == rhs.device &&
           inode == rhs.inode &&
           size == rhs.size &&
           change_seconds == rhs.change_seconds &&
           change_nanoseconds == rhs.change_nanoseconds &&
           generation == rhs.generation;
}

bool file_id_t::operator!=(const file_id_t &rhs) const
{
    return ! (*this == rhs);
}

template<typename T>
int compare(T a, T b)
{
    if (a < b)
    {
        return -1;
    }
    else if (a > b)
    {
        return 1;
    }
    return 0;
}

bool file_id_t::operator<(const file_id_t &rhs) const
{
    /* Compare each field, stopping when we get to a non-equal field */
    int ret = 0;
    if (! ret) ret = compare(device, rhs.device);
    if (! ret) ret = compare(inode, rhs.inode);
    if (! ret) ret = compare(size, rhs.size);
    if (! ret) ret = compare(generation, rhs.generation);
    if (! ret) ret = compare(change_seconds, rhs.change_seconds);
    if (! ret) ret = compare(change_nanoseconds, rhs.change_nanoseconds);
    return ret < 0;
}
