#ifndef STORM_STORAGE_PRISM_PROGRAM_H_
#define STORM_STORAGE_PRISM_PROGRAM_H_

#include <memory>
#include <map>
#include <vector>
#include <set>
#include <boost/container/flat_set.hpp>
#include <boost/optional.hpp>

#include "src/storage/prism/Constant.h"
#include "src/storage/prism/Formula.h"
#include "src/storage/prism/Label.h"
#include "src/storage/prism/Module.h"
#include "src/storage/prism/RewardModel.h"
#include "src/storage/prism/SystemCompositionConstruct.h"
#include "src/storage/prism/InitialConstruct.h"
#include "src/storage/prism/Composition.h"
#include "src/utility/solver.h"
#include "src/utility/OsDetection.h"

namespace storm {
    namespace jani {
        class Model;
    }
    
    namespace prism {
        class Program : public LocatedInformation {
        public:
            /*!
             * An enum for the different model types.
             */
            enum class ModelType {UNDEFINED, DTMC, CTMC, MDP, CTMDP, MA};
            
            enum class ValidityCheckLevel  : unsigned {VALIDINPUT = 0, READYFORPROCESSING = 1};
            
            /*!
             * Creates a program with the given model type, undefined constants, global variables, modules, reward
             * models, labels and initial states.
             *
             * @param manager The manager responsible for the variables and expressions of the program.
             * @param modelType The type of the program.
             * @param constants The constants of the program.
             * @param globalBooleanVariables The global boolean variables of the program.
             * @param globalIntegerVariables The global integer variables of the program.
             * @param formulas The formulas defined in the program.
             * @param modules The modules of the program.
             * @param actionToIndexMap A mapping of action names to their indices.
             * @param rewardModels The reward models of the program.
             * @param labels The labels defined for this program.
             * @param initialConstruct The initial construct of the program. If none, then an initial construct is built
             * using the initial values of the variables.
             * @param compositionConstruct If not none, specifies how the modules are composed for the full system.
             * If none, the regular parallel composition is assumed.
             * @param filename The filename in which the program is defined.
             * @param lineNumber The line number in which the program is defined.
             * @param finalModel If set to true, the program is checked for input-validity, as well as some post-processing.
             */
            Program(std::shared_ptr<storm::expressions::ExpressionManager> manager, ModelType modelType, std::vector<Constant> const& constants, std::vector<BooleanVariable> const& globalBooleanVariables, std::vector<IntegerVariable> const& globalIntegerVariables, std::vector<Formula> const& formulas, std::vector<Module> const& modules, std::map<std::string, uint_fast64_t> const& actionToIndexMap, std::vector<RewardModel> const& rewardModels, std::vector<Label> const& labels, boost::optional<InitialConstruct> const& initialConstruct, boost::optional<SystemCompositionConstruct> const& compositionConstruct, std::string const& filename = "", uint_fast64_t lineNumber = 0, bool finalModel = true);
            
            // Provide default implementations for constructors and assignments.
            Program() = default;
            Program(Program const& other) = default;
            Program& operator=(Program const& other) = default;
#ifndef WINDOWS
            Program(Program&& other) = default;
            Program& operator=(Program&& other) = default;
#endif
            
            /*!
             * Retrieves the model type of the model.
             *
             * @return The type of the model.
             */
            ModelType getModelType() const;
            
            /*!
             * Retrieves whether the model is a discrete-time model, i.e. a DTMC or an MDP.
             *
             * @return True iff the model is a discrete-time model.
             */
            bool isDiscreteTimeModel() const;
            
            /*!
             * Retrieves whether the model is one without nondeterministic choices, i.e. a DTMC or a CTMC.
             */
            bool isDeterministicModel() const;

            /*!
             * Retrieves whether there are undefined constants of any type in the program.
             *
             * @return True iff there are undefined constants of any type in the program.
             */
            bool hasUndefinedConstants() const;

            /*!
             * Retrieves whether there are undefined constants appearing in any place other than the update probabilities
             * of the commands and the reward expressions. This is to be used for parametric model checking where the
             * parameters are only allowed to govern the probabilities, not the underlying graph of the model.
             *
             * @return True iff all undefined constants of the model only appear in update probabilities.
             */
            bool hasUndefinedConstantsOnlyInUpdateProbabilitiesAndRewards() const;
            
            /*!
             * Retrieves the undefined constants in the program.
             *
             * @return The undefined constants in the program.
             */
            std::vector<std::reference_wrapper<Constant const>> getUndefinedConstants() const;

