/*========================== begin_copyright_notice ============================
Copyright (C) 2021 Intel Corporation

SPDX-License-Identifier: MIT
============================= end_copyright_notice ===========================*/

#include "mem_model.h"
#include <cstdio>

// MemoryModel constructor
MemoryModel::MemoryModel()
{
    // Initially all surface pointers are NULL
    for (uint32_t bti = 0; bti < 256; bti++)
    {
        _mem[bti] = NULL;
    }
}
    
// MemoryModel destructor
MemoryModel::~MemoryModel()
{
    // Iterate over all potential surfaces
    for (uint32_t bti = 0; bti < 256; bti++)
    {
        // If exists
        if (_mem[bti])
        {
            // Delete it
            delete _mem[bti];
        }
    }
}

// Writing into a surface
// 
// bti  - surface ID (BTI index)
// addr - an offset into the surface
// data - data to be written
void MemoryModel::Write(uint32_t bti, uint32_t addr, uint32_t data)
{
    std::lock_guard<std::mutex> lock(_mutex);

    // If first access to the specific surface
    if (_mem[bti] == NULL)
    {
        // Create it
        _mem[bti] = new Surface;
    }
    
    // Perform a write into the surface
    _mem[bti]->Write(addr, data);
}

// Printing the memory image
void MemoryModel::Print(bool asFloat)
{
    // Iterate over all potential surfaces
    for (uint32_t bti = 0; bti < 256; bti++)
    {
        // If exists
        if (_mem[bti])
        {
            printf("\nSurface: 0x%02x\n=============\n\n", bti);

            // Print the surface
            _mem[bti]->Print(asFloat);
        }
    }
}


// Surface constructor
Surface::Surface() : _numOfWrites(0), _maxAddr(0)
{
    // Page Directory is initially empty
    memset(_pageDir, 0, 1024 * sizeof(PageTable*));
}

// Surface destructor
Surface::~Surface()
{
    // Iterate over all Page Directory entries
    for (uint32_t pdi = 0; pdi < 1024; pdi++)
    {
        PageTable* pageTable = _pageDir[pdi];

        // If current Page Table exists
        if (pageTable)
        {
            // Iterate over all Page Table entries
            for (uint32_t pti = 0; pti < 1024; pti++)
            {
                uint32_t* page = pageTable->pte[pti];

                // If page exists
                if (page)
                {
                    // Delete it
                    delete[] page;
                    pageTable->pte[pti] = NULL;
                }
            }

            // Delete Page Table
            delete pageTable;
            _pageDir[pdi] = NULL;
        }
    }
}

// Writing into the surface
// 
// addr - an offset into the surface
// data - data to be written
void Surface::Write(uint32_t addr, uint32_t data)
{
    // Update statistics
    _numOfWrites++;
    if (_maxAddr < addr)
    {
        _maxAddr = addr;
    }

    // Get a pointer to required offset
    uint32_t* ptr = (uint32_t*)GetPtr(addr);

    // Write the data
    ptr[0] = data;
}


// Printing the surface image
void Surface::Print(bool asFloat)
{
    // Print surface statistics
    printf("Surface: numOfWrites = %d maxAddr = 0x%08x\n", _numOfWrites, _maxAddr);

    // Iterate over all entries within the Page Directory
    for (uint32_t pdi = 0; pdi < 1024; pdi++)
    {
        PageTable* pageTable = _pageDir[pdi];

        // If corresponding Page Table exists
        if (pageTable)
        {
            // Compute the base described by the current Page Table
            uint32_t pageTableBase = pdi * FOUR_MB;

            // Iterate over all Page Table entries
            for (uint32_t pti = 0; pti < 1024; pti++)
            {
                uint32_t* page = pageTable->pte[pti];

                // If the current page exists
                if (page)
                {
                    // Compute the base of the current page
                    uint32_t pageBase = pageTableBase + pti * FOUR_KB;

                    // Dump all 4bytes of the current page
                    for (uint32_t a = 0; a < 1024; a++)
                    {
                        uint32_t addr = pageBase + a * 4;
                        uint32_t val  = page[a];

                        if (asFloat) printf("0x%08x: 0x%08x (%f)\n", addr, val, *(float*)&val);
                        else         printf("0x%08x: 0x%08x\n", addr, val);
                    }
                }
            }
        }
    }
}


// Computes the pointer to the surface corresponding to the given address
uint8_t * Surface::GetPtr(uint32_t addr)
{
    // Compute Page Directory index, Page Table index and the offset into the page from the address
    uint32_t pdi = addr >> 22;
    uint32_t pti = (addr & 0x003FF000) >> 12;
    uint32_t offset = addr & 0xFFF;

    // Get corresponding Page Table
    PageTable* pageTable = _pageDir[pdi];

    // If doesn't exist
    if (pageTable == NULL)
    {
        // Allocate one
        pageTable = new PageTable;

        // Update Page Directory
        _pageDir[pdi] = pageTable;
    }

    // Compute the current page
    uint32_t* page = pageTable->pte[pti];

    // If doesn't exist
    if (page == NULL)
    {
        // Allocate one
        page = new uint32_t[1024];
        // Initialize with zeros
        memset(page, 0, 0x1000);
        // Update Page Table
        pageTable->pte[pti] = page;
    }

    // Compute the pointer into the current page
    uint8_t* ptr = (uint8_t*)page + offset;

    return ptr;
}

