#define MAX_BLOCKS 64
#define HEAP_START 0x3000
char CURSOR[] = "ZMS> ";
typedef unsigned short uint16_t;
typedef signed short int16_t;
typedef unsigned char  uint8_t;
/*
typedef struct {
    uint16_t lo;
    uint16_t hi;
} uint32_t;
*/
unsigned short random = 1;

struct cmd {
    char *canonical_name;
    char *executable;
};

struct cmds {
    struct cmd *commands;
};

struct mem_block {
    uint16_t start; // Where the block starts, (e.g. 0x03000)
    uint16_t size; // Size of block (in bytes)
    uint8_t free; // 1 = free, 0 = used
    int16_t next_index;   // -1 = end
    uint8_t used; // 1 = being used, 0 = not
};

unsigned get_memory_kb(void);
asm(
"_get_memory_kb:\n"
"int 0x12\n"
"ret\n"
);

struct cmds command_registry;
uint16_t TOTAL_HEAP_SIZE; // Initialize var
uint8_t *memory_pool = (uint8_t *)0x3000; // Create a pointer "memory_pool" that points to to where we're storing memory
int free_list_head; // Initialize var

struct mem_block heap[MAX_BLOCKS]; // Create a heap with 64 entries

void init_heap(void){

TOTAL_HEAP_SIZE = get_memory_kb() * 1024 - HEAP_START; // Caclulate total heap size as total memory in Bytes minus the heap start (so metadata / reserved bios ranges)

    // Create basic / root empty block to start
    heap[0].start = 0; // Starts at beginning of memory space
    heap[0].size = TOTAL_HEAP_SIZE;  // entire size of heap
    heap[0].free = 1; // is free memory
    heap[0].next_index = -1; // Is last in heap
    heap[0].used = 1;

free_list_head = 0; // Set free_list_head to the first / initial heap block

for (int i = 1; i < MAX_BLOCKS; i++) { // For the rest of the entires
    heap[i].free = 1; // Set them to free
    heap[i].next_index = -1; // Mark them as the end of the heap
}

}

int find_block(uint16_t size) {
    int idx = free_list_head; // First entry in the heap

    while (idx != -1) { // While it's not the last one
        if (heap[idx].free && heap[idx].size >= size) { 
            return idx; // If the size of the block is greater than or equal to the required size and is free return it
        }
        idx = heap[idx].next_index; // Set to next block to check that one
    }
    return -1; // If no valid block can be found return -1
}
int find_free_struct_block(){
for(int i = 0;i<MAX_BLOCKS;i++){
            if(!heap[i].used){
                return i;
            };
    };
    return -1; // No free struct block found
}
int alloc(uint16_t size) {
    int idx = find_block(size); // Call function to find first block large enough
    if (idx == -1) return -1; // If no valid block was found forward the returned -1

    struct mem_block *b = &heap[idx]; // Get a pointer to the block (well: dereference heap/get the value of it at the index returned by find_block then create a pointer from that)

    if (b->size > size) { // If found block is larger than required
        int new_idx = find_free_struct_block(); // Get first unused (not being used to describe a block) entry in the struct block

        heap[new_idx].start = b->start + size; // Size is start of previous entry + new entry that's being reserved's size;
        heap[new_idx].size = b->size - size; // Size is size of previous entry minus what is being allocated by the call
        heap[new_idx].free = 1; // mark it as free
        heap[new_idx].next_index = b->next_index; // Have it's next entry be what used the be the next entry of the previous entry
        heap[new_idx].used = 1; // Mark it as now being used to describe a block

        b->size = size; // Set the size of the block being reserved to the requested size
        b->next_index = new_idx; // Set the next index of the block being reserved to the index of the remainder block
    }

    b->free = 0; // Mark the block as now having been allocated
    print("Allocated block starting at ");
    print_hex_word(heap[idx].start);
    print(" and continuing for ");
    print_hex_word(size);
    print("\r\n");
    return idx; // handle
}

void free_block(int idx) {
    heap[idx].free = 1; // Mark the block as free

    int next = heap[idx].next_index; // Grab the next index of the block we're freeing

    if (next != -1 && heap[next].free) { // If it's not the end and the next entry is free
        heap[idx].size += heap[next].size; // At the size of the next block to the block being freed
        heap[idx].next_index = heap[next].next_index; // Set the next index of the block being freed to that of the entry it's being merged with

        heap[next].free = 1; // Set the block it's being combined with to free.
        heap[next].used = 0; // Set the block it's combined with to unused since the metadata / entry is combined
    }
}

void* ptr_from_block(int idx) {
    return &memory_pool[heap[idx].start]; // Dereference the memory pool, heap from within it at the index requested and the start of that block. Ultimately getting a point to the memory address from the block index.
}

void putchar(char c){
    asm(
        "mov al,[bp+4]\n"
        "mov ah,0x0e\n"
        "int 0x10\n"
        "int 14h"
    );

}

