#pragma once

#include <boost/fusion/tuple.hpp>
#include <TopExp_Explorer.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS.hxx>
//#include <boost/operators.hpp>
#include <StdPrs_ToolShadedShape.hxx>
#include <limits>
#include <boost/array.hpp>
#include "NumericRange.hpp"
#include <cmath>
#include <Poly_Triangulation.hxx>
//#include <numeric>
//#include <functional>
//#include <boost/range/iterator_range.hpp>
//#include <boost/range/adaptor/transformed.hpp>
//#include <boost/range/numeric.hpp>

namespace OpenCASCADEUtils{
	//class ShapeFaceIterator: boost::incrementable<ShapeFaceIterator>{
	//	TopExp_Explorer m_e;
	//public:
	//	explicit ShapeFaceIterator(const TopoDS_Shape &s): m_e(s,TopAbs_FACE){}
	//	ShapeFaceIterator(){}
	//	ShapeFaceIterator &operator++(){
	//		m_e.Next();
	//	}
	//	//ShapeFaceIterator operator++( int ){
	//	//	ShapeFaceIterator r = *this;
	//	//	++(*this);
	//	//	return r;
	//	//}
	//	bool operator!=(const ShapeFaceIterator &) const{ return m_e.More(); }
	//	const TopoDS_Face &operator*() const{ return TopoDS::Face(m_e.Current()); }
	//};
	//class ShapeFaceMeshIterator: boost::incrementable<ShapeFaceMeshIterator>{
	//	ShapeFaceIterator m_sfi;
	//	TopLoc_Location m_l;
	//public:
	//	template <typename _ShapeFaceIteratorInitializer>
	//	ShapeFaceMeshIterator(const _ShapeFaceIteratorInitializer &s,const TopLoc_Location &l): m_sfi(s), m_l(l){}
	//	template <typename _ShapeFaceIteratorInitializer>
	//	explicit ShapeFaceMeshIterator(const _ShapeFaceIteratorInitializer &s): m_sfi(s){}
	//	ShapeFaceMeshIterator(){}
	//	ShapeFaceMeshIterator() &operator++(){ ++m_sfi; }
	//	bool operator!=(const ShapeFaceMeshIterator &rhs) const{ return m_sfi!=rhs.m_sfi; }
	//	const Poly_Triangulation &operator*() const{ return *StdPrs_ToolShadedShape::Triangulation(*m_sfi,m_l); }
	//};
	inline std::size_t GetMeshedShapeVertCount(const TopoDS_Shape &s){
		std::size_t result = 0;
		for(TopExp_Explorer e(s,TopAbs_FACE);e.More();e.Next()){
			result += StdPrs_ToolShadedShape::Triangulation(TopoDS::Face(e.Current()),TopLoc_Location())->NbNodes();
		}
		return result;
		//return boost::accumulate
		//(
		//	boost::make_iterator_range(ShapeFaceMeshIterator(s),ShapeFaceMeshIterator())
		//		|boost::adaptors::transformed(std::mem_fun_ref(Poly_Triangulation::NbNodes)),
		//	0
		//);
	}
	inline std::size_t GetMeshedShapeTriangleCount(const TopoDS_Shape &s){
		std::size_t result = 0;
		for(TopExp_Explorer e(s,TopAbs_FACE);e.More();e.Next()){
			result += StdPrs_ToolShadedShape::Triangulation(TopoDS::Face(e.Current()),TopLoc_Location())->NbTriangles();
		}
		return result;
		//return boost::accumulate
		//(
		//	boost::make_iterator_range(ShapeFaceMeshIterator(s),ShapeFaceMeshIterator())
		//		|boost::adaptors
		//);
	}

	//could use some iterators and vector output

	template< typename _OutputIterator >
	_OutputIterator CopyMeshedShapeIndices(_OutputIterator dest,const TopoDS_Shape &s)
	{
		Standard_Integer offset = 0;
		for(TopExp_Explorer e(s,TopAbs_FACE);e.More();e.Next()){
			Handle_Poly_Triangulation h = StdPrs_ToolShadedShape::Triangulation(TopoDS::Face(e.Current()),TopLoc_Location());
			const Poly_Array1OfTriangle &f = h->Triangles();
			const TColgp_Array1OfPnt &v = h->Nodes();
			offset-=v.Lower();
			Standard_Integer
				stride = (e.Current().Orientation() == TopAbs_REVERSED)?-1:1,
				first = 2-stride,
				last = 2*(1+stride);//(-1,3,0) or (1,1,4)
			for(Standard_Integer i=f.Lower();i<=f.Upper();++i){
				const Poly_Triangle & tri = f(i);
				for(int j=first;j!=last;j+=stride){
					*dest++ = tri(j)+offset;
				}
			}
			offset+=v.Lower();
			offset+=v.Length();
		}
		return dest;
	}

	template< typename _ScalarOutputIterator >
	_ScalarOutputIterator CopyComponentsOfMeshedShapeVertexNormals(_ScalarOutputIterator dest,const TopoDS_Shape &s){
		for(TopExp_Explorer e(s,TopAbs_FACE);e.More();e.Next()){
			Handle_Poly_Triangulation h = StdPrs_ToolShadedShape::Triangulation(TopoDS::Face(e.Current()),TopLoc_Location());
			Poly_Connect pc(h);
			TColgp_Array1OfDir n(1,h->NbNodes());
			StdPrs_ToolShadedShape::Normal(TopoDS::Face(e.Current()),pc,n);
			for(Standard_Integer i = n.Lower();i<=n.Upper();++i){
				const gp_Dir &d = n(i);
				*dest++ = d.X();
				*dest++ = d.Y();
				*dest++ = d.Z();
			}
		}
		return dest;
	}

	template< typename _ScalarOutputIterator >
	_ScalarOutputIterator CopyComponentsOfMeshedShapeVertexPositions(_ScalarOutputIterator dest, const TopoDS_Shape &s){
		for(TopExp_Explorer e(s,TopAbs_FACE);e.More();e.Next()){
			Handle_Poly_Triangulation h = StdPrs_ToolShadedShape::Triangulation(TopoDS::Face(e.Current()),TopLoc_Location());
			const TColgp_Array1OfPnt &v = h->Nodes();
			for(Standard_Integer i=v.Lower();i<=v.Upper();++i){
				const gp_Pnt &pt = v(i);
				*dest++ = pt.X();
				*dest++ = pt.Y();
				*dest++ = pt.Z();
			}
		}
		return dest;
	}

	boost::array<Standard_Real,6> GetMeshedShapeAABB(const TopoDS_Shape &s){
		boost::array<Standard_Real,6> r=
		{
			std::numeric_limits<Standard_Real>::max(),std::numeric_limits<Standard_Real>::max(),std::numeric_limits<Standard_Real>::max(),
			std::numeric_limits<Standard_Real>::min(),std::numeric_limits<Standard_Real>::min(),std::numeric_limits<Standard_Real>::min()
		};
		for(TopExp_Explorer e(s,TopAbs_FACE);e.More();e.Next()){
			Handle_Poly_Triangulation h = StdPrs_ToolShadedShape::Triangulation(TopoDS::Face(e.Current()),TopLoc_Location());
			const TColgp_Array1OfPnt &v = h->Nodes();
			for(Standard_Integer i=v.Lower();i<=v.Upper();++i){
				using namespace NumericRange;
				const gp_Pnt &pt = v(i);
				Extend(pt.X(),r[0],r[3]);
				Extend(pt.Y(),r[1],r[4]);
				Extend(pt.Z(),r[2],r[5]);
			}
		}
		return r;
	}

