#include "storm/solver/AbstractEquationSolver.h"

#include "storm/adapters/RationalNumberAdapter.h"
#include "storm/adapters/RationalFunctionAdapter.h"
#include "storm/exceptions/InvalidOperationException.h"
#include "storm/exceptions/InvalidStateException.h"
#include "storm/exceptions/UnmetRequirementException.h"
#include "storm/settings/SettingsManager.h"
#include "storm/settings/modules/GeneralSettings.h"
#include "storm/utility/constants.h"
#include "storm/utility/macros.h"
#include "storm/utility/SignalHandler.h"


namespace storm {
    namespace solver {
        
        template<typename ValueType>
        AbstractEquationSolver<ValueType>::AbstractEquationSolver() {
            if (storm::settings::getModule<storm::settings::modules::GeneralSettings>().isVerboseSet()) {
                this->progressMeasurement = storm::utility::ProgressMeasurement("iterations");
            }
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setTerminationCondition(std::unique_ptr<TerminationCondition<ValueType>> terminationCondition) {
            this->terminationCondition = std::move(terminationCondition);
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::resetTerminationCondition() {
            this->terminationCondition = nullptr;
        }
        
        template<typename ValueType>
        bool AbstractEquationSolver<ValueType>::hasCustomTerminationCondition() const {
            return static_cast<bool>(this->terminationCondition);
        }
        
        template<typename ValueType>
        TerminationCondition<ValueType> const& AbstractEquationSolver<ValueType>::getTerminationCondition() const {
            return *terminationCondition;
        }
        
        template<typename ValueType>
        std::unique_ptr<TerminationCondition<ValueType>> const& AbstractEquationSolver<ValueType>::getTerminationConditionPointer() const {
            return terminationCondition;
        }
        
        template<typename ValueType>
        bool AbstractEquationSolver<ValueType>::terminateNow(std::vector<ValueType> const& values, SolverGuarantee const& guarantee) const {
            if (!this->hasCustomTerminationCondition()) {
                return false;
            }
            
            return this->getTerminationCondition().terminateNow(values, guarantee);
        }
        
        template<typename ValueType>
        bool AbstractEquationSolver<ValueType>::hasRelevantValues() const {
            return static_cast<bool>(relevantValues);
        }
        
        template<typename ValueType>
        storm::storage::BitVector const& AbstractEquationSolver<ValueType>::getRelevantValues()const {
            return relevantValues.get();
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setRelevantValues(storm::storage::BitVector&& relevantValues) {
            this->relevantValues = std::move(relevantValues);
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::clearRelevantValues() {
            relevantValues = boost::none;
        }
        
        template<typename ValueType>
        bool AbstractEquationSolver<ValueType>::hasLowerBound(BoundType const& type) const {
            if (type == BoundType::Any) {
                return static_cast<bool>(lowerBound) || static_cast<bool>(lowerBounds);
            } else if (type == BoundType::Global) {
                return static_cast<bool>(lowerBound);
            } else if (type == BoundType::Local) {
                return static_cast<bool>(lowerBounds);
            }
            return false;
        }
        
        template<typename ValueType>
        bool AbstractEquationSolver<ValueType>::hasUpperBound(BoundType const& type) const {
            if (type == BoundType::Any) {
                return static_cast<bool>(upperBound) || static_cast<bool>(upperBounds);
            } else if (type == BoundType::Global) {
                return static_cast<bool>(upperBound);
            } else if (type == BoundType::Local) {
                return static_cast<bool>(upperBounds);
            }
            return false;
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setLowerBound(ValueType const& value) {
            lowerBound = value;
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setUpperBound(ValueType const& value) {
            upperBound = value;
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setBounds(ValueType const& lower, ValueType const& upper) {
            setLowerBound(lower);
            setUpperBound(upper);
        }
        
        template<typename ValueType>
        ValueType const& AbstractEquationSolver<ValueType>::getLowerBound() const {
            return lowerBound.get();
        }
        
        template<typename ValueType>
        ValueType AbstractEquationSolver<ValueType>::getLowerBound(bool convertLocalBounds) const {
            if (lowerBound) {
                return lowerBound.get();
            } else if (convertLocalBounds) {
                return *std::min_element(lowerBounds->begin(), lowerBounds->end());
            }
            STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "No lower bound available but some was requested.");
            return ValueType();
        }
        
        template<typename ValueType>
        ValueType const& AbstractEquationSolver<ValueType>::getUpperBound() const {
            return upperBound.get();
        }

        template<typename ValueType>
        ValueType AbstractEquationSolver<ValueType>::getUpperBound(bool convertLocalBounds) const {
            if (upperBound) {
                return upperBound.get();
            } else if (convertLocalBounds) {
                return *std::max_element(upperBounds->begin(), upperBounds->end());
            }
            STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "No upper bound available but some was requested.");
            return ValueType();
        }

        
        template<typename ValueType>
        std::vector<ValueType> const& AbstractEquationSolver<ValueType>::getLowerBounds() const {
            return lowerBounds.get();
        }
        
        template<typename ValueType>
        std::vector<ValueType> const& AbstractEquationSolver<ValueType>::getUpperBounds() const {
            return upperBounds.get();
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setLowerBounds(std::vector<ValueType> const& values) {
            lowerBounds = values;
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setLowerBounds(std::vector<ValueType>&& values) {
            lowerBounds = std::move(values);
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setUpperBounds(std::vector<ValueType> const& values) {
            upperBounds = values;
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setUpperBounds(std::vector<ValueType>&& values) {
            upperBounds = std::move(values);
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setBounds(std::vector<ValueType> const& lower, std::vector<ValueType> const& upper) {
            setLowerBounds(lower);
            setUpperBounds(upper);
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::setBoundsFromOtherSolver(AbstractEquationSolver<ValueType> const& other) {
            if (other.hasLowerBound(BoundType::Global)) {
                this->setLowerBound(other.getLowerBound());
            }
            if (other.hasLowerBound(BoundType::Local)) {
                this->setLowerBounds(other.getLowerBounds());
            }
            if (other.hasUpperBound(BoundType::Global)) {
                this->setUpperBound(other.getUpperBound());
            }
            if (other.hasUpperBound(BoundType::Local)) {
                this->setUpperBounds(other.getUpperBounds());
            }
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::clearBounds() {
            lowerBound = boost::none;
            upperBound = boost::none;
            lowerBounds = boost::none;
            upperBounds = boost::none;
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::createLowerBoundsVector(std::vector<ValueType>& lowerBoundsVector) const {
            if (this->hasLowerBound(BoundType::Local)) {
                lowerBoundsVector = this->getLowerBounds();
            } else {
                ValueType lowerBound = this->hasLowerBound(BoundType::Global) ? this->getLowerBound() : storm::utility::zero<ValueType>();
                for (auto& e : lowerBoundsVector) {
                    e = lowerBound;
                }
            }
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::createUpperBoundsVector(std::vector<ValueType>& upperBoundsVector) const {
            STORM_LOG_ASSERT(this->hasUpperBound(), "Expecting upper bound(s).");
            if (this->hasUpperBound(BoundType::Global)) {
                upperBoundsVector.assign(upperBoundsVector.size(), this->getUpperBound());
            } else {
                upperBoundsVector.assign(this->getUpperBounds().begin(), this->getUpperBounds().end());
            }
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::createUpperBoundsVector(std::unique_ptr<std::vector<ValueType>>& upperBoundsVector, uint64_t length) const {
            STORM_LOG_ASSERT(this->hasUpperBound(), "Expecting upper bound(s).");
            if (!upperBoundsVector) {
                if (this->hasUpperBound(BoundType::Local)) {
                    STORM_LOG_ASSERT(length == this->getUpperBounds().size(), "Mismatching sizes.");
                    upperBoundsVector = std::make_unique<std::vector<ValueType>>(this->getUpperBounds());
                } else {
                    upperBoundsVector = std::make_unique<std::vector<ValueType>>(length, this->getUpperBound());
                }
            } else {
                createUpperBoundsVector(*upperBoundsVector);
            }
        }
        
        template<typename ValueType>
        bool AbstractEquationSolver<ValueType>::isShowProgressSet() const {
            return this->progressMeasurement.is_initialized();
        }
        
        template<typename ValueType>
        uint64_t AbstractEquationSolver<ValueType>::getShowProgressDelay() const {
            STORM_LOG_ASSERT(this->isShowProgressSet(), "Tried to get the progress message delay but progress is not shown.");
            return this->progressMeasurement->getShowProgressDelay();
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::startMeasureProgress(uint64_t startingIteration) const {
            if (this->isShowProgressSet()) {
                this->progressMeasurement->startNewMeasurement(startingIteration);
            }
        }
        
        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::showProgressIterative(uint64_t iteration, boost::optional<uint64_t> const& bound) const {
            if (this->isShowProgressSet()) {
                if (bound) {
                    this->progressMeasurement->setMaxCount(bound.get());
                }
                this->progressMeasurement->updateProgress(iteration);
            }
        }


        template<typename ValueType>
        void AbstractEquationSolver<ValueType>::reportStatus(SolverStatus status, boost::optional<uint64_t> const& iterations) const {
            if (iterations) {
                switch (status) {
                    case SolverStatus::Converged:
                        STORM_LOG_TRACE("Iterative solver converged after " << iterations.get() << " iterations.");
                        break;
                    case SolverStatus::TerminatedEarly:
                        STORM_LOG_TRACE("Iterative solver terminated early after " << iterations.get() << " iterations.");
                        break;
                    case SolverStatus::MaximalIterationsExceeded:
                        STORM_LOG_WARN("Iterative solver did not converge after " << iterations.get() << " iterations.");
                        break;
                    case SolverStatus::Aborted:
                        STORM_LOG_WARN("Iterative solver was aborted after " << iterations.get() << " iterations.");
                        break;
                    default:
                        STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "Iterative solver terminated unexpectedly.");
                }
            } else {
                switch (status) {
                    case SolverStatus::Converged:
                        STORM_LOG_TRACE("Solver converged.");
                        break;
                    case SolverStatus::TerminatedEarly:
                        STORM_LOG_TRACE("Solver terminated early.");
                        break;
                    case SolverStatus::MaximalIterationsExceeded:
                        STORM_LOG_ASSERT(false, "Non-iterative solver should not exceed maximal number of iterations.");
                        STORM_LOG_WARN("Solver did not converge.");
                        break;
                    case SolverStatus::Aborted:
                        STORM_LOG_WARN("Solver was aborted.");
                        break;
                    default:
                        STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "Solver terminated unexpectedly.");
                }
            }
        }


        template<typename ValueType>
        SolverStatus AbstractEquationSolver<ValueType>::updateStatus(SolverStatus status, bool earlyTermination, uint64_t iterations, uint64_t maximalNumberOfIterations) const {
            if (status != SolverStatus::Converged) {
                if (earlyTermination) {
                    status = SolverStatus::TerminatedEarly;
                } else if (iterations >= maximalNumberOfIterations) {
                    status = SolverStatus::MaximalIterationsExceeded;
                } else if (storm::utility::resources::isTerminate()) {
                    status = SolverStatus::Aborted;
                }
            }
            return status;
        }

        template<typename ValueType>
        SolverStatus AbstractEquationSolver<ValueType>::updateStatus(SolverStatus status, std::vector<ValueType> const& x, SolverGuarantee const& guarantee, uint64_t iterations, uint64_t maximalNumberOfIterations) const {
            return this->updateStatus(status, this->hasCustomTerminationCondition() && this->getTerminationCondition().terminateNow(x, guarantee), iterations, maximalNumberOfIterations);
        }


        template class AbstractEquationSolver<double>;
        template class AbstractEquationSolver<float>;

#ifdef STORM_HAVE_CARL
        template class AbstractEquationSolver<storm::RationalNumber>;
        template class AbstractEquationSolver<storm::RationalFunction>;
#endif
        
    }
}