<?php

use Doom\Texture\TextureResolver;
use Doom\Wad\File;
use Doom\Wad\Types\Lump;

function s16(int $value): string
{
    return pack('v', $value & 0xFFFF);
}

/**
 * @param array<int, array{originX:int, originY:int, patch:int, stepDir:int, colorMap:int}> $patches
 */
function textureRecord(string $name, bool $masked, int $width, int $height, array $patches): string
{
    $data = pack('a8VvvVv', $name, $masked ? 1 : 0, $width, $height, 0, count($patches));

    foreach ($patches as $patch) {
        $data .= s16($patch['originX']);
        $data .= s16($patch['originY']);
        $data .= pack('v', $patch['patch']);
        $data .= pack('v', $patch['stepDir']);
        $data .= pack('v', $patch['colorMap']);
    }

    return $data;
}

/**
 * @param string[] $records
 */
function textureXData(array $records): string
{
    $count = count($records);
    $offset = 4 + ($count * 4);
    $offsets = [];
    $body = '';

    foreach ($records as $record) {
        $offsets[] = $offset;
        $body .= $record;
        $offset += strlen($record);
    }

    $data = pack('V', $count);
    foreach ($offsets as $itemOffset) {
        $data .= pack('V', $itemOffset);
    }

    return $data . $body;
}

/**
 * @param string[] $names
 */
function pnamesData(array $names): string
{
    $data = pack('V', count($names));
    foreach ($names as $name) {
        $data .= pack('a8', $name);
    }

    return $data;
}

function appendLump(File $wad, string $name, string $data): void
{
    $index = $wad->lumps->count();

    $wad->lumps->push(new Lump(
        offset: 0,
        size: strlen($data),
        name: strtoupper($name),
        data: $data,
        index: $index,
    ));
}

test('resolves textures using PNAMES and patch lumps', function (): void {
    $wad = new File();

    appendLump($wad, 'PNAMES', pnamesData(['PATCHA', 'PATCHB']));

    appendLump($wad, 'TEXTURE1', textureXData([
        textureRecord('TEXA', false, 64, 64, [
            ['originX' => 0, 'originY' => 0, 'patch' => 0, 'stepDir' => 0, 'colorMap' => 0],
        ]),
        textureRecord('TEXB', false, 32, 16, [
            ['originX' => 8, 'originY' => -2, 'patch' => 0, 'stepDir' => 0, 'colorMap' => 0],
        ]),
    ]));

    appendLump($wad, 'TEXTURE2', textureXData([
        textureRecord('TEXA', true, 128, 32, [
            ['originX' => 4, 'originY' => 5, 'patch' => 1, 'stepDir' => 0, 'colorMap' => 0],
            ['originX' => 0, 'originY' => 0, 'patch' => 5, 'stepDir' => 0, 'colorMap' => 0],
        ]),
    ]));

    appendLump($wad, 'PATCHA', '');

    $resolved = $wad->resolveTextures();

    expect($resolved)->toHaveCount(2);
    expect($resolved)->toHaveKey('TEXA');
    expect($resolved)->toHaveKey('TEXB');

    $texA = $resolved['TEXA'];
    expect($texA->masked)->toBeTrue();
    expect($texA->width)->toBe(128);
    expect($texA->height)->toBe(32);
    expect($texA->patches)->toHaveCount(2);
    expect($texA->patches[0]->patchName)->toBe('PATCHB');
    expect($texA->patches[0]->lumpIndex)->toBeNull();
    expect($texA->patches[0]->isResolved())->toBeFalse();
    expect($texA->patches[1]->patchName)->toBeNull();

    $texB = TextureResolver::forWad($wad)->resolveByName('texb');
    expect($texB)->not->toBeNull();
    expect($texB->patches[0]->patchName)->toBe('PATCHA');
    expect($texB->patches[0]->lumpIndex)->toBe(3);
    expect($texB->patches[0]->isResolved())->toBeTrue();
});

test('throws when PNAMES lump is missing', function (): void {
    $wad = new File();
    appendLump($wad, 'TEXTURE1', textureXData([
        textureRecord('TEXA', false, 16, 16, [
            ['originX' => 0, 'originY' => 0, 'patch' => 0, 'stepDir' => 0, 'colorMap' => 0],
        ]),
    ]));

    expect(fn () => $wad->resolveTextures())->toThrow(RuntimeException::class);
});
