#include "ExplicitDFTModelBuilder.h"

#include <map>
#include <storm/exceptions/IllegalArgumentException.h>

#include "storm/models/sparse/MarkovAutomaton.h"
#include "storm/models/sparse/Ctmc.h"
#include "storm/utility/constants.h"
#include "storm/utility/vector.h"
#include "storm/utility/bitoperations.h"
#include "storm/utility/ProgressMeasurement.h"
#include "storm/exceptions/InvalidArgumentException.h"
#include "storm/exceptions/UnexpectedException.h"
#include "storm/settings/SettingsManager.h"
#include "storm/logic/AtomicLabelFormula.h"
#include "storm-dft/settings/modules/FaultTreeSettings.h"


namespace storm {
    namespace builder {

        template<typename ValueType, typename StateType>
        ExplicitDFTModelBuilder<ValueType, StateType>::ModelComponents::ModelComponents() : transitionMatrix(), stateLabeling(), markovianStates(), exitRates(), choiceLabeling() {
            // Intentionally left empty.
        }

        template<typename ValueType, typename StateType>
        ExplicitDFTModelBuilder<ValueType, StateType>::MatrixBuilder::MatrixBuilder(bool canHaveNondeterminism) : mappingOffset(0), stateRemapping(), currentRowGroup(0), currentRow(0), canHaveNondeterminism((canHaveNondeterminism)) {
            // Create matrix builder
            builder = storm::storage::SparseMatrixBuilder<ValueType>(0, 0, 0, false, canHaveNondeterminism, 0);
        }

        template<typename ValueType, typename StateType>
        ExplicitDFTModelBuilder<ValueType, StateType>::ExplicitDFTModelBuilder(storm::storage::DFT<ValueType> const& dft, storm::storage::DFTIndependentSymmetries const& symmetries, std::set<size_t> const& relevantEvents, bool allowDCForRelevantEvents) :
                dft(dft),
                stateGenerationInfo(std::make_shared<storm::storage::DFTStateGenerationInfo>(dft.buildStateGenerationInfo(symmetries))),
                relevantEvents(relevantEvents),
                generator(dft, *stateGenerationInfo),
                matrixBuilder(!generator.isDeterministicModel()),
                stateStorage(dft.stateBitVectorSize()),
                explorationQueue(1, 0, 0.9, false)
        {
            // Set relevant events
            this->dft.setRelevantEvents(this->relevantEvents, allowDCForRelevantEvents);
            // Mark top level element as relevant
            this->dft.getElement(this->dft.getTopLevelIndex())->setRelevance(true);
            if (this->relevantEvents.empty()) {
                // Only interested in top level event -> introduce unique failed state
                this->uniqueFailedState = true;
                STORM_LOG_DEBUG("Using unique failed state with id 0.");
            }

            // Compute independent subtrees
            if (dft.topLevelType() == storm::storage::DFTElementType::OR) {
                // We only support this for approximation with top level element OR
                for (auto const& child : dft.getGate(dft.getTopLevelIndex())->children()) {
                    // Consider all children of the top level gate
                    std::vector<size_t> isubdft;
                    if (child->nrParents() > 1 || child->hasOutgoingDependencies()) {
                        STORM_LOG_TRACE("child " << child->name() << "does not allow modularisation.");
                        isubdft.clear();
                    } else if (dft.isGate(child->id())) {
                        isubdft = dft.getGate(child->id())->independentSubDft(false);
                    } else {
                        STORM_LOG_ASSERT(dft.isBasicElement(child->id()), "Child is no BE.");
                        if(dft.getBasicElement(child->id())->hasIngoingDependencies()) {
                            STORM_LOG_TRACE("child " << child->name() << "does not allow modularisation.");
                            isubdft.clear();
                        } else {
                            isubdft = {child->id()};
                        }
                    }
                    if(isubdft.empty()) {
                        subtreeBEs.clear();
                        break;
                    } else {
                        // Add new independent subtree
                        std::vector<size_t> subtree;
                        for (size_t id : isubdft) {
                            if (dft.isBasicElement(id)) {
                                subtree.push_back(id);
                            }
                        }
                        subtreeBEs.push_back(subtree);
                    }
                }
            }
            if (subtreeBEs.empty()) {
                // Consider complete tree
                std::vector<size_t> subtree;
                for (size_t id = 0; id < dft.nrElements(); ++id) {
                    if (dft.isBasicElement(id)) {
                        subtree.push_back(id);
                    }
                }
                subtreeBEs.push_back(subtree);
            }

            for (auto tree : subtreeBEs) {
                std::stringstream stream;
                stream << "Subtree: ";
                for (size_t id : tree) {
                    stream << id << ", ";
                }
                STORM_LOG_TRACE(stream.str());
            }
        }

