/*
 * IceWM
 *
 * Copyright (C) 1997-2002 Marko Macek
 */
#include "config.h"
#include "base.h"
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pwd.h>

#if defined(__GXX_RTTI) && (__GXX_ABI_VERSION >= 1002)
#define HAVE_GCC_ABI_DEMANGLE
#endif
#ifdef HAVE_GCC_ABI_DEMANGLE
#include <cxxabi.h>
#endif
#if defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_EXECINFO_H)
#include <execinfo.h>
#endif

#include "intl.h"
#include "ascii.h"

using namespace ASCII;

#ifdef DEBUG
bool debug = false;
#endif

static void endMsg(const char *msg) {
    if (*msg == 0 || msg[strlen(msg)-1] != '\n') {
        fputc('\n', stderr);
    }
    fflush(stderr);
}

void die(int exitcode, char const *msg, ...) {
    fprintf(stderr, "%s: ", ApplicationName);

    va_list ap;
    va_start(ap, msg);
    vfprintf(stderr, msg, ap);
    va_end(ap);
    endMsg(msg);

    exit(exitcode);
}

void precondition(const char *expr, const char *file, int line) {
    fprintf(stderr, "%s: PRECONDITION FAILED at %s:%d: ( %s )\n",
            ApplicationName, file, line, expr);
    fflush(stderr);
    show_backtrace();
    abort();
}

void warn(char const *msg, ...) {
    fprintf(stderr, "%s: ", ApplicationName);
    fputs(_("Warning: "), stderr);

    va_list ap;
    va_start(ap, msg);
    vfprintf(stderr, msg, ap);
    va_end(ap);
    endMsg(msg);
}

void fail(char const *msg, ...) {
    int errcode = errno;
    fprintf(stderr, "%s: ", ApplicationName);
    fputs(_("Warning: "), stderr);

    va_list ap;
    va_start(ap, msg);
    vfprintf(stderr, msg, ap);
    va_end(ap);
    fprintf(stderr, ": %s\n", strerror(errcode));
    fflush(stderr);
}

void msg(char const *msg, ...) {
    fprintf(stderr, "%s: ", ApplicationName);

    va_list ap;
    va_start(ap, msg);
    vfprintf(stderr, msg, ap);
    va_end(ap);
    endMsg(msg);
}

void tlog(char const *msg, ...) {
    timeval now;
    gettimeofday(&now, nullptr);
    struct tm tmbuf = {};
    struct tm *loc = localtime_r(&now.tv_sec, &tmbuf);

    fprintf(stderr, "%02d:%02d:%02d.%03u: %s: ", loc->tm_hour,
            loc->tm_min, loc->tm_sec,
            (unsigned)(now.tv_usec / 1000),
            ApplicationName);

    va_list ap;
    va_start(ap, msg);
    vfprintf(stderr, msg, ap);
    va_end(ap);
    endMsg(msg);
}

char *cstrJoin(char const *str, ...) {
    va_list ap;
    char const *s;
    char *res, *p;
    int len = 0;

    if (str == nullptr)
        return nullptr;

    va_start(ap, str);
    s = str;
    while (s) {
        len += strlen(s);
        s = va_arg(ap, char *);
    }
    va_end(ap);

    if ((p = res = new char[len + 1]) == nullptr)
        return nullptr;

    va_start(ap, str);
    s = str;
    while (s) {
        len = strlen(s);
        memcpy(p, s, len);
        p += len;
        s = va_arg(ap, char *);
    }
    va_end(ap);
    *p = 0;
    return res;
}

#if (__GNUC__ == 3) || defined(__clang__)

extern "C" void __cxa_pure_virtual() {
    warn("BUG: Pure virtual method called. Terminating.");
    abort();
}

/* time to rewrite in C */

#endif

#ifdef NEED_ALLOC_OPERATORS

static void *MALLOC(unsigned int len) {
    if (len == 0) return 0;
    return malloc(len);
}

static void FREE(void *p) {
    if (p) free(p);
}

void *operator new(size_t len) {
    return MALLOC(len);
}

void *operator new[](size_t len) {
    if (len == 0) len = 1;
    return MALLOC(len);
}

