/**
  *  \file u/t_game_v3_udata_reader.cpp
  *  \brief Test for game::v3::udata::Reader
  */

#include "game/v3/udata/reader.hpp"

#include "t_game_v3_udata.hpp"
#include "afl/io/constmemorystream.hpp"
#include "game/timestamp.hpp"
#include "afl/string/format.hpp"

namespace {
    /* An actual util.dat file (from Titan 12) */
    const uint8_t UTILDAT[] = {
        0x0d, 0x00, 0x59, 0x00, 0x30, 0x38, 0x2d, 0x30, 0x39, 0x2d, 0x32, 0x30,
        0x31, 0x39, 0x30, 0x39, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x32, 0x11, 0x00,
        0x07, 0x00, 0x04, 0x01, 0x9e, 0x69, 0xd7, 0x7e, 0xd7, 0xad, 0xff, 0xc9,
        0x29, 0x32, 0xb3, 0xa3, 0x30, 0x67, 0x5a, 0x94, 0x60, 0x18, 0x07, 0x74,
        0xb8, 0x1e, 0x77, 0x94, 0x68, 0x22, 0x9a, 0x53, 0xd3, 0xab, 0xdf, 0x91,
        0x54, 0x69, 0x74, 0x61, 0x6e, 0x20, 0x31, 0x32, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x11, 0x00, 0x12,
        0x00, 0x0a, 0x00, 0xfa, 0x07, 0x41, 0x07, 0x45, 0x00, 0x1c, 0x00, 0x06,
        0x00, 0x50, 0x00, 0x01, 0x00, 0x01, 0x00, 0x11, 0x00, 0x12, 0x00, 0x0b,
        0x00, 0xc2, 0x0b, 0x0a, 0x08, 0x0f, 0x00, 0xf6, 0x00, 0x06, 0x00, 0x72,
        0x00, 0x01, 0x00, 0x01, 0x00, 0x11, 0x00, 0x12, 0x00, 0x23, 0x00, 0x29,
        0x0a, 0x4c, 0x09, 0x2c, 0x00, 0x50, 0x01, 0x06, 0x00, 0x08, 0x01, 0x01,
        0x00, 0x00, 0x00, 0x11, 0x00, 0x12, 0x00, 0x24, 0x00, 0x84, 0x08, 0xcb,
        0x06, 0x33, 0x00, 0x07, 0x00, 0x06, 0x00, 0x17, 0x00, 0x01, 0x00, 0x01,
        0x00, 0x11, 0x00, 0x12, 0x00, 0x29, 0x00, 0xe5, 0x08, 0x49, 0x0a, 0x47,
        0x00, 0xad, 0x00, 0x06, 0x00, 0x8f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x05,
        0x00, 0x0c, 0x00, 0x93, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x05, 0x00, 0x0c, 0x00, 0x27, 0x00, 0x17, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0c, 0x00, 0x46,
        0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
        0x00, 0x0c, 0x00, 0xa2, 0x01, 0x3a, 0x00, 0x03, 0x00, 0x64, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x26, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
        0x00, 0x2c, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33,
        0x00, 0x66, 0x00, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x50, 0x6f, 0x69,
        0x6e, 0x74, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20, 0x20, 0x02, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x25, 0x00, 0x28,
        0x00, 0x0b, 0x00, 0xff, 0xff, 0x42, 0x00, 0xff, 0xff, 0x51, 0x00, 0xff,
        0xff, 0x64, 0x00, 0xff, 0xff, 0x19, 0x01, 0xff, 0xff, 0x1a, 0x01, 0xff,
        0xff, 0x2a, 0x01, 0xff, 0xff, 0x85, 0x01, 0xff, 0xff, 0xb6, 0x01, 0xff,
        0xff, 0xb9, 0x01, 0xff, 0xff, 0x1e, 0x00, 0x00, 0x00
    };

