#include <stdio.h>
#include <elf.h>
#include <android/log.h>
#include <malloc.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include "fake_dlfcn.h"
#include "utils.h"

#define TAG_NAME  "fake_dlfcn"

#define log_info(fmt,args...) __android_log_print(ANDROID_LOG_INFO, TAG_NAME, (const char *) fmt, ##args)
#define log_err(fmt,args...) __android_log_print(ANDROID_LOG_ERROR, TAG_NAME, (const char *) fmt, ##args)


#ifdef LOG_DBG
#define log_dbg log_info
#else
#define log_dbg(...)
#endif

#ifdef __arm__
#define Elf_Ehdr Elf32_Ehdr
#define Elf_Phdr Elf32_Phdr
#define Elf_Shdr Elf32_Shdr
#define Elf_Sym  Elf32_Sym
#elif defined(__aarch64__)
#define Elf_Ehdr Elf64_Ehdr
#define Elf_Phdr Elf64_Phdr
#define Elf_Shdr Elf64_Shdr
#define Elf_Sym  Elf64_Sym
#else
#error "Arch unknown, please port me"
#endif




int fake_dlclose(void *handle)
{
    if(handle) {
        struct so_info *so_info = (struct so_info *) handle;
        if(so_info->dynsym) free(so_info->dynsym);	/* we're saving dynsym and dynstr */
        if(so_info->dynstr) free(so_info->dynstr);	/* from library file just in case */
        if(so_info->symtab) free(so_info->symtab);	/* from library file just in case */
        free(so_info);
    }
    return 0;
}

#pragma clang diagnostic push
#pragma ide diagnostic ignored "err_ovl_ambiguous_call"
/* flags are ignored */

