#include <cinttypes>
#include <base.hpp>

#include "zygisk.hpp"
#include <lsplt.hpp>

using namespace std;
static vector<lsplt::MapInfo> find_maps(const char *name) {
    auto maps = lsplt::MapInfo::Scan();
    for (auto iter = maps.begin(); iter != maps.end();) {
        if (iter->path != name) {
            iter = maps.erase(iter);
        } else {
            ++iter;
        }
    }
    return maps;
}

std::pair<void *, size_t> find_map_range(const char *name, unsigned long inode) {
    auto maps = find_maps(name);
    uintptr_t start = 0u;
    uintptr_t end = 0u;
    for (const auto &map : maps) {
        if (map.inode == inode) {
            if (start == 0) {
                start = map.start;
                end = map.end;
            } else if (map.start == end) {
                end = map.end;
            }
        }
    }
    LOGD("found map %s with start = %zx, end = %zx\n", name, start, end);
    return make_pair(reinterpret_cast<void *>(start), end - start);
}

void unmap_all(const char *name) {
    auto maps = find_maps(name);
    for (auto &info : maps) {
        void *addr = reinterpret_cast<void *>(info.start);
        size_t size = info.end - info.start;
        if (info.perms & PROT_READ) {
            // Make sure readable pages are still readable
            void *dummy = xmmap(nullptr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
            mremap(dummy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
        } else {
            munmap(addr, size);
        }
    }
}

void remap_all(const char *name) {
    auto maps = find_maps(name);
    for (auto &info : maps) {
        void *addr = reinterpret_cast<void *>(info.start);
        size_t size = info.end - info.start;
        void *copy = xmmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
        if ((info.perms & PROT_READ) == 0) {
            mprotect(addr, size, PROT_READ);
        }
        memcpy(copy, addr, size);
        mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
        mprotect(addr, size, info.perms);
    }
}

uintptr_t get_function_off(int pid, uintptr_t addr, char *lib) {
    for (auto &info : lsplt::MapInfo::Scan()) {
        if (addr >= info.start && addr < info.end) {
            if (lib)
                strcpy(lib, info.path.data());
            return addr - info.start + info.offset;
        }
    }
    return 0;
}

uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off) {
    for (auto &info : lsplt::MapInfo::Scan()) {
        if (info.path == lib && (info.perms & PROT_EXEC)) {
            return info.start - info.offset + off;
        }
    }
    return 0;
}
