/**
 * @file   apply_test.cpp
 * @author Nat Goodspeed
 * @date   2022-12-19
 * @brief  Test for apply.
 * 
 * $LicenseInfo:firstyear=2022&license=viewerlgpl$
 * Copyright (c) 2022, Linden Research, Inc.
 * $/LicenseInfo$
 */

// Precompiled header
#include "linden_common.h"
// associated header
#include "apply.h"
// STL headers
// std headers
#include <iomanip>
// external library headers
// other Linden headers
#include "llsd.h"
#include "llsdutil.h"
#include <array>
#include <string>
#include <vector>

// for ensure_equals
std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& stringvec)
{
    const char* delim = "[";
    for (const auto& str : stringvec)
    {
        out << delim << std::quoted(str);
        delim = ", ";
    }
    return out << ']';
}

// the above must be declared BEFORE ensure_equals(std::vector<std::string>)
#include "../test/lltut.h"

/*****************************************************************************
*   TUT
*****************************************************************************/
namespace tut
{
    namespace statics
    {
        /*------------------------------ data ------------------------------*/
        // Although we're using types from the LLSD namespace, we're not
        // constructing LLSD values, but rather instances of the C++ types
        // supported by LLSD.
        static LLSD::Boolean b{true};
        static LLSD::Integer i{17};
        static LLSD::Real    f{3.14};
        static LLSD::String  s{ "hello" };
        static LLSD::UUID    uu{ "baadf00d-dead-beef-baad-feedb0ef" };
        static LLSD::Date    dt{ "2022-12-19" };
        static LLSD::URI     uri{ "http://secondlife.com" };
        static LLSD::Binary  bin{ 0x01, 0x02, 0x03, 0x04, 0x05 };

        static std::vector<LLSD::String> quick
        {
            "The", "quick", "brown", "fox", "etc."
        };

        static std::array<int, 5> fibs
        {
            0, 1, 1, 2, 3
        };

        // ensure that apply() actually reaches the target method --
        // lack of ensure_equals() failure could be due to no-op apply()
        bool called{ false };
        // capture calls from collect()
        std::vector<std::string> collected;

        /*------------------------- test functions -------------------------*/
        void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s,
                     const LLSD::UUID& uu, const LLSD::Date& dt,
                     const LLSD::URI& uri, const LLSD::Binary& bin)
        {
            called = true;
            ensure_equals(  "b mismatch", b,   statics::b);
            ensure_equals(  "i mismatch", i,   statics::i);
            ensure_equals(  "f mismatch", f,   statics::f);
            ensure_equals(  "s mismatch", s,   statics::s);
            ensure_equals( "uu mismatch", uu,  statics::uu);
            ensure_equals( "dt mismatch", dt,  statics::dt);
            ensure_equals("uri mismatch", uri, statics::uri);
            ensure_equals("bin mismatch", bin, statics::bin);
        }

        void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4)
        {
            called = true;
            ensure_equals("s0 mismatch", s0, statics::quick[0]);
            ensure_equals("s1 mismatch", s1, statics::quick[1]);
            ensure_equals("s2 mismatch", s2, statics::quick[2]);
            ensure_equals("s3 mismatch", s3, statics::quick[3]);
            ensure_equals("s4 mismatch", s4, statics::quick[4]);
        }

        void ints(int i0, int i1, int i2, int i3, int i4)
        {
            called = true;
            ensure_equals("i0 mismatch", i0, statics::fibs[0]);
            ensure_equals("i1 mismatch", i1, statics::fibs[1]);
            ensure_equals("i2 mismatch", i2, statics::fibs[2]);
            ensure_equals("i3 mismatch", i3, statics::fibs[3]);
            ensure_equals("i4 mismatch", i4, statics::fibs[4]);
        }

        void sdfunc(const LLSD& sd)
        {
            called = true;
            ensure_equals("sd mismatch", sd.asInteger(), statics::i);
        }

        void intfunc(int i)
        {
            called = true;
            ensure_equals("i mismatch", i, statics::i);
        }

        void voidfunc()
        {
            called = true;
        }

        // recursion tail
        void collect()
        {
            called = true;
        }

        // collect(arbitrary)
        template <typename... ARGS>
        void collect(const std::string& first, ARGS&&... rest)
        {
            statics::collected.push_back(first);
            collect(std::forward<ARGS>(rest)...);
        }
    } // namespace statics

    struct apply_data
    {
        apply_data()
        {
            // reset called before each test
            statics::called = false;
            statics::collected.clear();
        }
    };
    typedef test_group<apply_data> apply_group;
    typedef apply_group::object object;
    apply_group applygrp("apply");

    template<> template<>
    void object::test<1>()
    {
        set_test_name("apply(tuple)");
        LL::apply(statics::various,
                  std::make_tuple(statics::b, statics::i, statics::f, statics::s,
                                  statics::uu, statics::dt, statics::uri, statics::bin));
        ensure("apply(tuple) failed", statics::called);
    }

    template<> template<>
    void object::test<2>()
    {
        set_test_name("apply(array)");
        LL::apply(statics::ints, statics::fibs);
        ensure("apply(array) failed", statics::called);
    }

    template<> template<>
    void object::test<3>()
    {
        set_test_name("apply(vector)");
        LL::apply(statics::strings, statics::quick);
        ensure("apply(vector) failed", statics::called);
    }

    // The various apply(LLSD) tests exercise only the success cases because
    // the failure cases trigger assert() fail, which is hard to catch.
    template<> template<>
    void object::test<4>()
    {
        set_test_name("apply(LLSD())");
        LL::apply(statics::voidfunc, LLSD());
        ensure("apply(LLSD()) failed", statics::called);
    }

    template<> template<>
    void object::test<5>()
    {
        set_test_name("apply(fn(int), LLSD scalar)");
        LL::apply(statics::intfunc, LLSD(statics::i));
        ensure("apply(fn(int), LLSD scalar) failed", statics::called);
    }

    template<> template<>
    void object::test<6>()
    {
        set_test_name("apply(fn(LLSD), LLSD scalar)");
        // This test verifies that LLSDParam<LLSD> doesn't send the compiler
        // into infinite recursion when the target is itself LLSD.
        LL::apply(statics::sdfunc, LLSD(statics::i));
        ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called);
    }

    template<> template<>
    void object::test<7>()
    {
        set_test_name("apply(LLSD array)");
        LL::apply(statics::various,
                  llsd::array(statics::b, statics::i, statics::f, statics::s,
                              statics::uu, statics::dt, statics::uri, statics::bin));
        ensure("apply(LLSD array) failed", statics::called);
    }

    template<> template<>
    void object::test<8>()
    {
        set_test_name("VAPPLY()");
        // Make a std::array<std::string> from statics::quick. We can't call a
        // variadic function with a data structure of dynamic length.
        std::array<std::string, 5> strray;
        for (size_t i = 0; i < strray.size(); ++i)
            strray[i] = statics::quick[i];
        // This doesn't work: the compiler doesn't know which overload of
        // collect() to pass to LL::apply().
        // LL::apply(statics::collect, strray);
        // That's what VAPPLY() is for.
        VAPPLY(statics::collect, strray);
        ensure("VAPPLY() failed", statics::called);
        ensure_equals("collected mismatch", statics::collected, statics::quick);
    }
} // namespace tut
