/*
 * SPDX-FileCopyrightText: 2016-2016 CSSlayer <wengxt@gmail.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 */
#ifndef _FCITX_UTILS_MISC_H_
#define _FCITX_UTILS_MISC_H_

#include <optional>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

#include <unistd.h>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
#include <fcitx-utils/fcitxutils_export.h>
#include <fcitx-utils/macros.h>

namespace fcitx {

template <class Parent, class Member>
inline std::ptrdiff_t
offsetFromPointerToMember(const Member Parent::*ptr_to_member) {
    const Parent *const parent = 0;
    const char *const member =
        reinterpret_cast<const char *>(&(parent->*ptr_to_member));
    return std::ptrdiff_t(member - reinterpret_cast<const char *>(parent));
}

template <class Parent, class Member>
inline Parent *parentFromMember(Member *member,
                                const Member Parent::*ptr_to_member) {
    return reinterpret_cast<Parent *>(reinterpret_cast<char *>(member) -
                                      offsetFromPointerToMember(ptr_to_member));
}

template <class Parent, class Member>
inline const Parent *parentFromMember(const Member *member,
                                      const Member Parent::*ptr_to_member) {
    return reinterpret_cast<const Parent *>(
        reinterpret_cast<const char *>(member) -
        offsetFromPointerToMember(ptr_to_member));
}

template <typename Iter>
class KeyIterator {
public:
    using iterator_category = typename Iter::iterator_category;
    using value_type = typename Iter::value_type::first_type;
    using difference_type = typename Iter::difference_type;
    using reference = typename Iter::value_type::first_type &;
    using pointer = typename Iter::value_type::first_type *;

    KeyIterator(Iter iter) : iter_(iter) {}
    FCITX_INLINE_DEFINE_DEFAULT_DTOR_AND_COPY(KeyIterator)

    bool operator==(const KeyIterator &other) const noexcept {
        return iter_ == other.iter_;
    }
    bool operator!=(const KeyIterator &other) const noexcept {
        return !operator==(other);
    }

    KeyIterator &operator++() {
        iter_++;
        return *this;
    }

    KeyIterator operator++(int) {
        auto old = iter_;
        ++(*this);
        return {old};
    }

    reference operator*() { return iter_->first; }

    pointer operator->() { return &iter_->first; }

private:
    Iter iter_;
};

template <typename It>
struct IterRange {
    It begin_, end_;
    It begin() const { return begin_; }
    It end() const { return end_; }
};

template <typename Iter>
IterRange<Iter> MakeIterRange(Iter begin, Iter end) {
    return {begin, end};
}

struct EnumHash {
    template <typename T>
    auto operator()(T const value) const {
        return std::hash<std::underlying_type_t<T>>()(
            static_cast<std::underlying_type_t<T>>(value));
    }
};

template <typename Callback>
class [[nodiscard]] ScopeExit {
public:
    explicit ScopeExit(Callback callback) : callback_(std::move(callback)) {}

    ScopeExit(ScopeExit &&) = delete;
    ScopeExit(const ScopeExit &) = delete;
    ScopeExit &operator=(const ScopeExit &) = delete;
    ScopeExit &operator=(ScopeExit &&) = delete;

    ~ScopeExit() noexcept { invoke(); }

    void release() noexcept { callback_.reset(); }
    void invoke() noexcept {
        if (callback_) {
            (*callback_)();
            callback_.reset();
        }
    }

private:
    std::optional<Callback> callback_;
};

template <typename Callback>
ScopeExit(Callback) -> ScopeExit<Callback>;

FCITXUTILS_EXPORT void startProcess(const std::vector<std::string> &args,
                                    const std::string &workingDirectory = {});

FCITXUTILS_EXPORT std::string getProcessName(pid_t pid);

template <auto FreeFunction>
struct FunctionDeleter {
    template <typename T>
    void operator()(T *p) const {
        if (p) {
            FreeFunction(const_cast<std::remove_const_t<T> *>(p));
        }
    }
};
template <typename T, auto FreeFunction = std::free>
using UniqueCPtr = std::unique_ptr<T, FunctionDeleter<FreeFunction>>;
static_assert(
    sizeof(char *) == sizeof(UniqueCPtr<char>),
    "UniqueCPtr size is not same as raw pointer."); // ensure no overhead

using UniqueFilePtr = std::unique_ptr<FILE, FunctionDeleter<std::fclose>>;

template <typename T>
inline auto makeUniqueCPtr(T *ptr) {
    return UniqueCPtr<T>(ptr);
}

#ifndef _WIN32
FCITXUTILS_DEPRECATED_EXPORT ssize_t getline(UniqueCPtr<char> &lineptr,
                                             size_t *n, std::FILE *stream);
#endif

/**
 * Util function to check whether fcitx is running in flatpak.
 *
 * If environment variable FCITX_OVERRIDE_FLATPAK is true, it will return true.
 * Otherwise it will simply check the existence of file /.flatpak-info .
 *
 * @since 5.0.24
 */
FCITXUTILS_EXPORT bool isInFlatpak();

/**
 * Util function that returns whether it is compile against android.
 *
 * @since 5.1.2
 */
constexpr bool isAndroid() {
#if defined(ANDROID) || defined(__ANDROID__)
    return true;
#else
    return false;
#endif
}

/**
 * Util function that returns whether it is compile against apple.
 *
 * @since 5.1.7
 */
constexpr bool isApple() {
#if defined(__APPLE__)
    return true;
#else
    return false;
#endif
}

/**
 * Util function that returns whether it is compile against iOS (real device or
 * simulator).
 *
 * @since 5.1.20
 */
constexpr bool isIOS() {
#ifdef TARGET_OS_IPHONE
    return TARGET_OS_IPHONE == 1;
#else
    return false;
#endif
}

/**
 * Util function that returns whether it is compile against emscripten.
 *
 * @since 5.1.12
 */
constexpr bool isEmscripten() {
#if defined(__EMSCRIPTEN__)
    return true;
#else
    return false;
#endif
}

/**
 * Util function that returns whether it is compile against Windows.
 *
 * @since 5.1.13
 */
constexpr bool isWindows() {
#if defined(_WIN32)
    return true;
#else
    return false;
#endif
}

} // namespace fcitx

#endif // _FCITX_UTILS_MISC_H_
