/*
    Copyright (c) 2011-2014 University of Zurich

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
*/

#include <plll/config.hpp>
#include "lll2-internal.hpp"
#include "transform.cpp"

#include <sstream>
#include <boost/bind.hpp>

#include <plll/arithmetic.hpp>
#include <plll/rational.hpp>
#if !defined(PLLL_CONFIG_NO_ARITHMETIC_LONGDOUBLE) || !defined(PLLL_CONFIG_NO_ARITHMETIC_DOUBLE)
  #include "nfp-wrapper.hpp"
#endif
#if !defined(PLLL_CONFIG_NO_ARITHMETIC_QUADDOUBLE) || !defined(PLLL_CONFIG_NO_ARITHMETIC_DOUBLEDOUBLE)
  #include "ddqd-wrapper.hpp"
#endif

namespace plll
{
    class LRIISelector : public LRIInterface
    {
    private:
        class Interface
        {
        private:
            long d_max_bits;
            boost::function<LRIInterface*(linalg::math_matrix<arithmetic::Integer> &)> d_interface_generator;
            mutable const LatticeReduction::GramSchmidtInformer * d_gsi;

        public:
            Interface(long max_bits, boost::function<LRIInterface*(linalg::math_matrix<arithmetic::Integer> &)> interfacegen)
                : d_max_bits(max_bits), d_interface_generator(interfacegen), d_gsi(NULL)
            {
            }

            ~Interface()
            {
            }

            LRIInterface * generateInterface(linalg::math_matrix<arithmetic::Integer> & matrix) const
            {
                return d_interface_generator(matrix);
            }

            long maxSupportedBits() const
            {
                return d_max_bits;
            }

            bool isFine(unsigned bits, unsigned dimension) const
            {
                long bits_needed = 2 * bits + arithmetic::approxLog2(dimension) + 4;
                return bits_needed <= d_max_bits;
            }
        };

        std::list<Interface> d_interfaces;
        mutable std::list<Interface>::const_iterator d_interface_gen, d_better_interface_gen;
        mutable STD_AUTO_PTR<LRIInterface> d_interface;
        mutable bool d_continue, d_reselect;
        mutable MaxBitsCallbackFunction d_maxbitscf;

        inline bool isBetter(unsigned bits, unsigned dimension) const
        {
            return (d_better_interface_gen == d_interfaces.end()) ? false : d_better_interface_gen->isFine(bits, dimension);
        }

        void maxBitsCallbackFunction(unsigned dimension, unsigned maxbits)
        {
            if (!d_maxbitscf.empty())
                d_maxbitscf(dimension, maxbits);
            if (d_verbose_function && Verbose::yieldsOutput(d_verbose_outputlevel, LatticeReduction::VL_Chatter))
            {
                std::ostringstream s;
                s << "...base has " << maxbits << " bits...";
                d_verbose_function(LatticeReduction::VL_Chatter, s.str());
            }
            if (!d_interface_gen->isFine(maxbits, dimension) || isBetter(maxbits, dimension))
            {
                d_reselect = true;
                throw change_interface_exception();
            }
        }

        bool isContinuing() const
        {
            if (d_reselect)
                selectInterface(true);
            bool v = d_continue;
            d_continue = false;
            return v;
        }

        unsigned maxBits() const
        {
            unsigned maxbits = 0;
            for (unsigned i = 0; i < d_lattice.rows(); ++i)
                for (unsigned j = 0; j < d_lattice.cols(); ++j)
                {
                    long b = arithmetic::approxLog2(d_lattice(i, j));
                    if (b > maxbits)
                        maxbits = b;
                }
            return maxbits;
        }

        void selectInterface(bool withContinue = false) const
        {
            if (d_interface.get())
            {
                if (d_verbose_function && Verbose::yieldsOutput(d_verbose_outputlevel, LatticeReduction::VL_Information))
                    d_verbose_function(LatticeReduction::VL_Information, "Applying interface change");
                d_interface.reset();
            }

            d_continue = withContinue;
            d_reselect = false;
            unsigned bits = maxBits();
            d_interface_gen = d_interfaces.end();
            for (std::list<Interface>::const_iterator i = d_interfaces.begin(); i != d_interfaces.end(); ++i)
            {
                if (i->isFine(bits, d_lattice.rows()))
                    d_interface_gen = i;
            }
            assert(d_interface_gen != d_interfaces.end());

            if (d_verbose_function && Verbose::yieldsOutput(d_verbose_outputlevel, LatticeReduction::VL_Information))
            {
                std::ostringstream s;
                s << "Selecting interface which supports up to " << d_interface_gen->maxSupportedBits() << " bits";
                d_verbose_function(LatticeReduction::VL_Information, s.str());
            }

            // Generate interface
            d_interface.reset(d_interface_gen->generateInterface(d_lattice));
            if (d_ensure_min_prec)
                d_interface->ensureMinimumPrecision(d_min_prec);
            if (d_set_verbose)
                d_interface->setupVerbose(d_verbose_outputlevel, d_verbose_function);

            // Select next interface
            d_better_interface_gen = d_interface_gen;
            ++d_better_interface_gen;
        }

        const LRIInterface & getInterface() const
        {
            return *d_interface;
        }

        LRIInterface & getInterface()
        {
            return *d_interface;
        }

        void unselectInterface() const
        {
            d_interface.reset();
        }

        class GSI : public LatticeReduction::GramSchmidtInformer
        {
        private:
            LRIISelector & d_selector;

        public:
            GSI(LRIISelector & selector)
                : d_selector(selector)
            {
            }

            virtual ~GSI()
            {
            }

            virtual double getGSCoefficientD(unsigned i, unsigned j) const
            {
                d_selector.selectInterface();
                double r = d_selector.getInterface().getInformer()->getGSCoefficientD(i, j);
                d_selector.unselectInterface();
                return r;
            }

            virtual long double getGSCoefficientLD(unsigned i, unsigned j) const
            {
                d_selector.selectInterface();
                long double r = d_selector.getInterface().getInformer()->getGSCoefficientLD(i, j);
                d_selector.unselectInterface();
                return r;
            }

            virtual arithmetic::Real getGSCoefficientR(unsigned i, unsigned j, const arithmetic::RealContext & rc) const
            {
                d_selector.selectInterface();
                arithmetic::Real r = d_selector.getInterface().getInformer()->getGSCoefficientR(i, j, rc);
                d_selector.unselectInterface();
                return r;
            }

            virtual double getGSSqNormD(unsigned i) const
            {
                d_selector.selectInterface();
                double r = d_selector.getInterface().getInformer()->getGSSqNormD(i);
                d_selector.unselectInterface();
                return r;
            }

            virtual long double getGSSqNormLD(unsigned i) const
            {
                d_selector.selectInterface();
                long double r = d_selector.getInterface().getInformer()->getGSSqNormLD(i);
                d_selector.unselectInterface();
                return r;
            }

            virtual arithmetic::Real getGSSqNormR(unsigned i, const arithmetic::RealContext & rc) const
            {
                d_selector.selectInterface();
                arithmetic::Real r = d_selector.getInterface().getInformer()->getGSSqNormR(i, rc);
                d_selector.unselectInterface();
                return r;
            }

            virtual double computeProjectionLengthD(unsigned k, unsigned b, const linalg::math_rowvector<arithmetic::Integer> & vec) const
            {
                d_selector.selectInterface();
                double r = d_selector.getInterface().getInformer()->computeProjectionLengthD(k, b, vec);
                d_selector.unselectInterface();
                return r;
            }

            virtual long double computeProjectionLengthLD(unsigned k, unsigned b, const linalg::math_rowvector<arithmetic::Integer> & vec) const
            {
                d_selector.selectInterface();
                long double r = d_selector.getInterface().getInformer()->computeProjectionLengthLD(k, b, vec);
                d_selector.unselectInterface();
                return r;
            }

            virtual arithmetic::Real computeProjectionLengthR(unsigned k, unsigned b, const linalg::math_rowvector<arithmetic::Integer> & vec, const arithmetic::RealContext & rc) const
            {
                d_selector.selectInterface();
                arithmetic::Real r = d_selector.getInterface().getInformer()->computeProjectionLengthR(k, b, vec, rc);
                d_selector.unselectInterface();
                return r;
            }
        };

        friend class GSI;

        GSI d_gsi;

        bool d_ensure_min_prec;
        unsigned long d_min_prec;

        bool d_set_verbose;
        LatticeReduction::VerboseOutputLevel d_verbose_outputlevel;
        LatticeReduction::VerboseFunction d_verbose_function;

    public:
        LRIISelector(linalg::math_matrix<arithmetic::Integer> & lattice, LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf)
            : LRIInterface(lattice), d_interface(), d_continue(false), d_reselect(false), d_gsi(*this),
              d_ensure_min_prec(false), d_min_prec(0), d_set_verbose(false), d_verbose_outputlevel(vol), d_verbose_function(vf)
        {
        }

        virtual ~LRIISelector()
        {
        }

        void addInterface(long max_bits, boost::function<LRIInterface*(linalg::math_matrix<arithmetic::Integer> &)> interfacegen)
        {
            d_interfaces.push_back(Interface(max_bits, interfacegen));
        }

        virtual void setupVerbose(LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf)
        {
            d_set_verbose = true;
            d_verbose_outputlevel = vol;
            d_verbose_function = vf;
        }

        virtual const LatticeReduction::GramSchmidtInformer * getInformer() const
        {
            return &d_gsi;
        }

        virtual void ensureMinimumPrecision(unsigned long prec)
        {
            d_ensure_min_prec = prec;
            d_min_prec = prec;
        }

        virtual void forceGSRebuild(bool b)
        {
        }

        virtual double getGSCoefficientD(unsigned i, unsigned j) const
        {
            selectInterface();
            double r = getInterface().getGSCoefficientD(i, j);
            unselectInterface();
            return r;
        }

        virtual long double getGSCoefficientLD(unsigned i, unsigned j) const
        {
            selectInterface();
            long double r = getInterface().getGSCoefficientLD(i, j);
            unselectInterface();
            return r;
        }

        virtual arithmetic::Real getGSCoefficientR(unsigned i, unsigned j, const arithmetic::RealContext & rc) const
        {
            selectInterface();
            arithmetic::Real r = getInterface().getGSCoefficientR(i, j, rc);
            unselectInterface();
            return r;
        }

        virtual double getGSSqNormD(unsigned i) const
        {
            selectInterface();
            double r = getInterface().getGSSqNormD(i);
            unselectInterface();
            return r;
        }

        virtual long double getGSSqNormLD(unsigned i) const
        {
            selectInterface();
            long double r = getInterface().getGSSqNormLD(i);
            unselectInterface();
            return r;
        }

        virtual arithmetic::Real getGSSqNormR(unsigned i, const arithmetic::RealContext & rc) const
        {
            selectInterface();
            arithmetic::Real r = getInterface().getGSSqNormR(i, rc);
            unselectInterface();
            return r;
        }

        virtual void modFlip(Transform & transform, unsigned i)
        {
            selectInterface();
            getInterface().modFlip(transform, i);
            unselectInterface();
        }

        virtual void modSwap(Transform & transform, unsigned i, unsigned j)
        {
            selectInterface();
            getInterface().modSwap(transform, i, j);
            unselectInterface();
        }

        virtual void modAdd(Transform & transform, unsigned i, unsigned j, const arithmetic::Integer & m)
        {
            selectInterface();
            getInterface().modAdd(transform, i, j, m);
            unselectInterface();
        }

        virtual void sortProjected(unsigned & begin, unsigned & end, Transform & transform)
        {
            selectInterface();
            getInterface().sortProjected(begin, end, transform);
            unselectInterface();
        }

        virtual void sizereduction(unsigned & begin, unsigned & end, Transform & transform)
        {
            selectInterface();
            getInterface().sizereduction(begin, end, transform);
            unselectInterface();
        }

        virtual void lll(unsigned & begin, unsigned & end, Transform & transform, double alpha, LatticeReduction::LLLMode mode,
                         LatticeReduction::CallbackFunction cf, LatticeReduction::CallbackFunction_LI cf2, double cf_int,
                         LatticeReduction::MinCallbackFunction mcf, LatticeReduction::MinCallbackFunction_LI mcf2,
                         MaxBitsCallbackFunction mbcf, bool anneal, LatticeReduction::AnnealCallbackFunction acf,
                         LatticeReduction::LLL_AnnealFunction af, LatticeReduction::DIMethod di, LatticeReduction::DIMode di_mode,
                         LatticeReduction::DIChoice di_choice, unsigned di_bs)
        {
            selectInterface(true);
            d_maxbitscf = mbcf;
            while (isContinuing())
                getInterface().lll(begin, end, transform, alpha, mode, cf, cf2, cf_int, mcf, mcf2,
                                   boost::bind(&LRIISelector::maxBitsCallbackFunction, this, _1, _2),
                                   anneal, acf, af, di, di_mode, di_choice, di_bs);
            unselectInterface();
        }