            /*!
             * Retrieves the undefined constants in the program as a comma-separated string.
             *
             * @return A string with the undefined constants in the program, separated by a comma
             */
            std::string getUndefinedConstantsAsString() const;

            /*!
             * Retrieves whether the given constant exists in the program.
             *
             * @param constantName The name of the constant to search.
             * @return True iff the constant exists in the program.
             */
            bool hasConstant(std::string const& constantName) const;
            
            /*!
             * Retrieves the constant with the given name if it exists.
             *
             * @param constantName The name of the constant to retrieve.
             * @return The constant with the given name if it exists.
             */
            Constant const& getConstant(std::string const& constantName) const;
            
            /*!
             * Retrieves a mapping of all defined constants to their defining expressions.
             *
             * @return A mapping from constants to their 'values'.
             */
            std::map<storm::expressions::Variable, storm::expressions::Expression> getConstantsSubstitution() const;
            
            /*!
             * Retrieves all constants defined in the program.
             *
             * @return The constants defined in the program.
             */
            std::vector<Constant> const& getConstants() const;
            
            /*!
             * Retrieves the number of all constants defined in the program.
             *
             * @return The number of constants defined in the program.
             */
            std::size_t getNumberOfConstants() const;
            
            /*!
             * Retrieves whether a global Boolean variable with the given name exists
             * 
             * @param variableName The name of the variable
             * @return true iff a global variable of type Boolean with the given name exists.
             */
            bool globalBooleanVariableExists(std::string const& variableName) const;
            
            /**
             * Retrieves whether a global Integer variable with the given name exists
             * 
             * @param variableName The name of the variable
             * @return true iff a global variable of type Integer with the given name exists.
             */
            bool globalIntegerVariableExists(std::string const& variableName) const;
            
            /*!
             * Retrieves the global boolean variables of the program.
             *
             * @return The global boolean variables of the program.
             */
            std::vector<BooleanVariable> const& getGlobalBooleanVariables() const;

            /*!
             * Retrieves a the global boolean variable with the given name.
             *
             * @param variableName The name of the global boolean variable to retrieve.
             * @return The global boolean variable with the given name.
             */
            BooleanVariable const& getGlobalBooleanVariable(std::string const& variableName) const;
            
            /*!
             * Retrieves the global integer variables of the program.
             *
             * @return The global integer variables of the program.
             */
            std::vector<IntegerVariable> const& getGlobalIntegerVariables() const;

            /*!
             * Retrieves a the global integer variable with the given name.
             *
             * @param variableName The name of the global integer variable to retrieve.
             * @return The global integer variable with the given name.
             */
            IntegerVariable const& getGlobalIntegerVariable(std::string const& variableName) const;

            /*!
             * Retrieves all expression variables used by this program.
             *
             * @return The set of expression variables used by this program.
             */
            std::set<storm::expressions::Variable> getAllExpressionVariables() const;
            
            /*!
             * Retrieves a list of expressions that characterize the legal ranges of all variables.
             *
             * @return A list of expressions that characterize the legal ranges of all variables.
             */
            std::vector<storm::expressions::Expression> getAllRangeExpressions() const;
            
            /*!
             * Retrieves the number of global boolean variables of the program.
             *
             * @return The number of global boolean variables of the program.
             */
            std::size_t getNumberOfGlobalBooleanVariables() const;
            
            /*!
             * Retrieves the number of global integer variables of the program.
             *
             * @return The number of global integer variables of the program.
             */
            std::size_t getNumberOfGlobalIntegerVariables() const;

            /*!
             * Retrieves the formulas defined in the program.
             *
             * @return The formulas defined in the program.
             */
            std::vector<Formula> const& getFormulas() const;
            
            /*!
             * Retrieves the number of formulas in the program.
             *
             * @return The number of formulas in the program.
             */
            std::size_t getNumberOfFormulas() const;
            
            /*!
             * Retrieves the number of modules in the program.
             *
             * @return The number of modules in the program.
             */
            std::size_t getNumberOfModules() const;
            
            /*!
             * Retrieves the module with the given index.
             *
             * @param index The index of the module to retrieve.
             * @return The module with the given index.
             */
            Module const& getModule(uint_fast64_t index) const;

            /*!
             * Retrieves whether the program has a module with the given name.
             *
             * @return True iff a module with the given name exists.
             */
            bool hasModule(std::string const& moduleName) const;
            
            /*!
             * Retrieves the module with the given name.
             *
             * @param moduleName The name of the module to retrieve.
             * @return The module with the given name.
             */
            Module const& getModule(std::string const& moduleName) const;
            
