#include "storm/utility/initialize.h"

#include "storm/settings/modules/GeneralSettings.h"
#include "storm/settings/modules/DebugSettings.h"
#include "storm-pomdp-cli/settings/modules/POMDPSettings.h"
#include "storm-pomdp-cli/settings/modules/QualitativePOMDPAnalysisSettings.h"
#include "storm-pomdp-cli/settings/modules/BeliefExplorationSettings.h"
#include "storm-pomdp-cli/settings/modules/ToParametricSettings.h"

#include "storm-pomdp-cli/settings/PomdpSettings.h"
#include "storm/analysis/GraphConditions.h"

#include "storm-cli-utilities/cli.h"
#include "storm-cli-utilities/model-handling.h"

#include "storm-pomdp/transformer/KnownProbabilityTransformer.h"
#include "storm-pomdp/transformer/ApplyFiniteSchedulerToPomdp.h"
#include "storm-pomdp/transformer/GlobalPOMDPSelfLoopEliminator.h"
#include "storm-pomdp/transformer/GlobalPomdpMecChoiceEliminator.h"
#include "storm-pomdp/transformer/PomdpMemoryUnfolder.h"
#include "storm-pomdp/transformer/BinaryPomdpTransformer.h"
#include "storm-pomdp/transformer/MakePOMDPCanonic.h"
#include "storm-pomdp/analysis/UniqueObservationStates.h"
#include "storm-pomdp/analysis/QualitativeAnalysisOnGraphs.h"
#include "storm-pomdp/modelchecker/BeliefExplorationPomdpModelChecker.h"
#include "storm-pomdp/analysis/FormulaInformation.h"
#include "storm-pomdp/analysis/IterativePolicySearch.h"
#include "storm-pomdp/analysis/OneShotPolicySearch.h"
#include "storm-pomdp/analysis/JaniBeliefSupportMdpGenerator.h"

#include "storm/api/storm.h"
#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h"
#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h"
#include "storm/utility/NumberTraits.h"
#include "storm/utility/Stopwatch.h"
#include "storm/utility/SignalHandler.h"

#include "storm/exceptions/UnexpectedException.h"
#include "storm/exceptions/NotSupportedException.h"

#include <typeinfo>

namespace storm {
    namespace pomdp {
        namespace cli {
            
            /// Perform preprocessings based on the graph structure (if requested or necessary). Return true, if some preprocessing has been done
            template<typename ValueType, storm::dd::DdType DdType>
            bool performPreprocessing(std::shared_ptr<storm::models::sparse::Pomdp<ValueType>>& pomdp, storm::pomdp::analysis::FormulaInformation& formulaInfo, storm::logic::Formula const& formula) {
                auto const& pomdpSettings = storm::settings::getModule<storm::settings::modules::POMDPSettings>();
                bool preprocessingPerformed = false;
                if (pomdpSettings.isSelfloopReductionSet()) {
                    storm::transformer::GlobalPOMDPSelfLoopEliminator<ValueType> selfLoopEliminator(*pomdp);
                    if (selfLoopEliminator.preservesFormula(formula)) {
                        STORM_PRINT_AND_LOG("Eliminating self-loop choices ...");
                        uint64_t oldChoiceCount = pomdp->getNumberOfChoices();
                        pomdp = selfLoopEliminator.transform();
                        STORM_PRINT_AND_LOG(oldChoiceCount - pomdp->getNumberOfChoices() << " choices eliminated through self-loop elimination." << std::endl);
                        preprocessingPerformed = true;
                    } else {
                        STORM_PRINT_AND_LOG("Not eliminating self-loop choices as it does not preserve the formula." << std::endl);
                    }
                }
                if (pomdpSettings.isQualitativeReductionSet() && formulaInfo.isNonNestedReachabilityProbability()) {
                    storm::analysis::QualitativeAnalysisOnGraphs<ValueType> qualitativeAnalysis(*pomdp);
                    STORM_PRINT_AND_LOG("Computing states with probability 0 ...");
                    storm::storage::BitVector prob0States = qualitativeAnalysis.analyseProb0(formula.asProbabilityOperatorFormula());
                    std::cout << prob0States << std::endl;
                    STORM_PRINT_AND_LOG(" done. " << prob0States.getNumberOfSetBits() << " states found." << std::endl);
                    STORM_PRINT_AND_LOG("Computing states with probability 1 ...");
                    storm::storage::BitVector  prob1States = qualitativeAnalysis.analyseProb1(formula.asProbabilityOperatorFormula());
                    STORM_PRINT_AND_LOG(" done. " << prob1States.getNumberOfSetBits() << " states found." << std::endl);
                    storm::pomdp::transformer::KnownProbabilityTransformer<ValueType> kpt = storm::pomdp::transformer::KnownProbabilityTransformer<ValueType>();
                    pomdp = kpt.transform(*pomdp, prob0States, prob1States);
                    // Update formulaInfo to changes from Preprocessing
                    formulaInfo.updateTargetStates(*pomdp, std::move(prob1States));
                    formulaInfo.updateSinkStates(*pomdp, std::move(prob0States));
                    preprocessingPerformed = true;
                }
                return preprocessingPerformed;
            }
            
