// Copyright (C) 2018 Andrzej Krzemienski.
//
// 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)
//
// See http://www.boost.org/lib/optional for documentation.
//
// You are welcome to contact the author at:
//  akrzemi1@gmail.com

#include "boost/optional/optional.hpp"

#ifdef BOOST_BORLANDC
#pragma hdrstop
#endif

#include "boost/core/ignore_unused.hpp"
#include "boost/core/lightweight_test.hpp"
#include "boost/core/lightweight_test_trait.hpp"
#include "boost/type_traits/is_same.hpp"


using boost::optional;
using boost::make_optional;
using boost::is_same;

template <typename Expected, typename Deduced>
void verify_type(Deduced)
{
  BOOST_TEST_TRAIT_TRUE(( is_same<Expected, Deduced> ));
}

struct Int
{
  int i;
  explicit Int(int i_) : i(i_) {}
};

struct convert_t
{
  typedef optional<Int> result_type;
  optional<Int> operator()(int i) { if (i != 0) return Int(i); else return boost::none; }
};

void test_flat_map_on_mutable_optional_with_function_object()
{
  {
    optional<int> oi (1);
    verify_type< optional<Int> >(oi.flat_map(convert_t()));
    optional<Int> oI = oi.flat_map(convert_t());
    BOOST_TEST(bool(oI));
    BOOST_TEST_EQ(1, oI->i);
  }
  {
    optional<int> oi (0);
    optional<Int> oI = oi.flat_map(convert_t());
    BOOST_TEST(!oI);
  }
  {
    optional<int> oi;
    optional<Int> oI = oi.flat_map(convert_t());
    BOOST_TEST(!oI);
  }
}

void test_flat_map_on_const_optional_with_function_object()
{
  {
    const optional<int> oi (1);
    verify_type< optional<Int> >(oi.flat_map(convert_t()));
    optional<Int> oI = oi.flat_map(convert_t());
    BOOST_TEST(bool(oI));
    BOOST_TEST_EQ(1, oI->i);
  }
  {
    const optional<int> oi (0);
    optional<Int> oI = oi.flat_map(convert_t());
    BOOST_TEST(!oI);
  }
  {
    const optional<int> oi;
    optional<Int> oI = oi.flat_map(convert_t());
    BOOST_TEST(!oI);
  }
}

void test_flat_map_with_lambda()
{
#if !defined BOOST_NO_CXX11_LAMBDAS && !defined BOOST_NO_CXX11_DECLTYPE_N3276
  {
    optional<int> oi (1);
    verify_type< optional<Int> >(oi.flat_map([](int i){ return optional<Int>(i == 0, Int(i)); }));
    optional<Int> oI = oi.flat_map([](int i){ return optional<Int>(i != 0, Int(i)); });
    BOOST_TEST(bool(oI));
    BOOST_TEST_EQ(1, oI->i);
  }
  {
    optional<int> oi (0);
    optional<Int> oI = oi.flat_map([](int i){ return optional<Int>(i != 0, Int(i)); });
    BOOST_TEST(!oI);
  }
  {
    optional<int> oi;
    optional<Int> oI = oi.flat_map([](int i){ return optional<Int>(i != 0, Int(i)); });
    BOOST_TEST(!oI);
  }
#endif // lambdas
}

struct get_opt_ref
{
  typedef optional<int&> result_type;
  optional<int&> operator()(int& i) { return i != 0 ? optional<int&>(i) : optional<int&>(); }
};

void test_flat_map_obj_to_ref()
{
  {
    optional<int> oi (2);
    verify_type< optional<int&> >(oi.flat_map(get_opt_ref()));
    optional<int&> ori = oi.flat_map(get_opt_ref());
    BOOST_TEST(bool(ori));
    BOOST_TEST_EQ(2, *ori);
    *ori = 3;
    BOOST_TEST(bool(oi));
    BOOST_TEST_EQ(3, *oi);
    BOOST_TEST_EQ(3, *ori);
  }
  {
    optional<int> oi (0);
    optional<int&> ori = oi.flat_map(get_opt_ref());
    BOOST_TEST(!ori);
  }
  {
    optional<int> oi;
    optional<int&> ori = oi.flat_map(get_opt_ref());
    BOOST_TEST(!ori);
  }
}