        virtual void bkz(unsigned & begin, unsigned & end, Transform & transform, double alpha, unsigned blocksize, LatticeReduction::BKZMode mode,
                         LatticeReduction::CallbackFunction cf, LatticeReduction::CallbackFunction_LI cf2, double cf_int,
                         LatticeReduction::MinCallbackFunction mcf, LatticeReduction::MinCallbackFunction_LI mcf2, MaxBitsCallbackFunction mbcf,
                         LatticeReduction::EnumCallbackFunction ecf, LatticeReduction::EnumCallbackFunction_LI ecf2, bool anneal,
                         LatticeReduction::AnnealCallbackFunction acf, LatticeReduction::BKZ_AnnealFunction af,
                         LatticeReduction::DIMethod di, LatticeReduction::DIMode di_mode, LatticeReduction::DIChoice di_choice, unsigned di_bs)
        {
            selectInterface(true);
            d_maxbitscf = mbcf;
            while (isContinuing())
                getInterface().bkz(begin, end, transform, alpha, blocksize, mode, cf, cf2, cf_int, mcf, mcf2,
                                   boost::bind(&LRIISelector::maxBitsCallbackFunction, this, _1, _2),
                                   ecf, ecf2, anneal, acf, af, di, di_mode, di_choice, di_bs);
            unselectInterface();
        }

        virtual void hkz(unsigned & begin, unsigned & end, Transform & transform, bool dual,
                         LatticeReduction::CallbackFunction cf, LatticeReduction::CallbackFunction_LI cf2, double cf_int,
                         LatticeReduction::MinCallbackFunction mcf, LatticeReduction::MinCallbackFunction_LI mcf2,
                         MaxBitsCallbackFunction mbcf,
                         LatticeReduction::EnumCallbackFunction ecf, LatticeReduction::EnumCallbackFunction_LI ecf2)
        {
            selectInterface(true);
            d_maxbitscf = mbcf;
            while (isContinuing())
                getInterface().hkz(begin, end, transform, dual, cf, cf2, cf_int, mcf, mcf2,
                                   boost::bind(&LRIISelector::maxBitsCallbackFunction, this, _1, _2),
                                   ecf, ecf2);
            unselectInterface();
        }

        virtual void svp(unsigned & begin, unsigned & end, Transform & transform, bool make_basis, bool extreme, bool dual,
                         LatticeReduction::CallbackFunction cf, LatticeReduction::CallbackFunction_LI cf2, double cf_int,
                         LatticeReduction::MinCallbackFunction mcf, LatticeReduction::MinCallbackFunction_LI mcf2,
                         MaxBitsCallbackFunction mbcf,
                         LatticeReduction::EnumCallbackFunction ecf, LatticeReduction::EnumCallbackFunction_LI ecf2)
        {
            selectInterface(true);
            d_maxbitscf = mbcf;
            while (isContinuing())
                getInterface().svp(begin, end, transform, make_basis, extreme, dual, cf, cf2, cf_int, mcf, mcf2,
                                   boost::bind(&LRIISelector::maxBitsCallbackFunction, this, _1, _2),
                                   ecf, ecf2);
            unselectInterface();
        }

        virtual bool isSizeReduced(unsigned begin, unsigned end) const
        {
            selectInterface();
            bool ret = getInterface().isSizeReduced(begin, end);
            unselectInterface();
            return ret;
        }

        virtual bool isLLLBasis(unsigned begin, unsigned end, double alpha, LatticeReduction::LLLMode mode,
                                LatticeReduction::DIMethod di, LatticeReduction::DIChoice di_choice, unsigned di_bs) const
        {
            selectInterface();
            bool ret = getInterface().isLLLBasis(begin, end, alpha, mode, di, di_choice, di_bs);
            unselectInterface();
            return ret;
        }

        virtual bool isBKZBasis(unsigned begin, unsigned end, double alpha, unsigned blocksize, LatticeReduction::BKZMode mode,
                                LatticeReduction::DIMethod di, LatticeReduction::DIChoice di_choice, unsigned di_bs) const
        {
            selectInterface();
            bool ret = getInterface().isBKZBasis(begin, end, alpha, blocksize, mode, di, di_choice, di_bs);
            unselectInterface();
            return ret;
        }

        virtual bool isHKZBasis(unsigned begin, unsigned end, bool dual) const
        {
            selectInterface();
            bool ret = getInterface().isHKZBasis(begin, end, dual);
            unselectInterface();
            return ret;
        }

        virtual bool isSVPBasis(unsigned begin, unsigned end, bool dual) const
        {
            selectInterface();
            bool ret = getInterface().isSVPBasis(begin, end, dual);
            unselectInterface();
            return ret;
        }
    };

    template<class RealTypeContext, class IntTypeContext>
    LRIInterface * CreateLRIInterfaceWithContexts(LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf,
                                                  linalg::math_matrix<arithmetic::Integer> & lattice, LatticeReduction::GramSchmidt gs, bool gsr,
                                                  LatticeReduction::SVPMode svp, unsigned max_cores,
                                                  const RealTypeContext & rc, const IntTypeContext & ic);

    template<class RealTypeContext>
    LRIInterface * CreateLRIInterface(LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf,
                                      linalg::math_matrix<arithmetic::Integer> & lattice, LatticeReduction::GramSchmidt gs, bool gsr,
                                      LatticeReduction::SVPMode svp, unsigned max_cores,
                                      LatticeReduction::Integers ints, const RealTypeContext & rc)
    {
        switch (ints)
        {
#if !defined(PLLL_CONFIG_NO_ARITHMETIC_BIGINT) && !defined(PLLL_CONFIG_NO_ARITHMETIC_LONGINT)
        case LatticeReduction::I_Auto:
        {
            LRIISelector * sel = new LRIISelector(lattice, vol, vf);
            // Add in decreasing order of supported precisions
            sel->addInterface(std::numeric_limits<long>::max(),
                              boost::bind(CreateLRIInterfaceWithContexts<RealTypeContext, arithmetic::IntegerContext>,
                                          vol, vf, _1, gs, gsr, svp, max_cores, rc, arithmetic::IntegerContext()));
            sel->addInterface(std::numeric_limits<long int>::digits - 1,
                              boost::bind(CreateLRIInterfaceWithContexts<RealTypeContext, arithmetic::NIntContext<long int> >,
                                          vol, vf, _1, gs, gsr, svp, max_cores, rc, arithmetic::NIntContext<long int>()));
            return sel;
        }
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_BIGINT
        case LatticeReduction::I_ArbitraryPrecision:
            return CreateLRIInterfaceWithContexts(vol, vf, lattice, gs, gsr, svp, max_cores, rc, arithmetic::IntegerContext());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_LONGINT
        case LatticeReduction::I_LongInt:
            return CreateLRIInterfaceWithContexts(vol, vf, lattice, gs, gsr, svp, max_cores, rc, arithmetic::NIntContext<long int>());
#endif
        }
        assert(!"Integer arithmetic not supported!");
        return NULL;
    }

    LRIInterface * CreateLRIInterface(LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf,
                                      linalg::math_matrix<arithmetic::Integer> & lattice,
                                      LatticeReduction::Arithmetic arith, LatticeReduction::Integers ints,
                                      LatticeReduction::GramSchmidt gs, bool gsr,
                                      LatticeReduction::SVPMode svp, unsigned max_cores)
    {
        switch(arith)
        {
        default:
#ifndef PLLL_CONFIG_NO_ARITHMETIC_LONGDOUBLE
        case LatticeReduction::A_LongDouble:   return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::NFPContext<long double>());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_REAL
        case LatticeReduction::A_Real:         return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::RealContext());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_RATIONAL
        case LatticeReduction::A_Rational:     return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::RationalContext());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_DOUBLE
        case LatticeReduction::A_Double:       return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::NFPContext<double>());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_DOUBLEDOUBLE
        case LatticeReduction::A_DoubleDouble: return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::DDQDContext<dd_real>());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_QUADDOUBLE
        case LatticeReduction::A_QuadDouble:   return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::DDQDContext<qd_real>());
#endif
        }
        assert(!"Real arithmetic not supported!");
        return NULL;
    }

    // Explicit instantiation of templates

#ifndef PLLL_CONFIG_NO_ARITHMETIC_BIGINT
    template void setUnit<arithmetic::IntegerContext>(linalg::math_matrix<arithmetic::Integer> &);
#endif

#ifndef PLLL_CONFIG_NO_ARITHMETIC_LONGINT
    template void setUnit<arithmetic::NIntContext<long> >(linalg::math_matrix<arithmetic::NInt<long> > &);
#endif
}


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <array>
#include <boost/functional/hash.hpp>
#include <cstddef>
#include <utility>

#include "DataStructures/FixedHashMap.hpp"
#include "DataStructures/Tensor/TypeAliases.hpp"
#include "Domain/Structure/MaxNumberOfNeighbors.hpp"
#include "Evolution/Systems/GeneralizedHarmonic/Tags.hpp"

/// \cond
template <typename TagsList>
class Variables;
namespace gsl {
template <typename>
class not_null;
}  // namespace gsl
template <size_t Dim>
class Direction;
template <size_t Dim>
class ElementId;
template <size_t Dim>
class Element;
template <size_t Dim>
class Mesh;
namespace EquationsOfState {
template <bool IsRelativistic, size_t ThermodynamicDim>
class EquationOfState;
}  // namespace EquationsOfState
/// \endcond

namespace grmhd::GhValenciaDivClean::fd {
/*!
 * \brief Reconstructs \f$\rho, p, Wv^i, B^i\f$, \f$\Phi\f$, and the spacetime
 * metric, then computes the Lorentz factor, upper spatial velocity, specific
 * internal energy, specific enthalpy, and the conserved variables. All results
 * are written into `vars_on_lower_face` and `vars_on_upper_face`.
 */
template <typename SpacetimeTagsToReconstruct, typename PrimsTags,
          typename SpacetimeAndConsTags, typename TagsList,
          size_t ThermodynamicDim, typename HydroReconstructor,
          typename SpacetimeReconstructor,
          typename ComputeGrmhdSpacetimeVarsFromReconstructedSpacetimeTags>
void reconstruct_prims_work(
    gsl::not_null<std::array<Variables<TagsList>, 3>*> vars_on_lower_face,
    gsl::not_null<std::array<Variables<TagsList>, 3>*> vars_on_upper_face,
    const HydroReconstructor& hydro_reconstructor,
    const SpacetimeReconstructor& spacetime_reconstructor,
    const ComputeGrmhdSpacetimeVarsFromReconstructedSpacetimeTags&
        spacetime_vars_for_grmhd,
    const Variables<PrimsTags>& volume_prims,
    const Variables<SpacetimeAndConsTags>& volume_spacetime_and_cons_vars,
    const EquationsOfState::EquationOfState<true, ThermodynamicDim>& eos,
    const Element<3>& element,
    const FixedHashMap<
        maximum_number_of_neighbors(3) + 1,
        std::pair<Direction<3>, ElementId<3>>, std::vector<double>,
        boost::hash<std::pair<Direction<3>, ElementId<3>>>>& neighbor_data,
    const Mesh<3>& subcell_mesh, size_t ghost_zone_size);

/*!
 * \brief Reconstructs \f$\rho, p, Wv^i, B^i\f$, \f$\Phi\f$, the spacetime
 * metric, \f$\Phi_{iab}\f$, and \f$\Pi_{ab}\f$, then computes the Lorentz
 * factor, upper spatial velocity, specific internal energy, specific enthalpy,
 * and the conserved variables. All results are written into `vars_on_face`.
 *
 * This is used on DG elements to reconstruct their subcell neighbors' solution
 * on the shared faces.
 */
template <
    typename TagsList, typename PrimsTags, size_t ThermodynamicDim,
    typename LowerHydroReconstructor, typename LowerSpacetimeReconstructor,
    typename UpperHydroReconstructor, typename UpperSpacetimeReconstructor,
    typename ComputeGrmhdSpacetimeVarsFromReconstructedSpacetimeTags>
void reconstruct_fd_neighbor_work(
    gsl::not_null<Variables<TagsList>*> vars_on_face,
    const LowerHydroReconstructor& reconstruct_lower_neighbor_hydro,
    const LowerSpacetimeReconstructor& reconstruct_lower_neighbor_spacetime,
    const UpperHydroReconstructor& reconstruct_upper_neighbor_hydro,
    const UpperSpacetimeReconstructor& reconstruct_upper_neighbor_spacetime,
    const ComputeGrmhdSpacetimeVarsFromReconstructedSpacetimeTags&
        spacetime_vars_for_grmhd,
    const Variables<PrimsTags>& subcell_volume_prims,
    const Variables<tmpl::list<
        gr::Tags::SpacetimeMetric<3>, GeneralizedHarmonic::Tags::Phi<3>,
        GeneralizedHarmonic::Tags::Pi<3>>>& subcell_volume_spacetime_vars,
    const EquationsOfState::EquationOfState<true, ThermodynamicDim>& eos,
    const Element<3>& element,
    const FixedHashMap<
        maximum_number_of_neighbors(3) + 1,
        std::pair<Direction<3>, ElementId<3>>, std::vector<double>,
        boost::hash<std::pair<Direction<3>, ElementId<3>>>>& neighbor_data,
    const Mesh<3>& subcell_mesh, const Direction<3>& direction_to_reconstruct,
    size_t ghost_zone_size);
}  // namespace grmhd::GhValenciaDivClean::fd


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T.Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_DEFINITION_DIVROUND2EVEN_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_DEFINITION_DIVROUND2EVEN_HPP_INCLUDED