            template<typename ValueType>
            void printResult(ValueType const& lowerBound, ValueType const& upperBound) {
                if (lowerBound == upperBound) {
                    if (storm::utility::isInfinity(lowerBound)) {
                        STORM_PRINT_AND_LOG("inf");
                    } else {
                        STORM_PRINT_AND_LOG(lowerBound);
                    }
                } else if (storm::utility::isInfinity<ValueType>(-lowerBound)) {
                    if (storm::utility::isInfinity(upperBound)) {
                        STORM_PRINT_AND_LOG("[-inf, inf] (width=inf)");
                    } else {
                        // Only upper bound is known
                        STORM_PRINT_AND_LOG("≤ " << upperBound);
                    }
                } else if (storm::utility::isInfinity(upperBound)) {
                    STORM_PRINT_AND_LOG("≥ " << lowerBound);
                } else {
                    STORM_PRINT_AND_LOG("[" << lowerBound << ", " << upperBound << "] (width=" << ValueType(upperBound - lowerBound) << ")");
                }
                if (storm::NumberTraits<ValueType>::IsExact) {
                    STORM_PRINT_AND_LOG(" (approx. ");
                    double roundedLowerBound = storm::utility::isInfinity<ValueType>(-lowerBound) ? -storm::utility::infinity<double>() : storm::utility::convertNumber<double>(lowerBound);
                    double roundedUpperBound = storm::utility::isInfinity(upperBound) ? storm::utility::infinity<double>() : storm::utility::convertNumber<double>(upperBound);
                    printResult(roundedLowerBound, roundedUpperBound);
                    STORM_PRINT_AND_LOG(")");
                }
            }

            MemlessSearchOptions fillMemlessSearchOptionsFromSettings() {
                storm::pomdp::MemlessSearchOptions options;
                auto const& qualSettings = storm::settings::getModule<storm::settings::modules::QualitativePOMDPAnalysisSettings>();


                options.onlyDeterministicStrategies = qualSettings.isOnlyDeterministicSet();
                uint64_t loglevel = 0;
                // TODO a big ugly, but we have our own loglevels (for technical reasons)
                if(storm::utility::getLogLevel() == l3pp::LogLevel::INFO) {
                    loglevel = 1;
                }
                else if(storm::utility::getLogLevel() == l3pp::LogLevel::DEBUG) {
                    loglevel = 2;
                }
                else if(storm::utility::getLogLevel() == l3pp::LogLevel::TRACE) {
                    loglevel = 3;
                }
                options.setDebugLevel(loglevel);
                options.validateEveryStep = qualSettings.validateIntermediateSteps();
                options.validateResult = qualSettings.validateFinalResult();

                options.pathVariableType = storm::pomdp::pathVariableTypeFromString(qualSettings.getLookaheadType());

                if (qualSettings.isExportSATCallsSet()) {
                    options.setExportSATCalls(qualSettings.getExportSATCallsPath());
                }

                return options;
            }