        template<typename ValueType, typename StateType>
        void ExplicitDFTModelBuilder<ValueType, StateType>::buildModel(size_t iteration, double approximationThreshold, storm::builder::ApproximationHeuristic approximationHeuristic) {
            STORM_LOG_TRACE("Generating DFT state space");
            usedHeuristic = approximationHeuristic;

            if (approximationThreshold > 0 && !this->uniqueFailedState) {
                // Approximation requires unique failed states
                // TODO lift this restriction
                STORM_LOG_WARN("Approximation requires unique failed state. Forcing use of unique failed state.");
                this->uniqueFailedState = true;
            }

            if (iteration < 1) {
                // Initialize
                switch (usedHeuristic) {
                    case storm::builder::ApproximationHeuristic::DEPTH:
                        explorationQueue = storm::storage::BucketPriorityQueue<ExplorationHeuristic>(dft.nrElements()+1, 0, 0.9, false);
                        break;
                    case storm::builder::ApproximationHeuristic::PROBABILITY:
                        explorationQueue = storm::storage::BucketPriorityQueue<ExplorationHeuristic>(200, 0, 0.9, true);
                        break;
                    case storm::builder::ApproximationHeuristic::BOUNDDIFFERENCE:
                        explorationQueue = storm::storage::BucketPriorityQueue<ExplorationHeuristic>(200, 0, 0.9, true);
                        break;
                    default:
                        STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentException, "Heuristic not known.");
                }
                modelComponents.markovianStates = storm::storage::BitVector(INITIAL_BITVECTOR_SIZE);

                if (this->uniqueFailedState) {
                    // Introduce explicit fail state
                    storm::generator::StateBehavior<ValueType, StateType> behavior = generator.createMergeFailedState([this] (DFTStatePointer const& state) {
                        size_t failedStateId = newIndex++;
                        STORM_LOG_ASSERT(failedStateId == 0, "Unique failed id is not 0.");
                        matrixBuilder.stateRemapping.push_back(0);
                        return failedStateId;
                    } );

                    matrixBuilder.setRemapping(0);
                    STORM_LOG_ASSERT(!behavior.empty(), "Behavior is empty.");
                    matrixBuilder.newRowGroup();
                    setMarkovian(behavior.begin()->isMarkovian());

                    // Now add self loop.
                    // TODO: maybe use general method.
                    STORM_LOG_ASSERT(behavior.getNumberOfChoices() == 1, "Wrong number of choices for failed state.");
                    STORM_LOG_ASSERT(behavior.begin()->size() == 1, "Wrong number of transitions for failed state.");
                    std::pair<StateType, ValueType> stateProbabilityPair = *(behavior.begin()->begin());
                    STORM_LOG_ASSERT(stateProbabilityPair.first == 0, "No self loop for failed state.");
                    STORM_LOG_ASSERT(storm::utility::isOne<ValueType>(stateProbabilityPair.second), "Probability for failed state != 1.");
                    matrixBuilder.addTransition(stateProbabilityPair.first, stateProbabilityPair.second);
                    matrixBuilder.finishRow();
                }

                // Build initial state
                this->stateStorage.initialStateIndices = generator.getInitialStates(std::bind(&ExplicitDFTModelBuilder::getOrAddStateIndex, this, std::placeholders::_1));
                STORM_LOG_ASSERT(stateStorage.initialStateIndices.size() == 1, "Only one initial state assumed.");
                initialStateIndex = stateStorage.initialStateIndices[0];
                STORM_LOG_TRACE("Initial state: " << initialStateIndex);
                // Initialize heuristic values for inital state
                STORM_LOG_ASSERT(!statesNotExplored.at(initialStateIndex).second, "Heuristic for initial state is already initialized");
                ExplorationHeuristicPointer heuristic;
                switch (usedHeuristic) {
                    case storm::builder::ApproximationHeuristic::DEPTH:
                        heuristic = std::make_shared<DFTExplorationHeuristicDepth<ValueType>>(initialStateIndex);
                        break;
                    case storm::builder::ApproximationHeuristic::PROBABILITY:
                        heuristic = std::make_shared<DFTExplorationHeuristicProbability<ValueType>>(initialStateIndex);
                        break;
                    case storm::builder::ApproximationHeuristic::BOUNDDIFFERENCE:
                        heuristic = std::make_shared<DFTExplorationHeuristicBoundDifference<ValueType>>(initialStateIndex);
                        break;
                    default:
                        STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentException, "Heuristic not known.");
                }
                heuristic->markExpand();
                statesNotExplored[initialStateIndex].second = heuristic;
                explorationQueue.push(heuristic);
            } else {
                initializeNextIteration();
            }

