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

// Copyright (c) 2014, Oracle and/or its affiliates.

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_TEST_MODULE
#define BOOST_TEST_MODULE test_concatenate_iterator
#endif

#include <boost/test/included/unit_test.hpp>

#include <cstddef>
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
#include <iterator>

#include <vector>
#include <list>

#include <boost/assign/std/vector.hpp>
#include <boost/assign/std/list.hpp>

#include <boost/core/ignore_unused.hpp>

#include "test_iterator_common.hpp"

#include <boost/geometry/iterators/concatenate_iterator.hpp>

using namespace boost::assign;


struct test_concatenate_iterator
{
    template
    <
        typename ConcatenateIterator,
        typename ConstConcatenateIterator,
        typename Container
    >
    static inline
    void test_using_max_element(ConcatenateIterator first,
                                ConcatenateIterator beyond,
                                ConstConcatenateIterator const_first,
                                ConstConcatenateIterator const_beyond,
                                Container const& c,
                                std::size_t other_size,
                                bool second_container)
    {
        typedef typename std::iterator_traits
            <
                ConcatenateIterator
            >::value_type value_type;

        if ( c.size() == 0 )
        {
            return;
        }
        
        ConcatenateIterator c_first = first;
        if ( second_container )
        {
            std::size_t counter(0);
            while ( counter != other_size )
            {
                ++counter;
                c_first++;
            }
        }

        ConcatenateIterator it_max = std::max_element(first, beyond);
        ConstConcatenateIterator const_it_max =
            std::max_element(const_first, const_beyond);

        BOOST_CHECK( it_max == const_it_max );
        BOOST_CHECK( *it_max == *const_it_max );

        value_type old_value = *c_first;
        value_type new_value = *it_max + 1;

        *c_first = *it_max + 1;
        BOOST_CHECK( *c.begin() == new_value );

#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::cout << std::endl;
        std::cout << "modified element of ";
        std::cout << (second_container ? "2nd" : "1st");
        std::cout << " container:" << std::endl;
        print_container(std::cout, c.begin(), c.end(),
                        (second_container ? "second  :" : "first  :"))
            << std::endl;
        print_container(std::cout, first, beyond, "combined:") << std::endl;
#endif

        *c_first = old_value;
        BOOST_CHECK( *c.begin() == old_value );
    }


    template <typename Container1, typename Container2>
    static inline void apply(Container1& c1, Container2& c2,
                             std::string const& case_id,
                             std::string const& containers_id)
    {
        boost::ignore_unused(case_id, containers_id);

#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::stringstream sstream;
        sstream << case_id << " [" << containers_id << "]";

        std::cout << "case id: " << sstream.str() << std::endl;
#endif
        typedef typename Container1::const_iterator const_iterator1;
        typedef typename Container2::const_iterator const_iterator2;
        typedef typename Container1::iterator iterator1;
        typedef typename Container2::iterator iterator2;

        typedef boost::geometry::concatenate_iterator
            <
                const_iterator1, const_iterator2,
                typename Container1::value_type const
            > const_concat_iterator;

        typedef boost::geometry::concatenate_iterator
            <
                iterator1, iterator2, typename Container1::value_type
            > concat_iterator;

        typedef typename std::iterator_traits
            <
                concat_iterator
            >::value_type value_type;

        // test constructors/assignment operators
        concat_iterator begin(c1.begin(), c1.end(), c2.begin(), c2.begin());
        concat_iterator end(c1.end(), c2.begin(), c2.end());
        const_concat_iterator const_begin(begin);
        const_concat_iterator const_end(end);
        const_begin = begin;
        const_end = end;

        concat_iterator begin2(c1.end(), c1.end(), c2.begin(), c2.begin());
        const_concat_iterator const_begin2(c1.end(), c1.end(),
                                           c2.begin(), c2.begin());

        BOOST_CHECK( c1.empty() || *begin == *c1.begin() );
        BOOST_CHECK( c1.empty() || *const_begin == *c1.begin() );

        BOOST_CHECK( c2.empty() || *begin2 == *c2.begin() );
        BOOST_CHECK( c2.empty() || *const_begin2 == *c2.begin() );


        // test copying, dereferencing and element equality
        std::vector<value_type> combined;
        std::copy(c1.begin(), c1.end(), std::back_inserter(combined));
        std::copy(c2.begin(), c2.end(), std::back_inserter(combined));
        test_equality(begin, end, combined);

        combined.clear();
        std::copy(begin, end, std::back_inserter(combined));
        test_equality(begin, end, combined);
        test_equality(const_begin, const_end, combined);

        combined.clear();
        std::copy(const_begin, const_end, std::back_inserter(combined));
        test_equality(begin, end, combined);
        test_equality(const_begin, const_end, combined);

        // test sizes (and std::distance)
        test_size(begin, end, combined);
        test_size(const_begin, const_end, combined);


#ifdef BOOST_GEOMETRY_TEST_DEBUG
        print_container(std::cout, c1.begin(), c1.end(), "first   :")
            << std::endl;
        print_container(std::cout, c2.begin(), c2.end(), "second  :")
            << std::endl;
        print_container(std::cout, begin, end,           "combined:")
            << std::endl;

        if ( begin != end )
        {
            std::cout << "min element: "
                      << *std::min_element(begin, end)
                      << std::endl;
            std::cout << "max element: "
                      << *std::max_element(const_begin, const_end)
                      << std::endl;
        }
#endif

        // perform reversals (std::reverse)
        test_using_reverse(begin, end, combined);

        // test std::max_element, dereferencing and value assigment
        test_using_max_element(begin, end, const_begin, const_end,
                               c1, c2.size(), false);
        test_using_max_element(begin, end, const_begin, const_end,
                               c2, c1.size(), true);

        // test std::count_if / std::remove_if
        test_using_remove_if(begin, end, combined);

#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::cout << "====================" << std::endl << std::endl;
#endif
    }
};


template <typename Container1, typename Container2>
inline void test_concatenation_of_containers(Container1& c1, Container2& c2,
                                             std::string const& containers_id)
{
#ifdef BOOST_GEOMETRY_TEST_DEBUG
    std::cout << std::endl << std::endl;
    std::cout << "************************************" << std::endl
              << " TESTING CONTAINERS COMBINATION: "
              << containers_id << std::endl
              << "************************************" << std::endl
              << std::endl;
#endif

    c1.clear();
    c2.clear();    
    test_concatenate_iterator::apply(c1, c2, "empty_both", containers_id);

    c2 += 10,11,12,13,14,15,16,17,18,19,20;
    test_concatenate_iterator::apply(c1, c2, "empty_first", containers_id);

    c2.clear();
    c1 += 0,1,2,3,4,5,6;
    test_concatenate_iterator::apply(c1, c2, "empty_second", containers_id);

    c1.clear();
    c2.clear();
    c1 += 0,1,2,3,4,5,6;
    c2 += 10,11,12,13,14,15,16,17,18,19,20;
    test_concatenate_iterator::apply(c1, c2, "non_empty", containers_id);
}


BOOST_AUTO_TEST_CASE( test_concatenate_iterator_all )
{
    std::vector<int> v, vv;
    std::list<int> l, ll;
        
    test_concatenation_of_containers(v, vv, "VV");
    test_concatenation_of_containers(v, l, "VL");
    test_concatenation_of_containers(l, v, "LV");
    test_concatenation_of_containers(l, ll, "LL");
}