#include <boost/simd/config.hpp>
#include <boost/dispatch/function/make_callable.hpp>
#include <boost/dispatch/hierarchy/functions.hpp>
#include <boost/simd/detail/dispatch.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
    BOOST_DISPATCH_MAKE_TAG(ext, divround2even_, boost::dispatch::elementwise_<divround2even_>);
  }

  namespace ext
  {
    BOOST_DISPATCH_FUNCTION_DECLARATION(tag, divround2even_);
  }

  BOOST_DISPATCH_CALLABLE_DEFINITION(tag::divround2even_,divround2even);

} }

#endif


#pragma once

#include <Geometry2d/Point.hpp>
#include <Geometry2d/Circle.hpp>
#include <boost/optional.hpp>
#include "planning/MotionInstant.hpp"
#include "Utils.hpp"
#include "Geometry2d/Shape.hpp"

namespace Planning {

class Path;

/*
 * This is a superclass for different MotionCommands.
 * Currently implemented are PathTarget, WorldVel, Pivot, DirectPathtarget, None
 */
class DynamicObstacle {
private:
    const Path* const path;
    const float radius;
    const Geometry2d::Point staticPoint;
    const std::shared_ptr<Geometry2d::Circle> staticObstacle;

public:
    DynamicObstacle(Geometry2d::Point staticPoint, float radius,
                    const Path* path = nullptr)
        : staticPoint(staticPoint),
          path(path),
          radius(radius),
          staticObstacle(
              std::make_shared<Geometry2d::Circle>(staticPoint, radius)) {}

    DynamicObstacle(Geometry2d::Circle circle)
        : staticPoint(circle.center),
          path(nullptr),
          radius(circle.radius()),
          staticObstacle(std::make_shared<Geometry2d::Circle>(circle)) {}

    DynamicObstacle(const Path* path, float radius);

    virtual ~DynamicObstacle() = default;

    bool hasPath() const { return path != nullptr; }

    const Path* getPath() const { return path; }

    // Radius = radius of obstacle
    float getRadius() const { return radius; }

    std::shared_ptr<Geometry2d::Circle> getStaticObstacle() const {
        return staticObstacle;
    }
};
}  // namespace Planning


#include "CorePch.h"
#include <rtp++/rto/CrossPathRtoEstimator.h>
#include <boost/bind.hpp>
#include <boost/circular_buffer.hpp>
#include <boost/date_time.hpp>
#include <rtp++/rto/MovingAveragePredictor.h>
#include <rtp++/rto/PacketInterarrivalTimePredictor.h>
#include <rtp++/rto/PredictorTypes.h>

#define SLIDING_WINDOW_SIZE 50
#define DELTA_QUEUE_SIZE 16

