#include "ublas_ser.hpp"
#include <boost/test/minimal.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <sstream>
#include <string>
#include <boost/range.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/logical.hpp>
//#include <algorithm>
//#include <limits>
//#include <boost/test/floating_point_comparison.hpp>
//#include <boost/test/test_tools.hpp>

namespace ublas = boost::numeric::ublas;

template<typename V>
std::string Saveit (V const& v) {
  std::ostringstream os;
  boost::archive::binary_oarchive oa (os);
  oa << v;
  return os.str();
}

template<typename V>
V Loadit (std::string const& s) {
  V v;
  std::istringstream is (s);
  boost::archive::binary_iarchive ia (is);
  ia >> v;
  return v;
}

// template<typename T>
// struct Scalar {
//   typedef T type;
// };

// template<typename T>
// struct Scalar<std::complex<T> > {
//   typedef T type;
// };

template<typename T>
struct is_cmplx : public boost::false_type {};

template<typename T>
struct is_cmplx<std::complex<T> > : public boost::true_type {};

//! both args are integral
template<typename a_t, typename b_t>
//bool is_close_scalar (a_t a, b_t b, const boost::true_type&) {
bool is_close_scalar (a_t a, b_t b, const boost::mpl::true_&) {
  return a == b;
}

//! at least one arg is not integral
template<typename a_t, typename b_t>
//bool is_close_scalar (a_t a, a_t b, const boost::false_type&) {
bool is_close_scalar (a_t a, b_t b, const boost::mpl::false_&) {
  return std::abs (a - b) < 1e-6;
}



//! scalar
template<typename a_t, typename b_t>
//inline bool is_close_2 (a_t a, b_t b, const boost::false_type&) {
inline bool is_close_2 (a_t a, b_t b, const boost::mpl::false_&) {
  return is_close_scalar (a, b, typename boost::mpl::and_<boost::is_integral<a_t>,boost::is_integral<b_t> >::type());
}

//! complex
/*!
  not using std::abs so this will also work on complex<int>
*/
template<typename a_t, typename b_t>
//inline bool is_close_2 (a_t a, b_t b, const boost::true_type&) {
inline bool is_close_2 (a_t a, b_t b, const boost::mpl::true_&) {
  BOOST_STATIC_ASSERT (is_cmplx<a_t>::value && is_cmplx<b_t>::value);
  return is_close_2 (real(a), real(b), boost::false_type()) &&
    is_close_2 (imag(a), imag(b), boost::false_type());
}

//! Works for integral, float, or complex
template<typename a_t, typename b_t>
inline bool is_close (a_t a, b_t b) {
  return is_close_2 (a, b, typename boost::mpl::or_<is_cmplx<a_t>,is_cmplx<b_t> >::type());
}

//! 2 sequences are close if they are same length and elements compare close
template<typename a_t, typename b_t>
inline bool is_close_seq (a_t const& a, b_t const& b) {
  if (boost::size (a) != boost::size (b))
    return false;
  typename boost::range_const_iterator<a_t>::type i = boost::begin (a);
  typename boost::range_const_iterator<b_t>::type j = boost::begin (b);
  for (; i != boost::end (a); ++i, ++j)
    if (!is_close (*i, *j))
      return false;

  return true;
}

template<typename a_t, typename b_t>
void check_close_seq (a_t const& a, b_t const&b) {
  BOOST_CHECK (is_close_seq (a, b));
}

// template<typename a_t, typename b_t>
// inline bool equal (a_t const& a, b_t const& b) {
//   return std::equal (boost::begin (a), boost::end (a), boost::begin (b));
// }

template<typename T>
void run_check() {
  ublas::vector<T> a (10);
  for (int i = 0; i < a.size(); ++i)
    a[i] = i;

  std::string s = Saveit (a);

  ublas::vector<T> b = Loadit<ublas::vector<T> > (s);
  check_close_seq (a, b);
}

int test_main( int, char *[] ) {
  run_check<int>();
  run_check<double>();
  run_check<std::complex<double> >();

  exit (0);
}
