// ***************************************************************** -*- C++ -*-
/*
 * Copyright (C) 2004-2021 Exiv2 authors
 * This program is part of the Exiv2 distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
 */

#include <exiv2/exiv2.hpp>
#include <stdint.h>
#include "slice.hpp"
#include "types.hpp"
#include "gtestwrapper.h"
using namespace Exiv2;

template <typename T>
class slice;

/*!
 * This namespace contains the helper-function get_test_data. It is intented
 * to be used for test with the slice fixture: it returns the appropriate
 * data to the constructor of slice. For (const) T==std::vector it returns the
 * fixtures meber vec_, for (const) T==int* it returns vec_.data()
 *
 * Due to C++98's limitations, this requires a separate traits class, that
 * specifies the return type *and* a specialization of get_test_data for each
 * case (maybe some can be reduced with SFINAE, but that ain't improving
 * readability either).
 *
 * Unfortunately, C++11 will probably only make the return_type_traits go away,
 * but not the template specializations of get_test_data (for that we need
 * C++17, so see you in 2025).
 */
namespace cpp_98_boilerplate
{
    template <typename T>
    struct return_type_traits
    {
        typedef T type;
    };

    template <typename U>
    struct return_type_traits<std::vector<U> >
    {
        typedef typename std::vector<U>& type;
    };

    template <typename U>
    struct return_type_traits<const std::vector<U> >
    {
        typedef const typename std::vector<U>& type;
    };

    template <typename T>
    typename return_type_traits<T>::type get_test_data(slice<T>& st);

}  // namespace cpp_98_boilerplate

/*!
 * Fixture for slice testing. Has one public vector of ints with size vec_size
 * that is filled with the numbers from 0 to vec_size - 1.
 *
 * The vector vec_ is used to construct slices either from a std::vector, or
 * from raw C-arrays. Which type is used, is set by the template parameter
 * T. Thus we guarantee, that the interface is completely independent of the
 * underlying datatype.
 *
 * @tparam T  Type that is used to construct a slice for testing.
 */
template <typename T>
class slice : public ::testing::Test
{
public:
    static const size_t vec_size = 10;

    virtual void SetUp()
    {
        vec_.reserve(vec_size);
        for (unsigned int i = 0; i < vec_size; ++i) {
            vec_.push_back(i);
        }
    }

    Slice<T> getTestSlice(size_t begin = 1, size_t end = vec_size - 1)
    {
        return Slice<T>(cpp_98_boilerplate::get_test_data<T>(*this), begin, end);
    }

    // TODO: once we have C++11: use initializer list
    std::vector<int> vec_;
};

// specializations of get_test_data are provided here, since they must have the
// full definition of slice available
namespace cpp_98_boilerplate
{
    template <>
    int* get_test_data<int*>(slice<int*>& st)
    {
        return st.vec_.data();
    }

    template <>
    const int* get_test_data<const int*>(slice<const int*>& st)
    {
        return st.vec_.data();
    }

    template <>
    std::vector<int>& get_test_data<std::vector<int> >(slice<std::vector<int> >& st)
    {
        return st.vec_;
    }

    template <>
    const std::vector<int>& get_test_data<const std::vector<int> >(slice<const std::vector<int> >& st)
    {
        return st.vec_;
    }
}  // namespace cpp_98_boilerplate

/*!
 * Fixture to run test for mutable slices.
 *
 * It adds nothing new, it is just a separate class, so that we can run
 * different tests on it.
 */
template <typename T>
class mutableSlice : public slice<T>
{
};

TYPED_TEST_CASE_P(slice);
TYPED_TEST_CASE_P(mutableSlice);

TYPED_TEST_P(slice, atAccess)
{
    // typedef Slice<TypeParam> slice_t;
    // const size_t begin = 1;
    // const size_t end = this->vec_.size() - 1;
    Slice<TypeParam> sl = this->getTestSlice();

    ASSERT_EQ(this->vec_.size() - 2, sl.size());

    for (unsigned int i = 0; i < sl.size(); ++i) {
        ASSERT_EQ(this->vec_.at(i + 1), sl.at(i));
    }
}

// TODO C++11: test range based for loop
TYPED_TEST_P(slice, iteratorAccess)
{
    Slice<TypeParam> sl = this->getTestSlice();

    std::vector<int>::const_iterator vec_it = this->vec_.begin() + 1;
    for (typename Slice<TypeParam>::const_iterator it = sl.cbegin(); it < sl.cend(); ++it, ++vec_it) {
        ASSERT_EQ(*it, *vec_it);
    }

    ASSERT_THROW(sl.at(sl.size()), std::out_of_range);
}

TYPED_TEST_P(slice, constructionFailsFromInvalidRange)
{
    // start > end
    ASSERT_THROW(this->getTestSlice(2, 1), std::out_of_range);
}

TYPED_TEST_P(slice, constructionFailsWithZeroLength)
{
    ASSERT_THROW(this->getTestSlice(1, 1), std::out_of_range);
}

/*!
 * Test the construction of subSlices and their behavior.
 */
