<?php
// manual load for testing. please use PSR style autoloader when you use flatbuffers.
require join(DIRECTORY_SEPARATOR, array(dirname(dirname(__FILE__)), "php", "Constants.php"));
require join(DIRECTORY_SEPARATOR, array(dirname(dirname(__FILE__)), "php", "ByteBuffer.php"));
require join(DIRECTORY_SEPARATOR, array(dirname(dirname(__FILE__)), "php", "FlatbufferBuilder.php"));
require join(DIRECTORY_SEPARATOR, array(dirname(dirname(__FILE__)), "php", "Table.php"));
require join(DIRECTORY_SEPARATOR, array(dirname(dirname(__FILE__)), "php", "Struct.php"));
foreach (glob(join(DIRECTORY_SEPARATOR, array(dirname(__FILE__), "MyGame", "Example", "*.php"))) as $file) {
    require $file;
}

function main()
{
    /// Begin Test
    $assert = new Assert();

    // First, let's test reading a FlatBuffer generated by C++ code:
    // This file was generated from monsterdata_test.json

    // Now test it:
    $data = file_get_contents('monsterdata_test.mon');
    $bb = Google\FlatBuffers\ByteBuffer::wrap($data);
    test_buffer($assert, $bb);

    // Second, let's create a FlatBuffer from scratch in JavaScript, and test it also.
    // We use an initial size of 1 to exercise the reallocation algorithm,
    // normally a size larger than the typical FlatBuffer you generate would be
    // better for performance.
    $fbb = new Google\FlatBuffers\FlatBufferBuilder(1);

    // We set up the same values as monsterdata.json:
    $str = $fbb->createString("MyMonster");
    $name = $fbb->createString('Fred');
    \MyGame\Example\Monster::startMonster($fbb);
    \MyGame\Example\Monster::addName($fbb, $name);
    $enemy = \MyGame\Example\Monster::endMonster($fbb);

    $inv = \MyGame\Example\Monster::CreateInventoryVector($fbb, array(0, 1, 2, 3, 4));

    $fred = $fbb->createString('Fred');
    \MyGame\Example\Monster::StartMonster($fbb);
    \MyGame\Example\Monster::AddName($fbb, $fred);
    $mon2 = \MyGame\Example\Monster::EndMonster($fbb);

    \MyGame\Example\Monster::StartTest4Vector($fbb, 2);
    \MyGame\Example\Test::CreateTest($fbb, 10, 20);
    \MyGame\Example\Test::CreateTest($fbb, 30, 40);
    $test4 = $fbb->endVector();

    $testArrayOfString = \MyGame\Example\Monster::CreateTestarrayofstringVector($fbb, array(
        $fbb->createString('test1'),
        $fbb->createString('test2')
    ));

    \MyGame\Example\Monster::StartMonster($fbb);
    \MyGame\Example\Monster::AddPos($fbb, \MyGame\Example\Vec3::CreateVec3($fbb,
        1.0, 2.0, 3.0, //float
        3.0, // double
        \MyGame\Example\Color::Green,
        5, //short
        6));
    \MyGame\Example\Monster::AddHp($fbb, 80);
    \MyGame\Example\Monster::AddName($fbb, $str);
    \MyGame\Example\Monster::AddInventory($fbb, $inv);
    \MyGame\Example\Monster::AddTestType($fbb, \MyGame\Example\Any::Monster);
    \MyGame\Example\Monster::AddTest($fbb, $mon2);
    \MyGame\Example\Monster::AddTest4($fbb, $test4);
    \MyGame\Example\Monster::AddTestarrayofstring($fbb, $testArrayOfString);
    \MyGame\Example\Monster::AddEnemy($fbb, $enemy);
    \MyGame\Example\Monster::AddTestbool($fbb, true);
    $mon = \MyGame\Example\Monster::EndMonster($fbb);

    \MyGame\Example\Monster::FinishMonsterBuffer($fbb, $mon);

    // Test it:
    test_buffer($assert, $fbb->dataBuffer());

    testByteBuffer($assert);
    fuzzTest1($assert);
//    testUnicode($assert);

    echo 'FlatBuffers php test: completed successfully' . PHP_EOL;
}