    class TestReader : public game::v3::udata::Reader {
     public:
        virtual bool handleRecord(uint16_t recordId, afl::base::ConstBytes_t data)
            { m_trace += afl::string::Format("(%d:%d)", recordId, data.size()); return true; }
        virtual void handleError(afl::io::Stream& /*in*/)
            { m_trace += "(err)"; }
        virtual void handleEnd()
            { m_trace += "(end)"; }
        const String_t& get() const
            { return m_trace; }
     private:
        String_t m_trace;
    };
}



/** Test check(): regular case. */
void
TestGameV3UdataReader::testCheck()
{
    afl::io::ConstMemoryStream ms(UTILDAT);
    game::Timestamp ts;
    bool result = game::v3::udata::Reader::check(ms, &ts);

    TS_ASSERT(result);
    TS_ASSERT_EQUALS(ms.getPos(), 0U);
    TS_ASSERT_EQUALS(ts.getTimestampAsString(), "08-09-201909:00:02");
}

/** Test check(): offset handling. */
void
TestGameV3UdataReader::testCheckOffset()
{
    const size_t N = 17;
    String_t image(N, 'x');
    image += afl::string::fromBytes(UTILDAT);

    afl::io::ConstMemoryStream ms(afl::string::toBytes(image));
    ms.setPos(17);

    game::Timestamp ts;
    bool result = game::v3::udata::Reader::check(ms, &ts);

    TS_ASSERT(result);
    TS_ASSERT_EQUALS(ms.getPos(), N);
    TS_ASSERT_EQUALS(ts.getTimestampAsString(), "08-09-201909:00:02");
}

/** Test check(): text file detection. */
void
TestGameV3UdataReader::testCheckText()
{
    afl::io::ConstMemoryStream ms(afl::string::toBytes("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis sem velit, ultrices et, fermentum auctor, rhoncus ut, ligula."));
    TS_ASSERT(!game::v3::udata::Reader::check(ms, 0));
}

/** Test check(): empty content. */
void
TestGameV3UdataReader::testCheckEmpty()
{
    afl::io::ConstMemoryStream ms(afl::base::Nothing);
    TS_ASSERT(!game::v3::udata::Reader::check(ms, 0));
}

/** Test check(): truncated in chunk header. */
void
TestGameV3UdataReader::testCheckTrunc1()
{
    afl::io::ConstMemoryStream ms(afl::base::ConstBytes_t(UTILDAT).trim(3));
    TS_ASSERT(!game::v3::udata::Reader::check(ms, 0));
}

/** Test check(): truncated in header payload. */
void
TestGameV3UdataReader::testCheckTrunc2()
{
    afl::io::ConstMemoryStream ms(afl::base::ConstBytes_t(UTILDAT).trim(30));
    TS_ASSERT(!game::v3::udata::Reader::check(ms, 0));
}

/** Test read(): regular sequence. */
void
TestGameV3UdataReader::testRead()
{
    afl::io::ConstMemoryStream ms(UTILDAT);
    TestReader rdr;
    rdr.read(ms);
    TS_ASSERT_EQUALS(rdr.get(),
                     "(13:89)"                              // control
                     "(17:18)(17:18)(17:18)(17:18)(17:18)"  // ion storms
                     "(5:12)(5:12)(5:12)(5:12)"             // exploration
                     "(38:16)"                              // activity
                     "(48:44)"                              // pal
                     "(51:102)"                             // player score
                     "(37:40)"                              // remote control
                     "(30:0)"                               // end marker
                     "(end)");
}

/** Test read(): read error (truncated). */
void
TestGameV3UdataReader::testReadFail()
{
    afl::io::ConstMemoryStream ms(afl::base::ConstBytes_t(UTILDAT).trim(11*12));
    TestReader rdr;
    rdr.read(ms);
    TS_ASSERT_EQUALS(rdr.get(), "(13:89)(17:18)(err)(end)");
}