namespace rtp_plus_plus
{
namespace rto
{

CrossPathRtoEstimator::CrossPathRtoEstimator(boost::asio::io_service& rIoService, const GenericParameters &applicationParameters)
  :m_rIoService(rIoService),
  m_referenceTimer(rIoService),
  m_eState(ST_LEARN_FLOW_ORDER),
  m_bShuttingdown(false),
  m_deltaQueue(DELTA_QUEUE_SIZE),
  m_uiWaitingForPacketFlow(0),
  m_uiCrossPathTimeout(0),
  m_uiReferenceSN(0),
  m_bRestartShortTimer(0),
  m_uiExpectingSN(0)
{
  PredictorType eType = getPredictorType(applicationParameters);
  boost::optional<double> prematureTimeoutProb = applicationParameters.getDoubleParameter(app::ApplicationParameters::pto);
  double dPrematureTimeoutProb = prematureTimeoutProb ? *prematureTimeoutProb : 0.05;

  switch (eType)
  {
    case PT_AR2:
    {
      VLOG(2) << "Using AR2 predictor for cross path prediction";
      m_pDeltaPredictor = std::unique_ptr<InterarrivalPredictor_t>(new PacketInterarrivalTimePredictor<uint64_t, int64_t>(dPrematureTimeoutProb));
      m_pCrosspathPredictor = std::unique_ptr<InterarrivalPredictor_t>(new PacketInterarrivalTimePredictor<uint64_t, int64_t>(dPrematureTimeoutProb));
      break;
    }
    case PT_MOVING_AVERAGE:
    default:
    {
      VLOG(2) << "Using moving average predictor for cross path prediction";
      boost::optional<uint32_t> historySize = applicationParameters.getUintParameter(app::ApplicationParameters::mavg_hist);
      const uint32_t uiHistorySize = historySize ? *historySize : 20;
      m_pDeltaPredictor = std::unique_ptr<InterarrivalPredictor_t>(new MovingAveragePredictor<uint64_t, int64_t>(uiHistorySize, 10, dPrematureTimeoutProb));
      m_pCrosspathPredictor = std::unique_ptr<InterarrivalPredictor_t>(new MovingAveragePredictor<uint64_t, int64_t>(uiHistorySize, 1, dPrematureTimeoutProb));
      break;
    }
  }
}

CrossPathRtoEstimator::~CrossPathRtoEstimator()
{
  m_statisticsManager.printStatistics();
  double dDeltaError = m_pDeltaPredictor->getErrorStandardDeviation();
  double dCrosspathError = m_pCrosspathPredictor->getErrorStandardDeviation();
  VLOG(2) << "Prediction errors: delta:" << dDeltaError << " Cross: " << dCrosspathError;
}

void CrossPathRtoEstimator::updateModel(const uint16_t uiFlowId, const uint16_t uiSN, const uint16_t uiFSSN, const boost::posix_time::ptime& tArrival)
{
  RtoData data(uiFlowId, uiSN, uiFSSN, tArrival);
  auto it = m_mFlowData.find(uiFlowId);
  if (it == m_mFlowData.end())
  {
    m_mFlowData[uiFlowId] = boost::circular_buffer<RtoData>(SLIDING_WINDOW_SIZE);
    m_mFlowData[uiFlowId].push_back(data);
    m_FlowIds.insert(uiFlowId);
  }
  else
  {
    // retrieve previously stored item to calculate delta
    RtoData& previousData = m_mFlowData[uiFlowId].back();
    uint32_t uiDifference = static_cast<uint32_t>((tArrival - previousData.Arrival).total_milliseconds());
#define LOG_MICRO_SECONDS
#ifdef LOG_MICRO_SECONDS
    uint32_t uiDifferenceUs = static_cast<uint32_t>((tArrival - previousData.Arrival).total_microseconds());
    uint32_t uiDeltaUs = static_cast<uint32_t>(((double)uiDifferenceUs/(uiSN - previousData.GSN) + 0.5));
    VLOG(2) << "Interarrival time " << uiDeltaUs << "us";
#endif
    uint32_t uiDelta = static_cast<uint32_t>(((double)uiDifference/(uiSN - previousData.GSN) + 0.5));
    VLOG(2) << "Difference: " << uiDifference << " Delta: " << uiDelta;
    m_pDeltaPredictor->insert(uiDeltaUs);
    m_deltaQueue.insert(uiDeltaUs);
    m_mFlowData[uiFlowId].push_back(data);
  }
}

#define FAIL 0
#define CONTINUE_WAITING 1
#define SUCCESS 2

int CrossPathRtoEstimator::determineCrossPathDifference(const uint16_t uiFlowId, const uint16_t uiSN, const uint16_t uiFSSN, const boost::posix_time::ptime& tArrival)
{
  // search For Missing Packet In Alternate Flow
  // look for packet from alternate flow
  if (uiFlowId == m_uiWaitingForPacketFlow)
  {
    boost::circular_buffer<RtoData>& dataQueue = m_mFlowData[uiFlowId];
    assert (!dataQueue.empty());
    // last elem
    auto it = dataQueue.rbegin();
    // last elem
    RtoData& last = *it;
    // m_reference = last;
    // first missing SN
    if (m_uiMissingSN == last.GSN)
    {
      // found the next packet on the slower flow
      // calculate difference
      // initialisation complete: calculate cross path timeout
      m_uiCrossPathTimeout = static_cast<uint32_t>((tArrival - m_reference.Arrival).total_milliseconds());
      uint32_t uiDifferenceUs = (tArrival - m_reference.Arrival).total_microseconds();
      VLOG(2) << "Crosspath Interarrival time " << uiDifferenceUs << "us";
      VLOG(2) << "Cross path timeout " <<  m_uiCrossPathTimeout << "ms";
      // m_deltaCrossPathQueue.insert(m_uiCrossPathTimeout);
      m_pCrosspathPredictor->insert(uiDifferenceUs);
      return SUCCESS;
    }
    else
    {
      // there might be packets with smaller that were sent earlier but if there is a packet with a bigger SN
      // we might have lost the packet
      if (uiSN > m_uiMissingSN)
      {
        VLOG(2) << "Lost packet " <<  m_uiMissingSN << ". Restarting path difference calculation";
        // go back to previous state
        return FAIL;
      }
    }
  }
  else
  {
    if (m_uiMissingSN == uiSN)
    {
      // the expected packet didn't arrive on the other flow: restart reference
      // if it's on the fastest flow reset the reference
      if (uiFlowId == m_vFlowOrder[0])
      {
        boost::circular_buffer<RtoData>& dataQueue = m_mFlowData[uiFlowId];
        assert(!dataQueue.empty());
        // last elem
        auto it = dataQueue.rbegin();
        // last elem
        RtoData& last = *it;
        m_reference = last;
        // first missing SN
        m_uiMissingSN = m_uiMissingSN + 1;
        m_uiWaitingForPacketFlow = m_vFlowOrder[1];
      }
      else
      {
        // go back to previous state
        return FAIL;
      }
    }
  }
  // m_uiWaitingForPacketFlow = m_vFlowOrder[1];

//    if (uiSN == m_uiMissingSN)
//    {
//      // initialisation complete: calculate cross path timeout
//      m_uiCrossPathTimeout = (tArrival - m_reference.Arrival).total_milliseconds();
//#ifdef LOG_MICRO_SECONDS
//      uint32_t uiDifferenceUs = (tArrival - m_reference.Arrival).total_microseconds();
//      VLOG(2) << "Crosspath Interarrival time " << uiDifferenceUs << "us";
//#endif
//      VLOG(2) << "Cross path timeout " <<  m_uiCrossPathTimeout << "ms";
//      // m_deltaCrossPathQueue.insert(m_uiCrossPathTimeout);
//      m_pCrosspathPredictor->insert(uiDifferenceUs);
//      return SUCCESS;
//    }
//    else
//    {
//      // there might be packets with smaller that were sent earlier but if there is a packet with a bigger SN
//      // we might have lost the packet
//      if (uiSN > m_uiMissingSN)
//      {
//        VLOG(2) << "Lost packet " <<  m_uiMissingSN << ". Restarting path difference calculation";
//        // go back to previous state
//        return FAIL;
//      }
//    }
//  }
  return CONTINUE_WAITING;
}

void CrossPathRtoEstimator::processNewPacket(const uint16_t uiFlowId, const uint16_t uiSN, const boost::posix_time::ptime &tArrival)
{
  // I: if SN is in long timer list, cancel long timer
  // prediction has started: if packet arrives
  // if SN is in long timer list, cancel long timer
  // else
  // stop reference timer?
  {
    boost::mutex::scoped_lock l(m_lock);
    auto it = m_mLongTimers.find(uiSN);
    if (it != m_mLongTimers.end())
    {
      // try cancel: might not succeed if asio has already scheduled handler
      m_mLongTimers[uiSN]->cancel();
      // the short timer had already expired, and a timeout was restarted
      // for the next higher sequence number: now restart that timer resynched
      // to the arrival times
      if (uiSN == m_uiExpectingSN - 1)
      {
        m_bRestartShortTimer = true;
        m_referenceTimer.cancel();
      }
    }
    else
    {
      m_bRestartShortTimer = false;
      // check if this is the current packet expected: it might be an older packet from another path
      // in which case we do NOT want to restart the timer
      if (uiSN >= m_uiExpectingSN)
      {
        m_referenceTimer.cancel();
      }
    }
  }

  // TODO: debug, fix this: resulted in double detection
#ifdef FSSN_GAP_DETECTION
  // II: if there is a gap in flow specific SN: stop long timer(s)
  // check last two packets in flow and look for gap in GSN, no gap in FSSN
  boost::circular_buffer<RtoData>& dataQueue = m_mFlowData[uiFlowId];
  if (dataQueue.size() > 1)
  {
    // last elem
    auto it = dataQueue.rbegin();
    // last elem
    RtoData& last = *it;
    auto it2 = it + 1;
    RtoData& secondLast = *it2;

    // HACK TO PREVENT FSSN DETECTION on slow flow to prevent double picking
    if (uiFlowId == m_vFlowOrder[0])
    {
      if (last.FSSN != secondLast.FSSN + 1)
      {
        for (uint16_t i = secondLast.FSSN + 1; i < last.FSSN; ++i)
        {
          VLOG(2) << LOG_MODIFY_WITH_CARE
                  << " On Packet Arrival Lost FlowId: " << uiFlowId << " FSSN: " << i << " Second last: " << secondLast.FSSN << " last: " << last.FSSN;
          m_mFlowStatistics[uiFlowId].assumePacketLost(tArrival, i);
          m_onLost(-1, uiFlowId, i);
        }
      }
    }
  }
#endif
}

void CrossPathRtoEstimator::checkState(const uint16_t uiFlowId, const uint16_t uiSN, const uint16_t uiFSSN, const boost::posix_time::ptime& tArrival)
{
  switch (m_eState)
  {
  case ST_LEARN_FLOW_ORDER:
  {
    if (learnFlowOrder())
      m_eState = ST_LEARN_PATH_DIFFERENCE;
    break;
  }
  case ST_LEARN_PATH_DIFFERENCE:
  {
    if (searchForReferencePacketInFastestFlow(uiFlowId))
    {
      m_eState = ST_LEARN_AWAITING_PACKET_FROM_ALTERNATE_FLOW;
      //VLOG(2) << "Initialisation complete: cross path timeout " <<  m_uiCrossPathTimeout << "ms";
      //LOG_FIRST_N(INFO, 1) << "RTO Algorithm initalised";
      // m_eState = ST_EST_AWAITING_PACKET_FROM_FASTEST_FLOW;
    }
    break;
  }
  case ST_LEARN_AWAITING_PACKET_FROM_ALTERNATE_FLOW:
  {
    int res = determineCrossPathDifference(uiFlowId, uiSN, uiFSSN, tArrival);
    if (res == SUCCESS)
    {
      VLOG(2) << "Initialisation complete: cross path timeout " <<  m_uiCrossPathTimeout << "ms";
      LOG_FIRST_N(INFO, 1) << "RTO Algorithm initalised";
      m_eState = ST_EST_AWAITING_PACKET_FROM_FASTEST_FLOW;
    }
    else if (res == FAIL)
    {
      // go back to previous state
      m_eState = ST_LEARN_PATH_DIFFERENCE;
    }
    break;
  }
  case ST_EST_AWAITING_PACKET_FROM_FASTEST_FLOW:
  {
    if (uiFlowId == m_vFlowOrder[0])
    {
      // start first timeout
      m_uiReferenceSN = uiSN;
      uint32_t uiNextSN = uiSN + 1;
      m_uiExpectingSN = uiNextSN;
      // make sure predictors are ready
      if (m_pDeltaPredictor->isReady() && m_pCrosspathPredictor->isReady())
      {
        // start smallest timer
        uint32_t uiDeltaUs = m_pDeltaPredictor->getLastPrediction();
        // TEST: RG See if this avoids false positives in RR scheduling along paths with similar delays
//        uint32_t uiDeltaUs = m_pDeltaPredictor->getLastPrediction() + 1000;
        m_referenceTimer.expires_from_now(boost::posix_time::microseconds(uiDeltaUs));

        // add delta here to prevent fast timer from being too inaccurate

        // Purely for logging
        boost::posix_time::ptime tNow = boost::posix_time::microsec_clock::universal_time();
        // TEST: RG See if this avoids false positives in RR scheduling along paths with similar delays
        // uint32_t uiCrossPredictorUs = m_pCrosspathPredictor->getLastPrediction();
        uint32_t uiCrossPredictorUs = m_pCrosspathPredictor->getLastPrediction() + 1000;
        boost::posix_time::ptime tLongTimeout = tNow + boost::posix_time::microseconds(uiCrossPredictorUs);

        VLOG(2) << LOG_MODIFY_WITH_CARE
                << " Now: " << tNow
                << " packet estimation SN: " << uiNextSN
                << " short: " << m_referenceTimer.expires_at()
                << " long: " << tLongTimeout
                << " delta: " << uiDeltaUs
                   ;

        m_referenceTimer.async_wait(boost::bind(&CrossPathRtoEstimator::onShortTimeout, this, _1, uiNextSN, uiDeltaUs) );
        m_eState = ST_EST_RUNNING_A;
      }
      else
      {
        VLOG(2) << "Waiting to start prediction, predictors not ready. Delta: " << m_pDeltaPredictor->isReady()
                   << " Cross: " << m_pCrosspathPredictor->isReady();
      }

    }
    else
    {
      // do nothing, wait for start
    }
    break;
  }
  case ST_EST_RUNNING_A:
  {
    // update cross path model if possible
    if (searchForReferencePacketInFastestFlow(uiFlowId))
    {
      m_eState = ST_EST_RUNNING_B;
    }

    processNewPacket(uiFlowId, uiSN, tArrival);
    break;
  }
  case ST_EST_RUNNING_B:
  {
    int res = determineCrossPathDifference(uiFlowId, uiSN, uiFSSN, tArrival);
    // The result does not matter: we are simply trying to update our cross path difference model
    if (res == FAIL || res == SUCCESS)
    {
      // go back to previous state
      m_eState = ST_EST_RUNNING_A;
    }

    processNewPacket(uiFlowId, uiSN, tArrival);
    break;
  }
  }
}

#define MIN_DELTA_ESTIMATES 5
bool CrossPathRtoEstimator::learnFlowOrder()
{
  if (m_mFlowData.size() > 1)
  {
    if (m_deltaQueue.size() < MIN_DELTA_ESTIMATES)
      return false;

    // estimate smallest flow by delta approximation
    bool bFirst = false;
    uint16_t uiGSNReference = 0;
    std::map<uint16_t, boost::posix_time::ptime> mEstimates;
    uint32_t uiDelta = m_deltaQueue.getAverage();
    for (auto& pair: m_mFlowData)
    {
      RtoData& data = pair.second.back();
      if (!bFirst)
      {
        bFirst = true;
        uiGSNReference = data.GSN;
        mEstimates[pair.first] = data.Arrival;
        VLOG(2) << "Ref: " << pair.first << " GSN: " << uiGSNReference << " T: " << data.Arrival;
      }
      else
      {
        if (data.GSN > uiGSNReference)
        {
          uint16_t iDiff = data.GSN - uiGSNReference;
          mEstimates[pair.first] = data.Arrival - boost::posix_time::microseconds(iDiff * uiDelta);
          VLOG(2) << "Next: " << pair.first << " GSN: " << data.GSN << " Delta: " << uiDelta << " Diff: " << iDiff << " T: " << data.Arrival << " Offset: " << mEstimates[pair.first];
        }
        else
        {
          uint16_t iDiff = uiGSNReference - data.GSN;
          mEstimates[pair.first] = data.Arrival + boost::posix_time::microseconds(iDiff * uiDelta);
          VLOG(2) << "Next: " << pair.first << " GSN: " << data.GSN << " Delta: " << uiDelta << " Diff: " << iDiff << " T: " << data.Arrival << " Offset: " << mEstimates[pair.first];
        }
      }
    }

    uint32_t uiReps = mEstimates.size();
    for (size_t i = 0; i < uiReps; ++i)
    {
      typedef std::pair<const uint16_t, boost::posix_time::ptime> Pair_t;
      auto it = std::min_element(mEstimates.begin(), mEstimates.end(), [](Pair_t& pair1, Pair_t& pair2)
      {
        return pair1.second < pair2.second;
      });
      assert (it != mEstimates.end());
      VLOG(2) << "Fastest Flow: " << it->first;
      m_vFlowOrder.push_back(it->first);
      mEstimates.erase(it);
   }
   return true;
  }
  return false;
}

bool CrossPathRtoEstimator::searchForReferencePacketInFastestFlow(const uint16_t uiFlowId)
{
  if (uiFlowId == m_vFlowOrder[0])
  {
    // check last two packets in flow and look for gap in GSN, no gap in FSSN
    boost::circular_buffer<RtoData>& dataQueue = m_mFlowData[uiFlowId];
    if (dataQueue.size() >= 1)
    {
      // last elem
      auto it = dataQueue.rbegin();
      // last elem
      RtoData& last = *it;
      m_reference = last;
      // first missing SN
      m_uiMissingSN = last.GSN + 1;
      m_uiWaitingForPacketFlow = m_vFlowOrder[1];
      return true;

//      auto it2 = it + 1;
//      RtoData& secondLast = *it2;
//      if (last.FSSN = secondLast.FSSN + 1)
//      {
//        // check for gap in GSN
//        if (last.GSN != secondLast.GSN + 1)
//        {
//          // store reference
//          m_reference = secondLast;
//          // first missing SN
//          m_uiMissingSN = secondLast.GSN + 1;
//          m_uiWaitingForPacketFlow = m_vFlowOrder[1];
//          return true;
//        }
//      }
    }
  }
//  else if (uiFlowId == m_uiWaitingForPacketFlow)
//  {
//    boost::circular_buffer<RtoData>& dataQueue = m_mFlowData[uiFlowId];
//    if (dataQueue.size() >= 1)
//    {
//      // last elem
//      auto it = dataQueue.rbegin();
//      // last elem
//      RtoData& last = *it;
//      // m_reference = last;
//      // first missing SN
//      if (m_uiMissingSN == last.GSN)
//      {
//        // found the next packet on the slower flow
//        // calculate difference
//        // initialisation complete: calculate cross path timeout
//        m_uiCrossPathTimeout = (last.Arrival - m_reference.Arrival).total_milliseconds();
//        uint32_t uiDifferenceUs = (last.Arrival - m_reference.Arrival).total_microseconds();
//        VLOG(2) << "Crosspath Interarrival time " << uiDifferenceUs << "us";
//        VLOG(2) << "Cross path timeout " <<  m_uiCrossPathTimeout << "ms";
//        // m_deltaCrossPathQueue.insert(m_uiCrossPathTimeout);
//        m_pCrosspathPredictor->insert(uiDifferenceUs);
//        return true;
//      }
//      // m_uiWaitingForPacketFlow = m_vFlowOrder[1];
//    }
//  }
  return false;
}

void CrossPathRtoEstimator::doOnPacketArrival(  const boost::posix_time::ptime& tArrival, const uint16_t uiSN, const uint16_t uiFlowId, const uint16_t uiFSSN)
{
//  m_statisticsManager.receivePacket(tArrival, uiSN);

  // update delta model: determine delta_transmission
  updateModel(uiFlowId, uiSN, uiFSSN, tArrival);

  checkState(uiFlowId, uiSN, uiFSSN, tArrival);
}

void CrossPathRtoEstimator::onLongTimeout(const boost::system::error_code& ec, uint16_t uiSN, uint32_t uiDelta)
{
  if (!m_bShuttingdown)
  {
    if (!ec)
    {
      if (!m_statisticsManager.hasPacketBeenReceivedRecently(uiSN))
      {
        boost::posix_time::ptime tNow = boost::posix_time::microsec_clock::universal_time();
        m_statisticsManager.assumePacketLost(tNow, uiSN);
        VLOG(2) << LOG_MODIFY_WITH_CARE
                << " Packet Long Timeout Lost SN: " << uiSN << " Delta: " << uiDelta;
        m_onLost(uiSN, -1, -1);
      }
      else
      {
        VLOG(2) << LOG_MODIFY_WITH_CARE
                << " Packet Long Timeout Received SN: " << uiSN << " in the meanwhile";
      }
    }
    else
    {
      VLOG(2) << LOG_MODIFY_WITH_CARE
              << " Packet Long Timeout Received SN: " << uiSN << " arrived in the meanwhile Delta: "<< uiDelta;
    }
  }

  boost::mutex::scoped_lock l(m_lock);
  boost::asio::deadline_timer* pTimer = m_mLongTimers[uiSN];
  delete pTimer;
  m_mLongTimers.erase(uiSN);
}

void CrossPathRtoEstimator::onShortTimeout(const boost::system::error_code& ec, uint16_t uiSN, uint32_t uiDelta)
{
  if (!m_bShuttingdown)
  {
    uint32_t uiNextSN = 0;
    if (!ec)
    {
      // check if packet has been received
      if (!m_statisticsManager.hasPacketBeenReceivedRecently(uiSN))
      {
        uint32_t uiNewDelta = m_pCrosspathPredictor->getLastPrediction() - uiDelta;
        VLOG(2) << "CP: Last predict: " << m_pCrosspathPredictor->getLastPrediction()
                << " Delta: " << uiDelta;
        boost::asio::deadline_timer* pTimer = new boost::asio::deadline_timer(m_rIoService);
        pTimer->expires_from_now(boost::posix_time::microseconds(uiNewDelta));
        pTimer->async_wait(boost::bind(&CrossPathRtoEstimator::onLongTimeout, this, _1, uiSN, uiNewDelta));

        {
          boost::mutex::scoped_lock l(m_lock);
          m_mLongTimers[uiSN] = pTimer;
        }

        VLOG(2) << LOG_MODIFY_WITH_CARE
                << " Packet Short Timeout SN: " << uiSN;

        if (!m_bRestartShortTimer)
        {
          uiNextSN = uiSN + 1;
        }
        else
        {
          uiNextSN = uiSN; // resync
          VLOG(2) << LOG_MODIFY_WITH_CARE
                  << " Packet resync SN: " << uiNextSN;
          m_bRestartShortTimer = false;
        }
      }
      else
      {
        VLOG(2) << "Short time out: " << uiDelta << "ms packet " << uiSN << " received";
        uiNextSN = uiSN + 1;
      }
    }
    else
    {
      // check flag to see if packet was on time i.e. packet arrived before short timeout
      // or if the timeout was cancelled for resync purposes
      if (!m_bRestartShortTimer)
      {
        // Still log for graphing of timeout estimation
        VLOG(2) << LOG_MODIFY_WITH_CARE
                << " Packet Short Timeout SN: " << uiSN;
        VLOG(2) << "Cancelled: Short time out: " << uiDelta << "ms packet " << uiSN << " received";
        uiNextSN = uiSN + 1;
      }
      else
      {
        // resync means that short timer is restarted: no special logging here
        VLOG(2) << "Cancelled: Short time out: " << uiDelta << "ms packet " << uiSN << " resync";
        uiNextSN = uiSN; // resync
        m_bRestartShortTimer = false;
      }
    }

    // Always start next short timer
    // cancelled: packet received: schedule next sample
    // start estimation of next highest SN
    m_uiExpectingSN = uiNextSN;
    // uint32_t uiNewDelta = m_deltaQueue.getAverage();
    uint32_t uiNewDelta = m_pDeltaPredictor->getLastPrediction();

    m_referenceTimer.expires_from_now(boost::posix_time::microseconds(uiNewDelta));

    // Purely for logging
    boost::posix_time::ptime tNow = boost::posix_time::microsec_clock::universal_time();
    uint32_t uiCrossPredictorUs = m_pCrosspathPredictor->getLastPrediction();
    boost::posix_time::ptime tLongTimeout = tNow + boost::posix_time::microseconds(uiCrossPredictorUs);

    VLOG(2) << LOG_MODIFY_WITH_CARE
            << " CP packet estimation"
            << " now: " << tNow
            << " SN: " << uiNextSN
            << " short: " << m_referenceTimer.expires_at()
            << " long: " << tLongTimeout
            << " delta: " << uiNewDelta
            << " delta_c: " << uiCrossPredictorUs
               ;

    m_referenceTimer.async_wait(boost::bind(&CrossPathRtoEstimator::onShortTimeout, this, _1, uiNextSN, uiNewDelta) );

  }
}

void CrossPathRtoEstimator::doStop()
{
  m_bShuttingdown = true;
  m_referenceTimer.cancel();
  for (auto& pair: m_mLongTimers)
  {
      pair.second->cancel();
  }
}

void CrossPathRtoEstimator::onRtpPacketAssumedLostInFlow(uint16_t uiFSSN, uint16_t uiFlowId)
{

}

} // rto
} // rtp_plus_plus