TYPED_TEST_P(slice, subSliceSuccessfulConstruction)
{
    typedef Slice<TypeParam> slice_t;

    // 0 1 2 3 4 5 6 7 8 9
    //       |     |       center_vals
    //         | |         middle
    slice_t center_vals = this->getTestSlice(3, 7);
    ASSERT_EQ(center_vals.size(), static_cast<size_t>(4));
    ASSERT_NO_THROW(center_vals.subSlice(1, 3));

    ASSERT_NO_THROW(center_vals.subSlice(1, center_vals.size()));
}

TYPED_TEST_P(slice, subSliceFunctions)
{
    Slice<TypeParam> middle = this->getTestSlice(3, 7).subSlice(1, 3);

    ASSERT_EQ(middle.size(), static_cast<size_t>(2));
    ASSERT_EQ(middle.at(1), static_cast<typename Slice<TypeParam>::value_type>(5));
}

TYPED_TEST_P(slice, subSliceFailedConstruction)
{
    // 0 1 2 3 4 5 6 7 8 9
    //         | |         middle
    Slice<TypeParam> middle = this->getTestSlice(4, 6);

    ASSERT_THROW(middle.subSlice(1, 5), std::out_of_range);
    ASSERT_THROW(middle.subSlice(2, 1), std::out_of_range);
    ASSERT_THROW(middle.subSlice(2, 2), std::out_of_range);
}

/*! try to cause integer overflows in a sub-optimal implementation */
TYPED_TEST_P(slice, subSliceConstructionOverflowResistance)
{
    Slice<TypeParam> center_vals = this->getTestSlice(3, 7);

    ASSERT_THROW(center_vals.subSlice(std::numeric_limits<size_t>::max() - 2, 3), std::out_of_range);
    ASSERT_THROW(center_vals.subSlice(2, std::numeric_limits<size_t>::max() - 1), std::out_of_range);
}

/*!
 * This function's purpose is only to check whether we can pass all slices by
 * constant reference.
 */
template <typename T>
void checkConstSliceValueAt(const Slice<T>& sl, typename Slice<T>::value_type value, size_t index)
{
    ASSERT_EQ(sl.at(index), value);
}

/*!
 * Check that the contents of the slice are ascending via an iterator based for
 * loop.
 */
template <typename T>
void checkConstSliceIterator(const Slice<T>& sl, typename Slice<T>::value_type first_value)
{
    for (typename Slice<T>::const_iterator it = sl.cbegin(); it < sl.cend(); ++it) {
        ASSERT_EQ(*it, first_value++);
    }
}

template <typename T>
void checkSubSlice(const Slice<T>& sl)
{
    ASSERT_EQ(sl.at(1), sl.subSlice(1, sl.size()).at(0));
}

/*!
 * Test that all slices can be also passed as const references and still work
 */
TYPED_TEST_P(slice, constMethodsPreserveConst)
{
    typedef Slice<TypeParam> slice_t;

    // 0 1 2 3 4 5 6 7 8 9
    //       |     |       center_vals
    slice_t center_vals = this->getTestSlice(3, 7);

    // check at() const works
    checkConstSliceValueAt(center_vals, 4, 1);

    checkConstSliceIterator(center_vals, 3);

    checkSubSlice(center_vals);
}

/*!
 * Test the non-const iterators
 */
TYPED_TEST_P(mutableSlice, iterators)
{
    typedef Slice<TypeParam> slice_t;
    slice_t sl = this->getTestSlice();

    ASSERT_EQ(*sl.begin(), static_cast<typename slice_t::value_type>(1));
    ASSERT_EQ(*sl.end(), static_cast<typename slice_t::value_type>(this->vec_size - 1));

    for (typename slice_t::iterator it = sl.begin(); it < sl.end(); ++it) {
        *it = 2 * (*it);
    }

    ASSERT_EQ(this->vec_.at(0), 0);
    for (size_t j = 1; j < this->vec_size - 1; ++j) {
        ASSERT_EQ(this->vec_.at(j), static_cast<typename slice_t::value_type>(2 * j));
        ASSERT_EQ(this->vec_.at(j), sl.at(j - 1));
    }
    ASSERT_EQ(this->vec_.at(this->vec_size - 1), static_cast<typename slice_t::value_type>(this->vec_size - 1));
}

/*!
 * Test the non-const version of at()
 */
TYPED_TEST_P(mutableSlice, at)
{
    typedef Slice<TypeParam> slice_t;
    slice_t sl = this->getTestSlice(2, 4);

    sl.at(0) = 6;
    sl.at(1) = 12;

    ASSERT_EQ(this->vec_.at(2), 6);
    ASSERT_EQ(this->vec_.at(3), 12);
    for (size_t j = 0; j < this->vec_size - 1; ++j) {
        if (j == 2 || j == 3) {
            continue;
        }
        ASSERT_EQ(this->vec_.at(j), static_cast<typename slice_t::value_type>(j));
    }
}