try {
    main();
    exit(0);
} catch(Exception $e) {
    printf("Fatal error: Uncaught exception '%s' with message '%s. in %s:%d\n", get_class($e), $e->getMessage(), $e->getFile(), $e->getLine());
    printf("Stack trace:\n");
    echo $e->getTraceAsString() . PHP_EOL;
    printf("  thrown in in %s:%d\n", $e->getFile(), $e->getLine());

    die(-1);
}

function test_buffer(Assert $assert, Google\FlatBuffers\ByteBuffer $bb) {

    $assert->ok(MyGame\Example\Monster::MonsterBufferHasIdentifier($bb));
    $monster = \MyGame\Example\Monster::GetRootAsMonster($bb);

    $assert->strictEqual($monster->GetHp(), 80);
    $assert->strictEqual($monster->GetMana(), 150); // default

    $assert->strictEqual($monster->GetName(), 'MyMonster');

    $pos = $monster->GetPos();
    $assert->strictEqual($pos->GetX(), 1.0);
    $assert->strictEqual($pos->GetY(), 2.0);
    $assert->strictEqual($pos->GetZ(), 3.0);

    $assert->Equal($pos->GetTest1(), 3.0);
    $assert->strictEqual($pos->GetTest2(), \MyGame\Example\Color::Green);

    $t = $pos->GetTest3();
    $assert->strictEqual($t->GetA(), 5);
    $assert->strictEqual($t->GetB(), 6);
    $assert->strictEqual($monster->GetTestType(), \MyGame\Example\Any::Monster);

    $monster2 = new \MyGame\Example\Monster();
    $assert->strictEqual($monster->GetTest($monster2) != null, true);
    $assert->strictEqual($monster2->GetName(), 'Fred');

    $assert->strictEqual($monster->GetInventoryLength(), 5);
    $invsum = 0;
    for ($i = 0; $i < $monster->GetInventoryLength(); $i++) {
        $invsum += $monster->GetInventory($i);
    }
    $assert->strictEqual($invsum, 10);

    $assert->strictEqual(bin2hex($monster->GetInventoryBytes()), "0001020304");

    $test_0 = $monster->GetTest4(0);
    $test_1 = $monster->GetTest4(1);
    $assert->strictEqual($monster->GetTest4Length(), 2);
    $assert->strictEqual($test_0->GetA() + $test_0->GetB() + $test_1->GetA() + $test_1->GetB(), 100);

    $assert->strictEqual($monster->GetTestarrayofstringLength(), 2);
    $assert->strictEqual($monster->GetTestarrayofstring(0), 'test1');
    $assert->strictEqual($monster->GetTestarrayofstring(1), 'test2');

    $fred = $monster->getEnemy();
    $assert->Equal('Fred', $fred->getName());

    $assert->strictEqual($monster->GetTestbool(), true);
}