//
// Copyright (c) 2018 James A. Chappell
//
// METAR decoder tests
//

#include "Metar.h"

#include <string>

#define BOOST_TEST_MODULE METAR
#include <boost/test/included/unit_test.hpp>

using namespace Storage_B::Weather;

BOOST_AUTO_TEST_CASE(uninitialized_observation_time)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasDay());
  BOOST_CHECK(!metar->hasHour());
  BOOST_CHECK(!metar->hasMinute());
}

BOOST_AUTO_TEST_CASE(observation_time)
{
  auto metar = Metar::Create("123456Z");

  BOOST_CHECK(metar->Day() == 12);
  BOOST_CHECK(metar->Hour() == 34);
  BOOST_CHECK(metar->Minute() == 56);
}

BOOST_AUTO_TEST_CASE(uninitialized_temperature)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasTemperature());
  BOOST_CHECK(!metar->hasDewPoint());
}

BOOST_AUTO_TEST_CASE(temperature_both_positive1)
{

  char buffer[8];
  strcpy(buffer, "08/06");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasTemperature());
  BOOST_CHECK(metar->hasDewPoint());

  BOOST_CHECK(metar->Temperature() == 8);
  BOOST_CHECK(metar->DewPoint() == 6);
}

BOOST_AUTO_TEST_CASE(temperature_negative_dew_point)
{
  char buffer[8];
  strcpy(buffer, "01/M01");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasTemperature());
  BOOST_CHECK(metar->hasDewPoint());

  BOOST_CHECK(metar->Temperature() == 1);
  BOOST_CHECK(metar->DewPoint() == -1);
}

BOOST_AUTO_TEST_CASE(temperature_both_negative)
{
  char buffer[8];
  strcpy(buffer, "M14/M15");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasTemperature());
  BOOST_CHECK(metar->hasDewPoint());

  BOOST_CHECK(metar->Temperature() == -14);
  BOOST_CHECK(metar->DewPoint() == -15);
}

BOOST_AUTO_TEST_CASE(temperature_both_positive2)
{
  char buffer[8];
  strcpy(buffer, "15/14");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasTemperature());
  BOOST_CHECK(metar->hasDewPoint());

  BOOST_CHECK(metar->Temperature() == 15);
  BOOST_CHECK(metar->DewPoint() == 14);
}

BOOST_AUTO_TEST_CASE(temperature_no_dew_positive)
{
  auto metar = Metar::Create("15/");

  BOOST_CHECK(metar->hasTemperature());
  BOOST_CHECK(!metar->hasDewPoint());

  BOOST_CHECK(metar->Temperature() == 15);
}

BOOST_AUTO_TEST_CASE(temperature_no_dew_negative)
{
  auto metar = Metar::Create("M07/");

  BOOST_CHECK(metar->hasTemperature());
  BOOST_CHECK(!metar->hasDewPoint());

  BOOST_CHECK(metar->Temperature() == -7);
}

BOOST_AUTO_TEST_CASE(uninitialized_temperatureNA)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasTemperatureNA());
  BOOST_CHECK(!metar->hasDewPointNA());
}

BOOST_AUTO_TEST_CASE(temperatureNA_1)
{
  char buffer[10];
  strcpy(buffer, "T00830067");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasTemperatureNA());
  BOOST_CHECK(metar->hasDewPointNA());

  BOOST_CHECK(metar->TemperatureNA() == 8.3);
  BOOST_CHECK(metar->DewPointNA() == 6.7);
}

BOOST_AUTO_TEST_CASE(temperatureNA_2)
{
  char buffer[10];
  strcpy(buffer, "T01830167");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasTemperatureNA());
  BOOST_CHECK(metar->hasDewPointNA());

  BOOST_CHECK(metar->TemperatureNA() == 18.3);
  BOOST_CHECK(metar->DewPointNA() == 16.7);
}

BOOST_AUTO_TEST_CASE(temperatureNA_both_negative)
{
  char buffer[10];
  strcpy(buffer, "T10171018");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasTemperatureNA());
  BOOST_CHECK(metar->hasDewPointNA());

  BOOST_CHECK(metar->TemperatureNA() == -1.7);
  BOOST_CHECK(metar->DewPointNA() == -1.8);
}

BOOST_AUTO_TEST_CASE(temperatureNA_NoDew)
{
  char buffer[10];
  strcpy(buffer, "T0028");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasTemperatureNA());
  BOOST_CHECK(!metar->hasDewPointNA());

  BOOST_CHECK(metar->TemperatureNA() == 2.8);
}

BOOST_AUTO_TEST_CASE(uninitialized_wind)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasWindSpeed());
  BOOST_CHECK(!metar->hasWindDirection());
  BOOST_CHECK(!metar->hasWindGust());
  BOOST_CHECK(!metar->hasWindSpeedUnits());
}

BOOST_AUTO_TEST_CASE(wind_kt)
{
  char buffer[9];
  strcpy(buffer, "25005KT");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(metar->hasWindDirection());

  BOOST_CHECK(metar->WindDirection() == 250);
  BOOST_CHECK(metar->WindSpeed() == 5);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(!metar->hasWindGust());
}

BOOST_AUTO_TEST_CASE(wind_kt_3digit)
{
  auto metar = Metar::Create("240105KT");

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(metar->hasWindDirection());

  BOOST_CHECK(metar->WindDirection() == 240);
  BOOST_CHECK(metar->WindSpeed() == 105);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(!metar->hasWindGust());
}

BOOST_AUTO_TEST_CASE(wind_kt_3digit_gust)
{
  auto metar = Metar::Create("240105G121KT");

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(metar->hasWindDirection());

  BOOST_CHECK(metar->WindDirection() == 240);
  BOOST_CHECK(metar->WindSpeed() == 105);

  BOOST_CHECK(metar->hasWindGust());
  BOOST_CHECK(metar->WindGust() == 121);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);
}

BOOST_AUTO_TEST_CASE(wind_vrb_kt_3digit_gust)
{
  auto metar = Metar::Create("VRB105G121KT");

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(!metar->hasWindDirection());
  BOOST_CHECK(metar->isVariableWindDirection());

  BOOST_CHECK(metar->WindSpeed() == 105);

  BOOST_CHECK(metar->hasWindGust());
  BOOST_CHECK(metar->WindGust() == 121);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);
}

BOOST_AUTO_TEST_CASE(wind_kt_gust)
{
  auto metar = Metar::Create("25005G12KT");

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(metar->hasWindDirection());
  BOOST_CHECK(!metar->isVariableWindDirection());

  BOOST_CHECK(metar->WindDirection() == 250);
  BOOST_CHECK(metar->WindSpeed() == 5);

  BOOST_CHECK(metar->hasWindGust());
  BOOST_CHECK(metar->WindGust() == 12);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);
}

BOOST_AUTO_TEST_CASE(wind_mps)
{
  char buffer[9];
  strcpy(buffer, "04503MPS");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(metar->hasWindDirection());

  BOOST_CHECK(metar->WindDirection() == 45);
  BOOST_CHECK(metar->WindSpeed() == 3);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::MPS);

  BOOST_CHECK(!metar->hasWindGust());
}

BOOST_AUTO_TEST_CASE(wind_mps_vrb)
{
  auto metar = Metar::Create("VRB03MPS");

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(!metar->hasWindDirection());

  BOOST_CHECK(metar->WindSpeed() == 3);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::MPS);

  BOOST_CHECK(!metar->hasWindGust());
}

BOOST_AUTO_TEST_CASE(wind_mps_3digit_gust)
{
  auto metar = Metar::Create("08090G102MPS");

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(metar->hasWindDirection());

  BOOST_CHECK(metar->WindDirection() == 80);
  BOOST_CHECK(metar->WindSpeed() == 90);

  BOOST_CHECK(metar->hasWindGust());
  BOOST_CHECK(metar->WindGust() == 102);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::MPS);
}

BOOST_AUTO_TEST_CASE(wind_kph)
{
  char buffer[9];
  strcpy(buffer, "04005KPH");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(metar->hasWindDirection());

  BOOST_CHECK(metar->WindDirection() == 40);
  BOOST_CHECK(metar->WindSpeed() == 5);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KPH);

  BOOST_CHECK(!metar->hasWindGust());
}

BOOST_AUTO_TEST_CASE(wind_vrb_kph_2digit_gust)
{
  auto metar = Metar::Create("VRB05G21KPH");

  BOOST_CHECK(metar->hasWindSpeed());
  BOOST_CHECK(!metar->hasWindDirection());

  BOOST_CHECK(metar->WindSpeed() == 5);

  BOOST_CHECK(metar->hasWindGust());
  BOOST_CHECK(metar->WindGust() == 21);

  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KPH);
}

BOOST_AUTO_TEST_CASE(uninitialized_altA)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasAltimeterA());
}

BOOST_AUTO_TEST_CASE(uninitialized_wind_var)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasMinWindDirection());
  BOOST_CHECK(!metar->hasMaxWindDirection());
}

BOOST_AUTO_TEST_CASE(wind_var)
{
  auto metar = Metar::Create("090V150");

  BOOST_CHECK(metar->hasMinWindDirection());
  BOOST_CHECK(metar->hasMaxWindDirection());

  BOOST_CHECK(metar->MinWindDirection() == 90);
  BOOST_CHECK(metar->MaxWindDirection() == 150);
}

BOOST_AUTO_TEST_CASE(altA)
{
  char buffer[9];
  strcpy(buffer, "A3006");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 30.06);
}

BOOST_AUTO_TEST_CASE(uninitialized_altQ)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasAltimeterQ());
}

BOOST_AUTO_TEST_CASE(altQ)
{
  char buffer[9];
  strcpy(buffer, "Q1020");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasAltimeterQ());
  BOOST_CHECK(metar->AltimeterQ() == 1020);
}

BOOST_AUTO_TEST_CASE(uninitialized_slp)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasSeaLevelPressure());
}

BOOST_AUTO_TEST_CASE(slp)
{
  char buffer[9];
  strcpy(buffer, "SLP177");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(metar->hasSeaLevelPressure());
  BOOST_CHECK(metar->SeaLevelPressure() == 1017.7);
}