TEST(pointerSlice, failedConstructionFromNullpointer)
{
    ASSERT_THROW(Slice<long*>(NULL, 1, 2), std::invalid_argument);
}

/*!
 * Test the construction of an invalid slices from a container (so that a proper
 * range check can be conducted)
 */
TEST(containerSlice, failedConstructionFromContainer)
{
    std::vector<int> tmp(10);
    // slice end too large
    ASSERT_THROW(Slice<std::vector<int> >(tmp, 1, tmp.size() + 1), std::out_of_range);
}

/*!
 * Test all functions from the makeSlice* family.
 */
TEST(containerSlice, makeSlice)
{
    std::string str = "this is a sentence";

    Slice<std::string> is = makeSlice(str, 5, 7);
    ASSERT_TRUE(std::equal(is.begin(), is.end(), "is"));

    Slice<std::string> sl_this = makeSliceUntil(str, 4);
    ASSERT_TRUE(std::equal(sl_this.begin(), sl_this.end(), "this"));

    Slice<std::string> sl_sentence = makeSliceFrom(str, 10);
    ASSERT_TRUE(std::equal(sl_sentence.begin(), sl_sentence.end(), "sentence"));

    Slice<std::string> sl_full = makeSlice(str);
    ASSERT_TRUE(std::equal(sl_full.begin(), sl_full.end(), str.c_str()));
}

struct stringSlice : public ::testing::Test
{
    std::string sentence;

    virtual void SetUp()
    {
        sentence = "this is a sentence";
    }
};

TEST_F(stringSlice, at)
{
    const Slice<const std::string> is_a = makeSlice(static_cast<const std::string&>(this->sentence), 5, 10);

    ASSERT_EQ(is_a.at(0), 'i');
    ASSERT_EQ(is_a.at(4), ' ');
}

TEST_F(stringSlice, atFailure)
{
    const Slice<const std::string> is_a = makeSlice(static_cast<const std::string&>(this->sentence), 5, 10);
    ASSERT_THROW(is_a.at(5), std::out_of_range);
}

TEST_F(stringSlice, size)
{
    const Slice<const std::string> is_a = makeSlice(static_cast<const std::string&>(this->sentence), 5, 10);
    ASSERT_EQ(is_a.size(), static_cast<size_t>(5));
}

TEST_F(stringSlice, mutateString)
{
    Slice<std::string> is_a_mutable = makeSlice(this->sentence, 5, 10);

    for (Slice<std::string>::iterator it = is_a_mutable.begin(); it < is_a_mutable.end(); ++it) {
        *it = ' ';
    }

    ASSERT_STREQ(this->sentence.c_str(), "this      sentence");
}

template <typename T>
struct dataBufSlice : public ::testing::Test
{
    static byte data[4];  // = {0xde, 0xad, 0xbe, 0xef};
    DataBuf buf;

    virtual void SetUp()
    {
        buf = DataBuf(data, sizeof(data));
    }
};

template <typename T>
byte dataBufSlice<T>::data[4] = {0xde, 0xad, 0xbe, 0xef};

TYPED_TEST_CASE_P(dataBufSlice);

TYPED_TEST_P(dataBufSlice, successfulConstruction)
{
    // just check that makeSlice appears to work
    ASSERT_EQ(makeSlice(static_cast<TypeParam>(this->buf), 1, 3).size(), static_cast<size_t>(2));
}

TYPED_TEST_P(dataBufSlice, failedConstruction)
{
    // check that we get an exception when end is larger than LONG_MAX
    ASSERT_THROW(
        makeSlice(static_cast<TypeParam>(this->buf), 1, static_cast<size_t>(std::numeric_limits<long>::max()) + 1),
        std::invalid_argument);

    // check that we get an exception when end is larger than the DataBuf
    ASSERT_THROW(makeSlice(static_cast<TypeParam>(this->buf), 1, 5), std::out_of_range);
}

//
// GTest boilerplate to get the tests running for all the different types
//
REGISTER_TYPED_TEST_CASE_P(slice, atAccess, iteratorAccess, constructionFailsFromInvalidRange,
                           constructionFailsWithZeroLength, subSliceSuccessfulConstruction, subSliceFunctions,
                           subSliceFailedConstruction, subSliceConstructionOverflowResistance,
                           constMethodsPreserveConst);

typedef ::testing::Types<const std::vector<int>, std::vector<int>, int*, const int*> test_types_t;
INSTANTIATE_TYPED_TEST_CASE_P(slice, slice, test_types_t);

REGISTER_TYPED_TEST_CASE_P(mutableSlice, iterators, at);
typedef ::testing::Types<std::vector<int>, int*> mut_test_types_t;
INSTANTIATE_TYPED_TEST_CASE_P(slice, mutableSlice, mut_test_types_t);

REGISTER_TYPED_TEST_CASE_P(dataBufSlice, successfulConstruction, failedConstruction);
typedef ::testing::Types<DataBuf&, const DataBuf&> data_buf_types_t;
INSTANTIATE_TYPED_TEST_CASE_P(slice, dataBufSlice, data_buf_types_t);
