#ifndef MEMFLOW_H
#define MEMFLOW_H

#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

/**
 * Identifies the byte order of a architecture
 *
 * This enum is used when reading/writing to/from the memory of a target system.
 * The memory will be automatically converted to the endianess memflow is currently running on.
 *
 * See the [wikipedia article](https://en.wikipedia.org/wiki/Endianness) for more information on the subject.
 */
enum Endianess
#ifdef __cplusplus
  : uint8_t
#endif // __cplusplus
 {
    /**
     * Little Endianess
     */
    LittleEndian,
    /**
     * Big Endianess
     */
    BigEndian,
};
#ifndef __cplusplus
typedef uint8_t Endianess;
#endif // __cplusplus

typedef struct ArchitectureObj ArchitectureObj;

typedef struct CloneablePhysicalMemoryObj CloneablePhysicalMemoryObj;

/**
 * Holds an inventory of available connectors.
 */
typedef struct ConnectorInventory ConnectorInventory;

typedef struct OsProcessInfoObj OsProcessInfoObj;

typedef struct OsProcessModuleInfoObj OsProcessModuleInfoObj;

typedef struct PhysicalMemoryObj PhysicalMemoryObj;

typedef struct PhysicalReadData PhysicalReadData;

typedef struct PhysicalWriteData PhysicalWriteData;

typedef struct VirtualMemoryObj VirtualMemoryObj;

typedef struct VirtualReadData VirtualReadData;

typedef struct VirtualWriteData VirtualWriteData;

/**
 * This type represents a address on the target system.
 * It internally holds a `u64` value but can also be used
 * when working in 32-bit environments.
 *
 * This type will not handle overflow for 32-bit or 64-bit addresses / lengths.
 */
typedef uint64_t Address;
/**
 * A address with the value of zero.
 *
 * # Examples
 *
 * ```
 * use memflow::types::Address;
 *
 * println!("address: {}", Address::NULL);
 * ```
 */
#define Address_NULL 0

/**
 * Describes the type of a page using a bitflag.
 */
typedef uint8_t PageType;
/**
 * The page explicitly has no flags.
 */
#define PageType_NONE (uint8_t)0
/**
 * The page type is not known.
 */
#define PageType_UNKNOWN (uint8_t)1
/**
 * The page contains page table entries.
 */
#define PageType_PAGE_TABLE (uint8_t)2
/**
 * The page is a writeable page.
 */
#define PageType_WRITEABLE (uint8_t)4
/**
 * The page is read only.
 */
#define PageType_READ_ONLY (uint8_t)8
/**
 * The page is not executable.
 */
#define PageType_NOEXEC (uint8_t)16

/**
 * This type represents a wrapper over a [address](address/index.html)
 * with additional information about the containing page in the physical memory domain.
 *
 * This type will mostly be used by the [virtual to physical address translation](todo.html).
 * When a physical address is translated from a virtual address the additional information
 * about the allocated page the virtual address points to can be obtained from this structure.
 *
 * Most architectures have support multiple page sizes (see [huge pages](todo.html))
 * which will be represented by the containing `page` of the `PhysicalAddress` struct.
 */
typedef struct PhysicalAddress {
    Address address;
    PageType page_type;
    uint8_t page_size_log2;
} PhysicalAddress;

typedef struct PhysicalMemoryMetadata {
    uintptr_t size;
    bool readonly;
} PhysicalMemoryMetadata;

/**
 * Type alias for a PID.
 */
typedef uint32_t PID;

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

extern const struct ArchitectureObj *X86_32;

extern const struct ArchitectureObj *X86_32_PAE;

extern const struct ArchitectureObj *X86_64;

void log_init(int32_t level_num);

/**
 * Helper to convert `Address` to a `PhysicalAddress`
 *
 * This will create a `PhysicalAddress` with `UNKNOWN` PageType.
 */
struct PhysicalAddress addr_to_paddr(Address address);

/**
 * Create a new connector inventory
 *
 * This function will try to find connectors using PATH environment variable
 *
 * Note that all functions go through each directories, and look for a `memflow` directory,
 * and search for libraries in those.
 *
 * # Safety
 *
 * ConnectorInventory is inherently unsafe, because it loads shared libraries which can not be
 * guaranteed to be safe.
 */
struct ConnectorInventory *inventory_scan(void);

/**
 * Create a new inventory with custom path string
 *
 * # Safety
 *
 * `path` must be a valid null terminated string
 */
struct ConnectorInventory *inventory_scan_path(const char *path);

/**
 * Add a directory to an existing inventory
 *
 * # Safety
 *
 * `dir` must be a valid null terminated string
 */
int32_t inventory_add_dir(struct ConnectorInventory *inv, const char *dir);