BOOST_AUTO_TEST_CASE(uninitialized_visibility)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasVisibility());
  BOOST_CHECK(!metar->hasVisibilityUnits());
}

BOOST_AUTO_TEST_CASE(visibility_meters)
{
  auto metar = Metar::Create("1500");

  BOOST_CHECK(metar->hasVisibility());
  BOOST_CHECK(metar->Visibility() == 1500);

  BOOST_CHECK(metar->hasVisibilityUnits());
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::M);
}

BOOST_AUTO_TEST_CASE(visibility_integer_sm)
{
  auto metar = Metar::Create("10SM");

  BOOST_CHECK(metar->hasVisibility());
  BOOST_CHECK(metar->Visibility() == 10);

  BOOST_CHECK(metar->hasVisibilityUnits());
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);
}

BOOST_AUTO_TEST_CASE(visibility_fraction_sm_1)
{
  auto metar = Metar::Create("1/4SM");

  BOOST_CHECK(metar->hasVisibility());
  BOOST_CHECK(metar->Visibility() == 0.25);

  BOOST_CHECK(metar->hasVisibilityUnits());
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);
  BOOST_CHECK(!metar->isVisibilityLessThan());
}

BOOST_AUTO_TEST_CASE(visibility_fraction_sm_2)
{
  auto metar = Metar::Create("5/16SM");

  BOOST_CHECK(metar->hasVisibility());
  BOOST_CHECK(metar->Visibility() == (5.0 / 16.0));

  BOOST_CHECK(metar->hasVisibilityUnits());
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);
}

BOOST_AUTO_TEST_CASE(visibility_fraction_sm_3)
{
  auto metar = Metar::Create("2 1/2SM");

  BOOST_CHECK(metar->hasVisibility());
  BOOST_CHECK(metar->Visibility() == 2.5);

  BOOST_CHECK(metar->hasVisibilityUnits());
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);
}

BOOST_AUTO_TEST_CASE(visibility_LT)
{
  auto metar = Metar::Create("M1/4SM");

  BOOST_CHECK(metar->hasVisibility());
  BOOST_CHECK(metar->Visibility() == 0.25);

  BOOST_CHECK(metar->hasVisibilityUnits());
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);
  BOOST_CHECK(metar->isVisibilityLessThan());
  BOOST_CHECK(!metar->isCAVOK());
}

BOOST_AUTO_TEST_CASE(visibility_CAVOK)
{
  auto metar = Metar::Create("CAVOK");

  BOOST_CHECK(!metar->hasVisibility());
  BOOST_CHECK(!metar->hasVisibilityUnits());
  BOOST_CHECK(metar->isCAVOK());
}

BOOST_AUTO_TEST_CASE(uninitialized_vert_visibility)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(!metar->hasVerticalVisibility());
}

BOOST_AUTO_TEST_CASE(vert_visibility)
{
  auto metar = Metar::Create("VV105");

  BOOST_CHECK(metar->hasVerticalVisibility());
  BOOST_CHECK(metar->VerticalVisibility() == 10500);
}

BOOST_AUTO_TEST_CASE(uninitialized_cloud_layer)
{
  auto metar = Metar::Create("");

  BOOST_CHECK(metar->NumCloudLayers() == 0);
  BOOST_CHECK(metar->Layer(0) == nullptr);
}

BOOST_AUTO_TEST_CASE(cloud_layer_2_layers)
{
  auto metar = Metar::Create("BKN004 OVC008");

  BOOST_CHECK(metar->NumCloudLayers() == 2);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::BKN);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 4);
  BOOST_CHECK(!metar->Layer(0)->hasCloudType());
  BOOST_CHECK(metar->Layer(1)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(1)->Altitude() == 8);
  BOOST_CHECK(!metar->Layer(1)->hasCloudType());
}

BOOST_AUTO_TEST_CASE(cloud_layer_3_layers)
{
  auto metar = Metar::Create("FEW004 SCT080 OVC120");

  BOOST_CHECK(metar->NumCloudLayers() == 3);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::FEW);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 4);
  BOOST_CHECK(!metar->Layer(0)->hasCloudType());
  BOOST_CHECK(metar->Layer(1)->Cover() == Clouds::cover::SCT);
  BOOST_CHECK(metar->Layer(1)->Altitude() == 80);
  BOOST_CHECK(!metar->Layer(1)->hasCloudType());
  BOOST_CHECK(metar->Layer(2)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(2)->Altitude() == 120);
  BOOST_CHECK(!metar->Layer(2)->hasCloudType());
}

BOOST_AUTO_TEST_CASE(cloud_layer_3_layers_tempo)
{
  auto metar = Metar::Create("KSTL FEW004 SCT080 TEMPO OVC120");

  BOOST_CHECK(metar->NumCloudLayers() == 3);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::FEW);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 4);
  BOOST_CHECK(!metar->Layer(0)->hasCloudType());
  BOOST_CHECK(!metar->Layer(0)->Temporary());
  BOOST_CHECK(metar->Layer(1)->Cover() == Clouds::cover::SCT);
  BOOST_CHECK(metar->Layer(1)->Altitude() == 80);
  BOOST_CHECK(!metar->Layer(1)->hasCloudType());
  BOOST_CHECK(!metar->Layer(1)->Temporary());
  BOOST_CHECK(metar->Layer(2)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(2)->Altitude() == 120);
  BOOST_CHECK(!metar->Layer(2)->hasCloudType());
  BOOST_CHECK(metar->Layer(2)->Temporary());
}

BOOST_AUTO_TEST_CASE(cloud_layer_3_layers_cloud_types)
{
  auto metar = Metar::Create("FEW004TCU SCT080CB OVC120ACC");

  BOOST_CHECK(metar->NumCloudLayers() == 3);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::FEW);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 4);
  BOOST_CHECK(metar->Layer(0)->CloudType() == Clouds::type::TCU);
  BOOST_CHECK(metar->Layer(1)->Cover() == Clouds::cover::SCT);
  BOOST_CHECK(metar->Layer(1)->Altitude() == 80);
  BOOST_CHECK(metar->Layer(1)->CloudType() == Clouds::type::CB);
  BOOST_CHECK(metar->Layer(2)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(2)->Altitude() == 120);
  BOOST_CHECK(metar->Layer(2)->CloudType() == Clouds::type::ACC);
}

BOOST_AUTO_TEST_CASE(phenom_tempo)
{
    auto metar = Metar::Create("EDDH VCBLSN TEMPO SHSN");

    BOOST_CHECK(metar->NumPhenomena() == 2);

    BOOST_CHECK(metar->Phenomenon(0)[0] == Phenom::phenom::SNOW);
    BOOST_CHECK(metar->Phenomenon(0).Vicinity() == true);
    BOOST_CHECK(metar->Phenomenon(0).Blowing() == true);
    BOOST_CHECK(metar->Phenomenon(0).Temporary() == false);

    BOOST_CHECK(metar->Phenomenon(1)[0] == Phenom::phenom::SHOWER);
    BOOST_CHECK(metar->Phenomenon(1)[1] == Phenom::phenom::SNOW);
    BOOST_CHECK(metar->Phenomenon(1).Temporary() == true);
}

BOOST_AUTO_TEST_CASE(real_METAR_1)
{
  char buffer[100];
  strcpy(buffer, "KSTL 231751Z 27009KT 10SM OVC015 09/06 A3029 RMK AO2 SLP260 T00940061 10100 20078 53002");

  auto metar = Metar::Create(buffer);

  BOOST_CHECK(!metar->hasMessageType());

  BOOST_CHECK(metar->hasICAO());
  BOOST_CHECK(strcmp(metar->ICAO(), "KSTL") == 0);

  BOOST_CHECK(metar->Day() == 23);
  BOOST_CHECK(metar->Hour() == 17);
  BOOST_CHECK(metar->Minute() == 51);

  BOOST_CHECK(metar->WindDirection() == 270);
  BOOST_CHECK(metar->WindSpeed() == 9);
  BOOST_CHECK(!metar->hasWindGust());
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(!metar->hasMinWindDirection());
  BOOST_CHECK(!metar->hasMaxWindDirection());

  BOOST_CHECK(metar->Visibility() == 10);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);

  BOOST_CHECK(metar->NumCloudLayers() == 1);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 15);

  BOOST_CHECK(!metar->hasVerticalVisibility());

  BOOST_CHECK(metar->Temperature() == 9);
  BOOST_CHECK(metar->DewPoint() == 6);

  BOOST_CHECK(!metar->hasAltimeterQ());
  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 30.29);

  BOOST_CHECK(metar->hasSeaLevelPressure());
  BOOST_CHECK(metar->SeaLevelPressure() == 1026.0);

  BOOST_CHECK(metar->TemperatureNA() == 9.4);
  BOOST_CHECK(metar->DewPointNA() == 6.1);
}

BOOST_AUTO_TEST_CASE(real_METAR_2)
{
  std::string metar_str = "METAR LBBG 041600Z 12012MPS 090V150 1400 R04/P1500N R22/P1500U +SN BKN022 OVC050 M04/M07 Q1020 NOSIG 8849//91=";

  auto metar = Metar::Create(metar_str.c_str());

  BOOST_CHECK(metar->hasMessageType());
  BOOST_CHECK(metar->MessageType() == Metar::message_type::METAR);

  BOOST_CHECK(metar->hasICAO());
  BOOST_CHECK(strcmp(metar->ICAO(), "LBBG") == 0);

  BOOST_CHECK(metar->Day() == 4);
  BOOST_CHECK(metar->Hour() == 16);
  BOOST_CHECK(metar->Minute() == 0);

  BOOST_CHECK(metar->WindDirection() == 120);
  BOOST_CHECK(metar->WindSpeed() == 12);
  BOOST_CHECK(!metar->hasWindGust());
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::MPS);

  BOOST_CHECK(metar->hasMinWindDirection());
  BOOST_CHECK(metar->hasMaxWindDirection());

  BOOST_CHECK(metar->MinWindDirection() == 90);
  BOOST_CHECK(metar->MaxWindDirection() == 150);

  BOOST_CHECK(metar->Visibility() == 1400);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::M);

  BOOST_CHECK(metar->NumPhenomena() == 1);

  BOOST_CHECK(metar->Phenomenon(0)[0] == Phenom::phenom::SNOW);
  BOOST_CHECK(metar->Phenomenon(0).Intensity() == Phenom::intensity::HEAVY);

  BOOST_CHECK(metar->NumCloudLayers() == 2);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::BKN);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 22);
  BOOST_CHECK(metar->Layer(1)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(1)->Altitude() == 50);

  BOOST_CHECK(!metar->hasVerticalVisibility());

  BOOST_CHECK(metar->Temperature() == -4);
  BOOST_CHECK(metar->DewPoint() == -7);

  BOOST_CHECK(!metar->hasAltimeterA());
  BOOST_CHECK(metar->hasAltimeterQ());
  BOOST_CHECK(metar->AltimeterQ() == 1020);

  BOOST_CHECK(!metar->hasSeaLevelPressure());

  BOOST_CHECK(!metar->hasTemperatureNA());
  BOOST_CHECK(!metar->hasDewPointNA());
}

BOOST_AUTO_TEST_CASE(real_METAR_3)
{
  std::string metar_str = "SPECI KSTL 221513Z 07005KT 2SM -RA BR OVC005 02/02 A3041 RMK AO2 P0001 T00220022";

  auto metar = Metar::Create(metar_str.c_str());

  BOOST_CHECK(metar->hasMessageType());
  BOOST_CHECK(metar->MessageType() == Metar::message_type::SPECI);

  BOOST_CHECK(metar->hasICAO());
  BOOST_CHECK(strcmp(metar->ICAO(), "KSTL") == 0);

  BOOST_CHECK(metar->Day() == 22);
  BOOST_CHECK(metar->Hour() == 15);
  BOOST_CHECK(metar->Minute() == 13);

  BOOST_CHECK(metar->WindDirection() == 70);
  BOOST_CHECK(metar->WindSpeed() == 5);
  BOOST_CHECK(!metar->hasWindGust());
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(!metar->hasMinWindDirection());
  BOOST_CHECK(!metar->hasMaxWindDirection());

  BOOST_CHECK(metar->Visibility() == 2);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);

  BOOST_CHECK(metar->NumPhenomena() == 2);

  BOOST_CHECK(metar->Phenomenon(0)[0] == Phenom::phenom::RAIN);
  BOOST_CHECK(metar->Phenomenon(0).Intensity() == Phenom::intensity::LIGHT);

  BOOST_CHECK(metar->Phenomenon(1)[0] == Phenom::phenom::MIST);
  BOOST_CHECK(metar->Phenomenon(1).Intensity() == Phenom::intensity::NORMAL);

  BOOST_CHECK(metar->NumCloudLayers() == 1);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 5);

  BOOST_CHECK(!metar->hasVerticalVisibility());

  BOOST_CHECK(metar->Temperature() == 2);
  BOOST_CHECK(metar->DewPoint() == 2);

  BOOST_CHECK(!metar->hasAltimeterQ());
  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 30.41);

  BOOST_CHECK(!metar->hasSeaLevelPressure());

  BOOST_CHECK(metar->TemperatureNA() == 2.2);
  BOOST_CHECK(metar->DewPointNA() == 2.2);
}

