// Copyright 2022 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <memory>

#include "benchmark/benchmark.h"
#include "src/tint/utils/rtti/switch.h"

namespace tint {
namespace {

struct Base : public Castable<Base> {};
struct A : public Castable<A, Base> {};
struct AA : public Castable<AA, A> {};
struct AAA : public Castable<AAA, AA> {};
struct AAB : public Castable<AAB, AA> {};
struct AAC : public Castable<AAC, AA> {};
struct AB : public Castable<AB, A> {};
struct ABA : public Castable<ABA, AB> {};
struct ABB : public Castable<ABB, AB> {};
struct ABC : public Castable<ABC, AB> {};
struct AC : public Castable<AC, A> {};
struct ACA : public Castable<ACA, AC> {};
struct ACB : public Castable<ACB, AC> {};
struct ACC : public Castable<ACC, AC> {};
struct B : public Castable<B, Base> {};
struct BA : public Castable<BA, B> {};
struct BAA : public Castable<BAA, BA> {};
struct BAB : public Castable<BAB, BA> {};
struct BAC : public Castable<BAC, BA> {};
struct BB : public Castable<BB, B> {};
struct BBA : public Castable<BBA, BB> {};
struct BBB : public Castable<BBB, BB> {};
struct BBC : public Castable<BBC, BB> {};
struct BC : public Castable<BC, B> {};
struct BCA : public Castable<BCA, BC> {};
struct BCB : public Castable<BCB, BC> {};
struct BCC : public Castable<BCC, BC> {};
struct C : public Castable<C, Base> {};
struct CA : public Castable<CA, C> {};
struct CAA : public Castable<CAA, CA> {};
struct CAB : public Castable<CAB, CA> {};
struct CAC : public Castable<CAC, CA> {};
struct CB : public Castable<CB, C> {};
struct CBA : public Castable<CBA, CB> {};
struct CBB : public Castable<CBB, CB> {};
struct CBC : public Castable<CBC, CB> {};
struct CC : public Castable<CC, C> {};
struct CCA : public Castable<CCA, CC> {};
struct CCB : public Castable<CCB, CC> {};
struct CCC : public Castable<CCC, CC> {};

using AllTypes = std::tuple<Base,
                            A,
                            AA,
                            AAA,
                            AAB,
                            AAC,
                            AB,
                            ABA,
                            ABB,
                            ABC,
                            AC,
                            ACA,
                            ACB,
                            ACC,
                            B,
                            BA,
                            BAA,
                            BAB,
                            BAC,
                            BB,
                            BBA,
                            BBB,
                            BBC,
                            BC,
                            BCA,
                            BCB,
                            BCC,
                            C,
                            CA,
                            CAA,
                            CAB,
                            CAC,
                            CB,
                            CBA,
                            CBB,
                            CBC,
                            CC,
                            CCA,
                            CCB,
                            CCC>;

std::vector<std::unique_ptr<Base>> MakeObjects() {
    std::vector<std::unique_ptr<Base>> out;
    out.emplace_back(std::make_unique<Base>());
    out.emplace_back(std::make_unique<A>());
    out.emplace_back(std::make_unique<AA>());
    out.emplace_back(std::make_unique<AAA>());
    out.emplace_back(std::make_unique<AAB>());
    out.emplace_back(std::make_unique<AAC>());
    out.emplace_back(std::make_unique<AB>());
    out.emplace_back(std::make_unique<ABA>());
    out.emplace_back(std::make_unique<ABB>());
    out.emplace_back(std::make_unique<ABC>());
    out.emplace_back(std::make_unique<AC>());
    out.emplace_back(std::make_unique<ACA>());
    out.emplace_back(std::make_unique<ACB>());
    out.emplace_back(std::make_unique<ACC>());
    out.emplace_back(std::make_unique<B>());
    out.emplace_back(std::make_unique<BA>());
    out.emplace_back(std::make_unique<BAA>());
    out.emplace_back(std::make_unique<BAB>());
    out.emplace_back(std::make_unique<BAC>());
    out.emplace_back(std::make_unique<BB>());
    out.emplace_back(std::make_unique<BBA>());
    out.emplace_back(std::make_unique<BBB>());
    out.emplace_back(std::make_unique<BBC>());
    out.emplace_back(std::make_unique<BC>());
    out.emplace_back(std::make_unique<BCA>());
    out.emplace_back(std::make_unique<BCB>());
    out.emplace_back(std::make_unique<BCC>());
    out.emplace_back(std::make_unique<C>());
    out.emplace_back(std::make_unique<CA>());
    out.emplace_back(std::make_unique<CAA>());
    out.emplace_back(std::make_unique<CAB>());
    out.emplace_back(std::make_unique<CAC>());
    out.emplace_back(std::make_unique<CB>());
    out.emplace_back(std::make_unique<CBA>());
    out.emplace_back(std::make_unique<CBB>());
    out.emplace_back(std::make_unique<CBC>());
    out.emplace_back(std::make_unique<CC>());
    out.emplace_back(std::make_unique<CCA>());
    out.emplace_back(std::make_unique<CCB>());
    out.emplace_back(std::make_unique<CCC>());
    return out;
}

void CastableLargeSwitch(::benchmark::State& state) {
    auto objects = MakeObjects();
    size_t i = 0;
    for (auto _ : state) {
        auto* object = objects[i % objects.size()].get();
        Switch(
            object,  //
            [&](const AAA*) { ::benchmark::DoNotOptimize(i += 40); },
            [&](const AAB*) { ::benchmark::DoNotOptimize(i += 50); },
            [&](const AAC*) { ::benchmark::DoNotOptimize(i += 60); },
            [&](const ABA*) { ::benchmark::DoNotOptimize(i += 80); },
            [&](const ABB*) { ::benchmark::DoNotOptimize(i += 90); },
            [&](const ABC*) { ::benchmark::DoNotOptimize(i += 100); },
            [&](const ACA*) { ::benchmark::DoNotOptimize(i += 120); },
            [&](const ACB*) { ::benchmark::DoNotOptimize(i += 130); },
            [&](const ACC*) { ::benchmark::DoNotOptimize(i += 140); },
            [&](const BAA*) { ::benchmark::DoNotOptimize(i += 170); },
            [&](const BAB*) { ::benchmark::DoNotOptimize(i += 180); },
            [&](const BAC*) { ::benchmark::DoNotOptimize(i += 190); },
            [&](const BBA*) { ::benchmark::DoNotOptimize(i += 210); },
            [&](const BBB*) { ::benchmark::DoNotOptimize(i += 220); },
            [&](const BBC*) { ::benchmark::DoNotOptimize(i += 230); },
            [&](const BCA*) { ::benchmark::DoNotOptimize(i += 250); },
            [&](const BCB*) { ::benchmark::DoNotOptimize(i += 260); },
            [&](const BCC*) { ::benchmark::DoNotOptimize(i += 270); },
            [&](const CA*) { ::benchmark::DoNotOptimize(i += 290); },
            [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); },
            [&](const CAB*) { ::benchmark::DoNotOptimize(i += 310); },
            [&](const CAC*) { ::benchmark::DoNotOptimize(i += 320); },
            [&](const CBA*) { ::benchmark::DoNotOptimize(i += 340); },
            [&](const CBB*) { ::benchmark::DoNotOptimize(i += 350); },
            [&](const CBC*) { ::benchmark::DoNotOptimize(i += 360); },
            [&](const CCA*) { ::benchmark::DoNotOptimize(i += 380); },
            [&](const CCB*) { ::benchmark::DoNotOptimize(i += 390); },
            [&](const CCC*) { ::benchmark::DoNotOptimize(i += 400); },
            [&](Default) { ::benchmark::DoNotOptimize(i += 123); });
        i = (i * 31) ^ (i << 5);
    }
}

BENCHMARK(CastableLargeSwitch);

void CastableMediumSwitch(::benchmark::State& state) {
    auto objects = MakeObjects();
    size_t i = 0;
    for (auto _ : state) {
        auto* object = objects[i % objects.size()].get();
        Switch(
            object,  //
            [&](const ACB*) { ::benchmark::DoNotOptimize(i += 130); },
            [&](const BAA*) { ::benchmark::DoNotOptimize(i += 170); },
            [&](const BAB*) { ::benchmark::DoNotOptimize(i += 180); },
            [&](const BBA*) { ::benchmark::DoNotOptimize(i += 210); },
            [&](const BBB*) { ::benchmark::DoNotOptimize(i += 220); },
            [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); },
            [&](const CCA*) { ::benchmark::DoNotOptimize(i += 380); },
            [&](const CCB*) { ::benchmark::DoNotOptimize(i += 390); },
            [&](const CCC*) { ::benchmark::DoNotOptimize(i += 400); },
            [&](Default) { ::benchmark::DoNotOptimize(i += 123); });
        i = (i * 31) ^ (i << 5);
    }
}

