#include <fesa/model/domain.hpp>

#include <cstddef>
#include <stdexcept>

namespace fesa {

Domain::Domain(
    std::vector<Node> nodes,
    std::vector<BeamElement> beam_elements,
    std::vector<IsotropicElastic> materials,
    std::vector<BeamSection> sections,
    std::vector<NodeSet> node_sets,
    std::vector<ElementSet> element_sets,
    StepDefinition step)
    : nodes_{std::move(nodes)},
      beam_elements_{std::move(beam_elements)},
      materials_{std::move(materials)},
      sections_{std::move(sections)},
      node_sets_{std::move(node_sets)},
      element_sets_{std::move(element_sets)},
      step_{std::move(step)} {
    for (std::size_t index = 0; index < nodes_.size(); ++index) {
        node_indices_.emplace(nodes_[index].id.value(), index);
        node_origin_indices_.emplace(origin_key(nodes_[index].origin), index);
    }
    for (std::size_t index = 0; index < beam_elements_.size(); ++index) {
        beam_element_indices_.emplace(
            beam_elements_[index].id.value(), index);
        beam_element_origin_indices_.emplace(
            origin_key(beam_elements_[index].origin), index);
    }
    for (std::size_t index = 0; index < materials_.size(); ++index) {
        material_indices_.emplace(materials_[index].id.value(), index);
    }
    for (std::size_t index = 0; index < sections_.size(); ++index) {
        section_indices_.emplace(sections_[index].id.value(), index);
    }
}

const Node& Domain::node(const NodeId id) const {
    const auto found = node_indices_.find(id.value());
    if (found == node_indices_.end()) {
        throw std::out_of_range{"Node ID is not present in the Domain."};
    }
    return nodes_[found->second];
}

const Node& Domain::node(const EntityOrigin& origin) const {
    const auto found = node_origin_indices_.find(origin_key(origin));
    if (found == node_origin_indices_.end()) {
        throw std::out_of_range{
            "Node origin is not present in the Domain."};
    }
    return nodes_[found->second];
}

const IsotropicElastic& Domain::material(const MaterialId id) const {
    const auto found = material_indices_.find(id.value());
    if (found == material_indices_.end()) {
        throw std::out_of_range{
            "Material ID is not present in the Domain."};
    }
    return materials_[found->second];
}

const BeamSection& Domain::section(const SectionId id) const {
    const auto found = section_indices_.find(id.value());
    if (found == section_indices_.end()) {
        throw std::out_of_range{
            "Section ID is not present in the Domain."};
    }
    return sections_[found->second];
}

Domain::OriginKey Domain::origin_key(const EntityOrigin& origin) {
    return {origin.instance_name, origin.local_label};
}

}  // namespace fesa
