#pragma once

// system headers
#include <algorithm>
#include <climits>
#include <cstring>
#include <sstream>
#include <string>
#include <unistd.h>
#include <vector>

// libraries
#include <boost/filesystem.hpp>

namespace linuxdeploy {
    namespace util {
        namespace misc {
            static inline bool ltrim(std::string& s, char to_trim = ' ') {
                // TODO: find more efficient way to check whether elements have been removed
                size_t initialLength = s.length();
                s.erase(s.begin(), std::find_if(s.begin(), s.end(), [to_trim](int ch) {
                    return ch != to_trim;
                }));
                return s.length() < initialLength;
            }

            static inline bool rtrim(std::string& s, char to_trim = ' ') {
                // TODO: find more efficient way to check whether elements have been removed
                auto initialLength = s.length();
                s.erase(std::find_if(s.rbegin(), s.rend(), [to_trim](int ch) {
                    return ch != to_trim;
                }).base(), s.end());
                return s.length() < initialLength;
            }

            static inline bool trim(std::string& s, char to_trim = ' ') {
                // returns true if either modifies s
                auto ltrim_result = ltrim(s, to_trim);
                return rtrim(s, to_trim) && ltrim_result;
            }

            static std::vector<std::string> split(const std::string& s, char delim = ' ') {
                std::vector<std::string> result;

                std::stringstream ss(s);
                std::string item;

                while (std::getline(ss, item, delim)) {
                    result.push_back(item);
                }

                return result;
            }

            static std::vector<std::string> splitLines(const std::string& s) {
                return split(s, '\n');
            }

            static std::string join(const std::vector<std::string> &strings, const std::string &delimiter) {
                std::string result;
                for (size_t i = 0; i < strings.size(); i++) {
                    result += strings[i];

                    if (i != strings.size() - 1) {
                        result += delimiter;
                    }
                }
                return result;
            }

            static inline std::string strLower(std::string s) {
                std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
                return s;
            }

            static bool stringStartsWith(const std::string& string, const std::string& prefix) {
                // sanity check
                if (string.size() < prefix.size())
                    return false;

                return strncmp(string.c_str(), prefix.c_str(), prefix.size()) == 0;
            }

            static bool stringEndsWith(const std::string& string, const std::string& suffix) {
                // sanity check
                if (string.size() < suffix.size())
                    return false;

                return strcmp(string.c_str() + (string.size() - suffix.size()), suffix.c_str()) == 0;
            }

            static bool stringContains(const std::string& string, const std::string& part) {
                return string.find(part) != std::string::npos;
            }

            static std::string getOwnExecutablePath() {
                // FIXME: reading /proc/self/exe line is Linux specific
                std::vector<char> buf(PATH_MAX, '\0');

                if (readlink("/proc/self/exe", buf.data(), buf.size()) < 0) {
                    return "";
                }

                return buf.data();
            }

            // very simple but for our purposes good enough which like algorithm to find binaries in $PATH
            static boost::filesystem::path which(const std::string& name) {
                const auto* path = getenv("PATH");

                namespace bf = boost::filesystem;

                if (path == nullptr)
                    return "";

                for (const auto& binDir : split(path, ':')) {
                    if (!bf::is_directory(binDir)) {
                        continue;
                    }

                    for (bf::directory_iterator it(binDir); it != bf::directory_iterator{}; ++it) {
                        const auto binary = it->path();

                        if (binary.filename() == name) {
                            // TODO: check if file is executable (skip otherwise)
                            return binary;
                        }
                    }
                }

                return {};
            };
        }
    }
}