BENCHMARK(CastableMediumSwitch);

void CastableSmallSwitch(::benchmark::State& state) {
    auto objects = MakeObjects();
    size_t i = 0;
    for (auto _ : state) {
        auto* object = objects[i % objects.size()].get();
        Switch(
            object,  //
            [&](const AAB*) { ::benchmark::DoNotOptimize(i += 30); },
            [&](const CAC*) { ::benchmark::DoNotOptimize(i += 290); },
            [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); });
        i = (i * 31) ^ (i << 5);
    }
}

BENCHMARK(CastableSmallSwitch);

}  // namespace
}  // namespace tint

TINT_INSTANTIATE_TYPEINFO(tint::Base);
TINT_INSTANTIATE_TYPEINFO(tint::A);
TINT_INSTANTIATE_TYPEINFO(tint::AA);
TINT_INSTANTIATE_TYPEINFO(tint::AAA);
TINT_INSTANTIATE_TYPEINFO(tint::AAB);
TINT_INSTANTIATE_TYPEINFO(tint::AAC);
TINT_INSTANTIATE_TYPEINFO(tint::AB);
TINT_INSTANTIATE_TYPEINFO(tint::ABA);
TINT_INSTANTIATE_TYPEINFO(tint::ABB);
TINT_INSTANTIATE_TYPEINFO(tint::ABC);
TINT_INSTANTIATE_TYPEINFO(tint::AC);
TINT_INSTANTIATE_TYPEINFO(tint::ACA);
TINT_INSTANTIATE_TYPEINFO(tint::ACB);
TINT_INSTANTIATE_TYPEINFO(tint::ACC);
TINT_INSTANTIATE_TYPEINFO(tint::B);
TINT_INSTANTIATE_TYPEINFO(tint::BA);
TINT_INSTANTIATE_TYPEINFO(tint::BAA);
TINT_INSTANTIATE_TYPEINFO(tint::BAB);
TINT_INSTANTIATE_TYPEINFO(tint::BAC);
TINT_INSTANTIATE_TYPEINFO(tint::BB);
TINT_INSTANTIATE_TYPEINFO(tint::BBA);
TINT_INSTANTIATE_TYPEINFO(tint::BBB);
TINT_INSTANTIATE_TYPEINFO(tint::BBC);
TINT_INSTANTIATE_TYPEINFO(tint::BC);
TINT_INSTANTIATE_TYPEINFO(tint::BCA);
TINT_INSTANTIATE_TYPEINFO(tint::BCB);
TINT_INSTANTIATE_TYPEINFO(tint::BCC);
TINT_INSTANTIATE_TYPEINFO(tint::C);
TINT_INSTANTIATE_TYPEINFO(tint::CA);
TINT_INSTANTIATE_TYPEINFO(tint::CAA);
TINT_INSTANTIATE_TYPEINFO(tint::CAB);
TINT_INSTANTIATE_TYPEINFO(tint::CAC);
TINT_INSTANTIATE_TYPEINFO(tint::CB);
TINT_INSTANTIATE_TYPEINFO(tint::CBA);
TINT_INSTANTIATE_TYPEINFO(tint::CBB);
TINT_INSTANTIATE_TYPEINFO(tint::CBC);
TINT_INSTANTIATE_TYPEINFO(tint::CC);
TINT_INSTANTIATE_TYPEINFO(tint::CCA);
TINT_INSTANTIATE_TYPEINFO(tint::CCB);
TINT_INSTANTIATE_TYPEINFO(tint::CCC);