            template<typename ValueType>
            void performQualitativeAnalysis(std::shared_ptr<storm::models::sparse::Pomdp<ValueType>> const& origpomdp, storm::pomdp::analysis::FormulaInformation const& formulaInfo, storm::logic::Formula const& formula) {
                auto const& qualSettings = storm::settings::getModule<storm::settings::modules::QualitativePOMDPAnalysisSettings>();
                auto const& coreSettings = storm::settings::getModule<storm::settings::modules::CoreSettings>();
                std::stringstream sstr;
                origpomdp->printModelInformationToStream(sstr);
                STORM_LOG_INFO(sstr.str());
                STORM_LOG_THROW(formulaInfo.isNonNestedReachabilityProbability(), storm::exceptions::NotSupportedException, "Qualitative memoryless scheduler search is not implemented for this property type.");
                STORM_LOG_TRACE("Run qualitative preprocessing...");
                storm::models::sparse::Pomdp<ValueType> pomdp(*origpomdp);
                storm::analysis::QualitativeAnalysisOnGraphs<ValueType> qualitativeAnalysis(pomdp);
                // After preprocessing, this might be done cheaper.
                storm::storage::BitVector surelyNotAlmostSurelyReachTarget = qualitativeAnalysis.analyseProbSmaller1(
                        formula.asProbabilityOperatorFormula());
                pomdp.getTransitionMatrix().makeRowGroupsAbsorbing(surelyNotAlmostSurelyReachTarget);
                storm::storage::BitVector targetStates = qualitativeAnalysis.analyseProb1(formula.asProbabilityOperatorFormula());
                bool computedSomething = false;
                if (qualSettings.isMemlessSearchSet()) {
                    computedSomething = true;
                    storm::expressions::ExpressionManager expressionManager;
                    std::shared_ptr<storm::utility::solver::SmtSolverFactory> smtSolverFactory = std::make_shared<storm::utility::solver::Z3SmtSolverFactory>();
                    storm::pomdp::MemlessSearchOptions options = fillMemlessSearchOptionsFromSettings();
                    uint64_t lookahead = qualSettings.getLookahead();
                    if (lookahead == 0) {
                        lookahead = pomdp.getNumberOfStates();
                    }
                    if (qualSettings.getMemlessSearchMethod() == "one-shot") {
                        storm::pomdp::OneShotPolicySearch<ValueType> memlessSearch(pomdp, targetStates,
                                                                                   surelyNotAlmostSurelyReachTarget,
                                                                                   smtSolverFactory);
                        if (qualSettings.isWinningRegionSet()) {
                            STORM_LOG_ERROR("Computing winning regions is not supported by the one-shot method.");
                        } else {
                            memlessSearch.analyzeForInitialStates(lookahead);
                        }
                    } else if (qualSettings.getMemlessSearchMethod() == "iterative") {
                        storm::pomdp::IterativePolicySearch<ValueType> search(pomdp, targetStates,
                                                                              surelyNotAlmostSurelyReachTarget,
                                                                              smtSolverFactory, options);
                        if (qualSettings.isWinningRegionSet()) {
                            search.computeWinningRegion(lookahead);
                        } else {
                            search.analyzeForInitialStates(lookahead);
                        }

                        if (qualSettings.isPrintWinningRegionSet()) {
                            search.getLastWinningRegion().print();
                            std::cout << std::endl;
                        }
                        if (qualSettings.isExportWinningRegionSet()) {
                            std::size_t hash = pomdp.hash();
                            search.getLastWinningRegion().storeToFile(qualSettings.exportWinningRegionPath(),
                                                                      "model hash: " + std::to_string(hash));
                        }

                        search.finalizeStatistics();
                        if (pomdp.getInitialStates().getNumberOfSetBits() == 1) {
                            uint64_t initialState = pomdp.getInitialStates().getNextSetIndex(0);
                            uint64_t initialObservation = pomdp.getObservation(initialState);
                            // TODO this is inefficient.
                            uint64_t offset = 0;
                            for (uint64_t state = 0; state < pomdp.getNumberOfStates(); ++state) {
                                if (state == initialState) {
                                    break;
                                }
                                if (pomdp.getObservation(state) == initialObservation) {
                                    ++offset;
                                }
                            }

                            if (search.getLastWinningRegion().isWinning(initialObservation,
                                                                        offset)) {
                                STORM_PRINT_AND_LOG("Initial state is safe!"
                                                            << std::endl);
                            } else {
                                STORM_PRINT_AND_LOG("Initial state may not be safe."
                                                            << std::endl);
                            }
                        } else {
                            STORM_LOG_WARN("Output for multiple initial states is incomplete");
                        }

                        if (coreSettings.isShowStatisticsSet()) {
                            STORM_PRINT_AND_LOG("#STATS Number of belief support states: "
                                                        << search.getLastWinningRegion().beliefSupportStates() << std::endl);
                            if (qualSettings.computeExpensiveStats()) {
                                auto wbss = search.getLastWinningRegion().computeNrWinningBeliefs();
                                STORM_PRINT_AND_LOG(
                                        "#STATS Number of winning belief support states: [" << wbss.first << "," << wbss.second
                                                                                     << "]");
                            }
                            search.getStatistics().print();
                        }

                    } else {
                        STORM_LOG_ERROR("This method is not implemented.");
                    }
                }
                if (qualSettings.isComputeOnBeliefSupportSet()) {
                    computedSomething = true;
                    storm::pomdp::qualitative::JaniBeliefSupportMdpGenerator<ValueType> janicreator(pomdp);
                    janicreator.generate(targetStates, surelyNotAlmostSurelyReachTarget);
                    bool initialOnly = !qualSettings.isWinningRegionSet();
                    janicreator.verifySymbolic(initialOnly);
                    STORM_PRINT_AND_LOG("Initial state is safe: " <<  janicreator.isInitialWinning() << "\n");
                }
                STORM_LOG_THROW(computedSomething, storm::exceptions::InvalidSettingsException, "Nothing to be done, did you forget to set a method?");

            }
            