            /*!
             * Retrieves all modules of the program.
             *
             * @return All modules of the program.
             */
            std::vector<Module> const& getModules() const;
            
            /*!
             * Retrieves the mapping of action names to their indices.
             *
             * @return The mapping of action names to their indices.
             */
            std::map<std::string, uint_fast64_t> const& getActionNameToIndexMapping() const;
            
            /*!
             * Retrieves whether the program specifies an initial construct.
             */
            bool hasInitialConstruct() const;
            
            /*!
             * Retrieves the initial construct of the program.
             *
             * @return The initial construct of the program.
             */
            InitialConstruct const& getInitialConstruct() const;

            /*!
             * Retrieves an optional containing the initial construct of the program if there is any and nothing otherwise.
             *
             * @return The initial construct of the program.
             */
            boost::optional<InitialConstruct> const& getOptionalInitialConstruct() const;

            /*!
             * Retrieves an expression characterizing the initial states.
             *
             * @return an expression characterizing the initial states.
             */
            storm::expressions::Expression getInitialStatesExpression() const;
            
            /*!
             * Retrieves whether the program specifies a system composition in terms of process algebra operations over
             * the modules.
             *
             * @return True iff the program specifies a system composition.
             */
            bool specifiesSystemComposition() const;
            
            /*!
             * If the program specifies a system composition construct, this method retrieves it.
             *
             * @return The system composition construct as specified by the program.
             */
            SystemCompositionConstruct const& getSystemCompositionConstruct() const;
            
            /*!
             * Retrieves the system composition construct (if any) and none otherwise.
             *
             * @return The system composition construct specified by the program or none.
             */
            boost::optional<SystemCompositionConstruct> getOptionalSystemCompositionConstruct() const;
            
            /*!
             * Retrieves the default system composition for this program.
             *
             * @return The default system composition.
             */
            std::shared_ptr<Composition> getDefaultSystemComposition() const;
            
            /*!
             * Retrieves the set of actions present in the program.
             *
             * @return The set of actions present in the program.
             */
            std::set<std::string> const& getActions() const;
            
            /*!
             * Retrieves the set of synchronizing action indices present in the program.
             *
             * @return The set of synchronizing action indices present in the program.
             */
            std::set<uint_fast64_t> const& getSynchronizingActionIndices() const;
            
            /*!
             * Retrieves the action name of the given action index.
             *
             * @param actionIndex The index of the action whose name to retrieve.
             * @return The name of the action.
             */
            std::string const& getActionName(uint_fast64_t actionIndex) const;
            
            /*!
             * Retrieves the index of the action with the given name.
             *
             * @param actionName The name of the action.
             * @return The index of the action.
             */
            uint_fast64_t getActionIndex(std::string const& actionName) const;
            
            /*!
             * Retrieves whether the program has an action with the given name.
             *
             * @return True iff the program has an action with the given name.
             */
            bool hasAction(std::string const& actionName) const;

            /*!
             * Retrieves whether the program has an action with the given index.
             *
             * @return True iff the program has an action with the given index.
             */
            bool hasAction(uint_fast64_t const& actionIndex) const;
            
            /*!
             * Retrieves the indices of all modules within this program that contain commands that are labelled with the
             * given action.
             *
             * @param action The name of the action the modules are supposed to possess.
             * @return A set of indices of all matching modules.
             */
            std::set<uint_fast64_t> const& getModuleIndicesByAction(std::string const& action) const;

            /*!
             * Retrieves the indices of all modules within this program that contain commands that are labelled with the
             * given action index.
             *
             * @param actionIndex The index of the action the modules are supposed to possess.
             * @return A set of indices of all matching modules.
             */
            std::set<uint_fast64_t> const& getModuleIndicesByActionIndex(uint_fast64_t actionIndex) const;
            
            /*!
             * Retrieves the index of the module in which the given variable name was declared.
             *
             * @param variableName The name of the variable to search.
             * @return The index of the module in which the given variable name was declared.
             */
            uint_fast64_t getModuleIndexByVariable(std::string const& variableName) const;
            
            /*!
             * Retrieves whether the program has reward models.
             *
             * @return True iff the program has at least one reward model.
             */
            bool hasRewardModel() const;
            
            /*!
             * Retrieves whether the program has a reward model with the given name.
             *
             * @param name The name of the reward model to look for.
             * @return True iff the program has a reward model with the given name.
             */
            bool hasRewardModel(std::string const& name) const;
            