void *fake_dlopen(const char *libpath, int flags)
{
    FILE *maps;
    char buff[1024];
    struct so_info *so_info = 0;
    off_t load_addr, size;
    int k, fd = -1, found = 0;
    void *shoff;
    Elf_Ehdr *elf = MAP_FAILED;

#define fatal(fmt,args...) do { log_err(fmt,##args); goto err_exit; } while(0)

    maps = fopen("/proc/self/maps", "r");
    if(!maps) fatal("failed to open maps");

    while(!found && fgets(buff, sizeof(buff), maps))
        if(strstr(buff, libpath)) found = 1;

    fclose(maps);

    if(!found) fatal("%s not found in my userspace", libpath);

    long offset = 0;
    int pos = 0;
    if(sscanf(buff, "%lx-%*lx %*4s %lx %*lx:%*lx %*d%n", &load_addr, &offset, &pos) != 2)
        fatal("failed to read load address for %s", libpath);

    if(0 != offset) return NULL;
    char* path = wd_util_trim(buff + pos);

    log_info("%s loaded in Android at 0x%08lx", libpath, load_addr);

    /* Now, mmap the same library once again */

    fd = open(path, O_RDONLY);
    if(fd < 0) fatal("failed to open %s", path);

    size = lseek(fd, 0, SEEK_END);
    if(size <= 0) fatal("lseek() failed for %s", libpath);

    elf = (Elf_Ehdr *) mmap(0, size, PROT_READ, MAP_SHARED, fd, 0);
    close(fd);
    fd = -1;

    if(elf == MAP_FAILED) fatal("mmap() failed for %s", libpath);

    so_info = (struct so_info *) calloc(1, sizeof(struct so_info));
    if(!so_info) fatal("no memory for %s", libpath);

    so_info->load_addr = (void *) load_addr;

    shoff = ((void *) elf) + elf->e_shoff;
    Elf_Shdr *shstrndx = ((Elf_Shdr *) shoff+elf->e_shstrndx);
    char* shstr=((void *) elf) + shstrndx->sh_offset;

    void* phoff = ((void *) elf) + elf->e_phoff;
    size_t load_size = 0;
    int find_bias = 0;
    for(int j = 0; j < elf->e_phnum; j++, phoff += elf->e_phentsize) {
        Elf_Phdr* ph = phoff;
        if(ph->p_type == PT_LOAD) {
            if(!find_bias) {
                so_info->bias = ph->p_vaddr;
                so_info->base = so_info->load_addr - so_info->bias;
                find_bias = 1;
            }
            size_t addr = ph->p_vaddr + ph ->p_memsz;
            if(load_size < addr) {
                load_size = addr;
            }
        }
    }
    so_info->mem_size = load_size;

    log_info("so_info->bias: 0x%x, so_info->mem_size: 0x%x", so_info->bias, so_info->mem_size);

    for(k = 0; k < elf->e_shnum; k++, shoff += elf->e_shentsize)  {

        Elf_Shdr *sh = (Elf_Shdr *) shoff;
        log_dbg("%s: k=%d shdr=%p type=%x", __func__, k, sh, sh->sh_type);
        char* shname = shstr+sh->sh_name;
        if(strcmp(shname,".symtab") == 0){
            if(so_info->symtab) fatal("%s: duplicate SYMTAB sections", libpath); /* .dynsym */
            so_info->symtab = malloc(sh->sh_size);
            if(!so_info->symtab) fatal("%s: no memory for .symtab", libpath);
            memcpy(so_info->symtab, ((void *) elf) + sh->sh_offset, sh->sh_size);
            so_info->nsymtabs = (sh->sh_size/sizeof(Elf_Sym)) ;
        }else if(strcmp(shname,".strtab")==0){
            so_info->strtab = malloc(sh->sh_size);
            if(!so_info->strtab) fatal("%s: no memory for .strtab", libpath);
            memcpy(so_info->strtab, ((void *) elf) + sh->sh_offset, sh->sh_size);
        }else if(strcmp(shname,".dynsym")==0){
            if(so_info->dynsym) fatal("%s: duplicate DYNSYM sections", libpath); /* .dynsym */
            so_info->dynsym = malloc(sh->sh_size);
            if(!so_info->dynsym) fatal("%s: no memory for .dynsym", libpath);
            memcpy(so_info->dynsym, ((void *) elf) + sh->sh_offset, sh->sh_size);
            so_info->nsyms = (sh->sh_size/sizeof(Elf_Sym)) ;
        }else if(strcmp(shname,".dynstr")==0){
            so_info->dynstr = malloc(sh->sh_size);
            if(!so_info->dynstr) fatal("%s: no memory for .dynstr", libpath);
            memcpy(so_info->dynstr, ((void *) elf) + sh->sh_offset, sh->sh_size);
        }else if(strcmp(shname,".text")==0){
          //  so_info->bias = (off_t) sh->sh_addr - (off_t) sh->sh_offset;
        }

        /*
        switch(sh->sh_type) {

            case SHT_SYMTAB:
                if(so_info->symtab) fatal("%s: duplicate SYMTAB sections", libpath);
                so_info->symtab = malloc(sh->sh_size);
                if(!so_info->symtab) fatal("%s: no memory for .symtab", libpath);
                memcpy(so_info->symtab, ((void *) elf) + sh->sh_offset, sh->sh_size);
                so_info->nsymtabs = (sh->sh_size/sizeof(Elf_Sym)) ;
                break;



            case SHT_DYNSYM:
                if(so_info->dynsym) fatal("%s: duplicate DYNSYM sections", libpath);
                so_info->dynsym = malloc(sh->sh_size);
                if(!so_info->dynsym) fatal("%s: no memory for .dynsym", libpath);
                memcpy(so_info->dynsym, ((void *) elf) + sh->sh_offset, sh->sh_size);
                so_info->nsyms = (sh->sh_size/sizeof(Elf_Sym)) ;
                break;

            case SHT_STRTAB:
                if(so_info->dynstr) break;
                so_info->dynstr = malloc(sh->sh_size);
                if(!so_info->dynstr) fatal("%s: no memory for .dynstr", libpath);
                memcpy(so_info->dynstr, ((void *) elf) + sh->sh_offset, sh->sh_size);
                break;

            case SHT_PROGBITS:
                if(!so_info->dynstr || !so_info->dynsym||!so_info->symtab) break;

                so_info->bias = (off_t) sh->sh_addr - (off_t) sh->sh_offset;
                k = elf->e_shnum;
                break;
        }*/
    }

    munmap(elf, size);
    elf = 0;

    if(!so_info->dynstr || !so_info->dynsym) fatal("dynamic sections not found in %s", libpath);

#undef fatal

    log_dbg("%s: ok, dynsym = %p, dynstr = %p", libpath, so_info->dynsym, so_info->dynstr);

    return so_info;

    err_exit:
    if(fd >= 0) close(fd);
    if(elf != MAP_FAILED) munmap(elf, size);
    fake_dlclose(so_info);
    return 0;
}
#pragma clang diagnostic pop

void *fake_dlsym(void *handle, const char *name)
{
    int k;
    struct so_info *so_info = (struct so_info *) handle;
    Elf_Sym *sym = (Elf_Sym *) so_info->dynsym;
    char *strings = (char *) so_info->dynstr;

    for(k = 0; k < so_info->nsyms; k++, sym++)
        if(strcmp(strings + sym->st_name, name) == 0) {
            /*  NB: sym->st_value is an offset into the section for relocatables,
            but a VMA for shared libs or exe files, so we have to subtract the bias */
            void *ret = so_info->load_addr + sym->st_value - so_info->bias;
            log_info("%s found at %p", name, ret);
            return ret;
        }

    strings = so_info->strtab;
    sym = (Elf_Sym *) so_info->symtab;
    for(int m=0;m<so_info->nsymtabs;m++,sym++)
        if(strcmp(strings + sym->st_name, name) == 0) {
            /*  NB: sym->st_value is an offset into the section for relocatables,
            but a VMA for shared libs or exe files, so we have to subtract the bias */
            void *ret = so_info->load_addr + sym->st_value - so_info->bias;
            log_info("%s found at %p", name, ret);
            return ret;
        }
    return 0;
}