            template<typename ValueType, storm::dd::DdType DdType>
            bool performAnalysis(std::shared_ptr<storm::models::sparse::Pomdp<ValueType>> const& pomdp, storm::pomdp::analysis::FormulaInformation const& formulaInfo, storm::logic::Formula const& formula) {
                auto const& pomdpSettings = storm::settings::getModule<storm::settings::modules::POMDPSettings>();
                bool analysisPerformed = false;
                if (pomdpSettings.isBeliefExplorationSet()) {
                    STORM_PRINT_AND_LOG("Exploring the belief MDP... ");
                    auto options = storm::pomdp::modelchecker::BeliefExplorationPomdpModelCheckerOptions<ValueType>(pomdpSettings.isBeliefExplorationDiscretizeSet(), pomdpSettings.isBeliefExplorationUnfoldSet());
                    auto const& beliefExplorationSettings = storm::settings::getModule<storm::settings::modules::BeliefExplorationSettings>();
                    beliefExplorationSettings.setValuesInOptionsStruct(options);
                    storm::pomdp::modelchecker::BeliefExplorationPomdpModelChecker<storm::models::sparse::Pomdp<ValueType>> checker(pomdp, options);
                    auto result = checker.check(formula);
                    checker.printStatisticsToStream(std::cout);
                    if (storm::utility::resources::isTerminate()) {
                        STORM_PRINT_AND_LOG("\nResult till abort: ")
                    } else {
                        STORM_PRINT_AND_LOG("\nResult: ")
                    }
                    printResult(result.lowerBound, result.upperBound);
                    STORM_PRINT_AND_LOG(std::endl);
                    analysisPerformed = true;
                }
                if (pomdpSettings.isQualitativeAnalysisSet()) {
                    performQualitativeAnalysis(pomdp, formulaInfo, formula);
                    analysisPerformed = true;

                }
                if (pomdpSettings.isCheckFullyObservableSet()) {
                    STORM_PRINT_AND_LOG("Analyzing the formula on the fully observable MDP ... ");
                    auto resultPtr = storm::api::verifyWithSparseEngine<ValueType>(pomdp->template as<storm::models::sparse::Mdp<ValueType>>(), storm::api::createTask<ValueType>(formula.asSharedPointer(), true));
                    if (resultPtr) {
                        auto result = resultPtr->template asExplicitQuantitativeCheckResult<ValueType>();
                        result.filter(storm::modelchecker::ExplicitQualitativeCheckResult(pomdp->getInitialStates()));
                        if (storm::utility::resources::isTerminate()) {
                            STORM_PRINT_AND_LOG("\nResult till abort: ")
                        } else {
                            STORM_PRINT_AND_LOG("\nResult: ")
                        }
                        printResult(result.getMin(), result.getMax());
                        STORM_PRINT_AND_LOG(std::endl);
                    } else {
                        STORM_PRINT_AND_LOG("\nResult: Not available." << std::endl);
                    }
                    analysisPerformed = true;
                }
                return analysisPerformed;
            }
            
            
            template<typename ValueType, storm::dd::DdType DdType>
            bool performTransformation(std::shared_ptr<storm::models::sparse::Pomdp<ValueType>>& pomdp, storm::logic::Formula const& formula) {
                auto const& pomdpSettings = storm::settings::getModule<storm::settings::modules::POMDPSettings>();
                auto const& ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
                auto const& transformSettings = storm::settings::getModule<storm::settings::modules::ToParametricSettings>();
                bool transformationPerformed = false;
                bool memoryUnfolded = false;
                if (pomdpSettings.getMemoryBound() > 1) {
                    STORM_PRINT_AND_LOG("Computing the unfolding for memory bound " << pomdpSettings.getMemoryBound() << " and memory pattern '" << storm::storage::toString(pomdpSettings.getMemoryPattern()) << "' ...");
                    storm::storage::PomdpMemory memory = storm::storage::PomdpMemoryBuilder().build(pomdpSettings.getMemoryPattern(), pomdpSettings.getMemoryBound());
                    std::cout << memory.toString() << std::endl;
                    storm::transformer::PomdpMemoryUnfolder<ValueType> memoryUnfolder(*pomdp, memory);
                    pomdp = memoryUnfolder.transform();
                    STORM_PRINT_AND_LOG(" done." << std::endl);
                    pomdp->printModelInformationToStream(std::cout);
                    transformationPerformed = true;
                    memoryUnfolded = true;
                }
        
                // From now on the pomdp is considered memoryless
        
                if (transformSettings.isMecReductionSet()) {
                    STORM_PRINT_AND_LOG("Eliminating mec choices ...");
                    // Note: Elimination of mec choices only preserves memoryless schedulers.
                    uint64_t oldChoiceCount = pomdp->getNumberOfChoices();
                    storm::transformer::GlobalPomdpMecChoiceEliminator<ValueType> mecChoiceEliminator(*pomdp);
                    pomdp = mecChoiceEliminator.transform(formula);
                    STORM_PRINT_AND_LOG(" done." << std::endl);
                    STORM_PRINT_AND_LOG(oldChoiceCount - pomdp->getNumberOfChoices() << " choices eliminated through MEC choice elimination." << std::endl);
                    pomdp->printModelInformationToStream(std::cout);
                    transformationPerformed = true;
                }
        
                if (transformSettings.isTransformBinarySet() || transformSettings.isTransformSimpleSet()) {
                    if (transformSettings.isTransformSimpleSet()) {
                        STORM_PRINT_AND_LOG("Transforming the POMDP to a simple POMDP.");
                        pomdp = storm::transformer::BinaryPomdpTransformer<ValueType>().transform(*pomdp, true);
                    } else {
                        STORM_PRINT_AND_LOG("Transforming the POMDP to a binary POMDP.");
                        pomdp = storm::transformer::BinaryPomdpTransformer<ValueType>().transform(*pomdp, false);
                    }
                    pomdp->printModelInformationToStream(std::cout);
                    STORM_PRINT_AND_LOG(" done." << std::endl);
                    transformationPerformed = true;
                }
        
                if (pomdpSettings.isExportToParametricSet()) {
                    STORM_PRINT_AND_LOG("Transforming memoryless POMDP to pMC...");
                    storm::transformer::ApplyFiniteSchedulerToPomdp<ValueType> toPMCTransformer(*pomdp);
                    std::string transformMode = transformSettings.getFscApplicationTypeString();
                    auto pmc = toPMCTransformer.transform(storm::transformer::parsePomdpFscApplicationMode(transformMode));
                    STORM_PRINT_AND_LOG(" done." << std::endl);
                    pmc->printModelInformationToStream(std::cout);
                    if (transformSettings.allowPostSimplifications()) {
                        STORM_PRINT_AND_LOG("Simplifying pMC...");
                        pmc = storm::api::performBisimulationMinimization<storm::RationalFunction>(pmc->template as<storm::models::sparse::Dtmc<storm::RationalFunction>>(),{formula.asSharedPointer()}, storm::storage::BisimulationType::Strong)->template as<storm::models::sparse::Dtmc<storm::RationalFunction>>();
                        STORM_PRINT_AND_LOG(" done." << std::endl);
                        pmc->printModelInformationToStream(std::cout);
                    }
                    STORM_PRINT_AND_LOG("Exporting pMC...");
                    storm::analysis::ConstraintCollector<storm::RationalFunction> constraints(*pmc);
                    auto const& parameterSet = constraints.getVariables();
                    std::vector<storm::RationalFunctionVariable> parameters(parameterSet.begin(), parameterSet.end());
                    std::vector<std::string> parameterNames;
                    for (auto const& parameter : parameters) {
                        parameterNames.push_back(parameter.name());
                    }
                    storm::api::exportSparseModelAsDrn(pmc, pomdpSettings.getExportToParametricFilename(), parameterNames, !ioSettings.isExplicitExportPlaceholdersDisabled());
                    STORM_PRINT_AND_LOG(" done." << std::endl);
                    transformationPerformed = true;
                }
                if (transformationPerformed && !memoryUnfolded) {
                    STORM_PRINT_AND_LOG("Implicitly assumed restriction to memoryless schedulers for at least one transformation." << std::endl);
                }
                return transformationPerformed;
            }
            