            /*!
             * Retrieves the reward models of the program.
             *
             * @return The reward models of the program.
             */
            std::vector<RewardModel> const& getRewardModels() const;
            
            /*!
             * Retrieves the number of reward models in the program.
             *
             * @return The number of reward models in the program.
             */
            std::size_t getNumberOfRewardModels() const;
            
            /*!
             * Retrieves the reward model with the given name.
             *
             * @param rewardModelName The name of the reward model to return.
             * @return The reward model with the given name.
             */
            RewardModel const& getRewardModel(std::string const& rewardModelName) const;
            
            /*!
             * Retrieves the reward model with the given index.
             *
             * @param index The index of the reward model to return.
             * @return The reward model with the given index.
             */
            RewardModel const& getRewardModel(uint_fast64_t index) const;
            
            /*!
             * Checks whether the program has a label with the given name.
             *
             * @param labelName The label of the program.
             * @return True iff the label of the program.
             */
            bool hasLabel(std::string const& labelName) const;
            
            /*!
             * Retrieves all labels that are defined by the probabilitic program.
             *
             * @return A set of labels that are defined in the program.
             */
            std::vector<Label> const& getLabels() const;
            
            /*!
             * Retrieves the expression associated with the given label, if it exists.
             *
             * @param labelName The name of the label to retrieve.
             */
            storm::expressions::Expression const& getLabelExpression(std::string const& label) const;
            
            /*!
             * Retrieves a mapping from all labels in the program to their defining expressions.
             *
             * @return A mapping from label names to their expressions.
             */
            std::map<std::string, storm::expressions::Expression> getLabelToExpressionMapping() const;
            
            /*!
             * Retrieves the number of labels in the program.
             *
             * @return The number of labels in the program.
             */
            std::size_t getNumberOfLabels() const;

            /*!
             * Adds a label with the given name and defining expression to the program.
             *
             * @param name The name of the label. This name must not yet exist as a label name in the program.
             * @param statePredicateExpression The predicate that is described by the label.
             */
            void addLabel(std::string const& name, storm::expressions::Expression const& statePredicateExpression);
            
            /*!
             * Removes the label with the given name from the program.
             *
             * @param name The name of a label that exists within the program.
             */
            void removeLabel(std::string const& name);
            
            /*!
             * Removes all labels that are not contained in the given set from the program. Note: no check is performed
             * as to whether or not the given label names actually exist.
             *
             * @param labelSet The label set that is to be kept.
             */
            void filterLabels(std::set<std::string> const& labelSet);
            
            /*!
             * Creates a new program that drops all commands whose indices are not in the given set.
             *
             * @param indexSet The set of indices for which to keep the commands.
             */
            Program restrictCommands(boost::container::flat_set<uint_fast64_t> const& indexSet) const;
            
            /*!
             * Defines the undefined constants according to the given map and returns the resulting program.
             *
             * @param constantDefinitions A mapping from undefined constant to the expressions they are supposed
             * to be replaced with.
             * @return The program after all undefined constants in the given map have been replaced with their
             * definitions.
             */
            Program defineUndefinedConstants(std::map<storm::expressions::Variable, storm::expressions::Expression> const& constantDefinitions) const;
            
            /*!
             * Substitutes all constants appearing in the expressions of the program by their defining expressions. For
             * this to work, all constants need to be defined prior to calling this.
             *
             * @return The resulting program that only contains expressions over variables of the program.
             */
            Program substituteConstants() const;
            
            /**
             * Entry point for static analysis for simplify. As we use the same expression manager, we recommend to not use the original program any further. 
             * @return A simplified, equivalent program.
             */
            Program simplify();
            
            /*!
             * Checks the validity of the program. If the program is not valid, an exception is thrown with a message
             * that indicates the source of the problem.
             */
            void checkValidity(Program::ValidityCheckLevel lvl = Program::ValidityCheckLevel::READYFORPROCESSING) const;
            
            /*!
             * Creates an equivalent program that contains exactly one module.
             *
             * @param smtSolverFactory an SMT solver factory to use. If none is given, the default one is used.
             * @return The resulting program.
             */
            Program flattenModules(std::unique_ptr<storm::utility::solver::SmtSolverFactory> const& smtSolverFactory = std::unique_ptr<storm::utility::solver::SmtSolverFactory>(new storm::utility::solver::SmtSolverFactory())) const;
            
            friend std::ostream& operator<<(std::ostream& stream, Program const& program);
            
            /*!
             * Retrieves the manager responsible for the expressions of this program.
             *
             * @return The manager responsible for the expressions of this program.
             */
            storm::expressions::ExpressionManager const& getManager() const;