void operator delete (void *p) {
    FREE(p);
}

void operator delete[](void *p) {
    FREE(p);
}

#endif

/* Prefer this as a safer alternative over strcpy. Return strlen(from). */
#if !defined(HAVE_STRLCPY) || !HAVE_STRLCPY
size_t strlcpy(char *dest, const char *from, size_t dest_size)
{
    const char *in = from;
    if (dest_size > 0) {
        char *to = dest;
        char *const stop = to + dest_size - 1;
        while (to < stop && *in)
            *to++ = *in++;
        *to = '\0';
    }
    while (*in) ++in;
    return in - from;
}
#endif

/* Prefer this over strcat. Return strlen(dest) + strlen(from). */
#if !defined(HAVE_STRLCAT) || !HAVE_STRLCAT
size_t strlcat(char *dest, const char *from, size_t dest_size)
{
    char *to = dest;
    char *const stop = to + dest_size - 1;
    while (to < stop && *to) ++to;
    return to - dest + strlcpy(to, from, dest_size - (to - dest));
}
#endif

char* newstr(const char* str) {
    char* s = nullptr;
    if (str) {
        size_t len = strlen(str);
        s = new char[len + 1];
        if (s)
            memcpy(s, str, len + 1);
    }
    return s;
}

char *newstr(char const *str, char const *delim) {
    return (str != nullptr ? newstr(str, strcspn(str, delim)) : nullptr);
}

char *newstr(char const *str, int len) {
    char *s(nullptr);

    if (str != nullptr && len >= 0 && (s = new char[len + 1]) != nullptr) {
        memcpy(s, str, len);
        s[len] = '\0';
    }

    return s;
}

char* demangle(const char* str) {
#ifdef HAVE_GCC_ABI_DEMANGLE
    int status = 0;
    char* c_name = abi::__cxa_demangle(str, nullptr, nullptr, &status);
    if (c_name)
        return c_name;
#endif
    return strdup(str);
}

bool little() {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    return true;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    return false;
#else
#error undefined byte order
#endif
}

unsigned long strhash(const char* str) {
    unsigned long hash = 5381;
    for (; *str; ++str)
        hash = 33 * hash ^ (unsigned char) *str;
    return hash;
}

/*
 *      Returns zero if s2 is a prefix of s1.
 *
 *      Ie the following will match and return 0 with respective given
 *      arguments:
 *
 *              "--interface=/tmp" "--interface"
 */
int strpcmp(char const * str, char const * pfx, char const * delim) {
    if (str == nullptr || pfx == nullptr) return -1;
    while (*pfx == *str && *pfx != '\0') ++str, ++pfx;

    return (*pfx == '\0' && strchr(delim, *str) ? 0 : *str - *pfx);
}

#ifndef HAVE_MEMRCHR
void* memrchr(const void* ptr, char chr, size_t num) {
    char* str = (char *) ptr;
    char* q = str + num;
    while (q > str && *--q != chr);
    return q >= str && *q == chr ? q : nullptr;
}
#endif

tokens::tokens(char* data, const char* sep)
    : sep(sep)
    , save(nullptr)
    , tok(strtok_r(data, sep, &save))
{
}

char* tokens::operator++() {
    return tok = strtok_r(nullptr, sep, &save);
}

bool tokens::operator==(const char* str) const {
    return strcmp(tok, str) == 0;
}

bool GetShortArgument(char* &ret, const char *name, char** &argpp, char **endpp)
{
    unsigned int alen = strlen(name);
    if (**argpp != '-' || strncmp((*argpp) + 1, name, alen))
        return false;
    char ch = (*argpp)[1 + alen];
    if (ch) {
        ret = (*argpp) + 1 + alen + (ch == '=');
        return true;
    }
    else if (argpp + 1 >= endpp)
        return false;
    ++argpp;
    ret = *argpp;
    return true;
}

bool GetLongArgument(char* &ret, const char *name, char** &argpp, char **endpp)
{
    unsigned int alen = strlen(name);
    if (strncmp(*argpp, "--", 2) || strncmp((*argpp) + 2, name, alen))
        return false;
    char ch = (*argpp)[2 + alen];
    if (ch == '=') {
        ret = (*argpp) + 3 + alen;
        return true;
    }
    if (argpp + 1 >= endpp)
        return false;
    ++argpp;
    ret = *argpp;
    return true;
}