/**
 * Create a connector with given arguments
 *
 * This creates an instance of a `CloneablePhysicalMemory`. To use it for physical memory
 * operations, please call `downcast_cloneable` to create a instance of `PhysicalMemory`.
 *
 * Regardless, this instance needs to be freed using `connector_free`.
 *
 * # Arguments
 *
 * * `name` - name of the connector to use
 * * `args` - arguments to be passed to the connector upon its creation
 *
 * # Safety
 *
 * Both `name`, and `args` must be valid null terminated strings.
 *
 * Any error strings returned by the connector must not be outputed after the connector gets
 * freed, because that operation could cause the underlying shared library to get unloaded.
 */
struct CloneablePhysicalMemoryObj *inventory_create_connector(struct ConnectorInventory *inv,
                                                              const char *name,
                                                              const char *args);

/**
 * Clone a connector
 *
 * This method is useful when needing to perform multithreaded operations, as a connector is not
 * guaranteed to be thread safe. Every single cloned instance also needs to be freed using
 * `connector_free`.
 *
 * # Safety
 *
 * `conn` has to point to a a valid `CloneablePhysicalMemory` created by one of the provided
 * functions.
 */
struct CloneablePhysicalMemoryObj *connector_clone(const struct CloneablePhysicalMemoryObj *conn);

/**
 * Free a connector instance
 *
 * # Safety
 *
 * `conn` has to point to a valid `CloneablePhysicalMemoryObj` created by one of the provided
 * functions.
 *
 * There has to be no instance of `PhysicalMemory` created from the input `conn`, because they
 * will become invalid.
 */
void connector_free(struct CloneablePhysicalMemoryObj *conn);

/**
 * Free a connector inventory
 *
 * # Safety
 *
 * `inv` must point to a valid `ConnectorInventory` that was created using one of the provided
 * functions.
 */
void inventory_free(struct ConnectorInventory *inv);

/**
 * Downcast a cloneable physical memory into a physical memory object.
 *
 * This function will take a `cloneable` and turn it into a `PhysicalMemoryObj`, which then can be
 * used by physical memory functions.
 *
 * Please note that this does not free `cloneable`, and the reference is still valid for further
 * operations.
 */
struct PhysicalMemoryObj *downcast_cloneable(struct CloneablePhysicalMemoryObj *cloneable);

/**
 * Free a `PhysicalMemoryObj`
 *
 * This will free a reference to a `PhysicalMemoryObj`. If the physical memory object was created
 * using `downcast_cloneable`, this will NOT free the cloneable reference.
 *
 * # Safety
 *
 * `mem` must point to a valid `PhysicalMemoryObj` that was created using one of the provided
 * functions.
 */
void phys_free(struct PhysicalMemoryObj *mem);

/**
 * Read a list of values
 *
 * This will perform `len` physical memory reads on the provided `data`. Using lists is preferable
 * for performance, because then the underlying connectors can batch those operations.
 *
 * # Safety
 *
 * `data` must be a valid array of `PhysicalReadData` with the length of at least `len`
 */
int32_t phys_read_raw_list(struct PhysicalMemoryObj *mem,
                           struct PhysicalReadData *data,
                           uintptr_t len);

/**
 * Write a list of values
 *
 * This will perform `len` physical memory writes on the provided `data`. Using lists is preferable
 * for performance, because then the underlying connectors can batch those operations.
 *
 * # Safety
 *
 * `data` must be a valid array of `PhysicalWriteData` with the length of at least `len`
 */
int32_t phys_write_raw_list(struct PhysicalMemoryObj *mem,
                            const struct PhysicalWriteData *data,
                            uintptr_t len);

/**
 * Retrieve metadata about the physical memory object
 */
struct PhysicalMemoryMetadata phys_metadata(const struct PhysicalMemoryObj *mem);

/**
 * Read a single value into `out` from a provided `PhysicalAddress`
 *
 * # Safety
 *
 * `out` must be a valid pointer to a data buffer of at least `len` size.
 */
int32_t phys_read_raw_into(struct PhysicalMemoryObj *mem,
                           struct PhysicalAddress addr,
                           uint8_t *out,
                           uintptr_t len);

/**
 * Read a single 32-bit value from a provided `PhysicalAddress`
 */
uint32_t phys_read_u32(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr);

/**
 * Read a single 64-bit value from a provided `PhysicalAddress`
 */
uint64_t phys_read_u64(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr);

/**
 * Write a single value from `input` into a provided `PhysicalAddress`
 *
 * # Safety
 *
 * `input` must be a valid pointer to a data buffer of at least `len` size.
 */
int32_t phys_write_raw(struct PhysicalMemoryObj *mem,
                       struct PhysicalAddress addr,
                       const uint8_t *input,
                       uintptr_t len);

/**
 * Write a single 32-bit value into a provided `PhysicalAddress`
 */
int32_t phys_write_u32(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr, uint32_t val);

/**
 * Write a single 64-bit value into a provided `PhysicalAddress`
 */
int32_t phys_write_u64(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr, uint64_t val);

/**
 * Free a virtual memory object reference
 *
 * This function frees the reference to a virtual memory object.
 *
 * # Safety
 *
 * `mem` must be a valid reference to a virtual memory object.
 */