            if (approximationThreshold > 0.0) {
                switch (usedHeuristic) {
                    case storm::builder::ApproximationHeuristic::DEPTH:
                        approximationThreshold = iteration + 1;
                        break;
                    case storm::builder::ApproximationHeuristic::PROBABILITY:
                    case storm::builder::ApproximationHeuristic::BOUNDDIFFERENCE:
                        approximationThreshold = std::pow(2, -(double)iteration); // Need conversion to avoid overflow when negating
                        break;
                    default:
                        STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentException, "Heuristic not known.");
                }
            }
            exploreStateSpace(approximationThreshold);

            size_t stateSize = stateStorage.getNumberOfStates() + (this->uniqueFailedState ? 1 : 0);
            modelComponents.markovianStates.resize(stateSize);
            modelComponents.deterministicModel = generator.isDeterministicModel();

            // Fix the entries in the transition matrix according to the mapping of ids to row group indices
            STORM_LOG_ASSERT(matrixBuilder.getRemapping(initialStateIndex) == initialStateIndex, "Initial state should not be remapped.");
            // TODO: do not consider all rows?
            STORM_LOG_TRACE("Remap matrix: " << matrixBuilder.stateRemapping << ", offset: " << matrixBuilder.mappingOffset);
            matrixBuilder.remap();

            STORM_LOG_TRACE("State remapping: " << matrixBuilder.stateRemapping);
            STORM_LOG_TRACE("Markovian states: " << modelComponents.markovianStates);
            STORM_LOG_DEBUG("Model has " << stateSize << " states");
            STORM_LOG_DEBUG("Model is " << (generator.isDeterministicModel() ? "deterministic" : "non-deterministic"));

            // Build transition matrix
            modelComponents.transitionMatrix = matrixBuilder.builder.build(stateSize, stateSize);
            if (stateSize <= 15) {
                STORM_LOG_TRACE("Transition matrix: " << std::endl << modelComponents.transitionMatrix);
            } else {
                STORM_LOG_TRACE("Transition matrix: too big to print");
            }

            buildLabeling();
        }

        template<typename ValueType, typename StateType>
        void ExplicitDFTModelBuilder<ValueType, StateType>::initializeNextIteration() {
            STORM_LOG_TRACE("Refining DFT state space");

            // TODO: should be easier now as skipped states all are at the end of the matrix
            // Push skipped states to explore queue
            // TODO: remove
            for (auto const& skippedState : skippedStates) {
                statesNotExplored[skippedState.second.first->getId()] = skippedState.second;
                explorationQueue.push(skippedState.second.second);
            }

            // Initialize matrix builder again
            // TODO: avoid copy
            std::vector<uint_fast64_t> copyRemapping = matrixBuilder.stateRemapping;
            matrixBuilder = MatrixBuilder(!generator.isDeterministicModel());
            matrixBuilder.stateRemapping = copyRemapping;
            StateType nrStates = modelComponents.transitionMatrix.getRowGroupCount();
            STORM_LOG_ASSERT(nrStates == matrixBuilder.stateRemapping.size(), "No. of states does not coincide with mapping size.");

            // Start by creating a remapping from the old indices to the new indices
            std::vector<StateType> indexRemapping = std::vector<StateType>(nrStates, 0);
            auto iterSkipped = skippedStates.begin();
            size_t skippedBefore = 0;
            for (size_t i = 0; i < indexRemapping.size(); ++i) {
                while (iterSkipped != skippedStates.end() && iterSkipped->first <= i) {
                    STORM_LOG_ASSERT(iterSkipped->first < indexRemapping.size(), "Skipped is too high.");
                    ++skippedBefore;
                    ++iterSkipped;
                }
                indexRemapping[i] = i - skippedBefore;
            }

            // Set remapping
            size_t nrExpandedStates = nrStates - skippedBefore;
            matrixBuilder.mappingOffset = nrStates;
            STORM_LOG_TRACE("# expanded states: " << nrExpandedStates);
            StateType skippedIndex = nrExpandedStates;
            std::map<StateType, std::pair<DFTStatePointer, ExplorationHeuristicPointer>> skippedStatesNew;
            for (size_t id = 0; id < matrixBuilder.stateRemapping.size(); ++id) {
                StateType index = matrixBuilder.getRemapping(id);
                auto itFind = skippedStates.find(index);
                if (itFind != skippedStates.end()) {
                    // Set new mapping for skipped state
                    matrixBuilder.stateRemapping[id] = skippedIndex;
                    skippedStatesNew[skippedIndex] = itFind->second;
                    indexRemapping[index] = skippedIndex;
                    ++skippedIndex;
                } else {
                    // Set new mapping for expanded state
                    matrixBuilder.stateRemapping[id] = indexRemapping[index];
                }
            }
            STORM_LOG_TRACE("New state remapping: " << matrixBuilder.stateRemapping);
            std::stringstream ss;
            ss << "Index remapping:" << std::endl;
            for (auto tmp : indexRemapping) {
                ss << tmp << " ";
            }
            STORM_LOG_TRACE(ss.str());

            // Remap markovian states
            storm::storage::BitVector markovianStatesNew = storm::storage::BitVector(modelComponents.markovianStates.size(), true);
            // Iterate over all not set bits
            modelComponents.markovianStates.complement();
            size_t index = modelComponents.markovianStates.getNextSetIndex(0);
            while (index < modelComponents.markovianStates.size()) {
                markovianStatesNew.set(indexRemapping[index], false);
                index = modelComponents.markovianStates.getNextSetIndex(index+1);
            }
            STORM_LOG_ASSERT(modelComponents.markovianStates.size() - modelComponents.markovianStates.getNumberOfSetBits() == markovianStatesNew.getNumberOfSetBits(), "Remapping of markovian states is wrong.");
            STORM_LOG_ASSERT(markovianStatesNew.size() == nrStates, "No. of states does not coincide with markovian size.");
            modelComponents.markovianStates = markovianStatesNew;

            // Build submatrix for expanded states
            // TODO: only use row groups when necessary
            for (StateType oldRowGroup = 0; oldRowGroup < modelComponents.transitionMatrix.getRowGroupCount(); ++oldRowGroup) {
                if (indexRemapping[oldRowGroup] < nrExpandedStates) {
                    // State is expanded -> copy to new matrix
                    matrixBuilder.newRowGroup();
                    for (StateType oldRow = modelComponents.transitionMatrix.getRowGroupIndices()[oldRowGroup]; oldRow < modelComponents.transitionMatrix.getRowGroupIndices()[oldRowGroup+1]; ++oldRow) {
                        for (typename storm::storage::SparseMatrix<ValueType>::const_iterator itEntry = modelComponents.transitionMatrix.begin(oldRow); itEntry != modelComponents.transitionMatrix.end(oldRow); ++itEntry) {
                            auto itFind = skippedStates.find(itEntry->getColumn());
                            if (itFind != skippedStates.end()) {
                                // Set id for skipped states as we remap it later
                                matrixBuilder.addTransition(matrixBuilder.mappingOffset + itFind->second.first->getId(), itEntry->getValue());
                            } else {
                                // Set newly remapped index for expanded states
                                matrixBuilder.addTransition(indexRemapping[itEntry->getColumn()], itEntry->getValue());
                            }
                        }
                        matrixBuilder.finishRow();
                    }
                }
            }

            skippedStates = skippedStatesNew;

            STORM_LOG_ASSERT(matrixBuilder.getCurrentRowGroup() == nrExpandedStates, "Row group size does not match.");
            skippedStates.clear();
        }

        template<typename ValueType, typename StateType>
        void ExplicitDFTModelBuilder<ValueType, StateType>::exploreStateSpace(double approximationThreshold) {
            size_t nrExpandedStates = 0;
            size_t nrSkippedStates = 0;
            storm::utility::ProgressMeasurement progress("explored states");
            progress.startNewMeasurement(0);
            // TODO: do not empty queue every time but break before
            while (!explorationQueue.empty()) {
                // Get the first state in the queue
                ExplorationHeuristicPointer currentExplorationHeuristic = explorationQueue.pop();
                StateType currentId = currentExplorationHeuristic->getId();
                auto itFind = statesNotExplored.find(currentId);
                STORM_LOG_ASSERT(itFind != statesNotExplored.end(), "Id " << currentId << " not found");
                DFTStatePointer currentState = itFind->second.first;
                STORM_LOG_ASSERT(currentExplorationHeuristic == itFind->second.second, "Exploration heuristics do not match");
                STORM_LOG_ASSERT(currentState->getId() == currentId, "Ids do not match");
                // Remove it from the list of not explored states
                statesNotExplored.erase(itFind);
                STORM_LOG_ASSERT(stateStorage.stateToId.contains(currentState->status()), "State is not contained in state storage.");
                STORM_LOG_ASSERT(stateStorage.stateToId.getValue(currentState->status()) == currentId, "Ids of states do not coincide.");

                // Get concrete state if necessary
                if (currentState->isPseudoState()) {
                    // Create concrete state from pseudo state
                    currentState->construct();
                }
                STORM_LOG_ASSERT(!currentState->isPseudoState(), "State is pseudo state.");

                // Remember that the current row group was actually filled with the transitions of a different state
                matrixBuilder.setRemapping(currentId);

                matrixBuilder.newRowGroup();

                // Try to explore the next state
                generator.load(currentState);

                //if (approximationThreshold > 0.0 && nrExpandedStates > approximationThreshold && !currentExplorationHeuristic->isExpand()) {
                if (approximationThreshold > 0.0 && currentExplorationHeuristic->isSkip(approximationThreshold)) {
                    // Skip the current state
                    ++nrSkippedStates;
                    STORM_LOG_TRACE("Skip expansion of state: " << dft.getStateString(currentState));
                    setMarkovian(true);
                    // Add transition to target state with temporary value 0
                    // TODO: what to do when there is no unique target state?
                    STORM_LOG_ASSERT(this->uniqueFailedState, "Approximation only works with unique failed state");
                    matrixBuilder.addTransition(0, storm::utility::zero<ValueType>());
                    // Remember skipped state
                    skippedStates[matrixBuilder.getCurrentRowGroup() - 1] = std::make_pair(currentState, currentExplorationHeuristic);
                    matrixBuilder.finishRow();
                } else {
                    // Explore the current state
                    ++nrExpandedStates;
                    storm::generator::StateBehavior<ValueType, StateType> behavior = generator.expand(std::bind(&ExplicitDFTModelBuilder::getOrAddStateIndex, this, std::placeholders::_1));
                    STORM_LOG_ASSERT(!behavior.empty(), "Behavior is empty.");
                    setMarkovian(behavior.begin()->isMarkovian());

                    // Now add all choices.
                    for (auto const& choice : behavior) {
                        // Add the probabilistic behavior to the matrix.
                        for (auto const& stateProbabilityPair : choice) {
                            STORM_LOG_ASSERT(!storm::utility::isZero(stateProbabilityPair.second), "Probability zero.");
                            // Set transition to state id + offset. This helps in only remapping all previously skipped states.
                            matrixBuilder.addTransition(matrixBuilder.mappingOffset + stateProbabilityPair.first, stateProbabilityPair.second);
                            // Set heuristic values for reached states
                            auto iter = statesNotExplored.find(stateProbabilityPair.first);
                            if (iter != statesNotExplored.end()) {
                                // Update heuristic values
                                DFTStatePointer state = iter->second.first;
                                if (!iter->second.second) {
                                    // Initialize heuristic values
                                    ExplorationHeuristicPointer heuristic;
                                    switch (usedHeuristic) {
                                        case storm::builder::ApproximationHeuristic::DEPTH:
                                            heuristic = std::make_shared<DFTExplorationHeuristicDepth<ValueType>>(stateProbabilityPair.first, *currentExplorationHeuristic, stateProbabilityPair.second, choice.getTotalMass());
                                            break;
                                        case storm::builder::ApproximationHeuristic::PROBABILITY:
                                            heuristic = std::make_shared<DFTExplorationHeuristicProbability<ValueType>>(stateProbabilityPair.first, *currentExplorationHeuristic, stateProbabilityPair.second, choice.getTotalMass());
                                            break;
                                        case storm::builder::ApproximationHeuristic::BOUNDDIFFERENCE:
                                            heuristic = std::make_shared<DFTExplorationHeuristicBoundDifference<ValueType>>(stateProbabilityPair.first, *currentExplorationHeuristic, stateProbabilityPair.second, choice.getTotalMass());
                                            break;
                                        default:
                                            STORM_LOG_THROW(false, storm::exceptions::IllegalArgumentException, "Heuristic not known.");
                                    }

                                    iter->second.second = heuristic;
                                    if (state->hasFailed(dft.getTopLevelIndex()) || state->isFailsafe(dft.getTopLevelIndex()) || state->getFailableElements().hasDependencies() || (!state->getFailableElements().hasDependencies() && !state->getFailableElements().hasBEs())) {
                                        // Do not skip absorbing state or if reached by dependencies
                                        iter->second.second->markExpand();
                                    }
                                    if (usedHeuristic == storm::builder::ApproximationHeuristic::BOUNDDIFFERENCE) {
                                        // Compute bounds for heuristic now
                                        if (state->isPseudoState()) {
                                            // Create concrete state from pseudo state
                                            state->construct();
                                        }
                                        STORM_LOG_ASSERT(!currentState->isPseudoState(), "State is pseudo state.");

                                        // Initialize bounds
                                        // TODO: avoid hack
                                        ValueType lowerBound = getLowerBound(state);
                                        ValueType upperBound = getUpperBound(state);
                                        heuristic->setBounds(lowerBound, upperBound);
                                    }

                                    explorationQueue.push(heuristic);
                                } else if (!iter->second.second->isExpand()) {
                                    double oldPriority = iter->second.second->getPriority();
                                    if (iter->second.second->updateHeuristicValues(*currentExplorationHeuristic, stateProbabilityPair.second, choice.getTotalMass())) {
                                        // Update priority queue
                                        explorationQueue.update(iter->second.second, oldPriority);
                                    }
                                }
                            }
                        }
                        matrixBuilder.finishRow();
                    }
                }
                // Output number of currently explored states
                if (nrExpandedStates % 100 == 0) {
                    progress.updateProgress(nrExpandedStates);
                }
            } // end exploration

            STORM_LOG_INFO("Expanded " << nrExpandedStates << " states");
            STORM_LOG_INFO("Skipped " << nrSkippedStates << " states");
            STORM_LOG_ASSERT(nrSkippedStates == skippedStates.size(), "Nr skipped states is wrong");
        }

        template<typename ValueType, typename StateType>
        void ExplicitDFTModelBuilder<ValueType, StateType>::buildLabeling() {
            // Build state labeling
            modelComponents.stateLabeling = storm::models::sparse::StateLabeling(modelComponents.transitionMatrix.getRowGroupCount());
            // Initial state
            modelComponents.stateLabeling.addLabel("init");
            STORM_LOG_ASSERT(matrixBuilder.getRemapping(initialStateIndex) == initialStateIndex, "Initial state should not be remapped.");
            modelComponents.stateLabeling.addLabelToState("init", initialStateIndex);
            // Label all states corresponding to their status (failed, failed/dont care BE)
            modelComponents.stateLabeling.addLabel("failed");

            // Collect labels for all necessary elements
            for (size_t id = 0; id < dft.nrElements(); ++id) {
                std::shared_ptr<storage::DFTElement<ValueType> const> element = dft.getElement(id);
                if (element->isRelevant()) {
                    modelComponents.stateLabeling.addLabel(element->name() + "_fail");
                    modelComponents.stateLabeling.addLabel(element->name() + "_dc");
                }
            }

            // Set labels to states
            if (this->uniqueFailedState) {
                modelComponents.stateLabeling.addLabelToState("failed", 0);
            }
            for (auto const& stateIdPair : stateStorage.stateToId) {
                storm::storage::BitVector state = stateIdPair.first;
                size_t stateId = matrixBuilder.getRemapping(stateIdPair.second);
                if (dft.hasFailed(state, *stateGenerationInfo)) {
                    modelComponents.stateLabeling.addLabelToState("failed", stateId);
                }
                // Set fail/dont care status for each necessary element
                for (size_t id = 0; id < dft.nrElements(); ++id) {
                    std::shared_ptr<storage::DFTElement<ValueType> const> element = dft.getElement(id);
                    if (element->isRelevant()){
                        storm::storage::DFTElementState elementState = storm::storage::DFTState<ValueType>::getElementState(state, *stateGenerationInfo, element->id());
                        switch (elementState) {
                            case storm::storage::DFTElementState::Failed:
                                modelComponents.stateLabeling.addLabelToState(element->name() + "_fail", stateId);
                                break;
                            case storm::storage::DFTElementState::DontCare:
                                modelComponents.stateLabeling.addLabelToState(element->name() + "_dc", stateId);
                                break;
                            case storm::storage::DFTElementState::Operational:
                            case storm::storage::DFTElementState::Failsafe:
                                // do nothing
                                break;
                            default:
                                STORM_LOG_ASSERT(false, "Unknown element state " << elementState);
                        }
                    }
                }
            }

            STORM_LOG_TRACE(modelComponents.stateLabeling);
        }

        template<typename ValueType, typename StateType>
        std::shared_ptr<storm::models::sparse::Model<ValueType>> ExplicitDFTModelBuilder<ValueType, StateType>::getModel() {
            STORM_LOG_ASSERT(skippedStates.size() == 0, "Concrete model has skipped states");
            return createModel(false);
        }

        template<typename ValueType, typename StateType>
        std::shared_ptr<storm::models::sparse::Model<ValueType>> ExplicitDFTModelBuilder<ValueType, StateType>::getModelApproximation(bool lowerBound, bool expectedTime) {
            if (expectedTime) {
                // TODO: handle case with no skipped states
                changeMatrixBound(modelComponents.transitionMatrix, lowerBound);
                return createModel(true);
            } else {
                // Change model for probabilities
                // TODO: make nicer
                storm::storage::SparseMatrix<ValueType> matrix = modelComponents.transitionMatrix;
                storm::models::sparse::StateLabeling labeling = modelComponents.stateLabeling;
                if (lowerBound) {
                    // Set self loop for lower bound
                    for (auto it = skippedStates.begin(); it != skippedStates.end(); ++it) {
                        auto matrixEntry = matrix.getRow(it->first, 0).begin();
                        STORM_LOG_ASSERT(matrixEntry->getColumn() == 0, "Transition has wrong target state.");
                        STORM_LOG_ASSERT(!it->second.first->isPseudoState(), "State is still pseudo state.");
                        matrixEntry->setValue(storm::utility::one<ValueType>());
                        matrixEntry->setColumn(it->first);
                    }
                } else {
                    // Make skipped states failed states for upper bound
                    storm::storage::BitVector failedStates = modelComponents.stateLabeling.getStates("failed");
                    for (auto it = skippedStates.begin(); it != skippedStates.end(); ++it) {
                        failedStates.set(it->first);
                    }
                    labeling.setStates("failed", failedStates);
                }

                std::shared_ptr<storm::models::sparse::Model<ValueType>> model;
                if (modelComponents.deterministicModel) {
                    model = std::make_shared<storm::models::sparse::Ctmc<ValueType>>(std::move(matrix), std::move(labeling));
                } else {
                    // Build MA
                    // Compute exit rates
                    // TODO: avoid computing multiple times
                    modelComponents.exitRates = std::vector<ValueType>(modelComponents.markovianStates.size());
                    std::vector<typename storm::storage::SparseMatrix<ValueType>::index_type> indices = matrix.getRowGroupIndices();
                    for (StateType stateIndex = 0; stateIndex < modelComponents.markovianStates.size(); ++stateIndex) {
                        if (modelComponents.markovianStates[stateIndex]) {
                            modelComponents.exitRates[stateIndex] = matrix.getRowSum(indices[stateIndex]);
                        } else {
                            modelComponents.exitRates[stateIndex] = storm::utility::zero<ValueType>();
                        }
                    }
                    STORM_LOG_TRACE("Exit rates: " << modelComponents.exitRates);

                    storm::storage::sparse::ModelComponents<ValueType> maComponents(std::move(matrix), std::move(labeling));
                    maComponents.rateTransitions = true;
                    maComponents.markovianStates = modelComponents.markovianStates;
                    maComponents.exitRates = modelComponents.exitRates;
                    std::shared_ptr<storm::models::sparse::MarkovAutomaton<ValueType>> ma = std::make_shared<storm::models::sparse::MarkovAutomaton<ValueType>>(std::move(maComponents));
                    if (ma->hasOnlyTrivialNondeterminism()) {
                        // Markov automaton can be converted into CTMC
                        // TODO: change components which were not moved accordingly
                        model = ma->convertToCtmc();
                    } else {
                        model = ma;
                    }

                }

                if (model->getNumberOfStates() <= 15) {
                    STORM_LOG_TRACE("Transition matrix: " << std::endl << model->getTransitionMatrix());
                } else {
                    STORM_LOG_TRACE("Transition matrix: too big to print");
                }
                return model;
            }
        }

        template<typename ValueType, typename StateType>
        std::shared_ptr<storm::models::sparse::Model<ValueType>> ExplicitDFTModelBuilder<ValueType, StateType>::createModel(bool copy) {
            std::shared_ptr<storm::models::sparse::Model<ValueType>> model;

            if (modelComponents.deterministicModel) {
                // Build CTMC
                if (copy) {
                    model = std::make_shared<storm::models::sparse::Ctmc<ValueType>>(modelComponents.transitionMatrix, modelComponents.stateLabeling);
                } else {
                    model = std::make_shared<storm::models::sparse::Ctmc<ValueType>>(std::move(modelComponents.transitionMatrix), std::move(modelComponents.stateLabeling));
                }
            } else {
                // Build MA
                // Compute exit rates
                // TODO: avoid computing multiple times
                modelComponents.exitRates = std::vector<ValueType>(modelComponents.markovianStates.size());
                std::vector<typename storm::storage::SparseMatrix<ValueType>::index_type> indices = modelComponents.transitionMatrix.getRowGroupIndices();
                for (StateType stateIndex = 0; stateIndex < modelComponents.markovianStates.size(); ++stateIndex) {
                    if (modelComponents.markovianStates[stateIndex]) {
                        modelComponents.exitRates[stateIndex] = modelComponents.transitionMatrix.getRowSum(indices[stateIndex]);
                    } else {
                        modelComponents.exitRates[stateIndex] = storm::utility::zero<ValueType>();
                    }
                }
                STORM_LOG_TRACE("Exit rates: " << modelComponents.exitRates);

                std::shared_ptr<storm::models::sparse::MarkovAutomaton<ValueType>> ma;
                if (copy) {
                    storm::storage::sparse::ModelComponents<ValueType> maComponents(modelComponents.transitionMatrix, modelComponents.stateLabeling);
                    maComponents.rateTransitions = true;
                    maComponents.markovianStates = modelComponents.markovianStates;
                    maComponents.exitRates = modelComponents.exitRates;
                    ma = std::make_shared<storm::models::sparse::MarkovAutomaton<ValueType>>(std::move(maComponents));
                } else {
                    storm::storage::sparse::ModelComponents<ValueType> maComponents(std::move(modelComponents.transitionMatrix), std::move(modelComponents.stateLabeling));
                    maComponents.rateTransitions = true;
                    maComponents.markovianStates = std::move(modelComponents.markovianStates);
                    maComponents.exitRates = std::move(modelComponents.exitRates);
                    ma = std::make_shared<storm::models::sparse::MarkovAutomaton<ValueType>>(std::move(maComponents));
                }
                if (ma->hasOnlyTrivialNondeterminism()) {
                    // Markov automaton can be converted into CTMC
                    // TODO: change components which were not moved accordingly
                    model = ma->convertToCtmc();
                } else {
                    model = ma;
                }
            }

            if (model->getNumberOfStates() <= 15) {
                STORM_LOG_TRACE("Transition matrix: " << std::endl << model->getTransitionMatrix());
            } else {
                STORM_LOG_TRACE("Transition matrix: too big to print");
            }
            return model;
        }

        template<typename ValueType, typename StateType>
        void ExplicitDFTModelBuilder<ValueType, StateType>::changeMatrixBound(storm::storage::SparseMatrix<ValueType> & matrix, bool lowerBound) const {
            // Set lower bound for skipped states
            for (auto it = skippedStates.begin(); it != skippedStates.end(); ++it) {
                auto matrixEntry = matrix.getRow(it->first, 0).begin();
                STORM_LOG_ASSERT(matrixEntry->getColumn() == 0, "Transition has wrong target state.");
                STORM_LOG_ASSERT(!it->second.first->isPseudoState(), "State is still pseudo state.");

                ExplorationHeuristicPointer heuristic = it->second.second;
                if (storm::utility::isInfinity(heuristic->getUpperBound())) {
                    // Initialize bounds
                    ValueType lowerBound = getLowerBound(it->second.first);
                    ValueType upperBound = getUpperBound(it->second.first);
                    heuristic->setBounds(lowerBound, upperBound);
                }

                // Change bound
                if (lowerBound) {
                    matrixEntry->setValue(it->second.second->getLowerBound());
                } else {
                    matrixEntry->setValue(it->second.second->getUpperBound());
                }
            }
        }

        template<typename ValueType, typename StateType>
        ValueType ExplicitDFTModelBuilder<ValueType, StateType>::getLowerBound(DFTStatePointer const& state) const {
            // Get the lower bound by considering the failure of all possible BEs
            ValueType lowerBound = storm::utility::zero<ValueType>();
            for (state->getFailableElements().init(false); !state->getFailableElements().isEnd(); state->getFailableElements().next()) {
                lowerBound += state->getBERate(state->getFailableElements().get());
            }
            STORM_LOG_TRACE("Lower bound is " << lowerBound << " for state " << state->getId());
            return lowerBound;
        }

        template<typename ValueType, typename StateType>
        ValueType ExplicitDFTModelBuilder<ValueType, StateType>::getUpperBound(DFTStatePointer const& state) const {
            if (state->hasFailed(dft.getTopLevelIndex())) {
                return storm::utility::zero<ValueType>();
            }

            // Get the upper bound by considering the failure of all BEs
            ValueType upperBound = storm::utility::one<ValueType>();
            ValueType rateSum = storm::utility::zero<ValueType>();

            // Compute for each independent subtree
            for (std::vector<size_t> const& subtree : subtreeBEs) {
                // Get all possible rates
                std::vector<ValueType> rates;
                storm::storage::BitVector coldBEs(subtree.size(), false);
                for (size_t i = 0; i < subtree.size(); ++i) {
                    size_t id = subtree[i];
                    if (state->isOperational(id)) {
                        // Get BE rate
                        ValueType rate = state->getBERate(id);
                        if (storm::utility::isZero<ValueType>(rate)) {
                            // Get active failure rate for cold BE
                            auto be = dft.getBasicElement(id);
                            switch (be->type()) {
                                case storm::storage::DFTElementType::BE_EXP:
                                {
                                    auto beExp = std::static_pointer_cast<storm::storage::BEExponential<ValueType> const>(be);
                                    rate = beExp->activeFailureRate();
                                    STORM_LOG_ASSERT(!storm::utility::isZero<ValueType>(rate), "Failure rate should not be zero.");
                                    // Mark BE as cold
                                    coldBEs.set(i, true);
                                    break;
                                }
                                case storm::storage::DFTElementType::BE_CONST:
                                {
                                    // Ignore BE which cannot fail
                                    continue;
                                }
                                default:
                                    STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "BE of type '" << be->type() << "' is not known.");
                                    break;
                            }
                        }
                        rates.push_back(rate);
                        rateSum += rate;
                    }
                }

                STORM_LOG_ASSERT(rates.size() > 0, "No rates failable");

                // Sort rates
                std::sort(rates.begin(), rates.end());
                std::vector<size_t> rateCount(rates.size(), 0);
                size_t lastIndex = 0;
                for (size_t i = 0; i < rates.size(); ++i) {
                    if (rates[lastIndex] != rates[i]) {
                        lastIndex = i;
                    }
                    ++rateCount[lastIndex];
                }

                for (size_t i = 0; i < rates.size(); ++i) {
                    // Cold BEs cannot fail in the first step
                    if (!coldBEs.get(i) && rateCount[i] > 0) {
                        std::iter_swap(rates.begin() + i, rates.end() - 1);
                        // Compute AND MTTF of subtree without current rate and scale with current rate
                        upperBound += rates.back() * rateCount[i] * computeMTTFAnd(rates, rates.size() - 1);
                        // Swap back
                        // TODO try to avoid swapping back
                        std::iter_swap(rates.begin() + i, rates.end() - 1);
                    }
                }
            }

            STORM_LOG_TRACE("Upper bound is " << (rateSum / upperBound) << " for state " << state->getId());
            STORM_LOG_ASSERT(!storm::utility::isZero(upperBound), "Upper bound is 0");
            STORM_LOG_ASSERT(!storm::utility::isZero(rateSum), "State is absorbing");
            return rateSum / upperBound;
        }

        template<typename ValueType, typename StateType>
        ValueType ExplicitDFTModelBuilder<ValueType, StateType>::computeMTTFAnd(std::vector<ValueType> const& rates, size_t size) const {
            ValueType result = storm::utility::zero<ValueType>();
            if (size == 0) {
                return result;
            }

            // TODO: works only for <64 BEs!
            // WARNING: this code produces wrong results for more than 32 BEs
            /*for (size_t i = 1; i < 4 && i <= rates.size(); ++i) {
                size_t permutation = smallestIntWithNBitsSet(static_cast<size_t>(i));
                ValueType sum = storm::utility::zero<ValueType>();
                do {
                    ValueType permResult = storm::utility::zero<ValueType>();
                    for(size_t j = 0; j < rates.size(); ++j) {
                        if(permutation & static_cast<size_t>(1 << static_cast<size_t>(j))) {
                            // WARNING: if the first bit is set, it also recognizes the 32nd bit as set
                            // TODO: fix
                            permResult += rates[j];
                        }
                    }
                    permutation = nextBitPermutation(permutation);
                    STORM_LOG_ASSERT(!storm::utility::isZero(permResult), "PermResult is 0");
                    sum += storm::utility::one<ValueType>() / permResult;
                } while(permutation < (static_cast<size_t>(1) << rates.size()) && permutation != 0);
                if (i % 2 == 0) {
                    result -= sum;
                } else {
                    result += sum;
                }
            }*/

            // Compute result with permutations of size <= 3
            ValueType one = storm::utility::one<ValueType>();
            for (size_t i1 = 0; i1 < size; ++i1) {
                // + 1/a
                ValueType sum = rates[i1];
                result += one / sum;
                for (size_t i2 = 0; i2 < i1; ++i2) {
                    // - 1/(a+b)
                    ValueType sum2 = sum + rates[i2];
                    result -= one / sum2;
                    for (size_t i3 = 0; i3 < i2; ++i3) {
                        // + 1/(a+b+c)
                        result += one / (sum2 + rates[i3]);
                    }
                }
            }

            STORM_LOG_ASSERT(!storm::utility::isZero(result), "UpperBound is 0");
            return result;
        }

        template<typename ValueType, typename StateType>
        StateType ExplicitDFTModelBuilder<ValueType, StateType>::getOrAddStateIndex(DFTStatePointer const& state) {
            StateType stateId;
            bool changed = false;

            if (stateGenerationInfo->hasSymmetries()) {
                // Order state by symmetry
                STORM_LOG_TRACE("Check for symmetry: " << dft.getStateString(state));
                changed = state->orderBySymmetry();
                STORM_LOG_TRACE("State " << (changed ? "changed to " : "did not change") << (changed ? dft.getStateString(state) : ""));
            }

            if (stateStorage.stateToId.contains(state->status())) {
                // State already exists
                stateId = stateStorage.stateToId.getValue(state->status());
                STORM_LOG_TRACE("State " << dft.getStateString(state) << " with id " << stateId << " already exists");
                if (!changed) {
                    // Check if state is pseudo state
                    // If state is explored already the possible pseudo state was already constructed
                    auto iter = statesNotExplored.find(stateId);
                    if (iter != statesNotExplored.end() && iter->second.first->isPseudoState()) {
                        // Create pseudo state now
                        STORM_LOG_ASSERT(iter->second.first->getId() == stateId, "Ids do not match.");
                        STORM_LOG_ASSERT(iter->second.first->status() == state->status(), "Pseudo states do not coincide.");
                        state->setId(stateId);
                        // Update mapping to map to concrete state now
                        // TODO: just change pointer?
                        statesNotExplored[stateId] = std::make_pair(state, iter->second.second);
                        // We do not push the new state on the exploration queue as the pseudo state was already pushed
                        STORM_LOG_TRACE("Created pseudo state " << dft.getStateString(state));
                    }
                }
            } else {
                // State does not exist yet
                STORM_LOG_ASSERT(state->isPseudoState() == changed, "State type (pseudo/concrete) wrong.");
                // Create new state
                state->setId(newIndex++);
                stateId = stateStorage.stateToId.findOrAdd(state->status(), state->getId());
                STORM_LOG_ASSERT(stateId == state->getId(), "Ids do not match.");
                // Insert state as not yet explored
                ExplorationHeuristicPointer nullHeuristic;
                statesNotExplored[stateId] = std::make_pair(state, nullHeuristic);
                // Reserve one slot for the new state in the remapping
                matrixBuilder.stateRemapping.push_back(0);
                STORM_LOG_TRACE("New " << (state->isPseudoState() ? "pseudo" : "concrete") << " state: " << dft.getStateString(state));
            }
            return stateId;
        }

        template<typename ValueType, typename StateType>
        void ExplicitDFTModelBuilder<ValueType, StateType>::setMarkovian(bool markovian) {
            if (matrixBuilder.getCurrentRowGroup() > modelComponents.markovianStates.size()) {
                // Resize BitVector
                modelComponents.markovianStates.resize(modelComponents.markovianStates.size() + INITIAL_BITVECTOR_SIZE);
            }
            modelComponents.markovianStates.set(matrixBuilder.getCurrentRowGroup() - 1, markovian);
        }

        template<typename ValueType, typename StateType>
        void ExplicitDFTModelBuilder<ValueType, StateType>::printNotExplored() const {
            std::cout << "states not explored:" << std::endl;
            for (auto it : statesNotExplored) {
                std::cout << it.first << " -> " << dft.getStateString(it.second.first) << std::endl;
            }
        }


        // Explicitly instantiate the class.
        template class ExplicitDFTModelBuilder<double>;

#ifdef STORM_HAVE_CARL
        template class ExplicitDFTModelBuilder<storm::RationalFunction>;
#endif

    } // namespace builder
} // namespace storm