<?php
// ScopedStreamTest.php
// Created: 2025-03-12
// Updated: 2025-03-12

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\{CoversClass,UsesClass};
use Index\Http\Streams\{ScopedStream,Stream};

#[CoversClass(ScopedStream::class)]
#[UsesClass(Stream::class)]
final class ScopedStreamTest extends TestCase {
    public function testScopedStream(): void {
        // why use test data when you can just use the source file
        $stream = Stream::createStreamFromFile(__FILE__, 'rb');
        $stream->seek(5, SEEK_SET);

        // potentially account for Windows moments
        $offset = $stream->read(2) === "\r\n" ? 7 : 6;
        $length = 23;

        $scoped = new ScopedStream($stream, $offset, $length);
        $this->assertEquals('// ScopedStreamTest.php', (string)$scoped);
        $this->assertEquals($length, $scoped->getSize());

        // rewind the underlying stream, scoped should should account for tell() < 0
        $stream->rewind();
        $read = $scoped->read(100);
        $this->assertEquals($length, strlen($read));
        $this->assertEquals('// ScopedStreamTest.php', $read);

        // read beyond end + eof
        $stream->seek(-50, SEEK_END);
        $read = $scoped->read(100);
        $this->assertEmpty($read);
        $this->assertTrue($scoped->eof());

        // SEEK_SET
        $scoped->seek(5);
        $this->assertEquals($offset + 5, $stream->tell());
        $read = $scoped->read(5);
        $this->assertEquals($offset + 10, $stream->tell());
        $this->assertEquals('opedS', $read);

        $scoped->rewind(); // calls seek(offset) internally
        $this->assertEquals($offset, $stream->tell());

        // SEEK_CUR
        $scoped->seek(4, SEEK_CUR);
        $read = $scoped->read(4);
        $this->assertEquals('cope', $read);

        $read = $stream->read(4);
        $this->assertEquals('dStr', $read);
        $this->assertEquals(12, $scoped->tell());
        $this->assertEquals($offset + 12, $stream->tell());

        $scoped->seek(100, SEEK_CUR);
        $read = $scoped->read(100);
        $this->assertEmpty($read);
        $this->assertTrue($scoped->eof());
        $this->assertFalse($stream->eof());

        $scoped->seek(-100, SEEK_CUR);
        $read = $scoped->read(5);
        $this->assertEquals('// Sc', $read);

        // SEEK_END + eof behaviour
        $stream->seek(0, SEEK_END);
        $read = $stream->read(1);
        $this->assertTrue($scoped->eof());
        $this->assertTrue($stream->eof());

        $scoped->seek(0, SEEK_END);
        $this->assertFalse($scoped->eof());
        $this->assertFalse($stream->eof());

        // double wrap to test the alternate path in getContents
        $double = new ScopedStream($scoped, 5, 12);
        $double->seek(5, SEEK_CUR);
        $this->assertEquals('tre', $double->read(3));
        $this->assertEquals('opedStreamTe', (string)$double);

        // now you're scoping with scopes
        $triple = ScopedStream::scopeTo($double, 2, 12);
        $this->assertInstanceOf(ScopedStream::class, $double->stream);
        $this->assertInstanceOf(Stream::class, $triple->stream);
        $this->assertEquals($offset + 7, $triple->offset);
        $this->assertEquals('edStreamTe', (string)$triple);
        $triple->rewind();
        $this->assertEquals('edStreamTe', $triple->read(100));
        $this->assertTrue($triple->eof());
    }
}