void virt_free(struct VirtualMemoryObj *mem);

/**
 * Read a list of values
 *
 * This will perform `len` virtual memory reads on the provided `data`. Using lists is preferable
 * for performance, because then the underlying connectors can batch those operations, and virtual
 * translation function can cut down on read operations.
 *
 * # Safety
 *
 * `data` must be a valid array of `VirtualReadData` with the length of at least `len`
 */
int32_t virt_read_raw_list(struct VirtualMemoryObj *mem,
                           struct VirtualReadData *data,
                           uintptr_t len);

/**
 * Write a list of values
 *
 * This will perform `len` virtual memory writes on the provided `data`. Using lists is preferable
 * for performance, because then the underlying connectors can batch those operations, and virtual
 * translation function can cut down on read operations.
 *
 * # Safety
 *
 * `data` must be a valid array of `VirtualWriteData` with the length of at least `len`
 */
int32_t virt_write_raw_list(struct VirtualMemoryObj *mem,
                            const struct VirtualWriteData *data,
                            uintptr_t len);

/**
 * Read a single value into `out` from a provided `Address`
 *
 * # Safety
 *
 * `out` must be a valid pointer to a data buffer of at least `len` size.
 */
int32_t virt_read_raw_into(struct VirtualMemoryObj *mem, Address addr, uint8_t *out, uintptr_t len);

/**
 * Read a single 32-bit value from a provided `Address`
 */
uint32_t virt_read_u32(struct VirtualMemoryObj *mem, Address addr);

/**
 * Read a single 64-bit value from a provided `Address`
 */
uint64_t virt_read_u64(struct VirtualMemoryObj *mem, Address addr);

/**
 * Write a single value from `input` into a provided `Address`
 *
 * # Safety
 *
 * `input` must be a valid pointer to a data buffer of at least `len` size.
 */
int32_t virt_write_raw(struct VirtualMemoryObj *mem,
                       Address addr,
                       const uint8_t *input,
                       uintptr_t len);

/**
 * Write a single 32-bit value into a provided `Address`
 */
int32_t virt_write_u32(struct VirtualMemoryObj *mem, Address addr, uint32_t val);

/**
 * Write a single 64-bit value into a provided `Address`
 */
int32_t virt_write_u64(struct VirtualMemoryObj *mem, Address addr, uint64_t val);

uint8_t arch_bits(const struct ArchitectureObj *arch);

Endianess arch_endianess(const struct ArchitectureObj *arch);

uintptr_t arch_page_size(const struct ArchitectureObj *arch);

uintptr_t arch_size_addr(const struct ArchitectureObj *arch);

uint8_t arch_address_space_bits(const struct ArchitectureObj *arch);

/**
 * Free an architecture reference
 *
 * # Safety
 *
 * `arch` must be a valid heap allocated reference created by one of the API's functions.
 */
void arch_free(struct ArchitectureObj *arch);

bool is_x86_arch(const struct ArchitectureObj *arch);

Address os_process_info_address(const struct OsProcessInfoObj *obj);

PID os_process_info_pid(const struct OsProcessInfoObj *obj);

/**
 * Retreive name of the process
 *
 * This will copy at most `max_len` characters (including the null terminator) into `out` of the
 * name.
 *
 * # Safety
 *
 * `out` must be a buffer with at least `max_len` size
 */
uintptr_t os_process_info_name(const struct OsProcessInfoObj *obj, char *out, uintptr_t max_len);

const struct ArchitectureObj *os_process_info_sys_arch(const struct OsProcessInfoObj *obj);

const struct ArchitectureObj *os_process_info_proc_arch(const struct OsProcessInfoObj *obj);

/**
 * Free a OsProcessInfoObj reference
 *
 * # Safety
 *
 * `obj` must point to a valid `OsProcessInfoObj`, and was created using one of the API's
 * functions.
 */
void os_process_info_free(struct OsProcessInfoObj *obj);

Address os_process_module_address(const struct OsProcessModuleInfoObj *obj);

Address os_process_module_parent_process(const struct OsProcessModuleInfoObj *obj);

Address os_process_module_base(const struct OsProcessModuleInfoObj *obj);

uintptr_t os_process_module_size(const struct OsProcessModuleInfoObj *obj);

/**
 * Retreive name of the module
 *
 * This will copy at most `max_len` characters (including the null terminator) into `out` of the
 * name.
 *
 * # Safety
 *
 * `out` must be a buffer with at least `max_len` size
 */
uintptr_t os_process_module_name(const struct OsProcessModuleInfoObj *obj,
                                 char *out,
                                 uintptr_t max_len);

/**
 * Free a OsProcessModuleInfoObj reference
 *
 * # Safety
 *
 * `obj` must point to a valid `OsProcessModuleInfoObj`, and was created using one of the API's
 * functions.
 */
void os_process_module_free(struct OsProcessModuleInfoObj *obj);

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus

#endif /* MEMFLOW_H */