BOOST_AUTO_TEST_CASE(real_METAR_4)
{
  const char *metar_str = "KSTL 262051Z VRB04KT 10SM CLR 16/M01 A3023 RMK AO2 SLP242 T01561006 57015";

  auto metar = Metar::Create(metar_str);

  BOOST_CHECK(metar->hasICAO());
  BOOST_CHECK(strcmp(metar->ICAO(), "KSTL") == 0);

  BOOST_CHECK(metar->Day() == 26);
  BOOST_CHECK(metar->Hour() == 20);
  BOOST_CHECK(metar->Minute() == 51);

  BOOST_CHECK(!metar->hasWindDirection());
  BOOST_CHECK(metar->isVariableWindDirection());
  BOOST_CHECK(metar->WindSpeed() == 4);
  BOOST_CHECK(!metar->hasWindGust());
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(!metar->hasMinWindDirection());
  BOOST_CHECK(!metar->hasMaxWindDirection());

  BOOST_CHECK(metar->Visibility() == 10);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);

  BOOST_CHECK(metar->NumPhenomena() == 0);

  BOOST_CHECK(metar->Phenomenon(0)[0] == Phenom::phenom::NONE);
  BOOST_CHECK(metar->Phenomenon(1)[1] == Phenom::phenom::NONE);

  BOOST_CHECK(metar->NumCloudLayers() == 1);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::CLR);
  BOOST_CHECK(!metar->Layer(0)->hasAltitude());

  BOOST_CHECK(!metar->hasVerticalVisibility());

  BOOST_CHECK(metar->Temperature() == 16);
  BOOST_CHECK(metar->DewPoint() == -1);

  BOOST_CHECK(!metar->hasAltimeterQ());
  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 30.23);

  BOOST_CHECK(metar->hasSeaLevelPressure());
  BOOST_CHECK(metar->SeaLevelPressure() == 1024.2);

  BOOST_CHECK(metar->TemperatureNA() == 15.6);
  BOOST_CHECK(metar->DewPointNA() == -0.6);
}

BOOST_AUTO_TEST_CASE(real_METAR_5)
{
  const char *metar_str = "KHLN 041610Z 28009KT 1/2SM SN FZFG VV007 M10/M12 A2998 RMK AO2 P0001 T11001117";

  auto metar = Metar::Create(metar_str);

  BOOST_CHECK(metar->hasICAO());
  BOOST_CHECK(strcmp(metar->ICAO(), "KHLN") == 0);

  BOOST_CHECK(metar->Day() == 4);
  BOOST_CHECK(metar->Hour() == 16);
  BOOST_CHECK(metar->Minute() == 10);

  BOOST_CHECK(metar->WindDirection() == 280);
  BOOST_CHECK(metar->WindSpeed() == 9);
  BOOST_CHECK(!metar->hasWindGust());
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(!metar->hasMinWindDirection());
  BOOST_CHECK(!metar->hasMaxWindDirection());

  BOOST_CHECK(metar->NumPhenomena() == 2);

  BOOST_CHECK(metar->Phenomenon(0)[0] == Phenom::phenom::SNOW);
  BOOST_CHECK(metar->Phenomenon(0).Intensity() == Phenom::intensity::NORMAL);

  BOOST_CHECK(metar->Phenomenon(1)[0] == Phenom::phenom::FOG);
  BOOST_CHECK(metar->Phenomenon(1).Freezing() == true);
  BOOST_CHECK(metar->Phenomenon(1).Intensity() == Phenom::intensity::NORMAL);

  BOOST_CHECK(metar->Visibility() == 0.5);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);

  BOOST_CHECK(metar->NumCloudLayers() == 0);

  BOOST_CHECK(metar->hasVerticalVisibility());
  BOOST_CHECK(metar->VerticalVisibility() == 700);

  BOOST_CHECK(metar->Temperature() == -10);
  BOOST_CHECK(metar->DewPoint() == -12);

  BOOST_CHECK(!metar->hasAltimeterQ());
  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 29.98);

  BOOST_CHECK(!metar->hasSeaLevelPressure());

  BOOST_CHECK(metar->TemperatureNA() == -10.0);
  BOOST_CHECK(metar->DewPointNA() == -11.7);
}

BOOST_AUTO_TEST_CASE(real_METAR_6)
{
  const char *metar_str = "KSTL 051520Z 12017KT 5SM -TSRA BR OVC007CB 06/05 A2989 RMK AO2 LTG DSNT SE OCNL LTGIC SE TSB0854 TS SE MOV NE P0004 T00560050";

  auto metar = Metar::Create(metar_str);

  BOOST_CHECK(strcmp(metar->ICAO(), "KSTL") == 0);

  BOOST_CHECK(metar->Day() == 5);
  BOOST_CHECK(metar->Hour() == 15);
  BOOST_CHECK(metar->Minute() == 20);

  BOOST_CHECK(metar->WindDirection() == 120);
  BOOST_CHECK(metar->WindSpeed() == 17);
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(metar->Visibility() == 5);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);

  BOOST_CHECK(metar->NumPhenomena() == 2);

  BOOST_CHECK(metar->Phenomenon(0).ThunderStorm());
  BOOST_CHECK(metar->Phenomenon(0)[0] == Phenom::phenom::RAIN);
  BOOST_CHECK(metar->Phenomenon(0).Intensity() == Phenom::intensity::LIGHT);

  BOOST_CHECK(metar->Phenomenon(1)[0] == Phenom::phenom::MIST);
  BOOST_CHECK(metar->Phenomenon(1).Intensity() == Phenom::intensity::NORMAL);

  BOOST_CHECK(metar->NumCloudLayers() == 1);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 7);
  BOOST_CHECK(metar->Layer(0)->CloudType() == Clouds::type::CB);

  BOOST_CHECK(!metar->hasVerticalVisibility());

  BOOST_CHECK(metar->Temperature() == 6);
  BOOST_CHECK(metar->DewPoint() == 5);

  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 29.89);

  BOOST_CHECK(metar->TemperatureNA() == 5.6);
  BOOST_CHECK(metar->DewPointNA() == 5);
}

BOOST_AUTO_TEST_CASE(real_METAR_7)
{
  const char *metar_str = "KSTL 091651Z 10010KT 060V120 10SM FEW120 BKN250 07/M06 A2998 RMK AO2 SLP160 T00671056";

  auto metar = Metar::Create(metar_str);

  BOOST_CHECK(strcmp(metar->ICAO(), "KSTL") == 0);

  BOOST_CHECK(metar->Day() == 9);
  BOOST_CHECK(metar->Hour() == 16);
  BOOST_CHECK(metar->Minute() == 51);

  BOOST_CHECK(metar->WindDirection() == 100);
  BOOST_CHECK(metar->WindSpeed() == 10);
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(metar->MinWindDirection() == 60);
  BOOST_CHECK(metar->MaxWindDirection() == 120);

  BOOST_CHECK(metar->Visibility() == 10);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);

  BOOST_CHECK(metar->NumCloudLayers() == 2);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::FEW);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 120);
  BOOST_CHECK(metar->Layer(1)->Cover() == Clouds::cover::BKN);
  BOOST_CHECK(metar->Layer(1)->Altitude() == 250);

  BOOST_CHECK(!metar->hasVerticalVisibility());

  BOOST_CHECK(metar->Temperature() == 7);
  BOOST_CHECK(metar->DewPoint() == -6);

  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 29.98);

  BOOST_CHECK(metar->TemperatureNA() == 6.7);
  BOOST_CHECK(metar->DewPointNA() == -5.6);

  BOOST_CHECK(metar->SeaLevelPressure() == 1016.0);
}

BOOST_AUTO_TEST_CASE(real_METAR_8)
{
  const char *metar_str = "KSTL 192051Z 20004KT 10SM -RA FEW034 SCT048 OVC110 22/18 A2993 RMK AO2 PK WND 27032/2004 LTG DSNT E AND SE RAB06 TSB03E42 PRESFR SLP129 OCNL LTGIC DSNT E CB DSNT E MOV E P0003 60003 T02220178 58006 $";

  auto metar = Metar::Create(metar_str);

  BOOST_CHECK(strcmp(metar->ICAO(), "KSTL") == 0);

  BOOST_CHECK(metar->Day() == 19);
  BOOST_CHECK(metar->Hour() == 20);
  BOOST_CHECK(metar->Minute() == 51);

  BOOST_CHECK(metar->WindDirection() == 200);
  BOOST_CHECK(metar->WindSpeed() == 4);
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(metar->Visibility() == 10);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);

  BOOST_CHECK(metar->NumPhenomena() == 1);

  BOOST_CHECK(metar->Phenomenon(0)[0] == Phenom::phenom::RAIN);
  BOOST_CHECK(metar->Phenomenon(0).Intensity() == Phenom::intensity::LIGHT);

  BOOST_CHECK(metar->NumCloudLayers() == 3);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::FEW);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 34);
  BOOST_CHECK(metar->Layer(1)->Cover() == Clouds::cover::SCT);
  BOOST_CHECK(metar->Layer(1)->Altitude() == 48);
  BOOST_CHECK(metar->Layer(2)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(2)->Altitude() == 110);

  BOOST_CHECK(metar->Temperature() == 22);
  BOOST_CHECK(metar->DewPoint() == 18);

  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 29.93);

  BOOST_CHECK(metar->TemperatureNA() == 22.2);
  BOOST_CHECK(metar->DewPointNA() == 17.8);

  BOOST_CHECK(metar->SeaLevelPressure() == 1012.9);
}

BOOST_AUTO_TEST_CASE(real_METAR_9)
{
  const char *metar_str = "KSTL 261605Z 10006KT 7SM -TSRA FEW050CB OVC090 06/01 A3014 RMK AO2 LTG DSNT S AND SW TSB05 OCNL LTGIC SW-W TS SW-W MOV NE P0001 T00610006";

  auto metar = Metar::Create(metar_str);

  BOOST_CHECK(strcmp(metar->ICAO(), "KSTL") == 0);

  BOOST_CHECK(metar->Day() == 26);
  BOOST_CHECK(metar->Hour() == 16);
  BOOST_CHECK(metar->Minute() == 5);

  BOOST_CHECK(metar->WindDirection() == 100);
  BOOST_CHECK(metar->WindSpeed() == 6);
  BOOST_CHECK(metar->WindSpeedUnits() == Metar::speed_units::KT);

  BOOST_CHECK(metar->Visibility() == 7);
  BOOST_CHECK(metar->VisibilityUnits() == Metar::distance_units::SM);

  BOOST_CHECK(metar->NumPhenomena() == 1);

  BOOST_CHECK(metar->Phenomenon(0).Intensity() == Phenom::intensity::LIGHT);
  BOOST_CHECK(metar->Phenomenon(0).ThunderStorm());
  BOOST_CHECK(metar->Phenomenon(0)[0] == Phenom::phenom::RAIN);

  BOOST_CHECK(metar->NumCloudLayers() == 2);
  BOOST_CHECK(metar->Layer(0)->Cover() == Clouds::cover::FEW);
  BOOST_CHECK(metar->Layer(0)->Altitude() == 50);
  BOOST_CHECK(metar->Layer(0)->CloudType() == Clouds::type::CB);
  BOOST_CHECK(metar->Layer(1)->Cover() == Clouds::cover::OVC);
  BOOST_CHECK(metar->Layer(1)->Altitude() == 90);

  BOOST_CHECK(metar->Temperature() == 6);
  BOOST_CHECK(metar->DewPoint() == 1);

  BOOST_CHECK(metar->hasAltimeterA());
  BOOST_CHECK(metar->AltimeterA() == 30.14);

  BOOST_CHECK(metar->TemperatureNA() == 6.1);
  BOOST_CHECK(metar->DewPointNA() == 0.6);
}


// Boost.Geometry Index
// Unit Test

// Copyright (c) 2016 Adam Wulkiewicz, Lodz, Poland.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <rtree/test_rtree.hpp>

#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/point_xy.hpp>

template <typename Value, typename Point, typename Params>
void test_all()
{
    typedef bg::model::box<Point> Box;
    typedef bg::model::segment<Point> Seg;
    typedef bg::model::ring<Point> Ring;
    typedef bg::model::polygon<Point> Poly;
    typedef bg::model::multi_polygon<Poly> MPoly;
    typedef bg::model::linestring<Point> Ls;
    typedef bg::model::multi_linestring<Ls> MLs;
    typedef bg::model::multi_point<Point> MPt;

    bgi::rtree<Value, Params> rt;
    std::vector<Value> found;

    rt.query(bgi::intersects(Point()), back_inserter(found));
    rt.query(bgi::intersects(Seg()), back_inserter(found));
    rt.query(bgi::intersects(Box()), back_inserter(found));
    rt.query(bgi::intersects(Ring()), back_inserter(found));
    rt.query(bgi::intersects(Poly()), back_inserter(found));
    rt.query(bgi::intersects(MPoly()), back_inserter(found));
    rt.query(bgi::intersects(Ls()), back_inserter(found));
    rt.query(bgi::intersects(MLs()), back_inserter(found));
    rt.query(bgi::intersects(MPt()), back_inserter(found));
}

