#pragma once

#include "rapidcheck/Gen.h"
#include "rapidcheck/Compat.h"

namespace rc {
namespace gen {

/// Returns a generator based on the given generator but mapped with the given
/// mapping function.
template <typename T, typename Mapper>
Gen<Decay<typename rc::compat::return_type<Mapper,T>::type>> map(Gen<T> gen,
                                                         Mapper &&mapper);

/// Convenience function which calls `map(Gen<T>, Mapper)` with
/// `gen::arbitrary<T>`
template <typename T, typename Mapper>
Gen<Decay<typename rc::compat::return_type<Mapper,T>::type>> map(Mapper &&mapper);

/// Monadic bind. Takes a `Gen<T>` and a function from a `T` to a `Gen<U>` and
/// returns a `Gen<U>`. When shrinking, the value generated by the first
/// generator will be shrunk first, then the second.
template <typename T, typename Mapper>
Gen<typename rc::compat::return_type<Mapper,T>::type::ValueType>
mapcat(Gen<T> gen, Mapper &&mapper);

/// Flattens a generator of generators of `T` into a generator of `T`. This is
/// the monadic join operation.
template <typename T>
Gen<T> join(Gen<Gen<T>> gen);

/// Calls the given callable with values generated by the given generators. Has
/// tuple semantics when shrinking.
template <typename Callable, typename... Ts>
Gen<typename rc::compat::return_type<Callable,Ts...>::type>
apply(Callable &&callable, Gen<Ts>... gens);

/// Returns a generator that casts the generated values to `T` using
/// `static_cast<T>(...)`.
template <typename T, typename U>
Gen<T> cast(Gen<U> gen);

/// Returns a version of the given generator that always uses the specified
/// size.
template <typename T>
Gen<T> resize(int size, Gen<T> gen);

/// Returns a version of the given generator that scales the size by the given
/// factor before passing it to the underlying generator.
template <typename T>
Gen<T> scale(double scale, Gen<T> gen);

/// Creates a generator by taking a callable which gets passed the current size
/// and is expected to return a generator.
template <typename Callable>
Gen<typename rc::compat::return_type<Callable,int>::type::ValueType>
withSize(Callable &&callable);

/// Use this to disable shrinking for a given generator. When a failing case is
/// found, RapidCheck will not try to shrink values generated by the returned
/// generator.
template <typename T>
Gen<T> noShrink(Gen<T> gen);

/// Returns a version of the given generator that will try shrinks returned by
/// the given callable after the regular shrinks have been tried. Use this to
/// add further shrinking to an existing generator.
template <typename T, typename Shrink>
Gen<T> shrink(Gen<T> gen, Shrink &&shrink);

} // namespace gen
} // namespace rc

#include "Transform.hpp"
