#include "stdafx.h"
#include "pgBase.h"
#include "grcTexture.h"

#include "zlib.h"
#include <stdio.h>  // for test only

void BlockMap::allocate(DWORD dwRscFlags) {
    WORD   vpageCount = dwRscFlags & 0x7FF;            // virtual memory block page count
    WORD   largeVpageCount = vpageCount >> 4;
    DWORD   vpageSize = ((dwRscFlags >> 11) & 15)+8;
    WORD   ppageCount = (dwRscFlags >> 15) & 0x7FF;    // physical memory block page count;
    WORD   largePpageCount = ppageCount >> 4;
    DWORD   ppageSize = ((dwRscFlags >> 26) & 15)+8;
       
    wVirtualCount = largeVpageCount;        
    if (vpageCount & 1) wVirtualCount++;    
    if (vpageCount & 2) wVirtualCount++;
    if (vpageCount & 4) wVirtualCount++;
    if (vpageCount & 8) wVirtualCount++;

    wPhysicalCount = largePpageCount;
    if (ppageCount & 1) wPhysicalCount++;
    if (ppageCount & 2) wPhysicalCount++;
    if (ppageCount & 4) wPhysicalCount++;
    if (ppageCount & 8) wPhysicalCount++;

    // allocate memory block
    // on '360, each block allocated separately, 'physical' blocks allocated directly in the GPU memory
    // on PC, allocator can make separate memory block for each page or two blocks (one for the virtual part and one for physical)
    // .data:016EBC6E     g_bAllocateFragmentedStreamingMemory
    // here allocated only one memory block to simplify decompression of a resource
    BYTE * pMem = new BYTE[(vpageCount << vpageSize) + (ppageCount << ppageSize)];
    printf ("Creating block map: %d/%d virtual pages, 0x%x bytes, %d/%d physical pages, 0x%x bytes\n", 
        largeVpageCount, wVirtualCount, 1 << vpageSize, largePpageCount, wPhysicalCount, 1 << ppageSize);

    // initialize first block (always large page)
    blocks[0].dwOffset = 0x50000000;
    blocks[0].dwSize = 1 << (vpageSize+4);
    blocks[0].pData = pMem;

    // initialize other large pages
    for (WORD i = 1; i < largeVpageCount; i++) {
        blocks[i].dwSize = blocks[i-1].dwSize;
        blocks[i].dwOffset = blocks[i-1].dwOffset+blocks[i-1].dwSize;
        blocks[i].pData = blocks[i-1].pData + blocks[i-1].dwSize;
    }

    // intialize small pages (4 ifs instead two nested loops)
    WORD curPage = largeVpageCount;
    for (int i = 3; i >= 0; i--) 
        if (vpageCount & (1 << i)) {
            blocks[curPage].dwSize = 1 << (vpageSize+i);
            blocks[curPage].dwOffset = blocks[curPage-1].dwOffset+blocks[curPage-1].dwSize;
            blocks[curPage].pData = blocks[curPage-1].pData + blocks[curPage-1].dwSize;
            curPage++;
        }

    // initialize first physical block
    blocks[wVirtualCount].dwOffset = 0x60000000;
    blocks[wVirtualCount].dwSize = 1 << (ppageSize+4);
    blocks[wVirtualCount].pData = pMem+(vpageCount << vpageSize);

    // initialize large physical pages
    for (WORD i = wVirtualCount+1; i < wVirtualCount+largePpageCount; i++) {
        blocks[i].dwSize = blocks[i-1].dwSize;
        blocks[i].dwOffset = blocks[i-1].dwOffset + blocks[i-1].dwSize;
        blocks[i].pData = blocks[i-1].pData + blocks[i-1].dwSize;
    }

    // initialize small physical pages
    curPage = wVirtualCount+largePpageCount;
    for (int i = 3; i >= 0; i--) 
        if (ppageCount & (1 << i)) {
            blocks[curPage].dwSize = 1 << (ppageSize+i);
            blocks[curPage].dwOffset = blocks[curPage-1].dwOffset+blocks[curPage-1].dwSize;
            blocks[curPage].pData = blocks[curPage-1].pData + blocks[curPage-1].dwSize;
            curPage++;
        }
}

// free allocated memory and reset page counts
void BlockMap::release () {
    if ((wVirtualCount+wPhysicalCount) > 0)
        delete blocks[0].pData;
    wVirtualCount = wPhysicalCount = 0;
}

