#include "fesa/analysis/analysis_model.hpp"

#include <string>
#include <vector>

namespace fesa {

Result<AnalysisModel> AnalysisModel::create(const Domain& domain) {
    if (domain.steps().empty()) {
        return Result<AnalysisModel>::failure(Status::failure(
            FailureCategory::input,
            {{Severity::error,
              "invalid-model-cardinality",
              {domain.sourcePath(), 0U},
              "STEP",
              "0",
              "AnalysisModel requires exactly one static step."}}));
    }
    if (domain.steps().size() > 1U) {
        const auto& secondStep = domain.steps()[1];
        return Result<AnalysisModel>::failure(Status::failure(
            FailureCategory::input,
            {{Severity::error,
              "unsupported-multiple-step",
              secondStep.location,
              "STEP",
              secondStep.name,
              "AnalysisModel does not support multiple steps."}}));
    }
    return Result<AnalysisModel>::success(AnalysisModel{domain});
}

const Domain& AnalysisModel::domain() const noexcept {
    return *domain_;
}

const StaticStepDefinition& AnalysisModel::step() const noexcept {
    return domain_->steps().front();
}

const std::vector<EntityIndex>& AnalysisModel::activeElements() const noexcept {
    return activeElements_;
}

const std::vector<EntityIndex>& AnalysisModel::activeMaterials() const noexcept {
    return activeMaterials_;
}

const std::vector<EntityIndex>& AnalysisModel::activeSections() const noexcept {
    return activeSections_;
}

const std::vector<EntityIndex>&
AnalysisModel::activeBoundaryConditions() const noexcept {
    return activeBoundaryConditions_;
}

const std::vector<EntityIndex>& AnalysisModel::activeLoads() const noexcept {
    return activeLoads_;
}

AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} {
    std::vector<bool> reachableMaterials(domain.materials().size(), false);
    std::vector<bool> reachableSections(domain.sections().size(), false);

    for (std::size_t index = 0U; index < domain.elements().size(); ++index) {
        const auto& element = domain.elements()[index];
        activeElements_.push_back(static_cast<EntityIndex>(index));
        reachableMaterials[element.materialIndex] = true;
        reachableSections[element.sectionIndex] = true;
    }

    // Ascending vector positions are the stable internal order, independent
    // of first reachability or duplicate element assignments.
    for (std::size_t index = 0U; index < reachableMaterials.size(); ++index) {
        if (reachableMaterials[index]) {
            activeMaterials_.push_back(static_cast<EntityIndex>(index));
        }
    }
    for (std::size_t index = 0U; index < reachableSections.size(); ++index) {
        if (reachableSections[index]) {
            activeSections_.push_back(static_cast<EntityIndex>(index));
        }
    }

    for (std::size_t index = 0U;
         index < step().boundaries.size();
         ++index) {
        activeBoundaryConditions_.push_back(static_cast<EntityIndex>(index));
    }
    for (std::size_t index = 0U; index < step().loads.size(); ++index) {
        activeLoads_.push_back(static_cast<EntityIndex>(index));
    }
}

} // namespace fesa
