#include "fesa/model/model_types.hpp"

#include <gtest/gtest.h>

#include <array>
#include <cstdint>
#include <filesystem>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

namespace {

template<class T, class = void>
struct HasEquationId : std::false_type {};

template<class T>
struct HasEquationId<T, std::void_t<decltype(std::declval<T&>().equationId)>>
    : std::true_type {};

template<class T, class = void>
struct HasEquationIds : std::false_type {};

template<class T>
struct HasEquationIds<T, std::void_t<decltype(std::declval<T&>().equationIds)>>
    : std::true_type {};

} // namespace

TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
    const fesa::SourceLocation nodeLocation{
        std::filesystem::path{"models/beam.inp"}, 11U};
    const fesa::SourceEntityId firstIdentity{"Instance-A", 7, "0007"};
    const fesa::SourceEntityId equalIdentity{"Instance-A", 7, "0007"};
    const fesa::SourceEntityId laterIdentity{"Instance-B", 7, "0007"};

    EXPECT_EQ(
        std::make_tuple(
            firstIdentity.instanceName,
            firstIdentity.sourceLabel,
            firstIdentity.sourceLabelText),
        std::make_tuple(
            equalIdentity.instanceName,
            equalIdentity.sourceLabel,
            equalIdentity.sourceLabelText));
    EXPECT_LT(
        std::make_tuple(
            firstIdentity.instanceName,
            firstIdentity.sourceLabel,
            firstIdentity.sourceLabelText),
        std::make_tuple(
            laterIdentity.instanceName,
            laterIdentity.sourceLabel,
            laterIdentity.sourceLabelText));

    const fesa::Node node{firstIdentity, {1.0, 2.0, 3.0}, nodeLocation};
    const fesa::LinearElasticMaterial material{
        "Steel", 210.0e9, 0.3, {"models/beam.inp", 30U}};
    const fesa::GeneralBeamSection section{
        "Section-1",
        0.02,
        1.0e-5,
        0.0,
        2.0e-5,
        5.0e-6,
        {0.0, 1.0, 0.0},
        {{-0.1, 0.0}, {0.1, 0.0}},
        {"models/beam.inp", 40U}};
    const fesa::EulerBeam3DDefinition element{
        {"Instance-A", 3, "0003"},
        {fesa::EntityIndex{5}, fesa::EntityIndex{9}},
        fesa::EntityIndex{2},
        fesa::EntityIndex{4},
        {"models/beam.inp", 20U}};
    const fesa::BoundaryCondition boundary{
        "Root", 1, 6, 0.0, {"models/beam.inp", 60U}};
    const fesa::NodalLoad load{
        "Tip", 3, -1000.0, {"models/beam.inp", 70U}};
    const fesa::StaticStepDefinition step{
        "Step-1",
        {boundary},
        {load},
        0.1,
        1.0,
        1.0e-5,
        1.0,
        {"models/beam.inp", 50U}};
    const fesa::NodeSet nodeSet{
        "Root", std::string{"Instance-A"}, {5U}, {"models/beam.inp", 25U}};
    const fesa::ElementSet elementSet{
        "Beam", std::string{"Instance-A"}, {4U}, {"models/beam.inp", 26U}};
    const fesa::PartDefinition part{
        "BeamPart", {7, 8}, {3}, {"Root", "Tip"}, {"Beam"},
        {"models/beam.inp", 2U}};
    const fesa::InstanceDefinition instance{
        "Instance-A",
        "BeamPart",
        {{7, 5U}, {8, 9U}},
        {{3, 4U}},
        {"models/beam.inp", 80U}};

    fesa::ModelDefinition definition{};
    definition.sourcePath = "models/beam.inp";
    definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
    definition.heading = "Identity mapping fixture";
    definition.nodes = {node};
    definition.elements = {element};
    definition.materials = {material};
    definition.sections = {section};
    definition.nodeSets = {nodeSet};
    definition.elementSets = {elementSet};
    definition.parts = {part};
    definition.instances = {instance};
    definition.steps = {step};
    definition.warnings = {{
        fesa::Severity::warning,
        "ignored-output-request",
        {"models/beam.inp", 75U},
        "*OUTPUT",
        "",
        "Output request does not alter mandatory FESA results."}};

    ASSERT_EQ(definition.nodes.size(), 1U);
    EXPECT_EQ(definition.nodes[0].sourceId.sourceLabelText, "0007");
    EXPECT_EQ(definition.nodes[0].location.line, 11U);
    ASSERT_EQ(definition.instances.size(), 1U);
    EXPECT_EQ(definition.instances[0].partName, "BeamPart");
    EXPECT_EQ(definition.instances[0].nodeMappings[0].sourceLabel, 7);
    EXPECT_EQ(definition.instances[0].nodeMappings[0].internalIndex, 5U);
    EXPECT_EQ(definition.instances[0].elementMappings[0].internalIndex, 4U);
    EXPECT_EQ(definition.steps[0].initialIncrement, 0.1);
    EXPECT_EQ(definition.steps[0].timePeriod, 1.0);
    EXPECT_EQ(definition.steps[0].minimumIncrement, 1.0e-5);
    EXPECT_EQ(definition.steps[0].maximumIncrement, 1.0);
    ASSERT_EQ(definition.warnings.size(), 1U);
    EXPECT_EQ(definition.warnings[0].location.line, 75U);

    static_assert(!HasEquationId<fesa::Node>::value);
    static_assert(!HasEquationIds<fesa::Node>::value);
    static_assert(!HasEquationId<fesa::EulerBeam3DDefinition>::value);
    static_assert(!HasEquationIds<fesa::EulerBeam3DDefinition>::value);
}