bool GetArgument(char* &ret, const char *sn, const char *ln, char** &arg, char **end)
{
    bool got = false;
    if (arg && *arg && **arg == '-') {
        if (arg[0][1] == '-') {
            got = GetLongArgument(ret, ln, arg, end);
        } else {
            got = GetShortArgument(ret, sn, arg, end);
        }
    }
    return got;
}

bool is_short_switch(const char *arg, const char *name)
{
    return arg && *arg == '-' && 0 == strcmp(arg + 1, name);
}

bool is_long_switch(const char *arg, const char *name)
{
    return arg && *arg == '-' && arg[1] == '-' && 0 == strcmp(arg + 2, name);
}

bool is_switch(const char *arg, const char *short_name, const char *long_name)
{
    return is_short_switch(arg, short_name) || is_long_switch(arg, long_name);
}

bool is_copying_switch(const char *arg)
{
    return is_switch(arg, "C", "copying");
}

bool is_help_switch(const char *arg)
{
    return is_switch(arg, "h", "help") || is_switch(arg, "?", "?");
}

bool is_version_switch(const char *arg)
{
    return is_switch(arg, "V", "version");
}

void print_help_exit(const char *help)
{
    printf(_("Usage: %s [OPTIONS]\n"
             "Options:\n"
             "%s"
             "\n"
             "  -C, --copying       Prints license information and exits.\n"
             "  -V, --version       Prints version information and exits.\n"
             "  -h, --help          Prints this usage screen and exits.\n"
             "\n"),
            ApplicationName, help);
    exit(0);
}

void print_version_exit(const char *version)
{
    printf("%s %s, %s.\n", ApplicationName, version,
        "Copyright 1997-2012 Marko Macek, 2001 Mathias Hasselmann");
    exit(0);
}

void print_copying_exit()
{
    printf("%s\n",
    "IceWM is licensed under the GNU Library General Public License.\n"
    "See the file COPYING in the distribution for full details.\n"
    );
    exit(0);
}

void check_help_version(const char *arg, help_text_fun help, const char *version)
{
    if (is_help_switch(arg)) {
        print_help_exit(help());
    }
    if (is_version_switch(arg)) {
        print_version_exit(version);
    }
    if (is_copying_switch(arg)) {
        print_copying_exit();
    }
}

static const char* help_display()
{
    return "  -d, --display=NAME    NAME of the X server to use.\n";
}

void check_argv(int argc, char **argv, help_text_fun help, const char *version)
{
    if (ApplicationName == nullptr) {
        ApplicationName = my_basename(argv[0]);
    }
    for (char **arg = argv + 1; arg < argv + argc; ++arg) {
        if ('-' == arg[0][0]) {
            char c = ('-' == arg[0][1]) ? arg[0][2] : arg[0][1];
            if (c == '\0') {
                if ('-' == arg[0][1]) {
                    break;
                }
            }
            else if (strchr("h?vVcC", c)) {
                check_help_version(*arg,
                    help ? help : help_display,
                    version);
            }
            else if (c == 'd') {
                char* value(nullptr);
                if (GetArgument(value, "d", "display", arg, argv + argc)) {
                    setenv("DISPLAY", value, 1);
                }
            }
        }
    }
}

const char* errno_string() {
    return strerror(errno);
}

const char *my_basename(const char *path) {
    const char *base = ::strrchr(path, '/');
    return (base ? base + 1 : path);
}

bool isFile(const char* path) {
    struct stat s;
    return stat(path, &s) == 0 && S_ISREG(s.st_mode);
}

