#include "peconv/file_util.h"
#include "peconv/buffer_util.h"

#include <fstream>
#ifdef _DEBUG
    #include <iostream>
#endif
typedef LPVOID(WINAPI* PFN_MapViewOfFile)(
    HANDLE hFileMappingObject,
    DWORD dwDesiredAccess,
    DWORD dwFileOffsetHigh,
    DWORD dwFileOffsetLow,
    SIZE_T dwNumberOfBytesToMap
    );
#include <windows.h>
#include <iostream>

typedef LPVOID(WINAPI* PFN_MapViewOfFile)(
    HANDLE,
    DWORD,
    DWORD,
    DWORD,
    SIZE_T
    );
typedef BOOL(WINAPI* PFN_UnmapViewOfFile)(LPCVOID);
typedef DWORD(WINAPI* PFN_GetFileSize)(HANDLE, LPDWORD);

namespace peconv {
    ALIGNED_BUF load_file(IN const char* filename, OUT size_t& read_size)
    {
        read_size = 0;

        HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
        if (hFile == INVALID_HANDLE_VALUE) {
#ifdef _DEBUG
            std::cerr << "[-] Could not open file: " << filename << std::endl;
#endif
            return nullptr;
        }

        HANDLE hMapping = CreateFileMappingA(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
        if (!hMapping) {
#ifdef _DEBUG
            std::cerr << "[-] Could not create file mapping!" << std::endl;
#endif
            CloseHandle(hFile);
            return nullptr;
        }

        HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
        if (!hKernel32) {
#ifdef _DEBUG
            std::cerr << "[-] Could not get handle to kernel32.dll" << std::endl;
#endif
            CloseHandle(hMapping);
            CloseHandle(hFile);
            return nullptr;
        }

        PFN_MapViewOfFile pMapViewOfFile = (PFN_MapViewOfFile)GetProcAddress(hKernel32, "MapViewOfFile");
        PFN_UnmapViewOfFile pUnmapViewOfFile = (PFN_UnmapViewOfFile)GetProcAddress(hKernel32, "UnmapViewOfFile");
        PFN_GetFileSize pGetFileSize = (PFN_GetFileSize)GetProcAddress(hKernel32, "GetFileSize");

        if (!pMapViewOfFile || !pUnmapViewOfFile || !pGetFileSize) {
#ifdef _DEBUG
            std::cerr << "[-] Could not get required API functions from kernel32.dll" << std::endl;
#endif
            CloseHandle(hMapping);
            CloseHandle(hFile);
            return nullptr;
        }

        BYTE* dllRawData = (BYTE*)pMapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
        if (!dllRawData) {
#ifdef _DEBUG
            std::cerr << "[-] Failed to map view of file" << std::endl;
#endif
            CloseHandle(hMapping);
            CloseHandle(hFile);
            return nullptr;
        }

        size_t fileSize = pGetFileSize(hFile, nullptr);
        size_t toReadSize = (read_size != 0 && read_size <= fileSize) ? read_size : fileSize;

        if (IsBadReadPtr(dllRawData, toReadSize)) {
#ifdef _DEBUG
            std::cerr << "[-] Mapped memory is invalid: " << filename << std::endl;
#endif
            pUnmapViewOfFile(dllRawData);
            CloseHandle(hMapping);
            CloseHandle(hFile);
        }
        ALIGNED_BUF localCopy = peconv::alloc_aligned(toReadSize, PAGE_READWRITE);
        if (localCopy) {
            memcpy(localCopy, dllRawData, toReadSize);
            read_size = toReadSize;
        }
        else {
#ifdef _DEBUG
            std::cerr << "[-] Failed to allocate memory in current process" << std::endl;
#endif
            read_size = 0;
        }

        pUnmapViewOfFile(dllRawData);
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return localCopy;
    }
}

//load file content using ReadFile
peconv::ALIGNED_BUF peconv::read_from_file(IN const char *in_path, IN OUT size_t &read_size)
{
    HANDLE file = CreateFileA(in_path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (file == INVALID_HANDLE_VALUE) {
#ifdef _DEBUG
        std::cerr << "Cannot open the file for reading!" << std::endl;
#endif
        return nullptr;
    }
    size_t r_size = static_cast<size_t>(GetFileSize(file, 0));
    if (read_size != 0 && read_size <= r_size) {
        r_size = read_size;
    }
    PBYTE buffer = peconv::alloc_pe_buffer(r_size, PAGE_READWRITE);
    if (buffer == nullptr) {
#ifdef _DEBUG
        std::cerr << "Allocation has failed!" << std::endl;
#endif
        return nullptr;
    }
    DWORD out_size = 0;
    if (!ReadFile(file, buffer, r_size, &out_size, nullptr)) {
#ifdef _DEBUG
        std::cerr << "Reading failed!" << std::endl;
#endif
        peconv::free_pe_buffer(buffer, r_size);
        buffer = nullptr;
        read_size = 0;
    } else {
        read_size = r_size;
    }
    CloseHandle(file);
    return buffer;
}

//save the given buffer into a file
bool peconv::dump_to_file(IN const char *out_path, IN PBYTE dump_data, IN size_t dump_size)
{
    if (!out_path || !dump_data || !dump_size) return false;

    HANDLE file = CreateFileA(out_path, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if (file == INVALID_HANDLE_VALUE) {
#ifdef _DEBUG
        std::cerr << "Cannot open the file for writing!" << std::endl;
#endif
        return false;
    }
    DWORD written_size = 0;
    bool is_dumped = false;
    if (WriteFile(file, dump_data, (DWORD) dump_size, &written_size, nullptr)) {
        is_dumped = true;
    }
#ifdef _DEBUG
    else {
        std::cerr << "Failed to write to the file : " << out_path << std::endl;
    }
#endif
    CloseHandle(file);
    return is_dumped;
}

//free the buffer allocated by load_file/read_from_file
void peconv::free_file(IN peconv::ALIGNED_BUF buffer)
{
    peconv::free_aligned(buffer);
}

std::string peconv::get_file_name(IN const std::string str)
{
    size_t found = str.find_last_of("/\\");
    if (found == std::string::npos) {
        return str;
    }
    return str.substr(found + 1);
}

std::string peconv::get_directory_name(IN const std::string str)
{
    size_t found = str.find_last_of("/\\");
    if (found == std::string::npos) {
        return "";
    }
    return str.substr(0, found);
}
