#include "myalloc.h"
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>

struct mm_state
{
    int nblck_per_bnk;
    size_t s_block;
    char* act_banks;
    char* alloc_blocks;
};

/* Returns the module number based on the bank number which it contains. */
int bank_to_mod(int bank_num)
{
    const struct ram_info* info = hw_raminfo();
    int mod_num = floor(bank_num / info->nbanks_per_module);
    return mod_num;
}

/* Returns the bank number based on the block number which it contains. */
int block_to_bank(mm_state_t* st, int block_num)
{
    int bank_num = floor(block_num / st->nblck_per_bnk);
    return bank_num;
}

char* block_to_addr(mm_state_t* st, int block_num)
{
    const struct ram_info* info = hw_raminfo();
    int bank = floor(block_num / st->nblck_per_bnk);
    int mod = floor(bank / info->nbanks_per_module);
    int block_offs = (block_num % (info->nbanks_per_module * st->nblck_per_bnk));
    int byte_offs = block_offs * st->s_block;
    return (char*)(info->module_addrs[mod] + byte_offs);
}

int addr_to_block(mm_state_t* st, void* ptr)
{
    const struct ram_info* info = hw_raminfo();
    int nblock = info->nmodules * info->nbanks_per_module * st->nblck_per_bnk;
    int block_num;

    for (block_num = 0; block_num < nblock; block_num++)
    {
        // printf("gegeven %p mogelijk %p\n", ptr, (void*)block_to_addr(st, block_num));
        if ((void*)block_to_addr(st, block_num) == ptr)
        {
            break;
        }
    }
    return block_num;
}

// void init_bank_arr(mm_state_t* st, int nbank_state)
// {
//     return;
// }

// void init_block_arr(mm_state_t* st, int nblock_state)
// {
//     return;
// }

bool check_bank(mm_state_t* st, int bank_num)
{
    return (st->act_banks[bank_num / 8] & (1 << (7 - (bank_num % 8))))
           == (1 << (7 - (bank_num % 8)));
}

bool check_block(mm_state_t* st, int block_num)
{
    return (st->alloc_blocks[block_num / 8] & (1 << (7 - (block_num % 8))))
           == (1 << (7 - (block_num % 8)));
}

void set_bank(mm_state_t* st, int bank_num)
{
    const struct ram_info* info = hw_raminfo();
    int mod_num = bank_num / info->nbanks_per_module;
    //printf("%d %d\n", mod_num, bank_num);
    hw_activate(mod_num, (bank_num % info->nbanks_per_module));
    st->act_banks[bank_num / 8] |= (1 << (7 - (bank_num % 8)));
    return;
}

void set_block(mm_state_t* st, int block_num)
{
    //printf("%d ", block_num);
    int bank_num = block_num / st->nblck_per_bnk;
    if (!check_bank(st, bank_num))
    {
        set_bank(st, bank_num);
    }
    st->alloc_blocks[block_num / 8] |= (1 << (7 - (block_num % 8)));
}

void set_block_ran(mm_state_t* st, int fst, int nblock)
{
    for (int i = fst; i < (fst + nblock); i++)
    {
        set_block(st, i);
    }
}

void clr_block(mm_state_t* st, int block_num)
{
    st->alloc_blocks[block_num / 8] &= ~(1 << (7 - (block_num % 8)));
}

void clr_block_ran(mm_state_t* st, int fst, int nblock)
{
    for (int i = fst; i < (fst + nblock); i++)
    {
        clr_block(st, i);
    }
}

/* Returns the number of the first block
   which corresponds to the smalles fit.
   Returns NULL if no fit is found. */
int smallest_fit(mm_state_t* st, size_t nbytes)
{
    const struct ram_info* inf = hw_raminfo();
    int nblcks_req = ceil((float)nbytes / (float)st->s_block);
    int nblcks_tot = st->nblck_per_bnk * inf->nbanks_per_module * inf->nmodules;
    // nblck_per_mod = inf->nbanks_per_module * st->nblck_per_bnk;
    int sml_blck = 0;
    int cur_blck = 0;
    // int pos_in_bank = 0;
    int sml_blck_s = nblcks_tot;
    int cur_blck_s = nblcks_tot;
    bool new_blk = true;
    for (int i = 0; i < nblcks_tot; i++)
    {
        if (check_block(st, i) || !((i + 1) % st->nblck_per_bnk))
        {
            if (cur_blck_s < sml_blck_s && cur_blck >= nblcks_req)
            {
                sml_blck = cur_blck;
                sml_blck_s = cur_blck_s;
            }
            new_blk = true;
        }
        else
        {
            if (new_blk)
            {
                cur_blck = i;
                cur_blck_s = 1;
                new_blk = false;
            }
            else
            {
                cur_blck_s += 1;
            }
        }
    }
    //printf("S%d R%d\n", sml_blck_s, nblcks_req);
    if (sml_blck_s < nblcks_req)
    {
        return 0;
    }
    return sml_blck;
}