            template<typename ValueType, storm::dd::DdType DdType>
            void processOptionsWithValueTypeAndDdLib(storm::cli::SymbolicInput const& symbolicInput, storm::cli::ModelProcessingInformation const& mpi) {
                auto const& pomdpSettings = storm::settings::getModule<storm::settings::modules::POMDPSettings>();
                
                auto model = storm::cli::buildPreprocessExportModelWithValueTypeAndDdlib<DdType, ValueType>(symbolicInput, mpi);
                if (!model) {
                    STORM_PRINT_AND_LOG("No input model given." << std::endl);
                    return;
                }
                STORM_LOG_THROW(model->getType() == storm::models::ModelType::Pomdp && model->isSparseModel(), storm::exceptions::WrongFormatException, "Expected a POMDP in sparse representation.");
            
                std::shared_ptr<storm::models::sparse::Pomdp<ValueType>> pomdp = model->template as<storm::models::sparse::Pomdp<ValueType>>();
                if (!pomdpSettings.isNoCanonicSet()) {
                    storm::transformer::MakePOMDPCanonic<ValueType> makeCanonic(*pomdp);
                    pomdp = makeCanonic.transform();
                }
                
                std::shared_ptr<storm::logic::Formula const> formula;
                if (!symbolicInput.properties.empty()) {
                    formula = symbolicInput.properties.front().getRawFormula();
                    STORM_PRINT_AND_LOG("Analyzing property '" << *formula << "'" << std::endl);
                    STORM_LOG_WARN_COND(symbolicInput.properties.size() == 1, "There is currently no support for multiple properties. All other properties will be ignored.");
                }
            
                if (pomdpSettings.isAnalyzeUniqueObservationsSet()) {
                    STORM_PRINT_AND_LOG("Analyzing states with unique observation ..." << std::endl);
                    storm::analysis::UniqueObservationStates<ValueType> uniqueAnalysis(*pomdp);
                    std::cout << uniqueAnalysis.analyse() << std::endl;
                }
            
                if (formula) {
                    auto formulaInfo = storm::pomdp::analysis::getFormulaInformation(*pomdp, *formula);
                    STORM_LOG_THROW(!formulaInfo.isUnsupported(), storm::exceptions::InvalidPropertyException, "The formula '" << *formula << "' is not supported by storm-pomdp.");
                    
                    storm::utility::Stopwatch sw(true);
                    // Note that formulaInfo contains state-based information which potentially needs to be updated during preprocessing
                    if (performPreprocessing<ValueType, DdType>(pomdp, formulaInfo, *formula)) {
                        sw.stop();
                        STORM_PRINT_AND_LOG("Time for graph-based POMDP (pre-)processing: " << sw << "." << std::endl);
                        pomdp->printModelInformationToStream(std::cout);
                    }

                    sw.restart();
                    if (performTransformation<ValueType, DdType>(pomdp, *formula)) {
                        sw.stop();
                        STORM_PRINT_AND_LOG("Time for POMDP transformation(s): " << sw << "s." << std::endl);
                    }
                    
                    sw.restart();
                    if (performAnalysis<ValueType, DdType>(pomdp, formulaInfo, *formula)) {
                        sw.stop();
                        STORM_PRINT_AND_LOG("Time for POMDP analysis: " << sw << "s." << std::endl);
                    }
                    

                } else {
                    STORM_LOG_WARN("Nothing to be done. Did you forget to specify a formula?");
                }
            
            }
            