	Standard_Real GetMeshedShapeMaxVertexDistanceSquared(const TopoDS_Shape &s){
		Standard_Real r=0;
		for(TopExp_Explorer e(s,TopAbs_FACE);e.More();e.Next()){
			Handle_Poly_Triangulation h = StdPrs_ToolShadedShape::Triangulation(TopoDS::Face(e.Current()),TopLoc_Location());
			const TColgp_Array1OfPnt &v = h->Nodes();
			for(Standard_Integer i=v.Lower();i<=v.Upper();++i){
				using namespace NumericRange;
				const gp_Pnt &pt = v(i);
				ExtendR( pow(v(i).X(),2) + pow(v(i).Y(),2) + pow(v(i).Z(),2) , r);
			}
		}
		return r;
	}
}


// Copyright 2005-2008 Daniel James.
// Distributed under 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)

//  Based on Peter Dimov's proposal
//  http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf
//  issue 6.18.

#if !defined(BOOST_FUNCTIONAL_HASH_HASH_HPP)
#define BOOST_FUNCTIONAL_HASH_HASH_HPP

#include <boost/functional/hash_fwd.hpp>
#include <functional>
#include <boost/functional/detail/hash_float.hpp>
#include <boost/functional/detail/container_fwd.hpp>
#include <string>

#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
#include <boost/type_traits/is_pointer.hpp>
#endif

#if defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING)
#include <boost/type_traits/is_array.hpp>
#endif

#if BOOST_WORKAROUND(BOOST_MSVC, < 1300)
#include <boost/type_traits/is_const.hpp>
#endif

namespace boost
{
    std::size_t hash_value(bool);
    std::size_t hash_value(char);
    std::size_t hash_value(unsigned char);
    std::size_t hash_value(signed char);
    std::size_t hash_value(short);
    std::size_t hash_value(unsigned short);
    std::size_t hash_value(int);
    std::size_t hash_value(unsigned int);
    std::size_t hash_value(long);
    std::size_t hash_value(unsigned long);

#if !defined(BOOST_NO_INTRINSIC_WCHAR_T)
    std::size_t hash_value(wchar_t);
#endif

#if defined(BOOST_HAS_LONG_LONG)
    std::size_t hash_value(boost::long_long_type);
    std::size_t hash_value(boost::ulong_long_type);
#endif

#if !BOOST_WORKAROUND(__DMC__, <= 0x848)
    template <class T> std::size_t hash_value(T* const&);
#else
    template <class T> std::size_t hash_value(T*);
#endif

#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING)
    template< class T, unsigned N >
    std::size_t hash_value(const T (&array)[N]);

    template< class T, unsigned N >
    std::size_t hash_value(T (&array)[N]);
#endif

    std::size_t hash_value(float v);
    std::size_t hash_value(double v);
    std::size_t hash_value(long double v);

    template <class Ch, class A>
    std::size_t hash_value(std::basic_string<Ch, std::BOOST_HASH_CHAR_TRAITS<Ch>, A> const&);

    template <class A, class B>
    std::size_t hash_value(std::pair<A, B> const&);
    template <class T, class A>
    std::size_t hash_value(std::vector<T, A> const&);
    template <class T, class A>
    std::size_t hash_value(std::list<T, A> const& v);
    template <class T, class A>
    std::size_t hash_value(std::deque<T, A> const& v);
    template <class K, class C, class A>
    std::size_t hash_value(std::set<K, C, A> const& v);
    template <class K, class C, class A>
    std::size_t hash_value(std::multiset<K, C, A> const& v);
    template <class K, class T, class C, class A>
    std::size_t hash_value(std::map<K, T, C, A> const& v);
    template <class K, class T, class C, class A>
    std::size_t hash_value(std::multimap<K, T, C, A> const& v);

    template <class T>
    std::size_t hash_value(std::complex<T> const&);

    // Implementation

    namespace hash_detail
    {
        template <class T>
        inline std::size_t hash_value_signed(T val)
        {
             const int size_t_bits = std::numeric_limits<std::size_t>::digits;
             // ceiling(std::numeric_limits<T>::digits / size_t_bits) - 1
             const int length = (std::numeric_limits<T>::digits - 1)
                 / size_t_bits;

             std::size_t seed = 0;
             T positive = val < 0 ? -1 - val : val;

             // Hopefully, this loop can be unrolled.
             for(unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits)
             {
                 seed ^= (std::size_t) (positive >> i) + (seed<<6) + (seed>>2);
             }
             seed ^= (std::size_t) val + (seed<<6) + (seed>>2);

             return seed;
        }

        template <class T>
        inline std::size_t hash_value_unsigned(T val)
        {
             const int size_t_bits = std::numeric_limits<std::size_t>::digits;
             // ceiling(std::numeric_limits<T>::digits / size_t_bits) - 1
             const int length = (std::numeric_limits<T>::digits - 1)
                 / size_t_bits;

             std::size_t seed = 0;

             // Hopefully, this loop can be unrolled.
             for(unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits)
             {
                 seed ^= (std::size_t) (val >> i) + (seed<<6) + (seed>>2);
             }
             seed ^= (std::size_t) val + (seed<<6) + (seed>>2);

             return seed;
        }
    }

