#include "storm/abstraction/jani/AutomatonAbstractor.h"

#include "storm/abstraction/AbstractionInformation.h"
#include "storm/abstraction/BottomStateResult.h"
#include "storm/abstraction/GameBddResult.h"

#include "storm/storage/dd/DdManager.h"
#include "storm/storage/dd/Add.h"

#include "storm/storage/jani/Automaton.h"

#include "storm/settings/SettingsManager.h"

#include "storm-config.h"
#include "storm/adapters/CarlAdapter.h"

#include "storm/utility/macros.h"

namespace storm {
    namespace abstraction {
        namespace jani {
            
            using storm::settings::modules::AbstractionSettings;
            
            template <storm::dd::DdType DdType, typename ValueType>
            AutomatonAbstractor<DdType, ValueType>::AutomatonAbstractor(storm::jani::Automaton const& automaton, AbstractionInformation<DdType>& abstractionInformation, std::shared_ptr<storm::utility::solver::SmtSolverFactory> const& smtSolverFactory, bool useDecomposition) : smtSolverFactory(smtSolverFactory), abstractionInformation(abstractionInformation), edges(), automaton(automaton) {
                
                // For each concrete command, we create an abstract counterpart.
                uint64_t edgeId = 0;
                for (auto const& edge : automaton.getEdges()) {
                    edges.emplace_back(edgeId, edge, abstractionInformation, smtSolverFactory, useDecomposition);
                    ++edgeId;
                }
            }
            
            template <storm::dd::DdType DdType, typename ValueType>
            void AutomatonAbstractor<DdType, ValueType>::refine(std::vector<uint_fast64_t> const& predicates) {
                for (uint_fast64_t index = 0; index < edges.size(); ++index) {
                    STORM_LOG_TRACE("Refining edge with index " << index << ".");
                    edges[index].refine(predicates);
                }
            }
            
            template <storm::dd::DdType DdType, typename ValueType>
            storm::expressions::Expression const& AutomatonAbstractor<DdType, ValueType>::getGuard(uint64_t player1Choice) const {
                return edges[player1Choice].getGuard();
            }
            
            template <storm::dd::DdType DdType, typename ValueType>
            std::map<storm::expressions::Variable, storm::expressions::Expression> AutomatonAbstractor<DdType, ValueType>::getVariableUpdates(uint64_t player1Choice, uint64_t auxiliaryChoice) const {
                return edges[player1Choice].getVariableUpdates(auxiliaryChoice);
            }
            
            template <storm::dd::DdType DdType, typename ValueType>
            GameBddResult<DdType> AutomatonAbstractor<DdType, ValueType>::abstract() {
                // First, we retrieve the abstractions of all commands.
                std::vector<GameBddResult<DdType>> edgeDdsAndUsedOptionVariableCounts;
                uint_fast64_t maximalNumberOfUsedOptionVariables = 0;
                for (auto& edge : edges) {
                    edgeDdsAndUsedOptionVariableCounts.push_back(edge.abstract());
                    maximalNumberOfUsedOptionVariables = std::max(maximalNumberOfUsedOptionVariables, edgeDdsAndUsedOptionVariableCounts.back().numberOfPlayer2Variables);
                }
                
                // Then, we build the module BDD by adding the single command DDs. We need to make sure that all command
                // DDs use the same amount DD variable encoding the choices of player 2.
                storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddZero();
                for (auto const& edgeDd : edgeDdsAndUsedOptionVariableCounts) {
                    result |= edgeDd.bdd && this->getAbstractionInformation().getPlayer2ZeroCube(edgeDd.numberOfPlayer2Variables, maximalNumberOfUsedOptionVariables);
                }
                return GameBddResult<DdType>(result, maximalNumberOfUsedOptionVariables);
            }
            
            template <storm::dd::DdType DdType, typename ValueType>
            BottomStateResult<DdType> AutomatonAbstractor<DdType, ValueType>::getBottomStateTransitions(storm::dd::Bdd<DdType> const& reachableStates, uint_fast64_t numberOfPlayer2Variables) {
                BottomStateResult<DdType> result(this->getAbstractionInformation().getDdManager().getBddZero(), this->getAbstractionInformation().getDdManager().getBddZero());
                
                for (auto& edge : edges) {
                    BottomStateResult<DdType> commandBottomStateResult = edge.getBottomStateTransitions(reachableStates, numberOfPlayer2Variables);
                    result.states |= commandBottomStateResult.states;
                    result.transitions |= commandBottomStateResult.transitions;
                }
                
                return result;
            }
            
            template <storm::dd::DdType DdType, typename ValueType>
            storm::dd::Add<DdType, ValueType> AutomatonAbstractor<DdType, ValueType>::getEdgeUpdateProbabilitiesAdd() const {
                storm::dd::Add<DdType, ValueType> result = this->getAbstractionInformation().getDdManager().template getAddZero<ValueType>();
                for (auto const& edge : edges) {
                    result += edge.getEdgeUpdateProbabilitiesAdd();
                }
                return result;
            }
            
            template <storm::dd::DdType DdType, typename ValueType>
            std::vector<EdgeAbstractor<DdType, ValueType>> const& AutomatonAbstractor<DdType, ValueType>::getEdges() const {
                return edges;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            std::vector<EdgeAbstractor<DdType, ValueType>>& AutomatonAbstractor<DdType, ValueType>::getEdges() {
                return edges;
            }
            
            template <storm::dd::DdType DdType, typename ValueType>
            std::size_t AutomatonAbstractor<DdType, ValueType>::getNumberOfEdges() const {
                return edges.size();
            }

            template <storm::dd::DdType DdType, typename ValueType>
            AbstractionInformation<DdType> const& AutomatonAbstractor<DdType, ValueType>::getAbstractionInformation() const {
                return abstractionInformation.get();
            }
            
            template class AutomatonAbstractor<storm::dd::DdType::CUDD, double>;
            template class AutomatonAbstractor<storm::dd::DdType::Sylvan, double>;
#ifdef STORM_HAVE_CARL
			template class AutomatonAbstractor<storm::dd::DdType::Sylvan, storm::RationalFunction>;
#endif
        }
    }
}