            template <storm::dd::DdType DdType>
            void processOptionsWithDdLib(storm::cli::SymbolicInput const& symbolicInput, storm::cli::ModelProcessingInformation const& mpi) {
                STORM_LOG_ERROR_COND(mpi.buildValueType == mpi.verificationValueType, "Build value type differs from verification value type. Will ignore Verification value type.");
                switch (mpi.buildValueType) {
                    case storm::cli::ModelProcessingInformation::ValueType::FinitePrecision:
                        processOptionsWithValueTypeAndDdLib<double, DdType>(symbolicInput, mpi);
                        break;
                    case storm::cli::ModelProcessingInformation::ValueType::Exact:
                        STORM_LOG_THROW(DdType == storm::dd::DdType::Sylvan, storm::exceptions::UnexpectedException, "Exact arithmetic is only supported with Dd library Sylvan.");
                        processOptionsWithValueTypeAndDdLib<storm::RationalNumber, storm::dd::DdType::Sylvan>(symbolicInput, mpi);
                        break;
                    default:
                        STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected ValueType for model building.");
                }
            }
            
            void processOptions() {
                auto symbolicInput = storm::cli::parseSymbolicInput();
                storm::cli::ModelProcessingInformation mpi;
                std::tie(symbolicInput, mpi) = storm::cli::preprocessSymbolicInput(symbolicInput);
                switch (mpi.ddType) {
                    case storm::dd::DdType::CUDD:
                        processOptionsWithDdLib<storm::dd::DdType::CUDD>(symbolicInput, mpi);
                        break;
                    case storm::dd::DdType::Sylvan:
                        processOptionsWithDdLib<storm::dd::DdType::Sylvan>(symbolicInput, mpi);
                        break;
                    default:
                        STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected Dd Type.");
                }
            }
        }
    }
}