    inline std::size_t hash_value(bool v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(char v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(unsigned char v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(signed char v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(short v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(unsigned short v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(int v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(unsigned int v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(long v)
    {
        return static_cast<std::size_t>(v);
    }

    inline std::size_t hash_value(unsigned long v)
    {
        return static_cast<std::size_t>(v);
    }

#if !defined(BOOST_NO_INTRINSIC_WCHAR_T)
    inline std::size_t hash_value(wchar_t v)
    {
        return static_cast<std::size_t>(v);
    }
#endif

#if defined(BOOST_HAS_LONG_LONG)
    inline std::size_t hash_value(boost::long_long_type v)
    {
        return hash_detail::hash_value_signed(v);
    }

    inline std::size_t hash_value(boost::ulong_long_type v)
    {
        return hash_detail::hash_value_unsigned(v);
    }
#endif

    // Implementation by Alberto Barbati and Dave Harris.
#if !BOOST_WORKAROUND(__DMC__, <= 0x848)
    template <class T> std::size_t hash_value(T* const& v)
#else
    template <class T> std::size_t hash_value(T* v)
#endif
    {
        std::size_t x = static_cast<std::size_t>(
           reinterpret_cast<std::ptrdiff_t>(v));

        return x + (x >> 3);
    }

#if BOOST_WORKAROUND(BOOST_MSVC, < 1300)
    template <class T>
    inline void hash_combine(std::size_t& seed, T& v)
#else
    template <class T>
    inline void hash_combine(std::size_t& seed, T const& v)
#endif
    {
        boost::hash<T> hasher;
        seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
    }

    template <class It>
    inline std::size_t hash_range(It first, It last)
    {
        std::size_t seed = 0;

        for(; first != last; ++first)
        {
            hash_combine(seed, *first);
        }

        return seed;
    }

    template <class It>
    inline void hash_range(std::size_t& seed, It first, It last)
    {
        for(; first != last; ++first)
        {
            hash_combine(seed, *first);
        }
    }

#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x551))
    template <class T>
    inline std::size_t hash_range(T* first, T* last)
    {
        std::size_t seed = 0;

        for(; first != last; ++first)
        {
            boost::hash<T> hasher;
            seed ^= hasher(*first) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }

        return seed;
    }

    template <class T>
    inline void hash_range(std::size_t& seed, T* first, T* last)
    {
        for(; first != last; ++first)
        {
            boost::hash<T> hasher;
            seed ^= hasher(*first) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
    }
#endif

#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING)
    template< class T, unsigned N >
    inline std::size_t hash_value(const T (&array)[N])
    {
        return hash_range(array, array + N);
    }

    template< class T, unsigned N >
    inline std::size_t hash_value(T (&array)[N])
    {
        return hash_range(array, array + N);
    }
#endif

    template <class Ch, class A>
    inline std::size_t hash_value(std::basic_string<Ch, std::BOOST_HASH_CHAR_TRAITS<Ch>, A> const& v)
    {
        return hash_range(v.begin(), v.end());
    }

    inline std::size_t hash_value(float v)
    {
        return boost::hash_detail::float_hash_value(v);
    }

    inline std::size_t hash_value(double v)
    {
        return boost::hash_detail::float_hash_value(v);
    }

    inline std::size_t hash_value(long double v)
    {
        return boost::hash_detail::float_hash_value(v);
    }

    template <class A, class B>
    std::size_t hash_value(std::pair<A, B> const& v)
    {
        std::size_t seed = 0;
        hash_combine(seed, v.first);
        hash_combine(seed, v.second);
        return seed;
    }

    template <class T, class A>
    std::size_t hash_value(std::vector<T, A> const& v)
    {
        return hash_range(v.begin(), v.end());
    }

    template <class T, class A>
    std::size_t hash_value(std::list<T, A> const& v)
    {
        return hash_range(v.begin(), v.end());
    }

    template <class T, class A>
    std::size_t hash_value(std::deque<T, A> const& v)
    {
        return hash_range(v.begin(), v.end());
    }

    template <class K, class C, class A>
    std::size_t hash_value(std::set<K, C, A> const& v)
    {
        return hash_range(v.begin(), v.end());
    }

    template <class K, class C, class A>
    std::size_t hash_value(std::multiset<K, C, A> const& v)
    {
        return hash_range(v.begin(), v.end());
    }

    template <class K, class T, class C, class A>
    std::size_t hash_value(std::map<K, T, C, A> const& v)
    {
        return hash_range(v.begin(), v.end());
    }

    template <class K, class T, class C, class A>
    std::size_t hash_value(std::multimap<K, T, C, A> const& v)
    {
        return hash_range(v.begin(), v.end());
    }

    template <class T>
    std::size_t hash_value(std::complex<T> const& v)
    {
        boost::hash<T> hasher;
        std::size_t seed = hasher(v.imag());
        seed ^= hasher(v.real()) + (seed<<6) + (seed>>2);
        return seed;
    }

    //
    // boost::hash
    //

#if !BOOST_WORKAROUND(BOOST_MSVC, < 1300)
#define BOOST_HASH_SPECIALIZE(type) \
    template <> struct hash<type> \
         : public std::unary_function<type, std::size_t> \
    { \
        std::size_t operator()(type v) const \
        { \
            return boost::hash_value(v); \
        } \
    };

#define BOOST_HASH_SPECIALIZE_REF(type) \
    template <> struct hash<type> \
         : public std::unary_function<type, std::size_t> \
    { \
        std::size_t operator()(type const& v) const \
        { \
            return boost::hash_value(v); \
        } \
    };
#else
#define BOOST_HASH_SPECIALIZE(type) \
    template <> struct hash<type> \
         : public std::unary_function<type, std::size_t> \
    { \
        std::size_t operator()(type v) const \
        { \
            return boost::hash_value(v); \
        } \
    }; \
    \
    template <> struct hash<const type> \
         : public std::unary_function<const type, std::size_t> \
    { \
        std::size_t operator()(const type v) const \
        { \
            return boost::hash_value(v); \
        } \
    };

#define BOOST_HASH_SPECIALIZE_REF(type) \
    template <> struct hash<type> \
         : public std::unary_function<type, std::size_t> \
    { \
        std::size_t operator()(type const& v) const \
        { \
            return boost::hash_value(v); \
        } \
    }; \
    \
    template <> struct hash<const type> \
         : public std::unary_function<const type, std::size_t> \
    { \
        std::size_t operator()(type const& v) const \
        { \
            return boost::hash_value(v); \
        } \
    };
#endif

    BOOST_HASH_SPECIALIZE(bool)
    BOOST_HASH_SPECIALIZE(char)
    BOOST_HASH_SPECIALIZE(signed char)
    BOOST_HASH_SPECIALIZE(unsigned char)
#if !defined(BOOST_NO_INTRINSIC_WCHAR_T)
    BOOST_HASH_SPECIALIZE(wchar_t)
#endif
    BOOST_HASH_SPECIALIZE(short)
    BOOST_HASH_SPECIALIZE(unsigned short)
    BOOST_HASH_SPECIALIZE(int)
    BOOST_HASH_SPECIALIZE(unsigned int)
    BOOST_HASH_SPECIALIZE(long)
    BOOST_HASH_SPECIALIZE(unsigned long)

    BOOST_HASH_SPECIALIZE(float)
    BOOST_HASH_SPECIALIZE(double)
    BOOST_HASH_SPECIALIZE(long double)

    BOOST_HASH_SPECIALIZE_REF(std::string)
#if !defined(BOOST_NO_STD_WSTRING)
    BOOST_HASH_SPECIALIZE_REF(std::wstring)
#endif

#undef BOOST_HASH_SPECIALIZE
#undef BOOST_HASH_SPECIALIZE_REF

#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
    template <class T>
    struct hash<T*>
        : public std::unary_function<T*, std::size_t>
    {
        std::size_t operator()(T* v) const
        {
#if !BOOST_WORKAROUND(__SUNPRO_CC, <= 0x590)
            return boost::hash_value(v);
#else
            std::size_t x = static_cast<std::size_t>(
                reinterpret_cast<std::ptrdiff_t>(v));

            return x + (x >> 3);
#endif
        }
    };
#else
    namespace hash_detail
    {
        template <bool IsPointer>
        struct hash_impl;

        template <>
        struct hash_impl<true>
        {
            template <class T>
            struct inner
                : public std::unary_function<T, std::size_t>
            {
                std::size_t operator()(T val) const
                {
#if !BOOST_WORKAROUND(__SUNPRO_CC, <= 590)
                    return boost::hash_value(val);
#else
                    std::size_t x = static_cast<std::size_t>(
                        reinterpret_cast<std::ptrdiff_t>(val));

                    return x + (x >> 3);
#endif
                }
            };
        };
    }

    template <class T> struct hash
        : public boost::hash_detail::hash_impl<boost::is_pointer<T>::value>
            ::BOOST_NESTED_TEMPLATE inner<T>
    {
    };
#endif
}

#endif // BOOST_FUNCTIONAL_HASH_HASH_HPP

// Include this outside of the include guards in case the file is included
// twice - once with BOOST_HASH_NO_EXTENSIONS defined, and then with it
// undefined.

#if !defined(BOOST_HASH_NO_EXTENSIONS) \
    && !defined(BOOST_FUNCTIONAL_HASH_EXTENSIONS_HPP)
#include <boost/functional/hash/extensions.hpp>
#endif


///////////////////////////////////////////////////////////////
//  Copyright 2015 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_
//

#include "performance.hpp"
#include <boost/regex.hpp>
#include <boost/version.hpp>
#include <boost/lexical_cast.hpp>

struct boost_regex : public abstract_regex
{
private:
   boost::regex e;
   boost::cmatch what;
public:
   virtual bool set_expression(const char* pe, bool isperl)
   {
      e.assign(pe, isperl ? boost::regex::perl : boost::regex::extended);
      return e.status() == 0;
   }
   virtual bool match_test(const char* text);
   virtual unsigned find_all(const char* text);
   virtual std::string name();

   struct initializer
   {
      initializer()
      {
         boost_regex::register_instance(boost::shared_ptr<abstract_regex>(new boost_regex));
      }
      void do_nothing()const {}
   };
   static const initializer init;
};

const boost_regex::initializer boost_regex::init;

bool boost_regex::match_test(const char * text)
{
   return regex_match(text, what, e);
}

unsigned boost_regex::find_all(const char * text)
{
   boost::regex_iterator<const char*> i(text, text + std::strlen(text), e), j;
   unsigned count = 0;
   while(i != j)
   {
      ++i;
      ++count;
   }
   return count;
}

std::string boost_regex::name()
{
   init.do_nothing();
   return boost_name();
}


/**
 * \ file WhiteNoiseGeneratorFilter.cpp
 */

#include <iostream>

#include <ATK/Tools/WhiteNoiseGeneratorFilter.h>

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>

constexpr gsl::index PROCESSSIZE = 1024*1024*4;

BOOST_AUTO_TEST_CASE( WhiteNoiseGeneratorFilter_volume_test )
{
  ATK::WhiteNoiseGeneratorFilter<double> noisefilter;
  noisefilter.set_volume(10);
  BOOST_CHECK_EQUAL(noisefilter.get_volume(), 10);
}

BOOST_AUTO_TEST_CASE( WhiteNoiseGeneratorFilter_offset_test )
{
  ATK::WhiteNoiseGeneratorFilter<double> noisefilter;
  noisefilter.set_offset(10);
  BOOST_CHECK_EQUAL(noisefilter.get_offset(), 10);
}

BOOST_AUTO_TEST_CASE( WhiteNoiseGeneratorFilter_mean_test )
{
  ATK::WhiteNoiseGeneratorFilter<double> noisefilter;
  noisefilter.set_input_sampling_rate(PROCESSSIZE);
  noisefilter.set_output_sampling_rate(PROCESSSIZE);
  noisefilter.process(PROCESSSIZE);

  auto noise = noisefilter.get_output_array(0);
  auto mean = std::accumulate(noise, noise + PROCESSSIZE, 0.) / PROCESSSIZE;
  BOOST_CHECK_SMALL(mean, 0.001);
}

BOOST_AUTO_TEST_CASE( WhiteNoiseGeneratorFilter_std_test )
{
  ATK::WhiteNoiseGeneratorFilter<double> noisefilter;
  noisefilter.set_input_sampling_rate(PROCESSSIZE);
  noisefilter.set_output_sampling_rate(PROCESSSIZE);
  noisefilter.process(PROCESSSIZE);

  auto noise = noisefilter.get_output_array(0);
  auto std = (std::accumulate(noise, noise + PROCESSSIZE, 0., [](double a, double b) {return a + b*b;}) / PROCESSSIZE);
  BOOST_CHECK_CLOSE(std, 1./3, 0.05);
}


#include <iostream>
#include <sstream>
#include <cstring>
#include <vector>
#include <string>
#include <map>
#include <unistd.h>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <fstream>
using namespace std;

//Rachel Babikow, deadline project part 2, task #4

//time conversions (done by Ethan)
time_t strtot(std::string strtime)
{
	time_t deadtime;
	int yy, mon, dd, hh, mm;
	struct tm tmform;
	const char *chtime = strtime.c_str();
	sscanf(chtime, "%d-%d-%d %d:%d", &yy, &mon, &dd, &hh, &mm);
	tmform.tm_year = yy - 1900;
	tmform.tm_mon = mon - 1;
	tmform.tm_mday = dd;
	tmform.tm_hour = hh;
	tmform.tm_min = mm;
	tmform.tm_isdst = -1;
	deadtime = mktime(&tmform);
	return deadtime;
}
//delta calculation (done by Ethan)
std::string strdelta(time_t deldeadline)
{
	time_t curtime = time(0);
	double delta = difftime(deldeadline, curtime);
	char pos;
	if(delta > 0)
	{
		pos = '+';
	}
	else
	{
		pos = '-';
	}
	int delday = (int)(delta/86400);
	delta = delta - delday*86400;
	int delhour = (int)(delta/3600);
	delta = delta - delhour*3600;
	int delmin = (int)(delta/60);
	std::ostringstream os;
	os << "(" << pos << delday << "d, " << delhour << "h" << delmin << "m)";
	std::string dif = os.str();
	return dif;
}

//print deadline (done my Moe)
void printDeadline(map<string,string> deadline) {
  string deadlineName = deadline.begin()->first;
  string deadlineDate = deadline.begin()->second;
  string timeDifference = strdelta(strtot(deadlineDate));
  cout<<deadlineName<<" - "<<deadlineDate<<" "<<timeDifference<<endl;
}

//print deadlines (done my Moe)
void printDeadlines(vector<map<string,string>> deadlines) {
  int i = 1;
  vector<map<string,string>>::iterator it;

  for(it=deadlines.begin();it!=deadlines.end();it++) {
    cout<<i<<". ";
    printDeadline(*it);
  }
}

//check if the deadline # is an int
bool validInt(string deadline) {
	for (int i = 0; i < deadline.length(); i++) {
        	if (isdigit(deadline[i]) == false) {
            		return false;
  		}
	}
	return true;
}

int main(int argc, char** argv){

	//loadDeadlines();

	  // used to test (can erase)
 //	  vector< map <string, string> > myClass;
 //       vector< map <string, string> > myPersonal;
 //       vector< map <string, string> > myClassHidden;
 //       vector< map <string, string> > myPersonalHidden;
 //       map<string, string> deadlineOne;
 //       map<string, string> deadlineTwo;
 //       map<string, string> deadlineThree;
 //       map<string, string> deadlineFour;
 //       deadlineOne.insert(make_pair("Walk dog", "2001-05-24 10:50"));
 //       deadlineTwo.insert(make_pair("Make dinner", "2020-04-18 07:15"));
 //       deadlineThree.insert(make_pair("Go to work", "2020-09-18 09:09"));
 //       deadlineFour.insert(make_pair("take test", "2021-08-09 10:00"));
 //       myClass.push_back(deadlineThree);
 //       myPersonal.push_back(deadlineOne);
 //       myClassHidden.push_back(deadlineTwo);
 //       myPersonalHidden.push_back(deadlineFour);

	//command line: deadline
	if(argc == 1) {

		cout << "\ncsci 221:" << endl;
		printDeadlines(myClass);
		cout << "\npersonal:" << endl;
		printDeadlines(myPersonal);
		cout << "\n";
	}

	//command line: deadline add me "_____"
	else if(strcmp(argv[1], "add") == 0 && strcmp(argv[2], "me") == 0) {

		string deadline = "";
		for(int i = 4; i < argc; i ++) {
			deadline = deadline + (string) argv[i] + " ";
		}
		bool valid = false;
		if(argc == 8 && ((string) argv[4]).length() == 4 && ((string) argv[5]).length() == 2 && ((string) argv[6]).length() == 2 && ((string) argv[7]).length() == 5) {
				if(validInt(deadline) == 0 && deadline.find(":") == 13) {
					valid = true;
				}
				else {
					valid = false;
				}
		}
		if(valid == false) {
			cout << "Invalid arguements. Enter date as yyyy mm dd hh:mm" << endl;
		}
		else {
			string time = (string) argv[4] + "-" + argv[5] + "-" + argv[6] + " " + argv[7];
			time_t myTime = strtot(time);
                	cout << "Added deadline " << '"' << argv[3] << '"' << " " << strdelta(myTime) << endl;
			string list = "myPersonal";

			// FUNCTION NEEDED:
			// addDeadlines(list, (string) argv[3], time);

			//saveDeadlines();

		}
	}

	//command line: deadline rm #
	else if(strcmp(argv[1], "rm") == 0 && argc == 3) {

		if(validInt(argv[2]) == true) {
			if(stoi(argv[2]) <= (int) (myPersonal.size() + myClass.size()) && stoi(argv[2]) > 0 ) {
				string name;
				string date;
				string list;
				if(stoi(argv[2]) <= myClass.size()) {
					for(auto const& pair: myClass[stoi(argv[2])-1] ) {
						name = pair.first;
						date = pair.second;
						list = "myClass";
					}
				}
				else if(stoi(argv[2]) <= (int) (myClass.size() + myPersonal.size()) ) {
					for(auto const& pair: myPersonal[(myPersonal.size()+myClass.size())-stoi(argv[2])] ) {
                                                name = pair.first;
                                                date = pair.second;
						list = "myPersonal";
                                        }
				}
				time_t myTime = strtot(date);
				cout << "Removed deadline " << '"' << name << '"' << " " << strdelta(myTime) << endl;

				// FUNCTION NEEDED:
				// removeDeadline(list, name, date);

				//saveDeadlines();

			}
			else {
				cout << "Not a valid deadline" << endl;
			}
		}
		else{
			cout << "Invalid arguements" << endl;
		}
	}

	//command line: deadline hide #
	else if(strcmp(argv[1], "hide") == 0 && argc == 3){

		if(validInt(argv[2]) == true) {
                        if(stoi(argv[2]) <= (int) (myPersonal.size() + myPersonal.size()) && stoi(argv[2]) > 0) {
                                string name;
                                string date;
				string list;
				string hiddenList;
                                if(stoi(argv[2]) <= myClass.size()) {
                                        for(auto const& pair: myClass[stoi(argv[2])-1] ) {
                                                name = pair.first;
                                                date = pair.second;
						list = "myClass";
						hiddenList = "myClassHidden";
                                        }
                                }
                                else if(stoi(argv[2]) <= (int) (myPersonal.size() + myClass.size()) ) {
                                        for(auto const& pair: myPersonal[(myPersonal.size()+myClass.size())-stoi(argv[2])] ) {
                                                name = pair.first;
                                                date = pair.second;
						list = "myPersonal";
						hiddenList = "myPersonalHidden";
                                        }
                                }
				time_t myTime = strtot(date);
				cout << "Hiding deadline " << '"' << name << '"' << " " << strdelta(myTime) << endl;

				// FUNCTIONS NEEDED:
				// addDeadline(hiddenList, name, date);
				// removeDeadline(list, name, date);

				//saveDeadlines();
                        }
                        else {
                                cout << "Not a valid deadline" << endl;
                        }
                }
                else{
                        cout << "Invalid arguements" << endl;
                }
	}

	//command line: deadline hidden
	else if(strcmp(argv[1], "hidden") == 0 && argc == 2){

		cout << "Print hidden deadlines" << endl;

		cout << "\ncsci 221:" << endl;
                printDeadlines(myClassHidden);
                cout << "\npersonal:" << endl;
                printDeadlines(myPersonalHidden);
                cout << "\n";

	}

	//command line: deadline unhide #
	else if(strcmp(argv[1], "unhide") == 0 && argc == 3) {

		if(validInt(argv[2]) == true) {
                        if(stoi(argv[2]) <= (int) myClassHidden.size() + myPersonalHidden.size() && stoi(argv[2]) > 0) {
				string name;
				string date;
				string list;
				string hiddenList;
				if(stoi(argv[2]) <= (int) myClassHidden.size()) {
					for(auto const& pair: myClassHidden[stoi(argv[2])-1] ) {
                                		name = pair.first;
                                		date = pair.second;
						list = "myClass";
						hiddenList = "myClassHidden";
                                	}
				}
				else {
					for(auto const& pair: myPersonalHidden[myClassHidden.size() + myPersonalHidden.size() - stoi(argv[2])] ) {
                                                name = pair.first;
                                                date = pair.second;
						list = "myPersonal";
						hiddenList = "myPersonalHidden";
                                        }
				}
				time_t myTime = strtot(date);
				cout << "Unhiding deadline " << '"' << name << '"' << " " << strdelta(myTime) << endl;

				// FUNCTIONS NEEDED:
				// addDeadline(list, name, date);
                                // removeHiddenDeadline(hiddenList, name, date);

				//saveDeadlines();
                        }
                        else {
                                cout << "Not a valid deadline" << endl;
                        }
                }
                else{
                        cout << "Invalid arguements" << endl;
                }
       }

	//command line: deadline details #
	else if(strcmp(argv[1], "details") == 0 && argc == 3) {

		if(validInt(argv[2]) == true) {
 			if(stoi(argv[2]) <= (int) (myPersonal.size() + myClass.size()) && stoi(argv[2]) > 0 ) {
				cout << "show details" << endl;

				// FUNTION NEEDED:
				// showDetails(stoi(argv([2]);

				//saveDeadlines();
                        }
                        else {
                                cout << "Not a valid deadline" << endl;
                        }
                }
                else{
                        cout << "Invalid arguements" << endl;
                }
	}

	//command line: deadline edit #
	else if(strcmp(argv[1], "edit") == 0 && argc == 3) {

		 if(validInt(argv[2]) == true) {
                        if(stoi(argv[2]) <= (int) myClass.size() && stoi(argv[2]) > 0) {
				string name;
				for(auto const& pair: myClass[stoi(argv[2])-1] ) {
                                        name = pair.first;
                                }
				cout << "You cannot edit " << '"' << name << '"' << " since you are not the owner (jeckroth is)." << endl;
			}
			else if(stoi(argv[2]) > (int) myClass.size() && stoi(argv[2]) <= (int) (myClass.size() + myPersonal.size()) && stoi(argv[2]) > 0) {
                                cout << "edit deadline " << stoi(argv[2]) << endl;

				//FUNCTION NEEDED:
				// editDeadline(stoi(argv[2]);

				//saveDeadlines();
			}
                        else {
                                cout << "Not a valid deadline" << endl;
                        }
                }
                else{
                        cout << "Invalid arguements" << endl;
                }
	}

	//command line: invalid arguements
	else {
		cout << "Invalid arguement" << endl;
	}
}


#pragma once

#include <Eigen/Core>
#include <cassert>
#include <vector>

namespace boltzmann {
// homogeneous
namespace hg {
template <typename NUMERIC_T = double>
class RK4
{
 public:
  typedef NUMERIC_T numeric_t;

 private:
  typedef Eigen::VectorXd vec_t;

 public:
  RK4(int N_)
      : N(N_)
      , v_k1(N_)
      , v_k2(N_)
      , v_k3(N_)
      , v_k4(N_)
  { /* empty */
  }

  void apply(NUMERIC_T* dst,
             const NUMERIC_T* src,
             const std::function<void(NUMERIC_T* dst, const NUMERIC_T* src)>& f,
             double dt);

 private:
  int N;
  vec_t v_k1;
  vec_t v_k2;
  vec_t v_k3;
  vec_t v_k4;
};

// ----------------------------------------------------------------------
template <typename NUMERIC_T>
void
RK4<NUMERIC_T>::apply(NUMERIC_T* dst,
                      const NUMERIC_T* src,
                      const std::function<void(NUMERIC_T* dst, const NUMERIC_T* src)>& f,
                      double dt)
{
  assert(src != dst);

  typedef Eigen::Map<vec_t> map_vec_t;
  typedef Eigen::Map<const vec_t> map_const_vec_t;

  map_const_vec_t v_in(src, N);
  map_vec_t v_dst(dst, N);

  f(v_k1.data(), v_in.data());

  v_dst = v_in + 0.5 * dt * v_k1;
  f(v_k2.data(), v_dst.data());

  v_dst = v_in + 0.5 * dt * v_k2;
  f(v_k3.data(), v_dst.data());

  v_dst = v_in + dt * v_k3;
  f(v_k4.data(), v_dst.data());

  v_dst = v_in + dt / 6 * (v_k1 + 2 * v_k2 + 2 * v_k3 + v_k4);
}

}  // end hg
}  // end boltzmann


/*
 *  VSCSoundParameters.cpp
 *  SynthStation
 *
 *  Created by Jonathan Thorpe on 12/11/2011.
 *  Copyright 2011 NXP. All rights reserved.
 *
 */

#include "VSCSoundParameters.h"
#include "VSCException.h"
#include "VSCMath.h"

#include <map>
#include <string>
#include <boost/lexical_cast.hpp>

const unsigned int VSCSParameter::kChannelNotFound = kVSCSVoidChannel;
const unsigned int VSCSParameter::kIndexAll = UINT_MAX;

bool VSCSParameter::Key::operator<(const Key& otherKey) const {
	if (domain < otherKey.domain) return true;
	else return false;
	if (code < otherKey.code) return true;
	else return false;
	if (index < otherKey.index) return true;
	return false;
}

bool VSCSParameter::Key::operator==(const Key& otherKey) const {
	if (domain != otherKey.domain) return false;
	if (code != otherKey.code) return false;
	if (index != otherKey.index) return false;
	return true;
}

VSCSParameter::VSCSParameter() {

}

VSCSParameter::~VSCSParameter() {

}

VSCSParameter& VSCSParameter::sharedInstance(void) {
	static VSCSParameter singletonInstance;
	return singletonInstance;
}

/*
 *	This should convert so that linear volume gain [0 : 1] becomes [-infinity : 0]
 */
VSCSFloat VSCSParameter::linearToDB(VSCSFloat linear) {
	VSCSFloat dB = 10.0*std::log10(linear);
    return  dB;
}

VSCSFloat VSCSParameter::dBToLinear(VSCSFloat dB) {
    return std::pow(10.0, 0.1*dB);
}

/*
 *	Parameter labels
 */
std::string VSCSParameter::getLabelForParameterWithKey(VSCSParameter::Key k) {

	/*
	 *	Check if there are exact matches
	 */
	KeyLabelMap::iterator labelIterator = customizedKeyLabels.find(k);
	if (labelIterator != customizedKeyLabels.end())
		return labelIterator->second;

	switch (k.code) {
		case CodeGain:
			return "Gain (Lin)";
		case CodeDBGain:
			return "Gain (dB)";
		case CodeFrequency:
			return "Freq (Hz)";
		case CodeLogFrequency:
			return "Freq (Log Hz)";
//		case CodeDegPhase:
//			return "Phase (deg)";
		case CodeRadPhase:
			return "Phase (rad)";
		case CodeHarmonics:
			return "Harmonics";
		default:
			return "";
			break;
	}

	throw VSCSBadParameterException();

}

void VSCSParameter::setLabelForParameterWithKey(std::string label, Key k) {

	KeyLabelMap::iterator labelIterator = customizedKeyLabels.find(k);
	if (labelIterator != customizedKeyLabels.end())
		customizedKeyLabels.erase(k);

	customizedKeyLabels.insert(KeyLabelPair (k, label));

}

void VSCSParameter::revertLabelForParameterWithKeyToDefault(Key k) {

	KeyLabelMap::iterator labelIterator = customizedKeyLabels.find(k);
	if (labelIterator != customizedKeyLabels.end())
		customizedKeyLabels.erase(k);

}

VSCSParameter::ValueRange VSCSParameter::getRangeForParameterWithKey(Key k) {

	KeyRangeMap::iterator rangeIterator = customizedKeyRanges.find(k);
	if (rangeIterator != customizedKeyRanges.end())
		return rangeIterator->second;

	switch (k.code) {
		case CodeGain:
			return ValueRange (0.0, 1.0);
		case CodeDBGain:
			return ValueRange (-30.0, 0.0);
		case CodeFrequency:
			return ValueRange (20.0, 20000.0);
		case CodeLogFrequency:
			return ValueRange (std::log10(20.0), std::log10(20000.0));
//		case CodeDegPhase:
//			return ValueRange (-180.0, 180.0);
		case CodeRadPhase:
			return ValueRange (-vsc::kPI, vsc::kPI);
		case CodeHarmonics:
			return ValueRange (0.0, 10.0);
		default:
			return ValueRange (0.0, 0.0);
			break;
	}

	throw VSCSBadParameterException();

}

void VSCSParameter::setRangeForParameterWithKey(ValueRange valRange, Key k) {

	KeyRangeMap::iterator rangeIterator = customizedKeyRanges.find(k);
	if (rangeIterator != customizedKeyRanges.end())
		customizedKeyRanges.erase(k);

	customizedKeyRanges.insert(KeyRangePair (k, valRange));

}

void VSCSParameter::revertRangeForParameterWithKeyToDefault(Key k) {

	KeyRangeMap::iterator rangeIterator = customizedKeyRanges.find(k);
	if (rangeIterator != customizedKeyRanges.end())
		customizedKeyRanges.erase(k);

}


//==================================================================================================
/*!
    @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_ARCH_X86_SSE2_SIMD_FUNCTION_ILOGB_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_SSE2_SIMD_FUNCTION_ILOGB_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

#include <boost/simd/function/simd/bitwise_and.hpp>
#include <boost/simd/function/simd/bitwise_cast.hpp>
#include <boost/simd/function/simd/dec.hpp>
#include <boost/simd/function/simd/exponent.hpp>
#include <boost/simd/function/simd/group.hpp>
#include <boost/simd/function/simd/seladd.hpp>
#include <boost/simd/function/simd/split.hpp>
#include <boost/simd/function/simd/if_else.hpp>
#include <boost/simd/function/simd/if_zero_else.hpp>
#include <boost/simd/function/simd/is_eqz.hpp>
#include <boost/simd/function/simd/is_lez.hpp>
#include <boost/simd/function/simd/is_nez.hpp>
#include <boost/simd/function/simd/minus.hpp>
#include <boost/simd/function/simd/plus.hpp>
#include <boost/simd/function/simd/tofloat.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/constant.hpp>
#include <boost/simd/constant/zero.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd =  boost::dispatch;
  namespace bs =  boost::simd;
  BOOST_DISPATCH_OVERLOAD ( ilogb_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::int_<A0>, bs::sse_>
                         )
  {
    using result = bd::as_integer_t<A0>;
    BOOST_FORCEINLINE result operator() ( const A0 & a0)
    {
      using vtype = bd::as_integer_t<A0,unsigned>;
      return simd::bitwise_cast<A0>(if_zero_else( is_lez(a0)
                                                , ilogb(simd::bitwise_cast<vtype>(a0))
                                                )
                                   );
    }
  };
  BOOST_DISPATCH_OVERLOAD ( ilogb_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::uint8_<A0>, bs::sse_>
                          )
  {

    BOOST_FORCEINLINE A0 operator() ( const A0 & a0)
    {
#define MKN8 simd::bitwise_cast<A0>
      const A0 mask =  Constant<A0, 0x7f>();
      A0 n = a0;
      A0 i = One<A0>();
      n = bitwise_and(MKN8(_mm_srli_epi16(n, 1)), mask);
      i = seladd(is_nez(n), i, One<A0>());
      n = bitwise_and(MKN8(_mm_srli_epi16(n, 1)), mask);
      i = seladd(is_nez(n), i, One<A0>());
      n = bitwise_and(MKN8(_mm_srli_epi16(n, 1)), mask);
      i = seladd(is_nez(n), i, One<A0>());
      n = bitwise_and(MKN8(_mm_srli_epi16(n, 1)), mask);
      i = seladd(is_nez(n), i, One<A0>());
      n = bitwise_and(MKN8(_mm_srli_epi16(n, 1)), mask);
      i = seladd(is_nez(n), i, One<A0>());
      n = bitwise_and(MKN8(_mm_srli_epi16(n, 1)), mask);
      i = seladd(is_nez(n), i, One<A0>());
      n = bitwise_and(MKN8(_mm_srli_epi16(n, 1)), mask);
      i = seladd(is_nez(n), i, One<A0>());
      return dec(i);
    }
#undef MKN8
  };

  BOOST_DISPATCH_OVERLOAD ( ilogb_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::uint16_<A0>, bs::sse_>
                          )
  {
    using result = bd::as_integer_t<A0>;
    BOOST_FORCEINLINE result operator() ( const A0 & a0)
    {
      using up_t = detail::make_dependent_t<int32_t,A0>;
      using gen_t = pack<up_t, 32>;//this is not correct TODO using new upgrade possibilities
      gen_t a0h, a0l;
      split(a0, a0l, a0h);
      return group(ilogb(a0l), ilogb(a0h));
    }
  };
  BOOST_DISPATCH_OVERLOAD ( ilogb_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::unsigned_<A0>, bs::sse_>
                          )
  {
    using result = bd::as_integer_t<A0>;
    BOOST_FORCEINLINE result operator() ( const A0 & a0)
    {
      return simd::bitwise_cast<A0>(bs::exponent(tofloat(a0)));
    }
  };
} } }

#endif


#include <iostream>
#include <iterator>
#include <sstream>
#include <iomanip>
#include <map>

#include <boost/program_options.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/map.hpp>
#include <boost/mpi.hpp>

#include <benchmark.hpp>
#include <logging.hpp>

#include <constants.hpp>
#include <lapack.hpp>
#include <matrix_serialization.hpp>
#include <dielectric_function_v2.hpp>

namespace po  = boost::program_options;
namespace mpi = boost::mpi;

using R = double;

#ifdef ASSUME_REAL
using C = R;
#else
using C = std::complex<R>;
#endif

///////////////////////////////////////////////////////////////////////////////
/// \brief Returns the rank of the admin process.
///////////////////////////////////////////////////////////////////////////////
constexpr auto admin_rank() -> int { return 0; }

// ============================================================================
//                                   SETTINGS
// ============================================================================

///////////////////////////////////////////////////////////////////////////////
/// \brief Class representing the configurations options.

/// It is a small wrapper around boost::program_options, which read the
/// options from command line during construction.
///////////////////////////////////////////////////////////////////////////////

auto init_options() -> po::options_description
{
	po::options_description description;
	description.add_options()
		( "help", "Produce the help message." )
		( "out.file.log"
		, po::value<std::string>()->default_value("sample")
		, "Name of the file that will be used for logging. This is "
		  "not the full name, but rather a 'base'. The actual file name "
		  "will be \"[log-file].[PROCESS_RANK].log\"." )
		( "out.file.eps"
		, po::value<std::string>()->default_value("Epsilon")
		, "Name of the file that will be used for saving the computed "
		  "dielectric functions. This is, again, a base rather than the "
		  "actual name. The actual file name will be "
		  "\"[eps-file].[PROCESS_RANK].bin\", which indicates that "
		  "dielectric functions will be stored in binary format of the "
		  "boost::serialization library." )
		( "in.file.energies"
		, po::value<std::string>()->required()
		, "Name of the BIN file where the eigenenergies of the hamiltonian"
		  " are read from. This file must be in the format of the "
		  "boost::serialization library. This option is REQUIRED." )
		( "in.file.states"
		, po::value<std::string>()->required()
		, "Name of the BIN file where the eigenstates of the hamiltonian "
		  "are read from. This file must be in the format of the "
		  "boost::serialization library. This option is REQUIRED." )
		( "in.file.potential"
		, po::value<std::string>()->required()
		, "Name of the BIN file where the interaction potential "
		  "is read from. This file must be in the format of the "
		  "boost::serialization library. This option is REQUIRED." )
		( "in.frequency.start"
		, po::value<R>()->required()
		, "Starting frequency in eV. Must be a real value. This option "
		  "is REQUIRED." )
		( "in.frequency.stop"
		, po::value<R>()->required()
		, "Stopping frequency in eV. Must be a real value. This option "
		  "is REQUIRED." )
		( "in.frequency.step"
		, po::value<R>()->required()
		, "Step in frequency in eV. Must be a real value." );
	description.add(tcm::init_constants_options<double>());
	return description;
}

template <class _Help, class _Proceed>
auto parse_command_line( int argc, char** argv
                       , _Help&& help
                       , _Proceed&& proceed ) -> bool
{
	auto const desc = init_options();
	po::variables_map vm;

	po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);

	if (vm.count("help")) {
		help(desc);
		return false;
	}

	po::notify(vm);
	proceed(vm);
	return true;
}

auto log_file_name(int const rank, std::string file_name_base)
{
	return file_name_base + "." + std::to_string(rank) + ".log";
}

auto initialize_logging( int const rank
                       , std::string const& file_name_base) -> void
{
	using namespace boost::log;
	register_simple_formatter_factory<tcm::severity_level, char>("Severity");
	add_file_log
	(
		keywords::file_name = log_file_name(rank, file_name_base),
		keywords::format = "%LineID%: [%TimeStamp%] [%Severity%] %Message%",
		keywords::auto_flush = true
	);

	add_common_attributes();
}

template <class _R, class _C>
struct IPackage {
	std::tuple<_R, _R, _R>                frequency_range;
	std::string                           log_file_name_base;
	std::string                           eps_file_name_base;
	tcm::Matrix<_R>                       E;
	tcm::Matrix<_C>                       Psi;
	tcm::Matrix<std::complex<_R>>         V;
	std::map<std::string, _R>             constants;

private:
	friend boost::serialization::access;

	template<class _Archive>
	auto save(_Archive & ar, unsigned int const version) const -> void
	{
		ar << std::get<0>(frequency_range)
		   << std::get<1>(frequency_range)
		   << std::get<2>(frequency_range)
		   << log_file_name_base
		   << eps_file_name_base
		   << E
		   << Psi
		   << V
		   << constants;
	}

	template<class _Archive>
	auto load(_Archive & ar, unsigned int const version) -> void
	{
		ar >> std::get<0>(frequency_range)
		   >> std::get<1>(frequency_range)
		   >> std::get<2>(frequency_range)
		   >> log_file_name_base
		   >> eps_file_name_base
		   >> E
		   >> Psi
		   >> V
		   >> constants;
	}

	BOOST_SERIALIZATION_SPLIT_MEMBER()
};

template<class _T>
auto load_matrix(std::string const& file_name) -> tcm::Matrix<_T>
{
	std::ifstream in_stream{file_name};
	if(not in_stream)
		throw std::runtime_error{"Failed to open `" + file_name + "`."};
	boost::archive::binary_iarchive in_archive{in_stream};

	tcm::Matrix<_T> A;
	in_archive >> A;
	return A;
}

auto load_ipackage(po::variables_map const& vm) -> IPackage<R, C>
{
	return { std::make_tuple( vm["in.frequency.start"].as<R>()
	                        , vm["in.frequency.stop"].as<R>()
	                        , vm["in.frequency.step"].as<R>() )
	       , vm["out.file.log"].as<std::string>()
	       , vm["out.file.eps"].as<std::string>()
	       , load_matrix<R>(vm["in.file.energies"].as<std::string>())
	       , load_matrix<C>(vm["in.file.states"].as<std::string>())
	       , load_matrix<std::complex<R>>(vm["in.file.potential"].as<std::string>())
		   , tcm::load_constants<R, double, std::map<std::string, R>>(vm)
		   };
}

template <class _T, class _Logger>
auto cache( std::string const& message
          , tcm::Matrix<_T> const& X
		  , std::string const& file_name
          , _Logger & lg ) -> void
{
	LOG(lg, info) << "Caching: " << message << "...";

	std::ofstream out_stream{file_name};
	if(not out_stream)
		throw std::runtime_error{"Could not open `" + file_name + "`."};
	boost::archive::binary_oarchive out_archive{out_stream};
	out_archive << X;

	LOG(lg, info) << "Caching successfully finished.";
}

template<class _Real, class _Logger>
auto get_job( mpi::communicator const& world
            , std::tuple<_Real, _Real, _Real> const& range
			, _Logger & lg ) -> std::vector<_Real>
{
	LOG(lg, info) << "Calculating homework...";

	auto const  rank  = world.rank();
	auto const  size  = world.size();
	auto const& begin = std::get<0>(range);
	auto const& end   = std::get<1>(range);
	auto const& step  = std::get<2>(range);

	std::vector<_Real> homework;
	for(auto i = 0; begin + i * step <= end; ++i) {
		if(i % size == rank)
			homework.push_back(begin + i * step);
	}

	auto record = lg.open_record(boost::log::keywords::severity =
	                                 tcm::severity_level::info);
	if (record) {
		boost::log::record_ostream stream{record};
		stream << "Need to perform calculations for the following "
			   << "frequencies: {";
		if(not homework.empty()) {
			for(std::size_t i = 0; i < homework.size() - 1; ++i)
				stream << homework[i] << ", ";
			stream << homework.back();
		}
		stream << "}";
		stream.flush();
		lg.push_record(std::move(record));
	}

	return homework;
}

template<class _Number, class _R, class _C, class _Logger>
auto calculate_single( _Number const omega
                     , tcm::Matrix<_R> const& E
					 , tcm::Matrix<_C> const& Psi
					 , tcm::Matrix<std::complex<_R>> const& V
					 , std::map<std::string, _R> const& cs
                     , _Logger & lg
					 , std::string const& file_name_base ) -> void
{
	using namespace std::complex_literals;
	LOG(lg, info) << "Calculating dielectric function for omega = "
	              << omega << "...";

	auto const file_name_matrix =
		file_name_base + "." + std::to_string(std::real(omega))
		+ ".matrix.bin";
	auto const file_name_eigenvalues =
		file_name_base + "." + std::to_string(std::real(omega))
		+ ".eigenvalues.bin";
	auto const file_name_eigenstates =
		file_name_base + "." + std::to_string(std::real(omega))
		+ ".eigenstates.bin";

	auto epsilon = tcm::dielectric_function::make(omega, E, Psi, V, cs, lg);
	cache("Dielectric function matrix", epsilon, file_name_matrix, lg);

	LOG(lg, info) << "Diagonalizing dielectric function for omega = "
	              << omega << "...";

	using epsilon_type = typename decltype(epsilon)::value_type;
	tcm::Matrix<epsilon_type> W{epsilon.height(), 1};
	tcm::Matrix<epsilon_type> Z{epsilon.height(), epsilon.height()};
	tcm::lapack::geev(epsilon, W, Z);

	cache("Dielectric function eigenvalues", W, file_name_eigenvalues, lg);
	cache("Dielectric function eigenstates", Z, file_name_eigenstates, lg);

	LOG(lg, info) << "Done for omega = " << omega << "!";
}

auto run( mpi::communicator & world
        , IPackage<R, C> & input ) -> void
{
	mpi::broadcast(world, input, admin_rank());

	initialize_logging(world.rank(), input.log_file_name_base);
	boost::log::sources::severity_logger<tcm::severity_level> lg;

	auto const homework = get_job<R>(world, input.frequency_range, lg);
	if (homework.empty())
		return;
	for(auto const& w : homework) {
		calculate_single( std::complex<R>{w, input.constants.at("tau")}
		                , input.E
						, input.Psi
						, input.V
						, input.constants
						, lg
						, input.eps_file_name_base );
	}

	auto record = lg.open_record(boost::log::keywords::severity =
	                                 tcm::severity_level::info);
	if (record) {
		boost::log::record_ostream stream{record};
		stream << "Timings:\n";
		tcm::timing::report(stream);
		stream.flush();
		lg.push_record(std::move(record));
	}
}

int main(int argc, char** argv)
{
	mpi::environment env;
	mpi::communicator world;

	IPackage<R, C> input;
	bool proceed_with_calculation;
	if (world.rank() == admin_rank()) {
		proceed_with_calculation =
			parse_command_line( argc, argv
			                  , [&env] (auto _desc) { std::cout << _desc << '\n'; }
			                  , [&input] (auto _vm) { input = load_ipackage(_vm); } );
	}

	mpi::broadcast(world, proceed_with_calculation, admin_rank());
	if(not proceed_with_calculation)
		return EXIT_SUCCESS;

	run(world, input);

	return EXIT_SUCCESS;
}


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2019 Tinko Bartels, Berlin, Germany.

// 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)

#ifndef BOOST_GEOMETRY_EXTENSIONS_TRIANGULATION_ALGORITHMS_DELAUNAY_TRIANGULATION_HPP
#define BOOST_GEOMETRY_EXTENSIONS_TRIANGULATION_ALGORITHMS_DELAUNAY_TRIANGULATION_HPP

#include <vector>
#include <utility>
#include <tuple>
#include <iterator>
#include <algorithm>

#include <boost/geometry/core/coordinate_dimension.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/tag.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/algorithms/make.hpp>
#include <boost/geometry/algorithms/comparable_distance.hpp>
#include <boost/geometry/extensions/triangulation/geometries/triangulation.hpp>
#include <boost/geometry/extensions/triangulation/strategies/cartesian/in_circle_robust.hpp>
#include <boost/geometry/extensions/triangulation/strategies/delaunay_triangulation.hpp>
#include <boost/geometry/extensions/triangulation/strategies/cartesian/accelerated_shull.hpp>
#include <boost/geometry/strategies/cartesian/side_by_triangle.hpp>

namespace boost { namespace geometry
{

template
<
    typename PointContainer,
    typename Triangulation,
    typename SideStrategy = strategy::side::side_by_triangle<>,
    typename InCircleStrategy = strategy::in_circle::in_circle_robust<>,
    typename ConstructionStrategy =
        typename strategy::delaunay_triangulation::services::default_strategy
            <
                typename tag<typename PointContainer::value_type>::type,
                typename cs_tag<typename PointContainer::value_type>::type,
                dimension<typename PointContainer::value_type>::value
            >::type
>
inline void delaunay_triangulation(PointContainer const & in,
                                   Triangulation& out,
                                   bool legalize = true)
{
    ConstructionStrategy::template apply
        <
            PointContainer,
            Triangulation,
            SideStrategy,
            InCircleStrategy
        >(in, out, legalize);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_EXTENSIONS_TRIANGULATION_ALGORITHMS_DELAUNAY_TRIANGULATION_HPP