// MITC4-MODEL-001
TEST(DomainModel, Mitc4ShellRecordsPreserveSourceAndInternalIdentity) {
    const fesa::Mitc4ShellDefinition s4Element{
        {"Shell-Instance", 41, "0041"},
        fesa::ShellSourceElementType::s4,
        {fesa::EntityIndex{3},
         fesa::EntityIndex{5},
         fesa::EntityIndex{8},
         fesa::EntityIndex{13}},
        fesa::EntityIndex{2},
        fesa::EntityIndex{7},
        {"models/shell.inp", 20U}};
    const fesa::Mitc4ShellDefinition s4rElement{
        {"Shell-Instance", 42, "0042"},
        fesa::ShellSourceElementType::s4r,
        {fesa::EntityIndex{13},
         fesa::EntityIndex{8},
         fesa::EntityIndex{5},
         fesa::EntityIndex{3}},
        fesa::EntityIndex{2},
        fesa::EntityIndex{7},
        {"models/shell.inp", 21U}};

    EXPECT_EQ(s4Element.sourceId.instanceName, "Shell-Instance");
    EXPECT_EQ(s4Element.sourceId.sourceLabel, 41);
    EXPECT_EQ(s4Element.sourceId.sourceLabelText, "0041");
    EXPECT_EQ(s4Element.sourceType, fesa::ShellSourceElementType::s4);
    EXPECT_EQ(s4rElement.sourceType, fesa::ShellSourceElementType::s4r);
    EXPECT_NE(s4Element.sourceType, s4rElement.sourceType);
    EXPECT_EQ(
        fesa::kMitc4InternalFormulation,
        std::string_view{"FESA-MITC4"});
    EXPECT_EQ(
        s4Element.nodeIndices,
        (std::array<fesa::EntityIndex, 4>{3U, 5U, 8U, 13U}));
    EXPECT_EQ(s4Element.materialIndex, 2U);
    EXPECT_EQ(s4Element.sectionIndex, 7U);

    static_assert(!HasEquationId<fesa::Mitc4ShellDefinition>::value);
    static_assert(!HasEquationIds<fesa::Mitc4ShellDefinition>::value);
}