void print_hex_byte(unsigned char value){
    unsigned char n;

    n = value >> 4;

    if (n < 10)
        n += '0';
    else
        n += 'A' - 10;

    putchar(n);

    n = value & 15;

    if (n < 10)
        n += '0';
    else
        n += 'A' - 10;

    putchar(n);
}

int strcmp(const char *s1, const char *s2) {
    while (*s1 && *s2 && *s1 == *s2) {
        s1++;
        s2++;
    }
    return *s1 - *s2;
}

void print(char *s){
    while (*s){
       // print_hex_byte(*s);
        putchar(*s);
        s++;
    }
}

void reboot(){
asm("mov al, 0xFE\r\n"
"out 0x64, al");
}

void print_hex(char *s){
    while (*s){
        print_hex_byte(*s);
        s++;
    }
}

void clear_screen(){
    asm(
        "mov ah,0x06\n"
        "mov al,0\n"
        "mov bh,0x07\n"
        "mov cx,0\n"
        "mov dx,0x184f\n"
        "int 0x10\n"

        "mov ah,0x02\n"
        "mov bh,0\n"
        "mov dh,0\n"
        "mov dl,0\n"
        "int 0x10"
    );
}

void prompt(){
    print(CURSOR);
}

unsigned short rand(){
    random = random * 109;
    random = random + 1021;
    random = random % 32749;
    return random;
}


char getchar() {
    asm(
        "xor ah,ah\n"
        "int 0x16"
    );
}

void readline(char *buf) {
    char c;
    int i = 0;

    for (;;) {
        c = getchar();

        if (c == '\r') {
            putchar('\r');
            putchar('\n');
            break;
        }
        if(c == 0x08 || c == 0x7f){
        if(i > 0){
        putchar('\b');
        putchar(' ');
        putchar('\b');
        i--;
        }
        }else{
        if(i < 255){
        putchar(c);
        buf[i++] = c;
        }
        }
    }

    buf[i] = 0;
}

void cmd_memory_test(){
int block = alloc(32767);
if (block < 0) {
    print("alloc failed\r\n");
    return;
}
uint8_t test_pattern = 0xaa;
uint8_t *memory_to_test = ptr_from_block(block); 
uint8_t failure = 0;
print_hex_word(memory_to_test);
print("\r\n");
for(int i = 0;i<32767;i++){
memory_to_test[i] = test_pattern;
}
print("Set memory to test pattern - Testing\r\n");
for(int i = 0;i<32767;i++){
if(memory_to_test[i] != test_pattern){
print("Byte ");
print_hex_word(i);
print(" failed validation\r\n");
failure = 1;
print("Expected: ");
print_hex_word(test_pattern);
print(" got: ");
print_hex_word(memory_to_test[i]);
print(" Address: ");
print_hex_word((unsigned int)&memory_to_test[i]);
print("\r\n");
}
if(i % 1024 == 0){
print_dec(i/1024);
print(" | ");
}

}
if(failure == 1){
print("\r\nSome memory failed test.\r\n");
}
free_block(block);
print("Memory test complete\r\n");
}

void print_dec(unsigned int v){
    char buf[10];
    int i = 0;

    if(v == 0){
        putchar('0');
        return;
    }

    while(v > 0){
        buf[i++] = '0' + (v % 10);
        v /= 10;
    }

    while(i--){
        putchar(buf[i]);
    }
}

void print_hex_word(unsigned int value)
{
    print_hex_byte((value >> 8) & 0xFF);
    print_hex_byte(value & 0xFF);
}



void print_memory(){
    print("Memory: ");
    print_dec(get_memory_kb());
    print("KB RAM");
}

char read_to_string(char file){

}
/*
void populate_commands(){
for(int i = 0; i < cmd_count;i++){
command_registry += cmd;
}
}
*/
void terminal() {
    char buf[256];
    struct cmds commands;
    
    for (;;) {
        prompt();

        readline(buf);
        if(strcmp(buf, "ver") == 0){
            print("Indev crap, yay. This'll *eventually* return the hash of the source code or a version or something somehow.");
        }else if(strcmp(buf, "test") == 0){
            print("test returned");
        }else if(strcmp(buf, "clear") == 0){
            clear_screen();
        }else if(strcmp(buf, "memsize") == 0){
            print_memory();
        }else if(strcmp(buf, "fuck") == 0){
            reboot();
        }else if(strcmp(buf, "memtest") == 0){
            cmd_memory_test();
        }else if(strcmp(buf, "random") == 0){
            rand();
            print_dec(random);
        }else{
            print("Command not found. Buf was ");
            print(buf);
        }
        print("\r\n");
    }
}

void main(){
   // clear_screen();
    char *msg = "\r\nWelcome to ZMS!\r\n";
    print(msg);

    init_heap();
   // populate_commands();
    terminal();
    //for (;;);
}