//function testUnicode(Assert $assert) {
//    // missing unicode_test.mon, implemented later
//    $correct = file_get_contents('unicode_test.mon');
//    $json = json_decode(file_get_contents('unicode_test.json'));
//
//    // Test reading
//    $bb = flatbuffers\ByteBuffer::Wrap($correct);
//    $monster = \MyGame\Example\Monster::GetRootAsMonster($bb);
//    $assert->strictEqual($monster->GetName(), $json["name"]);
//
//    //$assert->deepEqual(new Buffer(monster.name(flatbuffers.Encoding.UTF8_BYTES)), new Buffer(json.name));
//    //assert.strictEqual(monster.testarrayoftablesLength(), json.testarrayoftables.length);
//    foreach ($json["testarrayoftables"]as $i => $table) {
//        $value = $monster->GetTestArrayOfTables($i);
//        $assert->strictEqual($value->GetName(), $table["name"]);
//        //assert.deepEqual(new Buffer(value.name(flatbuffers.Encoding.UTF8_BYTES)), new Buffer(table.name));
//    }
//    $assert->strictEqual($monster->GetTestarrayofstringLength(), $json["testarrayofstring"]["length"]);
//    foreach ($json["testarrayofstring"] as $i => $string) {
//        $assert->strictEqual($monster->GetTestarrayofstring($i), $string);
//        //assert.deepEqual(new Buffer(monster.testarrayofstring(i, flatbuffers.Encoding.UTF8_BYTES)), new Buffer(string));
//    }
//
//    // Test writing
//    $fbb = new FlatBuffers\FlatBufferBuilder(1);
//    $name = $fbb->CreateString($json["name"]);
//    $testarrayoftablesOffsets = array_map(function($table) use($fbb) {
//        $name = $fbb->CreateString($table["name"]);
//        \MyGame\Example\Monster::StartMonster($fbb);
//        \MyGame\Example\Monster::AddName($fbb, $name);
//        return \MyGame\Example\Monster::EndMonster($fbb);
//    }, $json["testarrayoftables"]);
//    $testarrayoftablesOffset = \MyGame\Example\Monster::CreateTestarrayoftablesVector($fbb,
//            $testarrayoftablesOffsets);
////    $testarrayofstringOffset = \MyGame\Example\Monster::CreateTestarrayofstringVector($fbb,
////            $json["testarrayofstring"].map(function(string) { return fbb.createString(string); }));
//
//    \MyGame\Example\Monster::startMonster($fbb);
//    \MyGame\Example\Monster::addTestarrayofstring($fbb, $testarrayoftablesOffset);
//    \MyGame\Example\Monster::addTestarrayoftables($fbb, $testarrayoftablesOffset);
//    \MyGame\Example\Monster::addName($fbb, $name);
//    \MyGame\Example\Monster::finishMonsterBuffer($fbb, \MyGame\Example\Monster::endMonster($fbb));
//    //;assert.deepEqual(new Buffer(fbb.asUint8Array()), correct);
//}