/* TEMP */

void print_bit(char* bit, int size)
{
    for (int k = 0; k < size; k++)
    {
        for (int i = 0; i < 8; i++)
        {
            printf("%d", !!((bit[k] << i) & 0x80));
        }
    }
    printf("\n");
}

mm_state_t* mm_initialize(void)
{
    const struct ram_info* info = hw_raminfo();
    int nbanks = (info->nbanks_per_module * info->nmodules);
    int nblck_per_bnk = 16;
    size_t s_bnk_arr = ceil(nbanks / 8);
    size_t s_blck_arr = ceil(((float)nbanks * (float)nblck_per_bnk) / 8);
    size_t s_state = sizeof (mm_state_t) + s_bnk_arr + s_blck_arr;
    int nbank_state = ceil((float)s_state / (float)info->bank_size);
    int nblock_state = ceil((float)s_state
                            / (float)(info->bank_size / nblck_per_bnk));
    for (int i = 0; i < nbank_state; i++)
    {
        hw_activate(0, i);
    }
    char *base = (char*)info->module_addrs[0];
    mm_state_t *st = (mm_state_t *)base;
    st->nblck_per_bnk = nblck_per_bnk;
    st->s_block = info->bank_size / nblck_per_bnk;
    st->act_banks = base + sizeof (mm_state_t);
    st->alloc_blocks = st->act_banks + s_bnk_arr;
    for (int i = 0; i < (int) s_bnk_arr; i++)
    {
        st->act_banks[i] = 0;
    }
    for (int i = 0; i < (int) s_blck_arr; i++)
    {
        st->alloc_blocks[i] = 0;
    }
    for (int i = 0; i < nbank_state; i++)
    {
        st->act_banks[i / 8] |= (1 << (7 - (i % 8)));
    }
    for (int i = 0; i < nblock_state; i++)
    {
        st->alloc_blocks[i / 8] |= (1 << (7 - (i % 8)));
    }
    // printf("%d\n", s_state);
    // printf("%d\n", s_bnk_arr);
    // printf("%d\n", s_blck_arr);
    // print_bit(st->act_banks, (int)s_bnk_arr);
    // print_bit(st->alloc_blocks, (int)s_blck_arr);
    return st;
}

void *mm_alloc(mm_state_t* st, size_t nbytes)
{
    int* nblck_ptr;
    void* ptr;
    const struct ram_info *info = hw_raminfo();
    int nbytes_p_mod = (info->nmodules
                          * info->nbanks_per_module
                          * info->bank_size);
    int nbytes_tot = nbytes + 8;

    if (nbytes_tot > nbytes_p_mod)
    {
        return NULL;
    }
    //printf("%d\n", nbytes_tot);
    int fst_blk_num = smallest_fit(st, nbytes_tot);
    if (!fst_blk_num)
    {
        return NULL;
    }
    int nblock = (int)ceil((float)nbytes_tot / (float)st->s_block);
    set_block_ran(st, fst_blk_num, nblock);
    nblck_ptr = (int*)block_to_addr(st, fst_blk_num);
    ptr = (void*)nblck_ptr + 8;
    *nblck_ptr = nblock;
    // printf("taart %d kaas %d baas %d\n", nblock, *nblck_ptr, sizeof (int));
    print_bit(st->act_banks, 5);
    print_bit(st->alloc_blocks, 84);

    return ptr;
}

void mm_free(mm_state_t* st, void *ptr)
{
    int* nblck_ptr = ptr - 8;
    // printf("%d blocknum %d\n", *nblck_ptr, addr_to_block(st, nblck_ptr));
    int fst_blck_num = addr_to_block(st, nblck_ptr);
    clr_block_ran(st, fst_blck_num, *nblck_ptr);
}