/*!
 * Entry point for the pomdp backend.
 *
 * @param argc The argc argument of main().
 * @param argv The argv argument of main().
 * @return Return code, 0 if successfull, not 0 otherwise.
 */
int main(const int argc, const char** argv) {
    //try {
        storm::utility::setUp();
        storm::cli::printHeader("Storm-pomdp", argc, argv);
        storm::settings::initializePomdpSettings("Storm-POMDP", "storm-pomdp");

        bool optionsCorrect = storm::cli::parseOptions(argc, argv);
        if (!optionsCorrect) {
            return -1;
        }
        storm::utility::Stopwatch totalTimer(true);
        storm::cli::setUrgentOptions();

        // Invoke storm-pomdp with obtained settings
        storm::pomdp::cli::processOptions();

        totalTimer.stop();
        if (storm::settings::getModule<storm::settings::modules::ResourceSettings>().isPrintTimeAndMemorySet()) {
            storm::cli::printTimeAndMemoryStatistics(totalTimer.getTimeInMilliseconds());
        }

    // All operations have now been performed, so we clean up everything and terminate.
        storm::utility::cleanUp();
        return 0;
    // } catch (storm::exceptions::BaseException const &exception) {
    //    STORM_LOG_ERROR("An exception caused Storm-pomdp to terminate. The message of the exception is: " << exception.what());
    //    return 1;
    //} catch (std::exception const &exception) {
    //    STORM_LOG_ERROR("An unexpected exception occurred and caused Storm-pomdp to terminate. The message of this exception is: " << exception.what());
    //    return 2;
    //}
}