// Low level stress/fuzz test: serialize/deserialize a variety of
// different kinds of data in different combinations
function fuzzTest1(Assert $assert)
{

    // Values we're testing against: chosen to ensure no bits get chopped
    // off anywhere, and also be different from eachother.
    $bool_val = true;
    $char_val = -127; // 0x81
    $uchar_val = 0xFF;
    $short_val = -32222; // 0x8222;
    $ushort_val = 0xFEEE;
    $int_val = 0x7fffffff | 0;
    // for now
    $uint_val = 1;
    $long_val = 2;
    $ulong_val = 3;

//    var uint_val   = 0xFDDDDDDD;
//    var long_val   = new flatbuffers.Long(0x44444444, 0x84444444);
//    var ulong_val  = new flatbuffers.Long(0xCCCCCCCC, 0xFCCCCCCC);

    $float_val = 3.14159;
    $double_val = 3.14159265359;

    $test_values_max = 11;
    $fields_per_object = 4;
    // current implementation is not good at encoding.
    $num_fuzz_objects = 1000;
    $builder = new Google\FlatBuffers\FlatBufferBuilder(1);

    // can't use same implementation due to PHP_INTMAX overflow issue.
    // we use mt_rand function to reproduce fuzzy test.
    mt_srand(48271);
    $objects = array();
    // Generate num_fuzz_objects random objects each consisting of
    // fields_per_object fields, each of a random type.
    for ($i = 0; $i < $num_fuzz_objects; $i++) {
        $builder->startObject($fields_per_object);
        for ($f = 0; $f < $fields_per_object; $f++) {
            $choice = mt_rand() % $test_values_max;
            switch ($choice) {
                case 0:
                    $builder->addBoolX($f, $bool_val, 0);
                    break;
                case 1:
                    $builder->addByteX($f, $char_val, 0);
                    break;
                case 2:
                    $builder->addSbyteX($f, $uchar_val, 0);
                    break;
                case 3:
                    $builder->addShortX($f, $short_val, 0);
                    break;
                case 4:
                    $builder->addUshortX($f, $ushort_val, 0);
                    break;
                case 5:
                    $builder->addIntX($f, $int_val, 0);
                    break;
                case 6:
                    $builder->addUintX($f, $uint_val, 0);
                    break;
                case 7:
                    $builder->addLongX($f, $long_val, 0);
                    break;
                case 8:
                    $builder->addUlongX($f, $ulong_val, 0);
                    break;
                case 9:
                    $builder->addFloatX($f, $float_val, 0);
                    break;
                case 10:
                    $builder->addDoubleX($f, $double_val, 0);
                    break;
            }
        }
        $objects[] = $builder->endObject();
    }
    $builder->prep(8, 0); // Align whole buffer.

    mt_srand(48271); // Reset
    $builder->finish($objects[count($objects) - 1]);

    $view = Google\FlatBuffers\ByteBuffer::wrap($builder->sizedByteArray());
    for ($i = 0; $i < $num_fuzz_objects; $i++) {
        $offset = $view->capacity() - $objects[$i];
        for ($f = 0; $f < $fields_per_object; $f++) {
            $choice = mt_rand() % $test_values_max;
            $vtable_offset = fieldIndexToOffset($f);
            $vtable = $offset - $view->getInt($offset);
            $assert->ok($vtable_offset < $view->getShort($vtable));
            $field_offset = $offset + $view->getShort($vtable + $vtable_offset);
            switch ($choice) {
                case 0:
                    $assert->strictEqual(!!$view->getBool($field_offset), $bool_val);
                    break;
                case 1:
                    $assert->strictEqual($view->getSbyte($field_offset), $char_val);
                    break;
                case 2:
                    $assert->strictEqual($view->getByte($field_offset), $uchar_val);
                    break;
                case 3:
                    $assert->strictEqual($view->getShort($field_offset), $short_val);
                    break;
                case 4:
                    $assert->strictEqual($view->getUShort($field_offset), $ushort_val);
                    break;
                case 5:
                    $assert->strictEqual($view->getInt($field_offset), $int_val);
                    break;
                case 6:
                    $assert->strictEqual($view->getUint($field_offset), $uint_val);
                    break;
                case 7:
                    if (PHP_INT_SIZE <= 4) break;
                    $assert->strictEqual($view->getLong($field_offset), $long_val);
                    break;
                case 8:
                    if (PHP_INT_SIZE <= 4) break;
                    $assert->strictEqual($view->getUlong($field_offset), $ulong_val);
                    break;
                case 9:
                    $assert->strictEqual(floor($view->getFloat($field_offset)), floor($float_val));
                    break;
                case 10:
                    $assert->strictEqual($view->getDouble($field_offset), $double_val);
                    break;
            }
        }
    }
}

function fieldIndexToOffset($field_id) {
    // Should correspond to what EndTable() below builds up.
    $fixed_fields = 2;  // Vtable size and Object Size.
    return ($field_id + $fixed_fields) * 2;
}