bool isExeFile(const char* path) {
    struct stat s;
    return stat(path, &s) == 0 && S_ISREG(s.st_mode)
        && (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
}

// parse a C-style identifier
char* identifier(const char* text) {
    const char* scan = text;
    if (*scan == '_' || isAlpha(*scan)) {
        while (*++scan == '_' || isAlnum(*scan)) { }
    }
    return text < scan ? newstr(text, int(scan - text)) : nullptr;
}

// free a string when out-of-scope
class strp {
public:
    strp(char* string = nullptr) : ptr(string) { }
    ~strp() { delete[] ptr; }
    operator char*() { return ptr; }
    void operator=(char* p) { ptr = p; }
private:
    char* ptr;
};

// obtain the home directory for a named user
char* userhome(const char* username) {
    const size_t buflen = 1234;
    char buf[buflen];
    passwd pwd, *result = nullptr;
    int get;
    if (nonempty(username)) {
        get = getpwnam_r(username, &pwd, buf, buflen, &result);
    } else {
        get = getpwuid_r(getuid(), &pwd, buf, buflen, &result);
    }
    if (get == 0 && result) {
        return newstr(result->pw_dir);
    } else {
        return nullptr;
    }
}

// expand a leading environment variable
char* dollar_expansion(const char* name) {
    if (name[0] == '$') {
        bool leftb = (name[1] == '{');
        strp ident = identifier(name + 1 + leftb);
        if (ident) {
            size_t idlen = strlen(ident);
            bool right = (name[1 + leftb + idlen] == '}');
            if (leftb == right) {
                char* expand = getenv(ident);
                if (expand) {
                    size_t xlen = strlen(expand);
                    size_t size = xlen + strlen(name + idlen);
                    char* path = new char[size];
                    if (path) {
                        strlcpy(path, expand, size);
                        strlcat(path, name + 1 + leftb + idlen + right, size);
                        return path;
                    }
                }
            }
        }
    }
    return nullptr;
}

// expand a leading tilde+slash or tilde+username
char* tilde_expansion(const char* name) {
    if (name[0] != '~') {
        return nullptr;
    }
    else if (name[1] == '/' || name[1] == '\0') {
        char* home = getenv("HOME");
        strp user;
        if (isEmpty(home)) {
            user = userhome(nullptr);
            home = user;
        }
        if (nonempty(home)) {
            size_t size = strlen(home) + strlen(name);
            char* path = new char[size];
            if (path) {
                strlcpy(path, home, size);
                strlcat(path, name + 1, size);
                return path;
            }
        }
    }
    else {
        strp ident = identifier(name + 1);
        if (ident) {
            size_t idlen = strlen(ident);
            if (name[1 + idlen] == '/' || name[1 + idlen] == '\0') {
                strp home = userhome(ident);
                if (home) {
                    size_t hlen = strlen(home);
                    size_t size = hlen + strlen(name + idlen);
                    char* path = new char[size];
                    if (path) {
                        strlcpy(path, home, size);
                        strlcat(path, name + 1 + idlen, size);
                        return path;
                    }
                }
            }
        }
    }
    return nullptr;
}

// lookup "name" in PATH and return a new string or 0.
char* path_lookup(const char* name) {
    if (isEmpty(name))
        return nullptr;

    if (name[0] == '~') {
        char* expand = tilde_expansion(name);
        if (expand) {
            if (isExeFile(expand)) {
                return expand;
            } else {
                delete[] expand;
            }
        }
        return nullptr;
    }

    if (name[0] == '$') {
        char* expand = dollar_expansion(name);
        if (expand) {
            if (isExeFile(expand)) {
                return expand;
            } else {
                delete[] expand;
            }
        }
        return nullptr;
    }

    if (strchr(name, '/')) {
        return isExeFile(name) ? newstr(name) : nullptr;
    }

    char* env = getenv("PATH");
    if (env == nullptr)
        return nullptr;

    const size_t namlen = strlen(name);

    for (char* str = env, *end; *str; str = (end + (*end == ':'))) {
        for (end = str; *end != ':' && *++end; );
        size_t dirlen = end - str;
        size_t length = dirlen + namlen + 3;
        const size_t bufsize = 1234;
        if (length < bufsize) {
            char filebuf[bufsize];
            if (dirlen) {
                memcpy(filebuf, str, dirlen);
                if (filebuf[dirlen - 1] != '/')
                    filebuf[dirlen++] = '/';
            }
            memcpy(filebuf + dirlen, name, namlen + 1);
            if (isExeFile(filebuf)) {
                return newstr(filebuf);
            }
        }
    }
    return nullptr;
}

#ifdef __gnu_hurd__
const char* getprogname() {
    return ApplicationName;
}
#endif

// get path of executable.
char* progpath() {
#if defined(__linux__) && defined(_GNU_SOURCE) && !defined(__ANDROID__)
    char* path = program_invocation_name;
    bool fail = (isEmpty(path) || !isExeFile(path));
    if (fail) {
        const size_t linksize = 123;
        char link[linksize];
        const size_t procsize = 42;
        char proc[procsize];
        snprintf(proc, procsize, "/proc/%d/exe", int(getpid()));
        ssize_t read = readlink(proc, link, linksize);
        if (inrange<ssize_t>(read, 1, ssize_t(linksize - 1))) {
            link[read] = 0;
            char* annotation = strstr(link, " (deleted)");
            if (annotation && annotation > link) {
                annotation[0] = 0;
            }
            if ((fail = access(link, R_OK | X_OK)) == 0) {
                path = program_invocation_name = newstr(link);
                INFO("1: set program_invocation_name %s", path);
            }
        }
    }
    if (fail && (path = path_lookup(path)) != nullptr) {
        program_invocation_name = path;
        INFO("2: set program_invocation_name %s", path);
    }
#else
    static char* path;
    if (path == 0)
        path = path_lookup(getprogname());
#endif
    return path;
}

FILE* process_open(const char* path, const char* args[], int* pid) {
    FILE* fp = nullptr;
    int fds[2];
    if (pipe(fds) == 0) {
        *pid = fork();
        if (*pid == -1) {
            close(fds[0]); close(fds[1]);
        }
        else if (*pid == 0) {
            close(fds[0]);
            dup2(fds[1], 1);
            close(fds[1]);
            execv(path, (char * const *) args);
            _exit(1);
        }
        else {
            close(fds[1]);
            fp = fdopen(fds[0], "r");
            if (fp == nullptr)
                close(fds[0]);
        }
    }
    return fp;
}

int process_close(FILE* fp, int pid) {
    if (fp)
        fclose(fp);
    int xit = -1;
    if (pid > 0) {
        int status = 0;
        waitpid(pid, &status, 0);
        if (WIFEXITED(status))
            xit = WEXITSTATUS(status);
    }
    return xit;
}

void show_backtrace(const int limit) {
#if defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_EXECINFO_H)
    const int fixed = 40;
    const int asize = (0 < limit) ? min(fixed, limit) : 20;
    void *array[fixed];
    const int count = backtrace(array, asize);
    const char tool[] = "/usr/bin/addr2line";
    char* prog = progpath();
    char* path = prog ? prog : path_lookup("icewm");

    timeval now;
    gettimeofday(&now, nullptr);
    struct tm *loc = localtime(&now.tv_sec);
    unsigned msec = unsigned(now.tv_usec / 1000);

    fprintf(stderr, "%02d:%02d:%02d.%03u: %s:\n", loc->tm_hour, loc->tm_min,
            loc->tm_sec, msec, "backtrace"); fflush(stderr);

    int status(1);
    if (path && access(tool, X_OK) == 0) {
        const char* args[30] = { tool, "-C", "-f", "-p", "-s", "-e", path, };
        char strings[20][32];
        for (int i = 0; i < count; ++i) {
            sprintf(strings[i], "%p", array[i]);
            args[7 + i] = strings[i];
        }
        args[7 + count] = nullptr;
        int pid = 0;
        FILE* fp = process_open(tool, args, &pid);
        if (fp) {
            const int linesize(256);
            char line[linesize];
            int lineCount = 0;
            while (fgets(line, linesize, fp)) {
                ++lineCount;
                if (strncmp(line, "?? ??:0", 7) &&
                    strncmp(line, "show_backtrace(", 15)) {
                    fputs(line, stderr);
                }
            }
            if (process_close(fp, pid) == 0 && lineCount >= count) {
                status = 0;
            }
        }
    }
    if (status) {
        backtrace_symbols_fd(array, count, 2);
    }
    fputs("end\n\n", stderr); fflush(stderr);
#endif
}

// vim: set sw=4 ts=4 et:
