#include "MiscFunctions.hpp"
#include "../defines.hpp"
#include <algorithm>
#include "../Compositor.hpp"
#include "../managers/TokenManager.hpp"
#include <optional>
#include <cstring>
#include <cmath>
#include <set>
#include <sys/utsname.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iomanip>
#include <sstream>
#ifdef HAS_EXECINFO
#include <execinfo.h>
#endif
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;

#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/sysctl.h>
#if defined(__DragonFly__)
#include <sys/kinfo.h> // struct kinfo_proc
#elif defined(__FreeBSD__)
#include <sys/user.h> // struct kinfo_proc
#endif

#if defined(__NetBSD__)
#undef KERN_PROC
#define KERN_PROC  KERN_PROC2
#define KINFO_PROC struct kinfo_proc2
#else
#define KINFO_PROC struct kinfo_proc
#endif
#if defined(__DragonFly__)
#define KP_PPID(kp) kp.kp_ppid
#elif defined(__FreeBSD__)
#define KP_PPID(kp) kp.ki_ppid
#else
#define KP_PPID(kp) kp.p_ppid
#endif
#endif

static const float transforms[][9] = {
    {
        1.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
    },
    {
        0.0f,
        1.0f,
        0.0f,
        -1.0f,
        0.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
    },
    {
        -1.0f,
        0.0f,
        0.0f,
        0.0f,
        -1.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
    },
    {
        0.0f,
        -1.0f,
        0.0f,
        1.0f,
        0.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
    },
    {
        -1.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
    },
    {
        0.0f,
        1.0f,
        0.0f,
        1.0f,
        0.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
    },
    {
        1.0f,
        0.0f,
        0.0f,
        0.0f,
        -1.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
    },
    {
        0.0f,
        -1.0f,
        0.0f,
        -1.0f,
        0.0f,
        0.0f,
        0.0f,
        0.0f,
        1.0f,
    },
};

std::string absolutePath(const std::string& rawpath, const std::string& currentPath) {
    auto value = rawpath;

    if (value[0] == '~') {
        static const char* const ENVHOME = getenv("HOME");
        value.replace(0, 1, std::string(ENVHOME));
    } else if (value[0] != '/') {
        auto currentDir = currentPath.substr(0, currentPath.find_last_of('/'));

        if (value[0] == '.') {
            if (value[1] == '.' && value[2] == '/') {
                auto parentDir = currentDir.substr(0, currentDir.find_last_of('/'));
                value.replace(0, 2 + currentPath.empty(), parentDir);
            } else if (value[1] == '/')
                value.replace(0, 1 + currentPath.empty(), currentDir);
            else
                value = currentDir + '/' + value;
        } else
            value = currentDir + '/' + value;
    }

    return value;
}

void addWLSignal(wl_signal* pSignal, wl_listener* pListener, void* pOwner, const std::string& ownerString) {
    ASSERT(pSignal);
    ASSERT(pListener);

    wl_signal_add(pSignal, pListener);

    Debug::log(LOG, "Registered signal for owner {:x}: {:x} -> {:x} (owner: {})", (uintptr_t)pOwner, (uintptr_t)pSignal, (uintptr_t)pListener, ownerString);
}

void removeWLSignal(wl_listener* pListener) {
    wl_list_remove(&pListener->link);
    wl_list_init(&pListener->link);

    Debug::log(LOG, "Removed listener {:x}", (uintptr_t)pListener);
}

void handleNoop(struct wl_listener* listener, void* data) {
    // Do nothing
}

std::string escapeJSONStrings(const std::string& str) {
    std::ostringstream oss;
    for (auto& c : str) {
        switch (c) {
            case '"': oss << "\\\""; break;
            case '\\': oss << "\\\\"; break;
            case '\b': oss << "\\b"; break;
            case '\f': oss << "\\f"; break;
            case '\n': oss << "\\n"; break;
            case '\r': oss << "\\r"; break;
            case '\t': oss << "\\t"; break;
            default:
                if ('\x00' <= c && c <= '\x1f') {
                    oss << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(c);
                } else {
                    oss << c;
                }
        }
    }
    return oss.str();
}

std::optional<float> getPlusMinusKeywordResult(std::string source, float relative) {
    try {
        return relative + stof(source);
    } catch (...) {
        Debug::log(ERR, "Invalid arg \"{}\" in getPlusMinusKeywordResult!", source);
        return {};
    }
}

bool isDirection(const std::string& arg) {
    return arg == "l" || arg == "r" || arg == "u" || arg == "d" || arg == "t" || arg == "b";
}

bool isDirection(const char& arg) {
    return arg == 'l' || arg == 'r' || arg == 'u' || arg == 'd' || arg == 't' || arg == 'b';
}

SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
    SWorkspaceIDName result = {WORKSPACE_INVALID, ""};

    if (in.starts_with("special")) {
        result.name = "special:special";

        if (in.length() > 8) {
            const auto NAME = in.substr(8);
            const auto WS   = g_pCompositor->getWorkspaceByName("special:" + NAME);

            return {WS ? WS->m_iID : g_pCompositor->getNewSpecialID(), "special:" + NAME};
        }

        result.id = SPECIAL_WORKSPACE_START;
        return result;
    } else if (in.starts_with("name:")) {
        const auto WORKSPACENAME = in.substr(in.find_first_of(':') + 1);
        const auto WORKSPACE     = g_pCompositor->getWorkspaceByName(WORKSPACENAME);
        if (!WORKSPACE) {
            result.id = g_pCompositor->getNextAvailableNamedWorkspace();
        } else {
            result.id = WORKSPACE->m_iID;
        }
        result.name = WORKSPACENAME;
    } else if (in.starts_with("empty")) {
        const bool same_mon = in.substr(5).contains("m");
        const bool next     = in.substr(5).contains("n");
        if ((same_mon || next) && !g_pCompositor->m_pLastMonitor) {
            Debug::log(ERR, "Empty monitor workspace on monitor null!");
            return {WORKSPACE_INVALID};
        }

        std::set<int> invalidWSes;
        if (same_mon) {
            for (auto& rule : g_pConfigManager->getAllWorkspaceRules()) {
                const auto PMONITOR = g_pCompositor->getMonitorFromName(rule.monitor);
                if (PMONITOR && (PMONITOR->ID != g_pCompositor->m_pLastMonitor->ID))
                    invalidWSes.insert(rule.workspaceId);
            }
        }

        int id = next ? g_pCompositor->m_pLastMonitor->activeWorkspaceID() : 0;
        while (++id < INT_MAX) {
            const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(id);
            if (!invalidWSes.contains(id) && (!PWORKSPACE || g_pCompositor->getWindowsOnWorkspace(id) == 0)) {
                result.id = id;
                return result;
            }
        }
    } else if (in.starts_with("prev")) {
        if (!g_pCompositor->m_pLastMonitor)
            return {WORKSPACE_INVALID};

        const auto PWORKSPACE = g_pCompositor->m_pLastMonitor->activeWorkspace;

        if (!valid(PWORKSPACE))
            return {WORKSPACE_INVALID};

        const auto PLASTWORKSPACE = g_pCompositor->getWorkspaceByID(PWORKSPACE->m_sPrevWorkspace.id);

        if (!PLASTWORKSPACE)
            return {WORKSPACE_INVALID};

        return {PLASTWORKSPACE->m_iID, PLASTWORKSPACE->m_szName};
    } else {
        if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) {
            bool absolute = in[1] == '~';
            if (!g_pCompositor->m_pLastMonitor) {
                Debug::log(ERR, "Relative monitor workspace on monitor null!");
                return {WORKSPACE_INVALID};
            }

            const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in.substr(absolute ? 2 : 1), 0);

            if (!PLUSMINUSRESULT.has_value())
                return {WORKSPACE_INVALID};

            result.id = (int)PLUSMINUSRESULT.value();

            int           remains = (int)result.id;

            std::set<int> invalidWSes;

            // Collect all the workspaces we can't jump to.
            for (auto& ws : g_pCompositor->m_vWorkspaces) {
                if (ws->m_bIsSpecialWorkspace || (ws->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID)) {
                    // Can't jump to this workspace
                    invalidWSes.insert(ws->m_iID);
                }
            }
            for (auto& rule : g_pConfigManager->getAllWorkspaceRules()) {
                const auto PMONITOR = g_pCompositor->getMonitorFromName(rule.monitor);
                if (!PMONITOR || PMONITOR->ID == g_pCompositor->m_pLastMonitor->ID) {
                    // Can't be invalid
                    continue;
                }
                // WS is bound to another monitor, can't jump to this
                invalidWSes.insert(rule.workspaceId);
            }

            // Prepare all named workspaces in case when we need them
            std::vector<int> namedWSes;
            for (auto& ws : g_pCompositor->m_vWorkspaces) {
                if (ws->m_bIsSpecialWorkspace || (ws->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID) || ws->m_iID >= 0)
                    continue;

                namedWSes.push_back(ws->m_iID);
            }
            std::sort(namedWSes.begin(), namedWSes.end());

            if (absolute) {
                // 1-index
                remains -= 1;

                // traverse valid workspaces until we reach the remains
                if ((size_t)remains < namedWSes.size()) {
                    result.id = namedWSes[remains];
                } else {
                    remains -= namedWSes.size();
                    result.id = 0;
                    while (remains >= 0) {
                        result.id++;
                        if (!invalidWSes.contains(result.id)) {
                            remains--;
                        }
                    }
                }
            } else {

                // Just take a blind guess at where we'll probably end up
                int  activeWSID    = g_pCompositor->m_pLastMonitor->activeWorkspace ? g_pCompositor->m_pLastMonitor->activeWorkspace->m_iID : 1;
                int  predictedWSID = activeWSID + remains;
                int  remainingWSes = 0;
                char walkDir       = in[1];

                // sanitize. 0 means invalid oob in -
                predictedWSID = std::max(predictedWSID, 0);

                // Count how many invalidWSes are in between (how bad the prediction was)
                int  beginID = in[1] == '+' ? activeWSID + 1 : predictedWSID;
                int  endID   = in[1] == '+' ? predictedWSID : activeWSID;
                auto begin   = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >=
                for (auto it = begin; *it <= endID && it != invalidWSes.end(); it++) {
                    remainingWSes++;
                }

                // Handle named workspaces. They are treated like always before other workspaces
                if (activeWSID < 0) {
                    // Behaviour similar to 'm'
                    // Find current
                    int currentItem = -1;
                    for (size_t i = 0; i < namedWSes.size(); i++) {
                        if (namedWSes[i] == activeWSID) {
                            currentItem = i;
                            break;
                        }
                    }

                    currentItem += remains;
                    currentItem = std::max(currentItem, 0);
                    if (currentItem >= (int)namedWSes.size()) {
                        // At the seam between namedWSes and normal WSes. Behave like r+[diff] at imaginary ws 0
                        int diff      = currentItem - (namedWSes.size() - 1);
                        predictedWSID = diff;
                        int  beginID  = 1;
                        int  endID    = predictedWSID;
                        auto begin    = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >=
                        for (auto it = begin; *it <= endID && it != invalidWSes.end(); it++) {
                            remainingWSes++;
                        }
                        walkDir = '+';
                    } else {
                        // We found our final ws.
                        remainingWSes = 0;
                        predictedWSID = namedWSes[currentItem];
                    }
                }

                // Go in the search direction for remainingWSes
                // The performance impact is directly proportional to the number of open and bound workspaces
                int finalWSID = predictedWSID;
                if (walkDir == '-') {
                    int beginID = finalWSID;
                    int curID   = finalWSID;
                    while (--curID > 0 && remainingWSes > 0) {
                        if (!invalidWSes.contains(curID)) {
                            remainingWSes--;
                        }
                        finalWSID = curID;
                    }
                    if (finalWSID <= 0 || invalidWSes.contains(finalWSID)) {
                        if (namedWSes.size()) {
                            // Go to the named workspaces
                            // Need remainingWSes more
                            int namedWSIdx = namedWSes.size() - remainingWSes;
                            // Sanitze
                            namedWSIdx = std::clamp(namedWSIdx, 0, (int)namedWSes.size() - 1);
                            finalWSID  = namedWSes[namedWSIdx];
                        } else {
                            // Couldn't find valid workspace in negative direction, search last first one back up positive direction
                            walkDir = '+';
                            // We know, that everything less than beginID is invalid, so don't bother with that
                            finalWSID     = beginID;
                            remainingWSes = 1;
                        }
                    }
                }
                if (walkDir == '+') {
                    int curID = finalWSID;
                    while (++curID < INT32_MAX && remainingWSes > 0) {
                        if (!invalidWSes.contains(curID)) {
                            remainingWSes--;
                        }
                        finalWSID = curID;
                    }
                }
                result.id = finalWSID;
            }

            const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(result.id);
            if (PWORKSPACE)
                result.name = g_pCompositor->getWorkspaceByID(result.id)->m_szName;
            else
                result.name = std::to_string(result.id);

        } else if ((in[0] == 'm' || in[0] == 'e') && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) {
            bool onAllMonitors = in[0] == 'e';
            bool absolute      = in[1] == '~';

            if (!g_pCompositor->m_pLastMonitor) {
                Debug::log(ERR, "Relative monitor workspace on monitor null!");
                return {WORKSPACE_INVALID};
            }

            // monitor relative
            const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in.substr(absolute ? 2 : 1), 0);

            if (!PLUSMINUSRESULT.has_value())
                return {WORKSPACE_INVALID};

            result.id = (int)PLUSMINUSRESULT.value();

            // result now has +/- what we should move on mon
            int              remains = (int)result.id;

            std::vector<int> validWSes;
            for (auto& ws : g_pCompositor->m_vWorkspaces) {
                if (ws->m_bIsSpecialWorkspace || (ws->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID && !onAllMonitors))
                    continue;

                validWSes.push_back(ws->m_iID);
            }

            std::sort(validWSes.begin(), validWSes.end());

            int currentItem = -1;

            if (absolute) {
                // 1-index
                currentItem = remains - 1;

                // clamp
                if (currentItem < 0) {
                    currentItem = 0;
                } else if (currentItem >= (int)validWSes.size()) {
                    currentItem = validWSes.size() - 1;
                }
            } else {
                // get the offset
                remains = remains < 0 ? -((-remains) % validWSes.size()) : remains % validWSes.size();

                // get the current item
                int activeWSID = g_pCompositor->m_pLastMonitor->activeWorkspace ? g_pCompositor->m_pLastMonitor->activeWorkspace->m_iID : 1;
                for (size_t i = 0; i < validWSes.size(); i++) {
                    if (validWSes[i] == activeWSID) {
                        currentItem = i;
                        break;
                    }
                }

                // apply
                currentItem += remains;

                // sanitize
                if (currentItem >= (int)validWSes.size()) {
                    currentItem = currentItem % validWSes.size();
                } else if (currentItem < 0) {
                    currentItem = validWSes.size() + currentItem;
                }
            }

            result.id   = validWSes[currentItem];
            result.name = g_pCompositor->getWorkspaceByID(validWSes[currentItem])->m_szName;
        } else {
            if (in[0] == '+' || in[0] == '-') {
                if (g_pCompositor->m_pLastMonitor) {
                    const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, g_pCompositor->m_pLastMonitor->activeWorkspaceID());
                    if (!PLUSMINUSRESULT.has_value())
                        return {WORKSPACE_INVALID};

                    result.id = std::max((int)PLUSMINUSRESULT.value(), 1);
                } else {
                    Debug::log(ERR, "Relative workspace on no mon!");
                    return {WORKSPACE_INVALID};
                }
            } else if (isNumber(in))
                result.id = std::max(std::stoi(in), 1);
            else {
                // maybe name
                const auto PWORKSPACE = g_pCompositor->getWorkspaceByName(in);
                if (PWORKSPACE)
                    result.id = PWORKSPACE->m_iID;
            }

            result.name = std::to_string(result.id);
        }
    }

    return result;
}

std::optional<std::string> cleanCmdForWorkspace(const std::string& inWorkspaceName, std::string dirtyCmd) {

    std::string cmd = trim(dirtyCmd);

    if (!cmd.empty()) {
        std::string       rules;
        const std::string workspaceRule = "workspace " + inWorkspaceName;

        if (cmd[0] == '[') {
            const int closingBracketIdx = cmd.find_last_of(']');
            auto      tmpRules          = cmd.substr(1, closingBracketIdx - 1);
            cmd                         = cmd.substr(closingBracketIdx + 1);

            auto rulesList = CVarList(tmpRules, 0, ';');

            bool hadWorkspaceRule = false;
            rulesList.map([&](std::string& rule) {
                if (rule.find("workspace") == 0) {
                    rule             = workspaceRule;
                    hadWorkspaceRule = true;
                }
            });

            if (!hadWorkspaceRule)
                rulesList.append(workspaceRule);

            rules = "[" + rulesList.join(";") + "]";
        } else {
            rules = "[" + workspaceRule + "]";
        }

        return std::optional<std::string>(rules + " " + cmd);
    }

    return std::nullopt;
}

float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2) {
    const float DX = std::max({0.0, p1.x - vec.x, vec.x - p2.x});
    const float DY = std::max({0.0, p1.y - vec.y, vec.y - p2.y});
    return DX * DX + DY * DY;
}

// Execute a shell command and get the output
std::string execAndGet(const char* cmd) {
    std::array<char, 128> buffer;
    std::string           result;
    using PcloseType = int (*)(FILE*);
    const std::unique_ptr<FILE, PcloseType> pipe(popen(cmd, "r"), static_cast<PcloseType>(pclose));
    if (!pipe) {
        Debug::log(ERR, "execAndGet: failed in pipe");
        return "";
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}

void logSystemInfo() {
    struct utsname unameInfo;

    uname(&unameInfo);

    Debug::log(LOG, "System name: {}", std::string{unameInfo.sysname});
    Debug::log(LOG, "Node name: {}", std::string{unameInfo.nodename});
    Debug::log(LOG, "Release: {}", std::string{unameInfo.release});
    Debug::log(LOG, "Version: {}", std::string{unameInfo.version});

    Debug::log(NONE, "\n");

#if defined(__DragonFly__) || defined(__FreeBSD__)
    const std::string GPUINFO = execAndGet("pciconf -lv | fgrep -A4 vga");
#elif defined(__arm__) || defined(__aarch64__)
    const std::string GPUINFO = execAndGet("cat /proc/device-tree/soc*/gpu*/compatible");
#else
    const std::string GPUINFO = execAndGet("lspci -vnn | grep VGA");
#endif
    Debug::log(LOG, "GPU information:\n{}\n", GPUINFO);

    if (GPUINFO.contains("NVIDIA")) {
        Debug::log(WARN, "Warning: you're using an NVIDIA GPU. Make sure you follow the instructions on the wiki if anything is amiss.\n");
    }

    // log etc
    Debug::log(LOG, "os-release:");

    Debug::log(NONE, "{}", execAndGet("cat /etc/os-release"));
}

void matrixProjection(float mat[9], int w, int h, wl_output_transform tr) {
    memset(mat, 0, sizeof(*mat) * 9);

    const float* t = transforms[tr];
    float        x = 2.0f / w;
    float        y = 2.0f / h;

    // Rotation + reflection
    mat[0] = x * t[0];
    mat[1] = x * t[1];
    mat[3] = y * t[3];
    mat[4] = y * t[4];

    // Translation
    mat[2] = -copysign(1.0f, mat[0] + mat[1]);
    mat[5] = -copysign(1.0f, mat[3] + mat[4]);

    // Identity
    mat[8] = 1.0f;
}

int64_t getPPIDof(int64_t pid) {
#if defined(KERN_PROC_PID)
    int mib[] = {
        CTL_KERN,           KERN_PROC, KERN_PROC_PID, (int)pid,
#if defined(__NetBSD__) || defined(__OpenBSD__)
        sizeof(KINFO_PROC), 1,
#endif
    };
    u_int      miblen = sizeof(mib) / sizeof(mib[0]);
    KINFO_PROC kp;
    size_t     sz = sizeof(KINFO_PROC);
    if (sysctl(mib, miblen, &kp, &sz, NULL, 0) != -1)
        return KP_PPID(kp);

    return 0;
#else
    std::string dir = "/proc/" + std::to_string(pid) + "/status";
    FILE*       infile;

    infile = fopen(dir.c_str(), "r");
    if (!infile)
        return 0;

    char*       line = nullptr;
    size_t      len  = 0;
    ssize_t     len2 = 0;

    std::string pidstr;

    while ((len2 = getline(&line, &len, infile)) != -1) {
        if (strstr(line, "PPid:")) {
            pidstr            = std::string(line, len2);
            const auto tabpos = pidstr.find_last_of('\t');
            if (tabpos != std::string::npos)
                pidstr = pidstr.substr(tabpos);
            break;
        }
    }

    fclose(infile);
    if (line)
        free(line);

    try {
        return std::stoll(pidstr);
    } catch (std::exception& e) { return 0; }
#endif
}

int64_t configStringToInt(const std::string& VALUE) {
    if (VALUE.starts_with("0x")) {
        // Values with 0x are hex
        const auto VALUEWITHOUTHEX = VALUE.substr(2);
        return stol(VALUEWITHOUTHEX, nullptr, 16);
    } else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) {
        const auto VALUEWITHOUTFUNC = VALUE.substr(5, VALUE.length() - 6);

        if (trim(VALUEWITHOUTFUNC).length() != 8) {
            Debug::log(WARN, "invalid length {} for rgba", VALUEWITHOUTFUNC.length());
            throw std::invalid_argument("rgba() expects length of 8 characters (4 bytes)");
        }

        const auto RGBA = std::stol(VALUEWITHOUTFUNC, nullptr, 16);

        // now we need to RGBA -> ARGB. The config holds ARGB only.
        return (RGBA >> 8) + 0x1000000 * (RGBA & 0xFF);
    } else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) {
        const auto VALUEWITHOUTFUNC = VALUE.substr(4, VALUE.length() - 5);

        if (trim(VALUEWITHOUTFUNC).length() != 6) {
            Debug::log(WARN, "invalid length {} for rgb", VALUEWITHOUTFUNC.length());
            throw std::invalid_argument("rgb() expects length of 6 characters (3 bytes)");
        }

        const auto RGB = std::stol(VALUEWITHOUTFUNC, nullptr, 16);

        return RGB + 0xFF000000; // 0xFF for opaque
    } else if (VALUE.starts_with("true") || VALUE.starts_with("on") || VALUE.starts_with("yes")) {
        return 1;
    } else if (VALUE.starts_with("false") || VALUE.starts_with("off") || VALUE.starts_with("no")) {
        return 0;
    }

    if (VALUE.empty() || !isNumber(VALUE))
        return 0;

    return std::stoll(VALUE);
}

Vector2D configStringToVector2D(const std::string& VALUE) {
    std::istringstream iss(VALUE);
    std::string        token;

    if (!std::getline(iss, token, ' ') && !std::getline(iss, token, ','))
        throw std::invalid_argument("Invalid string format");

    if (!isNumber(token))
        throw std::invalid_argument("Invalid x value");

    long long x = std::stoll(token);

    if (!std::getline(iss, token))
        throw std::invalid_argument("Invalid string format");

    if (!isNumber(token))
        throw std::invalid_argument("Invalid y value");

    long long y = std::stoll(token);

    if (std::getline(iss, token))
        throw std::invalid_argument("Invalid string format");

    return Vector2D((double)x, (double)y);
}

double normalizeAngleRad(double ang) {
    if (ang > M_PI * 2) {
        while (ang > M_PI * 2)
            ang -= M_PI * 2;
        return ang;
    }

    if (ang < 0.0) {
        while (ang < 0.0)
            ang += M_PI * 2;
        return ang;
    }

    return ang;
}

std::vector<SCallstackFrameInfo> getBacktrace() {
    std::vector<SCallstackFrameInfo> callstack;

#ifdef HAS_EXECINFO
    void*  bt[1024];
    size_t btSize;
    char** btSymbols;

    btSize    = backtrace(bt, 1024);
    btSymbols = backtrace_symbols(bt, btSize);

    for (size_t i = 0; i < btSize; ++i) {
        callstack.emplace_back(SCallstackFrameInfo{bt[i], std::string{btSymbols[i]}});
    }
#else
    callstack.emplace_back(SCallstackFrameInfo{nullptr, "configuration does not support execinfo.h"});
#endif

    return callstack;
}

void throwError(const std::string& err) {
    Debug::log(CRIT, "Critical error thrown: {}", err);
    throw std::runtime_error(err);
}

bool envEnabled(const std::string& env) {
    const auto ENV = getenv(env.c_str());
    if (!ENV)
        return false;
    return std::string(ENV) == "1";
}

std::pair<int, std::string> openExclusiveShm() {
    // Only absolute paths can be shared across different shm_open() calls
    std::string name = "/" + g_pTokenManager->getRandomUUID();

    for (size_t i = 0; i < 69; ++i) {
        int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);
        if (fd >= 0)
            return {fd, name};
    }

    return {-1, ""};
}

int allocateSHMFile(size_t len) {
    auto [fd, name] = openExclusiveShm();
    if (fd < 0)
        return -1;

    shm_unlink(name.c_str());

    int ret;
    do {
        ret = ftruncate(fd, len);
    } while (ret < 0 && errno == EINTR);

    if (ret < 0) {
        close(fd);
        return -1;
    }

    return fd;
}

bool allocateSHMFilePair(size_t size, int* rw_fd_ptr, int* ro_fd_ptr) {
    auto [fd, name] = openExclusiveShm();
    if (fd < 0) {
        return false;
    }

    // CLOEXEC is guaranteed to be set by shm_open
    int ro_fd = shm_open(name.c_str(), O_RDONLY, 0);
    if (ro_fd < 0) {
        shm_unlink(name.c_str());
        close(fd);
        return false;
    }

    shm_unlink(name.c_str());

    // Make sure the file cannot be re-opened in read-write mode (e.g. via
    // "/proc/self/fd/" on Linux)
    if (fchmod(fd, 0) != 0) {
        close(fd);
        close(ro_fd);
        return false;
    }

    int ret;
    do {
        ret = ftruncate(fd, size);
    } while (ret < 0 && errno == EINTR);
    if (ret < 0) {
        close(fd);
        close(ro_fd);
        return false;
    }

    *rw_fd_ptr = fd;
    *ro_fd_ptr = ro_fd;
    return true;
}
