#include "api.h"
#include "pal.h"
#include "pal_debug.h"

#define UNIT (pal_control.alloc_align)

static const char* volatile message = NULL;

void handler(PAL_PTR event, PAL_NUM arg, PAL_CONTEXT* context) {
    if (message)
        pal_printf("%s", message);

    while (*(unsigned char*)context->rip != 0x90) {
        context->rip++;
    }

    DkExceptionReturn(event);
}

int main(int argc, char** argv, char** envp) {
    char gipc_uri[20];
    int ret;

    DkSetExceptionHandler(handler, PAL_EVENT_MEMFAULT);

    if (argc > 1 && !memcmp(argv[1], "Child", 6)) {
        /* private memory */

        ret = DkStreamRead(pal_control.parent_process, 0, 20, gipc_uri, NULL, 0);

        if (ret > 0) {
            PAL_HANDLE ipc1 = DkStreamOpen(gipc_uri, 0, 0, 0, 0);

            if (ipc1) {
                pal_printf("Join Physical Memory Store OK\n");

                PAL_PTR mem_addr = 0;
                PAL_NUM mem_size = UNIT;
                PAL_FLG mem_prot = PAL_PROT_READ | PAL_PROT_WRITE;

                ret = DkPhysicalMemoryMap(ipc1, 1, &mem_addr, &mem_size, &mem_prot);

                if (ret > 0) {
                    pal_printf("[Test 1] Physical Memory Map   : %s\n", (char*)mem_addr);
                    memcpy((void*)mem_addr, "Hello World, Bob", 20);
                    pal_printf("[Test 1] Receiver After  Map   : %s\n", (char*)mem_addr);
                }

                ret = 0;
                DkStreamWrite(pal_control.parent_process, 0, sizeof(int), &ret, NULL);
                DkObjectClose(ipc1);
            }
        }

        /* private untouched memory */

        ret = DkStreamRead(pal_control.parent_process, 0, 20, gipc_uri, NULL, 0);

        if (ret > 0) {
            PAL_HANDLE ipc2 = DkStreamOpen(gipc_uri, 0, 0, 0, 0);

            if (ipc2) {
                pal_printf("Join Physical Memory Store OK\n");

                PAL_PTR mem_addr = 0;
                PAL_NUM mem_size = UNIT;
                PAL_FLG mem_prot = PAL_PROT_READ | PAL_PROT_WRITE;

                ret = DkPhysicalMemoryMap(ipc2, 1, &mem_addr, &mem_size, &mem_prot);

                if (ret > 0) {
                    pal_printf("[Test 2] Physical Memory Map   : %s\n", (char*)mem_addr);
                    memcpy((void*)mem_addr, "Hello World, Bob", 20);
                    pal_printf("[Test 2] Receiver After  Map   : %s\n", (char*)mem_addr);
                }

                ret = 0;
                DkStreamWrite(pal_control.parent_process, 0, sizeof(int), &ret, NULL);
                DkStreamDelete(ipc2, 0);
                DkObjectClose(ipc2);
            }
        }

        /* file-backed memory */

        ret = DkStreamRead(pal_control.parent_process, 0, 20, gipc_uri, NULL, 0);

        if (ret > 0) {
            PAL_HANDLE ipc3 = DkStreamOpen(gipc_uri, 0, 0, 0, 0);

            if (ipc3) {
                pal_printf("Join Physical Memory Store OK\n");

                PAL_PTR mem_addr = 0;
                PAL_NUM mem_size = UNIT;
                PAL_FLG mem_prot = PAL_PROT_READ | PAL_PROT_WRITE;

                ret = DkPhysicalMemoryMap(ipc3, 1, &mem_addr, &mem_size, &mem_prot);

                if (ret > 0) {
                    pal_printf("[Test 3] Physical Memory Map   : %s\n", (char*)mem_addr);
                    memcpy((void*)mem_addr, "Hello World, Bob", 20);
                    pal_printf("[Test 3] Receiver After  Map   : %s\n", (char*)mem_addr);
                }

                ret = 0;
                DkStreamWrite(pal_control.parent_process, 0, sizeof(int), &ret, NULL);
                DkObjectClose(ipc3);
            }
        }

        /* file-backed memory beyond file size */

        ret = DkStreamRead(pal_control.parent_process, 0, 20, gipc_uri, NULL, 0);

        if (ret > 0) {
            PAL_HANDLE ipc4 = DkStreamOpen(gipc_uri, 0, 0, 0, 0);

            if (ipc4) {
                pal_printf("Join Physical Memory Store OK\n");

                PAL_PTR mem_addr = 0;
                PAL_NUM mem_size = UNIT;
                PAL_FLG mem_prot = PAL_PROT_READ | PAL_PROT_WRITE;

                ret = DkPhysicalMemoryMap(ipc4, 1, &mem_addr, &mem_size, &mem_prot);

                if (ret > 0) {
                    message = "[Test 4] Physical Memory Map   : Memory Fault\n";
                    *(volatile int*)mem_addr;
                    __asm__ volatile("nop");
                    message = NULL;
                }

                ret = 0;
                DkStreamWrite(pal_control.parent_process, 0, sizeof(int), &ret, NULL);
                DkObjectClose(ipc4);
            }
        }

        /* large memory */

        ret = DkStreamRead(pal_control.parent_process, 0, 20, gipc_uri, NULL, 0);

        if (ret > 0) {
            PAL_HANDLE ipc5 = DkStreamOpen(gipc_uri, 0, 0, 0, 0);

            if (ipc5) {
                pal_printf("Join Physical Memory Store OK\n");

                PAL_PTR mem_addr = 0;
                PAL_NUM mem_size = UNIT * 1024 * 64;
                PAL_FLG mem_prot = PAL_PROT_READ | PAL_PROT_WRITE;

                ret = DkPhysicalMemoryMap(ipc5, 1, &mem_addr, &mem_size, &mem_prot);

                if (ret > 0) {
                    pal_printf("[Test 5] Physical Memory Map   : %s\n",
                               (char*)mem_addr + UNIT * 1024);
                }

                ret = 0;
                DkStreamWrite(pal_control.parent_process, 0, sizeof(int), &ret, NULL);
                DkObjectClose(ipc5);
            }
        }
    } else {
        PAL_STR args[3] = {"Ipc", "Child", 0};
        PAL_HANDLE proc = DkProcessCreate("file:Ipc", args);

        if (!proc)
            return 0;

        /* private memory */

        PAL_NUM key1;
        PAL_HANDLE ipc1 = DkCreatePhysicalMemoryChannel(&key1);

        if (ipc1) {
            snprintf(gipc_uri, 20, "gipc:%ld", key1);
            pal_printf("Create Physical Memory Store OK\n");

            void* mem1 = (void*)DkVirtualMemoryAlloc(NULL, UNIT, 0, PAL_PROT_READ | PAL_PROT_WRITE);

            if (mem1) {
                memcpy(mem1, "Hello World", 20);

                PAL_PTR mem_addr = mem1;
                PAL_NUM mem_size = UNIT;

                if (DkPhysicalMemoryCommit(ipc1, 1, &mem_addr, &mem_size)) {
                    pal_printf("[Test 1] Physical Memory Commit OK\n");
                    memcpy(mem1, "Hello World, Alice", 20);
                    pal_printf("[Test 1] Sender   After  Commit: %s\n", (char*)mem1);
                    DkStreamWrite(proc, 0, 20, gipc_uri, NULL);
                    memcpy(mem1, "Alice, Hello World", 20);
                    pal_printf("[Test 1] Sender   Before Map   : %s\n", (char*)mem1);
                    DkStreamRead(proc, 0, sizeof(int), &ret, NULL, 0);
                    pal_printf("[Test 1] Sender   After  Map   : %s\n", (char*)mem1);
                }
            }

            DkObjectClose(ipc1);
        }

        /* private untouched memory */

        PAL_NUM key2;
        PAL_HANDLE ipc2 = DkCreatePhysicalMemoryChannel(&key2);

        if (ipc2) {
            snprintf(gipc_uri, 20, "gipc:%ld", key2);
            pal_printf("Create Physical Memory Store OK\n");

            void* mem2 = (void*)DkVirtualMemoryAlloc(NULL, UNIT, 0, PAL_PROT_READ | PAL_PROT_WRITE);

            if (mem2) {
                PAL_PTR mem_addr = mem2;
                PAL_NUM mem_size = UNIT;

                if (DkPhysicalMemoryCommit(ipc2, 1, &mem_addr, &mem_size)) {
                    pal_printf("[Test 2] Physical Memory Commit OK\n");
                    memcpy(mem2, "Hello World, Alice", 20);
                    pal_printf("[Test 2] Sender   After  Commit: %s\n", (char*)mem2);
                    DkStreamWrite(proc, 0, 20, gipc_uri, NULL);
                    memcpy(mem2, "Alice, Hello World", 20);
                    pal_printf("[Test 2] Sender   Before Map   : %s\n", (char*)mem2);
                    DkStreamRead(proc, 0, sizeof(int), &ret, NULL, 0);
                    pal_printf("[Test 2] Sender   After  Map   : %s\n", (char*)mem2);
                }
            }

            DkObjectClose(ipc2);
        }

        /* file-backed memory */

        PAL_NUM key3;
        PAL_HANDLE ipc3 = DkCreatePhysicalMemoryChannel(&key3);

        if (ipc3) {
            snprintf(gipc_uri, 20, "gipc:%ld", key3);
            pal_printf("Create Physical Memory Store OK\n");

            void* mem3       = NULL;
            PAL_HANDLE file1 = DkStreamOpen("file:ipc_mapping.tmp", PAL_ACCESS_RDWR, 0, 0, 0);

            if (file1) {
                mem3 = (void*)DkStreamMap(file1, NULL, PAL_PROT_READ | PAL_PROT_WRITE, 0, UNIT);
                DkObjectClose(file1);
            }

            if (mem3) {
                PAL_PTR mem_addr = mem3;
                PAL_NUM mem_size = UNIT;

                if (DkPhysicalMemoryCommit(ipc3, 1, &mem_addr, &mem_size)) {
                    pal_printf("[Test 3] Physical Memory Commit OK\n");
                    DkStreamWrite(proc, 0, 20, gipc_uri, NULL);
                    pal_printf("[Test 3] Sender   After  Commit: %s\n", (char*)mem3);
                    DkStreamRead(proc, 0, sizeof(int), &ret, NULL, 0);
                    pal_printf("[Test 3] Sender   After  Map   : %s\n", (char*)mem3);
                }
            }

            DkObjectClose(ipc3);
        }

        /* file-backed memory beyond file size */

        PAL_NUM key4;
        PAL_HANDLE ipc4 = DkCreatePhysicalMemoryChannel(&key4);

        if (ipc4) {
            snprintf(gipc_uri, 20, "gipc:%ld", key4);
            pal_printf("Create Physical Memory Store OK\n");

            void* mem4       = NULL;
            PAL_HANDLE file2 = DkStreamOpen("file:ipc_mapping.tmp", PAL_ACCESS_RDWR, 0, 0, 0);

            if (file2) {
                mem4 = (void*)DkStreamMap(file2, NULL, PAL_PROT_READ | PAL_PROT_WRITE, UNIT, UNIT);
                DkObjectClose(file2);
            }

            if (mem4) {
                PAL_PTR mem_addr = mem4;
                PAL_NUM mem_size = UNIT;

                if (DkPhysicalMemoryCommit(ipc4, 1, &mem_addr, &mem_size)) {
                    pal_printf("[Test 4] Physical Memory Commit OK\n");
                    DkStreamWrite(proc, 0, 20, gipc_uri, NULL);
                    DkStreamRead(proc, 0, sizeof(int), &ret, NULL, 0);
                }
            }

            DkObjectClose(ipc4);
        }

        /* large memory */

        PAL_NUM key5;
        PAL_HANDLE ipc5 = DkCreatePhysicalMemoryChannel(&key5);

        if (ipc5) {
            snprintf(gipc_uri, 20, "gipc:%ld", key5);
            pal_printf("Create Physical Memory Store OK\n");

            void* mem5 = (void*)DkVirtualMemoryAlloc(NULL, UNIT * 1024 * 64, 0,
                                                     PAL_PROT_READ | PAL_PROT_WRITE);

            if (mem5) {
                pal_printf("Touch Memory at %p\n", mem5 + UNIT * 1024);

                memcpy(mem5 + UNIT * 1024, "Hello World", 20);

                PAL_PTR mem_addr = mem5;
                PAL_NUM mem_size = UNIT * 1024 * 64;

                DkStreamWrite(proc, 0, 20, gipc_uri, NULL);

                if (DkPhysicalMemoryCommit(ipc5, 1, &mem_addr, &mem_size)) {
                    pal_printf("[Test 5] Physical Memory Commit OK\n");
                    DkStreamRead(proc, 0, sizeof(int), &ret, NULL, 0);
                }
            }

            DkObjectClose(ipc1);
        }
    }

    return 0;
}