int test_main(int, char* [])
{
    typedef bg::model::d2::point_xy<double> Pt;
    typedef bg::model::box<Pt> Box;

    test_all< Pt, Pt, bgi::linear<16> >();
    test_all< Pt, Pt, bgi::quadratic<4> >();
    test_all< Pt, Pt, bgi::rstar<4> >();

    test_all< Box, Pt, bgi::linear<16> >();
    test_all< Box, Pt, bgi::quadratic<4> >();
    test_all< Box, Pt, bgi::rstar<4> >();

    return 0;
}


#include <boost/functional/hash.hpp>
#include <iostream>
#include <memory>
#include <vector>

struct Point
{
   int x, y;

   friend std::size_t
   hash_value(Point const& p)
   {
      std::size_t seed = 0x123456F;
      boost::hash_combine(seed, p.x);
      boost::hash_combine(seed, p.y);
      return seed;
   }
};

struct PointsObject
{
   virtual std::vector< Point >::const_iterator
   begin() const = 0;

   virtual std::vector< Point >::const_iterator
   end() const = 0;
};

struct Line
{
   Point start, end;

   friend std::size_t
   hash_value(Line const& l)
   {
      std::size_t seed = 0x543210F;
      boost::hash_combine(seed, l.start);
      boost::hash_combine(seed, l.end);
      return seed;
   }
};

struct VectorObject
{
   virtual std::vector< Line >::const_iterator
   begin() const = 0;

   virtual std::vector< Line >::const_iterator
   end() const = 0;

   virtual ~VectorObject() = default;
};

struct VectorRectangle : public VectorObject
{
   VectorRectangle(int x, int y, int width, int height)
   {
      lines.emplace_back(Line{Point{x, y}, Point{x + width, y}});
      lines.emplace_back(Line{Point{x + width, y}, Point{x + width, y + height}});
      lines.emplace_back(Line{Point{x, y}, Point{x, y + height}});
      lines.emplace_back(Line{Point{x, y + height}, Point{x + width, y + height}});
   }

   std::vector< Line >::const_iterator
   begin() const override
   {
      return lines.cbegin();
   }

   std::vector< Line >::const_iterator
   end() const override
   {
      return lines.cend();
   }

   virtual ~VectorRectangle() = default;

 private:
   std::vector< Line > lines;
};

struct VectorObj2PointsAdapter : public PointsObject
{
   explicit VectorObj2PointsAdapter(VectorObject const& obj)
   {
      for (auto const& line : obj)
      {
         add_line(line);
      }
   }

   virtual std::vector< Point >::const_iterator
   begin() const override
   {
      return points.cbegin();
   }

   virtual std::vector< Point >::const_iterator
   end() const override
   {
      return points.cend();
   }

 private:
   void
   add_line(Line const& line)
   {
      static int count = 0;
      std::cout << count++ << ": Generating points for line (no caching)\n";

      // no interpolation
      int left = std::min(line.start.x, line.end.x);
      int right = std::max(line.start.x, line.end.x);
      int top = std::min(line.start.y, line.end.y);
      int bottom = std::max(line.start.y, line.end.y);

      int dx = right - left;
      int dy = bottom - top;

      // only vertical or horizontal lines support
      if (dx == 0 && dy != 0) // vertical
      {
         for (int y = top; y <= bottom; ++y)
         {
            points.emplace_back(Point{left, y});
         }
      }
      else if (dx != 0 && dy == 0) // horizontal
      {
         for (int x = top; x <= right; ++x)
         {
            points.emplace_back(Point{x, top});
         }
      }
   }

   std::vector< Point > points;
};

struct VectorObj2PointsCachingAdapter : public PointsObject
{
   explicit VectorObj2PointsCachingAdapter(VectorObject const& obj)
   {
      for (auto const& line : obj)
      {
         boost::hash< Line > hash;
         auto line_hash = hash(line);

         auto cached_line = cache.find(line_hash);
         if (cached_line != cache.cend())
         {
            points.insert(points.end(), cached_line->second.begin(), cached_line->second.end());
         }
         else
         {
            add_line(line, line_hash);
         }
      }
   }

   virtual std::vector< Point >::const_iterator
   begin() const override
   {
      return points.cbegin();
   }

   virtual std::vector< Point >::const_iterator
   end() const override
   {
      return points.cend();
   }

 private:
   void
   add_line(Line const& line, std::size_t line_hash)
   {
      static int count = 0;
      std::cout << count++ << ": Generating points for line (no caching)\n";

      // no interpolation
      int left = std::min(line.start.x, line.end.x);
      int right = std::max(line.start.x, line.end.x);
      int top = std::min(line.start.y, line.end.y);
      int bottom = std::max(line.start.y, line.end.y);

      int dx = right - left;
      int dy = bottom - top;

      std::vector< Point > points;

      // only vertical or horizontal lines support
      if (dx == 0 && dy != 0) // vertical
      {
         for (int y = top; y <= bottom; ++y)
         {
            points.emplace_back(Point{left, y});
         }
      }
      else if (dx != 0 && dy == 0) // horizontal
      {
         for (int x = top; x <= right; ++x)
         {
            points.emplace_back(Point{x, top});
         }
      }

      cache[line_hash] = points;
   }

   static std::map< size_t, std::vector< Point > > cache;
   std::vector< Point > points;
};

std::map< size_t, std::vector< Point > > VectorObj2PointsCachingAdapter::cache{};

struct IDrawer
{
   virtual void
   drawPoints(PointsObject const&) = 0;

   virtual ~IDrawer() = default;
};

struct DrawerImpl : public IDrawer
{
   void
   drawPoints(PointsObject const& obj) override
   {
      (void)obj;
      std::cout << "Drawing points by DrawerImpl..\n";
   }

   virtual ~DrawerImpl() = default;
};

int
main()
{
   std::vector< std::unique_ptr< VectorObject > > vectorObjects;
   vectorObjects.emplace_back(std::make_unique< VectorRectangle >(10, 10, 100, 100));
   vectorObjects.emplace_back(std::make_unique< VectorRectangle >(30, 30, 60, 60));

   std::unique_ptr< IDrawer > drawer = std::make_unique< DrawerImpl >();

   for (int n = 0; n < 5; ++n)
   {
      for (auto const& obj : vectorObjects)
      {
         // Can't draw vector objects! only raw points...
         // drawer->drawPoints(*obj);
         auto points = VectorObj2PointsCachingAdapter(*obj);
         drawer->drawPoints(points);
      }
   }

   return 0;
}


// This file is part of the dune-stuff project:
//   https://github.com/wwu-numerik/dune-stuff
// Copyright holders: Rene Milk, Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
//
// Contributors: Sven Kaulmann

#ifndef DUNE_STUFF_COMMON_MATRIX_HH
#define DUNE_STUFF_COMMON_MATRIX_HH

#include <boost/numeric/conversion/cast.hpp>

#include <dune/common/dynmatrix.hh>
#include <dune/common/fmatrix.hh>

#include <dune/stuff/common/exceptions.hh>
#include <dune/stuff/common/fmatrix.hh>
#include <dune/stuff/common/vector.hh>

namespace Dune {
namespace Stuff {
namespace Common {

/**
 * \brief Traits to statically extract the information of a (mathematical) matrix.
 *
 *        If you want your matrix class to benefit from the operators defined in this header you have to manually
 *        specify a specialization of this class in your code with is_matrix defined to true and an appropriate
 *        static methods and members (see the specializations below).
 */
template <class MatType>
struct MatrixAbstraction
{
  typedef MatType MatrixType;
  typedef MatType ScalarType;
  typedef MatType S;

  static const bool is_matrix = false;

  static const bool has_static_size = false;

  static const size_t static_rows = std::numeric_limits<size_t>::max();

  static const size_t static_cols = std::numeric_limits<size_t>::max();

  static inline /*MatrixType*/ void create(const size_t /*rows*/, const size_t /*cols*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline /*MatrixType*/ void create(const size_t /*rows*/, const size_t /*cols*/, const ScalarType& /*val*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline /*size_t*/ void rows(const MatrixType& /*mat*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline /*size_t*/ void cols(const MatrixType& /*mat*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline void set_entry(MatrixType& /*mat*/, const size_t /*row*/, const size_t /*col*/,
                               const ScalarType& /*val*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline /*ScalarType*/ void get_entry(const MatrixType& /*mat*/, const size_t /*row*/, const size_t /*col*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }
};

template <class K>
struct MatrixAbstraction<Dune::DynamicMatrix<K>>
{
  typedef Dune::DynamicMatrix<K> MatrixType;
  typedef K ScalarType;
  typedef ScalarType S;

  static const bool is_matrix = true;

  static const bool has_static_size = false;

  static const size_t static_rows = std::numeric_limits<size_t>::max();

  static const size_t static_cols = std::numeric_limits<size_t>::max();

  static inline MatrixType create(const size_t rows, const size_t cols) { return MatrixType(rows, cols); }

  static inline MatrixType create(const size_t rows, const size_t cols, const ScalarType& val)
  {
    return MatrixType(rows, cols, val);
  }

  static inline size_t rows(const MatrixType& mat) { return mat.rows(); }

  static inline size_t cols(const MatrixType& mat) { return mat.cols(); }

  static inline void set_entry(MatrixType& mat, const size_t row, const size_t col, const ScalarType& val)
  {
    mat[row][col] = val;
  }

  static inline ScalarType get_entry(const MatrixType& mat, const size_t row, const size_t col)
  {
    return mat[row][col];
  }
};

template <class K, int N, int M>
struct MatrixAbstraction<Dune::FieldMatrix<K, N, M>>
{
  typedef Dune::FieldMatrix<K, N, M> MatrixType;
  typedef K ScalarType;
  typedef ScalarType S;

  static const bool is_matrix = true;

  static const bool has_static_size = true;

  static const size_t static_rows = N;

  static const size_t static_cols = M;

  static inline MatrixType create(const size_t rows, const size_t cols)
  {
    if (rows != N)
      DUNE_THROW(Dune::Stuff::Exceptions::shapes_do_not_match, "rows = " << rows << "\nN = " << int(N));
    if (cols != M)
      DUNE_THROW(Dune::Stuff::Exceptions::shapes_do_not_match, "cols = " << cols << "\nM = " << int(M));
    return MatrixType();
  }

  static inline MatrixType create(const size_t rows, const size_t cols, const ScalarType& val)
  {
    if (rows != N)
      DUNE_THROW(Dune::Stuff::Exceptions::shapes_do_not_match, "rows = " << rows << "\nN = " << int(N));
    if (cols != M)
      DUNE_THROW(Dune::Stuff::Exceptions::shapes_do_not_match, "cols = " << cols << "\nM = " << int(M));
    return MatrixType(val);
  }

  static inline size_t rows(const MatrixType& /*mat*/) { return boost::numeric_cast<size_t>(N); }

  static inline size_t cols(const MatrixType& /*mat*/) { return boost::numeric_cast<size_t>(M); }

  static inline void set_entry(MatrixType& mat, const size_t row, const size_t col, const ScalarType& val)
  {
    mat[row][col] = val;
  }

  static inline ScalarType get_entry(const MatrixType& mat, const size_t row, const size_t col)
  {
    return mat[row][col];
  }
};

template <class K, int N, int M>
struct MatrixAbstraction<Dune::Stuff::Common::FieldMatrix<K, N, M>>
{
  typedef Dune::Stuff::Common::FieldMatrix<K, N, M> MatrixType;
  typedef K ScalarType;
  typedef ScalarType S;

  static const bool is_matrix = true;

  static const bool has_static_size = true;

  static const size_t static_rows = N;

  static const size_t static_cols = M;

  static inline MatrixType create(const size_t rows, const size_t cols) { return MatrixType(rows, cols); }

  static inline MatrixType create(const size_t rows, const size_t cols, const ScalarType& val)
  {
    return MatrixType(rows, cols, val);
  }

  static inline size_t rows(const MatrixType& /*mat*/) { return N; }

  static inline size_t cols(const MatrixType& /*mat*/) { return M; }

  static inline void set_entry(MatrixType& mat, const size_t row, const size_t col, const ScalarType& val)
  {
    mat[row][col] = val;
  }

  static inline ScalarType get_entry(const MatrixType& mat, const size_t row, const size_t col)
  {
    return mat[row][col];
  }
};

template <class MatrixType>
struct is_matrix
{
  static const bool value = MatrixAbstraction<MatrixType>::is_matrix;
};

template <class MatrixType>
typename std::enable_if<is_matrix<MatrixType>::value, MatrixType>::type
    create(const size_t sz, const typename MatrixAbstraction<MatrixType>::S& val)
{
  return MatrixAbstraction<MatrixType>::create(sz, val);
}

} // namespace Common
} // namespace Stuff
} // namespace Dune

#endif // DUNE_STUFF_COMMON_MATRIX_HH