#include "xbox_portable.h"
#include "xbox_region.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SECTOR_SIZE 2048U
#define MAX_SECTORS 512U

static unsigned char g_sectors[MAX_SECTORS][SECTOR_SIZE];
static unsigned char g_dmi[SECTOR_SIZE];

static void put_le16(unsigned char *p, unsigned int value)
{
    p[0] = (unsigned char)(value & 0xffU);
    p[1] = (unsigned char)((value >> 8) & 0xffU);
}

static void put_le32(unsigned char *p, unsigned long value)
{
    p[0] = (unsigned char)(value & 0xffUL);
    p[1] = (unsigned char)((value >> 8) & 0xffUL);
    p[2] = (unsigned char)((value >> 16) & 0xffUL);
    p[3] = (unsigned char)((value >> 24) & 0xffUL);
}

static int fake_read10(void *context,
                       uint32_t lba,
                       uint32_t sectors,
                       unsigned char *buffer,
                       size_t buffer_size)
{
    uint32_t index;
    (void)context;

    if (!buffer || sectors == 0 || lba + sectors > MAX_SECTORS)
        return -1;
    if (buffer_size < (size_t)sectors * SECTOR_SIZE)
        return -1;

    for (index = 0; index < sectors; ++index)
        memcpy(buffer + ((size_t)index * SECTOR_SIZE),
               g_sectors[lba + index],
               SECTOR_SIZE);
    return 0;
}

static int fake_read_structure(void *context,
                               uint8_t format,
                               uint8_t layer,
                               unsigned char *buffer,
                               size_t buffer_size)
{
    (void)context;
    (void)layer;

    if (format != 0x04U || !buffer || buffer_size < sizeof(g_dmi))
        return -1;
    memcpy(buffer, g_dmi, sizeof(g_dmi));
    return 0;
}

static void require_true(int condition, const char *message)
{
    if (!condition) {
        fprintf(stderr, "FAIL: %s\n", message);
        exit(1);
    }
    printf("PASS: %s\n", message);
}

static void write_utf16_title(unsigned char *certificate, const unsigned short *title)
{
    size_t index;
    for (index = 0; index < 40U && title[index] != 0; ++index)
        put_le16(certificate + 0x00CU + (index * 2U), title[index]);
}

int main(void)
{
    static const unsigned short title[] = {
        'R','e','d',' ','F','a','c','t','i','o','n',0x00AE,' ','I','I',0
    };
    xbox_portable_metadata metadata;
    unsigned char *volume;
    unsigned char *root;
    unsigned char *xbe;
    unsigned char *certificate;

    memset(g_sectors, 0, sizeof(g_sectors));
    memset(g_dmi, 0, sizeof(g_dmi));

    volume = g_sectors[32];
    memcpy(volume, "MICROSOFT*XBOX*MEDIA", 20);
    put_le32(volume + 0x14U, 100U);
    put_le32(volume + 0x18U, SECTOR_SIZE);

    root = g_sectors[100];
    put_le16(root, 0U);
    put_le16(root + 2U, 0U);
    put_le32(root + 4U, 200U);
    put_le32(root + 8U, 2314240U);
    root[12U] = 0U;
    root[13U] = 11U;
    memcpy(root + 14U, "default.xbe", 11U);
    root[28U] = 0U;

    xbe = g_sectors[200];
    put_le32(xbe, 0x48454258UL);
    put_le32(xbe + 0x104U, 0x00010000UL);
    put_le32(xbe + 0x118U, 0x00010800UL);

    certificate = g_sectors[201];
    put_le32(certificate + 0x008U, 0x54510005UL);
    write_utf16_title(certificate, title);
    put_le32(certificate + 0x09CU, 0x00000020UL);
    put_le32(certificate + 0x0A0U, XB_REGION_US_CANADA);
    put_le32(certificate + 0x0A4U, 0x00000001UL);
    put_le32(certificate + 0x0A8U, 0U);
    put_le32(certificate + 0x0ACU, 1U);

    memcpy(g_dmi + 8U, "TQ00501A", 8U);

    require_true(
        xbox_portable_read_metadata(fake_read10,
                                    fake_read_structure,
                                    NULL,
                                    &metadata) == 1,
        "portable Xbox metadata parser accepts a valid XDFS/XBE fixture");
    require_true(metadata.valid == 1,
                 "portable Xbox metadata result is marked valid");
    require_true(metadata.xdfs_volume_lba == 32U,
                 "standard Xbox game-view XDFS volume LBA is retained");
    require_true(strcmp(metadata.title, "Red Faction\xC2\xAE II") == 0,
                 "UTF-16 XBE title is converted to UTF-8");
    require_true(strcmp(metadata.media_id, "TQ00501A") == 0,
                 "DMI media ID is retained");
    require_true(metadata.title_id == 0x54510005UL,
                 "XBE title ID is retained");
    require_true(metadata.game_region == XB_REGION_US_CANADA,
                 "raw XBE GameRegion mask is retained");
    require_true(strcmp(metadata.region, "North America") == 0,
                 "XBE GameRegion is formatted consistently");

    printf("XBOX PORTABLE METADATA CONTRACT: PASS\n");
    return 0;
}