            /*!
             * Retrieves the manager responsible for the expressions of this program.
             *
             * @return The manager responsible for the expressions of this program.
             */
            storm::expressions::ExpressionManager& getManager();

            /*!
             *
             */
            std::unordered_map<uint_fast64_t, std::string> buildCommandIndexToActionNameMap() const;

            std::unordered_map<uint_fast64_t, uint_fast64_t> buildCommandIndexToActionIndex() const;

            std::unordered_map<uint_fast64_t, std::string> buildActionIndexToActionNameMap() const;

            uint_fast64_t numberOfActions() const;

            uint_fast64_t largestActionIndex() const;
            
            /*!
             * Converts the PRISM model into an equivalent JANI model.
             */
            storm::jani::Model toJani(bool allVariablesGlobal = false) const;
            
        private:
            /*!
             * This function builds a command that corresponds to the synchronization of the given list of commands.
             *
             * @param newCommandIndex The index of the command to construct.
             * @param actionIndex The index of the action of the resulting command.
             * @param firstUpdateIndex The index of the first update of the resulting command.
             * @param actionName The name of the action of the resulting command.
             * @param commands The commands to synchronize.
             * @return The resulting command.
             */
            Command synchronizeCommands(uint_fast64_t newCommandIndex, uint_fast64_t actionIndex, uint_fast64_t firstUpdateIndex, std::string const& actionName, std::vector<std::reference_wrapper<Command const>> const& commands) const;
            
            /*!
             * Equips all global variables without initial values with initial values based on their type.
             */
            void createMissingInitialValues();
            
            // The manager responsible for the variables/expressions of the program.
            std::shared_ptr<storm::expressions::ExpressionManager> manager;
            
            // Creates the internal mappings.
            void createMappings();
            
            // The type of the model.
            ModelType modelType;
            
            // The constants of the program.
            std::vector<Constant> constants;
            
            // A mapping from constant names to their corresponding indices.
            std::map<std::string, uint_fast64_t> constantToIndexMap;

            // The global boolean variables.
            std::vector<BooleanVariable> globalBooleanVariables;
            
            // A mapping from global boolean variable names to their corresponding indices.
            std::map<std::string, uint_fast64_t> globalBooleanVariableToIndexMap;
            
            // The global integer variables.
            std::vector<IntegerVariable> globalIntegerVariables;
            
            // A mapping from global integer variable names to their corresponding indices.
            std::map<std::string, uint_fast64_t> globalIntegerVariableToIndexMap;
            
            // The formulas defined in the program.
            std::vector<Formula> formulas;
            
            // A mapping of formula names to their corresponding indices.
            std::map<std::string, uint_fast64_t> formulaToIndexMap;
            
            // The modules associated with the program.
            std::vector<Module> modules;
            
            // A mapping of module names to their indices.
            std::map<std::string, uint_fast64_t> moduleToIndexMap;
            
            // The reward models associated with the program.
            std::vector<RewardModel> rewardModels;
            
            // A mapping of reward models to their indices.
            std::map<std::string, uint_fast64_t> rewardModelToIndexMap;
            
            // The initial construct of the program.
            boost::optional<InitialConstruct> initialConstruct;
            
            // If set, this specifies the way the modules are composed to obtain the full system.
            boost::optional<SystemCompositionConstruct> systemCompositionConstruct;
            
            // The labels that are defined for this model.
            std::vector<Label> labels;
            
            // A mapping from labels to their indices.
            std::map<std::string, uint_fast64_t> labelToIndexMap;
            
            // A mapping from action names to their indices.
            std::map<std::string, uint_fast64_t> actionToIndexMap;

            // A mapping from action indices to their names.
            std::map<uint_fast64_t, std::string> indexToActionMap;
            
            // The set of actions present in this program.
            std::set<std::string> actions;

            // The set of synchronizing actions present in this program.
            std::set<uint_fast64_t> synchronizingActionIndices;
            
            // A map of actions to the set of modules containing commands labelled with this action.
            std::map<uint_fast64_t, std::set<uint_fast64_t>> actionIndicesToModuleIndexMap;
            
            // A mapping from variable names to the modules in which they were declared.
            std::map<std::string, uint_fast64_t> variableToModuleIndexMap;
        };
        
        std::ostream& operator<<(std::ostream& out, Program::ModelType const& type);
        
    } // namespace prism
} // namespace storm

#endif /* STORM_STORAGE_PRISM_PROGRAM_H_ */