function testByteBuffer(Assert $assert) {

    //Test: ByteBuffer_Length_MatchesBufferLength
    $buffer = str_repeat("\0", 100);
    $uut  = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Equal($uut->capacity(), strlen($buffer));

    //Test: ByteBuffer_PutBytePopulatesBufferAtZeroOffset
    $buffer = "\0";
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $uut->putByte(0, "\x63"); // 99
    $assert->Equal("\x63", $uut->_buffer[0]); // don't share buffer as php user might confuse reference.

    //Test: ByteBuffer_PutByteCannotPutAtOffsetPastLength
    $buffer = "\0";
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->putByte(1, "\x63"); // 99
    });

    //Test: ByteBuffer_PutShortPopulatesBufferCorrectly
    $buffer = str_repeat("\0", 2);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $uut->putShort(0, 1);

    // Ensure Endiannes was written correctly
    $assert->Equal(chr(0x01), $uut->_buffer[0]);
    $assert->Equal(chr(0x00), $uut->_buffer[1]);

    $buffer = str_repeat("\0", 2);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $uut->putShort(0, -32768);

    // Ensure Endiannes was written correctly
    $assert->Equal(chr(0x00), $uut->_buffer[0]);
    $assert->Equal(chr(0x80), $uut->_buffer[1]);

    //Test: ByteBuffer_PutShortCannotPutAtOffsetPastLength
    $buffer = "\0";
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->putShort(2, 2); // 99
    });

    //Test: ByteBuffer_PutShortChecksLength
    $buffer = "\0";
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->putShort(0, 2); // 99
    });

    //Test: ByteBuffer_PutShortChecksLengthAndOffset
    $buffer = str_repeat("\0", 2);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->putShort(1, 2); // 99
    });

    //Test: ByteBuffer_PutIntPopulatesBufferCorrectly
    $buffer = str_repeat("\0", 4);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $uut->putInt(0, 0x0A0B0C0D);
    $assert->Equal(chr(0x0D), $uut->_buffer[0]);
    $assert->Equal(chr(0x0C), $uut->_buffer[1]);
    $assert->Equal(chr(0x0B), $uut->_buffer[2]);
    $assert->Equal(chr(0x0A), $uut->_buffer[3]);

    $buffer = str_repeat("\0", 4);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $uut->putInt(0, -2147483648);
    $assert->Equal(chr(0x00), $uut->_buffer[0]);
    $assert->Equal(chr(0x00), $uut->_buffer[1]);
    $assert->Equal(chr(0x00), $uut->_buffer[2]);
    $assert->Equal(chr(0x80), $uut->_buffer[3]);

    //Test: ByteBuffer_PutIntCannotPutAtOffsetPastLength
    $buffer = str_repeat("\0", 4);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->putInt(2, 0x0A0B0C0D);
    });

    //Test: ByteBuffer_PutIntChecksLength
    $buffer = str_repeat("\0", 1);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->putInt(0, 0x0A0B0C0D);
    });

    //Test: ByteBuffer_PutIntChecksLengthAndOffset
    $buffer = str_repeat("\0", 4);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->putInt(2, 0x0A0B0C0D);
    });

    if (PHP_INT_SIZE > 4) {
        //Test: ByteBuffer_PutLongPopulatesBufferCorrectly
        $buffer = str_repeat("\0", 8);
        $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
        $uut->putLong(0, 0x010203040A0B0C0D);
        $assert->Equal(chr(0x0D), $uut->_buffer[0]);
        $assert->Equal(chr(0x0C), $uut->_buffer[1]);
        $assert->Equal(chr(0x0B), $uut->_buffer[2]);
        $assert->Equal(chr(0x0A), $uut->_buffer[3]);
        $assert->Equal(chr(0x04), $uut->_buffer[4]);
        $assert->Equal(chr(0x03), $uut->_buffer[5]);
        $assert->Equal(chr(0x02), $uut->_buffer[6]);
        $assert->Equal(chr(0x01), $uut->_buffer[7]);

        //Test: ByteBuffer_PutLongCannotPutAtOffsetPastLength
        $buffer = str_repeat("\0", 8);
        $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
        $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
            $uut->putLong(2, 0x010203040A0B0C0D);
        });

        //Test: ByteBuffer_PutLongCannotPutAtOffsetPastLength
        $buffer = str_repeat("\0", 1);
        $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
        $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
            $uut->putLong(0, 0x010203040A0B0C0D);
        });


        //Test: ByteBuffer_PutLongChecksLengthAndOffset
        $buffer = str_repeat("\0", 8);
        $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
        $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
            $uut->putLong(2, 0x010203040A0B0C0D);
        });
    }

    //Test: ByteBuffer_GetByteReturnsCorrectData
    $buffer = str_repeat("\0", 1);
    $buffer[0] = "\x63";
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Equal("\x63", $uut->get(0));

    //Test: ByteBuffer_GetByteChecksOffset
    $buffer = str_repeat("\0", 1);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->get(1);
    });

    //Test: ByteBuffer_GetShortReturnsCorrectData
    $buffer = str_repeat("\0", 2);
    $buffer[0] = chr(0x01);
    $buffer[1] = chr(0x00);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Equal(1, $uut->getShort(0));

    //Test: ByteBuffer_GetShortReturnsCorrectData (signed value)
    $buffer = str_repeat("\0", 2);
    $buffer[0] = chr(0x00);
    $buffer[1] = chr(0x80);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Equal(-32768, $uut->getShort(0));

    //Test: ByteBuffer_GetShortChecksOffset
    $buffer = str_repeat("\0", 2);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->getShort(2);
    });

    //Test: ByteBuffer_GetShortChecksLength
    $buffer = str_repeat("\0", 2);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->getShort(1);
    });

    //Test: ByteBuffer_GetIntReturnsCorrectData
    $buffer = str_repeat("\0", 4);
    $buffer[0] = chr(0x0D);
    $buffer[1] = chr(0x0C);
    $buffer[2] = chr(0x0B);
    $buffer[3] = chr(0x0A);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Equal(0x0A0B0C0D, $uut->getInt(0));

    $buffer = str_repeat("\0", 4);
    $buffer[0] = chr(0x00);
    $buffer[1] = chr(0x00);
    $buffer[2] = chr(0x00);
    $buffer[3] = chr(0x80);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Equal(-2147483648, $uut->getInt(0));

    //Test: ByteBuffer_GetIntChecksOffset
    $buffer = str_repeat("\0", 4);

    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->getInt(4);
    });

    //Test: ByteBuffer_GetIntChecksLength
    $buffer = str_repeat("\0", 2);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->getInt(0);
    });

    if (PHP_INT_SIZE > 4) {
        //Test: ByteBuffer_GetLongReturnsCorrectData
        $buffer = str_repeat("\0", 8);
        $buffer[0] = chr(0x0D);
        $buffer[1] = chr(0x0C);
        $buffer[2] = chr(0x0B);
        $buffer[3] = chr(0x0A);
        $buffer[4] = chr(0x04);
        $buffer[5] = chr(0x03);
        $buffer[6] = chr(0x02);
        $buffer[7] = chr(0x01);
        $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
        $assert->Equal(0x010203040A0B0C0D, $uut->getLong(0));

        //Test: Signed Long
        $buffer = str_repeat("\0", 8);
        $buffer[0] = chr(0x00);
        $buffer[1] = chr(0x00);
        $buffer[2] = chr(0x00);
        $buffer[3] = chr(0x00);
        $buffer[4] = chr(0x00);
        $buffer[5] = chr(0x00);
        $buffer[6] = chr(0x00);
        $buffer[7] = chr(0x80);
        $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
        $assert->Equal(-1 << 63, $uut->getLong(0));
    }

    //Test: ByteBuffer_GetLongChecksOffset
    $buffer = str_repeat("\0", 8);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->getLong(8);
    });

    //Test: ByteBuffer_GetLongChecksLength
    $buffer = str_repeat("\0", 7);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Throws(new OutOfRangeException(), function()  use ($uut) {
        $uut->getLong(0);
    });

    //Test: big endian
    $buffer = str_repeat("\0", 2);
    // 0xFF 0x00
    // Little Endian: 255
    // Big Endian: 65280
    $buffer[0] = chr(0xff);
    $buffer[1] = chr(0x00);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Equal(65280, $uut->readLittleEndian(0, 2, true));

    $buffer = str_repeat("\0", 4);
    $buffer[0] = chr(0x0D);
    $buffer[1] = chr(0x0C);
    $buffer[2] = chr(0x0B);
    $buffer[3] = chr(0x0A);
    $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer);
    $assert->Equal(0x0D0C0B0A, $uut->readLittleEndian(0, 4, true));

}

class Assert {
    public function ok($result, $message = "") {
        if (!$result){
            throw new Exception(!empty($message) ? $message : "{$result} is not true.");
        }
    }

    public function Equal($result, $expected, $message = "") {
        if ($result != $expected) {
            throw new Exception(!empty($message) ? $message : "given the result {$result} is not equals as {$expected}");
        }
    }


    public function strictEqual($result, $expected, $message = "") {
        if ($result !== $expected) {
            throw new Exception(!empty($message) ? $message : "given the result {$result} is not strict equals as {$expected}");
        }
    }

    public function Throws($class, Callable $callback) {
        try {
            $callback();

            throw new \Exception("passed statement don't throw an exception.");
        } catch (\Exception $e) {
            if (get_class($e) != get_class($class)) {
                throw new Exception("passed statement doesn't throw " . get_class($class) . ". throws " . get_class($e) . ": {$e->getMessage()}");
            }
        }
    }
}