// convert offset to pointer
void RelocationInfo::relocate(void **ptr) {
    DWORD offset = *(DWORD *)ptr;
    if (offset == 0) 
        return;
    DWORD type = offset >> 28;
    offset &= 0x0FFFFFFF;
    // TODO: check offset range
    if (type == 5)  
        *ptr = pBlockMap->blocks[0].pData+offset;
    else 
        *ptr = pBlockMap->blocks[pBlockMap->wVirtualCount].pData+offset;
}

pgBase::pgBase(RelocationInfo *pRelInfo) {
    pRelInfo->relocate ((void **)&m_pPageMap);
    // TODO: initialize page map
}

grcTexturePC::grcTexturePC(RelocationInfo *pRelInfo) {
    pRelInfo->relocate ((void **)&m_pszName);
    m_piTexture = NULL;
    pRelInfo->relocate ((void **)&m_pPrev);
    pRelInfo->relocate ((void **)&m_pNext);
    pRelInfo->relocate ((void **)&m_pPixelData);
}

grcTexturePC::~grcTexturePC() {
}

#define TEST
#ifdef TEST
void readResource (BYTE * ptr, DWORD length) {
    RscHeader * pHeader = (RscHeader *)ptr;
    printf ("RSC: magic = 0x%08x, version = %d, flags = %08x\n", pHeader->dwMagic, pHeader->dwVersion, pHeader->dwFlags);
    
    // streaming structures
    BlockMap blockMap;
    RelocationInfo  relInfo;
    blockMap.allocate (pHeader->dwFlags);
    relInfo.pBlockMap = &blockMap;
    
    // decompress contents
    DWORD dwLength = ((pHeader->dwFlags & 0x7FF) << (((pHeader->dwFlags >> 11) & 15)+8)) + (((pHeader->dwFlags >> 15) & 0x7FF) << (((pHeader->dwFlags >> 26) & 15)+8));
    uncompress (blockMap.getData(), &dwLength, ptr+sizeof (RscHeader), length-sizeof (RscHeader));
    
    pgDictionary<grcTexturePC> * wtd = new (blockMap.getData()) pgDictionary<grcTexturePC> (&relInfo);
    
    // check block map
    printf ("Loaded WTD: %d textures\n", wtd->m_data.m_wCount);
    for (int i = 0; i < wtd->m_data.m_wCount; i++) {
        grcTexturePC * t = wtd->m_data.m_pData[i];
        DWORD dataSize = t->m_wStride*t->m_wHeight; // calculate size of pixel data 
        for (int j = 2; j <= t->m_nbLevels; j++) 
            dataSize += (t->m_wStride*t->m_wHeight) >> ((j-1)*2);
        printf ("  #%03d: (%dx%d, %d : 0x%x) %s\n", i, t->m_wWidth, t->m_wHeight, t->m_nbLevels, dataSize, t->m_pszName);

        for (int k = blockMap.wVirtualCount; k < blockMap.wVirtualCount+blockMap.wPhysicalCount; k++) {
            int offset = ((BYTE *)(t->m_pPixelData)) - blockMap.blocks[k].pData;
            // printf ("-- (%d) offset = %d\n", k, offset);
            if (offset >= 0 && offset < (int)blockMap.blocks[k].dwSize) {
                // found!
                if (dataSize+offset > blockMap.blocks[k].dwSize) 
                    printf ("    >> !! ERROR: texture data crosses page boundary\n");
                else 
                    printf ("    >> page: %d, offset = 0x%x, 0x%0x bytes left\n", k, offset, blockMap.blocks[k].dwSize - (offset+dataSize));
            }
        }
    }
    blockMap.release ();
}

void main (int argc, char ** argv) {
    if (argc < 2)
        return;
    HANDLE  hFile = CreateFile (argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf ("ERROR: Unable to open file '%s'\n", argv[1]);
        return;
    }
    DWORD l = GetFileSize (hFile, NULL);
    HANDLE hMap = CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, l, NULL);
    if (hMap != INVALID_HANDLE_VALUE) {
        BYTE * ptr = (BYTE *)MapViewOfFile (hMap, FILE_MAP_READ, 0, 0, l);
        readResource (ptr, l);
        UnmapViewOfFile (ptr);
    }
    
    CloseHandle (hMap);
    CloseHandle (hFile);
}
#endif // TEST