optional<int&> get_opt_int_ref(Int& i)
{
  return i.i ? optional<int&>(i.i) : optional<int&>();
}

void test_flat_map_ref_to_ref()
{
  {
    Int I (5);
    optional<Int&> orI (I);
    verify_type< optional<int&> >(orI.flat_map(get_opt_int_ref));
    optional<int&> ori = orI.flat_map(get_opt_int_ref);
    BOOST_TEST(bool(ori));
    BOOST_TEST_EQ(5, *ori);
    *ori = 6;
    BOOST_TEST_EQ(6, *ori);
    BOOST_TEST_EQ(6, I.i);
  }
  {
    Int I (0);
    optional<Int&> orI (I);
    optional<int&> ori = orI.flat_map(get_opt_int_ref);
    BOOST_TEST(!ori);
  }
  {
    optional<Int&> orI;
    optional<int&> ori = orI.flat_map(get_opt_int_ref);
    BOOST_TEST(!ori);
  }
}

optional< optional<Int> > make_opt_int(int i)
{
  if (i == 0)
    return boost::none;
  else if (i == 1)
    return boost::make_optional(optional<Int>());
  else
    return boost::make_optional(boost::make_optional(Int(i)));
}

void test_flat_map_opt_opt()
{
  {
    optional<int> oi (9);
    verify_type<optional<optional<Int> > >(oi.flat_map(make_opt_int));
    optional<optional<Int> > ooI = oi.flat_map(make_opt_int);
    BOOST_TEST(bool(ooI));
    BOOST_TEST(bool(*ooI));
    BOOST_TEST_EQ(9, (**ooI).i);
  }
  {
    optional<int> oi (1);
    optional<optional<Int> > ooI = oi.flat_map(make_opt_int);
    BOOST_TEST(bool(ooI));
    BOOST_TEST(!*ooI);
  }
  {
    optional<int> oi (0);
    optional<optional<Int> > ooI = oi.flat_map(make_opt_int);
    BOOST_TEST(!ooI);
  }
  {
    optional<int> oi;
    optional<optional<Int> > ooI = oi.flat_map(make_opt_int);
    BOOST_TEST(!ooI);
  }
}

#if (!defined BOOST_NO_CXX11_REF_QUALIFIERS) && (!defined BOOST_OPTIONAL_DETAIL_NO_RVALUE_REFERENCES)
struct MoveOnly
{
  int value;
  explicit MoveOnly(int i) : value(i) {}
  MoveOnly(MoveOnly && r) : value(r.value) { r.value = 0; }
  MoveOnly& operator=(MoveOnly && r) { value = r.value; r.value = 0; return *this; }

private:
  MoveOnly(MoveOnly const&);
  void operator=(MoveOnly const&);
};

MoveOnly makeMoveOnly(int i)
{
  return MoveOnly(i);
}

optional<MoveOnly> makeOptMoveOnly(int i)
{
  return optional<MoveOnly>(MoveOnly(i));
}

optional<int> get_val(MoveOnly m)
{
  return optional<int>(m.value != 0, m.value);
}

void test_flat_map_move_only()
{
  {
    optional<MoveOnly> om (makeMoveOnly(1)), om2 (makeMoveOnly(2));
    verify_type<optional<int> >(boost::move(om).flat_map(get_val));
    optional<int> oi = boost::move(om2).flat_map(get_val);
    BOOST_TEST(bool(oi));
    BOOST_TEST_EQ(2, *oi);
  }
  {
    optional<int> oj = makeOptMoveOnly(4).flat_map(get_val);
    BOOST_TEST(bool(oj));
    BOOST_TEST_EQ(4, *oj);
  }
  {
    optional<int> oj = optional<MoveOnly>().flat_map(get_val);
    BOOST_TEST(!oj);
  }
}

#endif // no rvalue refs

int main()
{
  test_flat_map_on_mutable_optional_with_function_object();
  test_flat_map_on_const_optional_with_function_object();
  test_flat_map_with_lambda();
  test_flat_map_obj_to_ref();
  test_flat_map_ref_to_ref();
  test_flat_map_opt_opt();

#if (!defined BOOST_NO_CXX11_REF_QUALIFIERS) && (!defined BOOST_OPTIONAL_DETAIL_NO_RVALUE_REFERENCES)
  test_flat_map_move_only();
#endif

  return boost::report_errors();
}
