#ifndef OBJECT_ENCODER_HPP
#define OBJECT_ENCODER_HPP

#include <boost/assert.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>

#include <algorithm>
#include <iterator>
#include <string>
#include <vector>

#include <cstdint>
#include <climits>

namespace osrm
{
namespace engine
{

namespace detail
{
static_assert(CHAR_BIT == 8u, "we assume a byte holds 8 bits");
static_assert(sizeof(char) == 1u, "we assume a char is one byte large");

using Base64FromBinary = boost::archive::iterators::base64_from_binary<
    boost::archive::iterators::transform_width<const char *, // sequence of chars
                                               6,            // get view of 6 bit
                                               8             // from sequence of 8 bit
                                               >>;

using BinaryFromBase64 = boost::archive::iterators::transform_width<
    boost::archive::iterators::binary_from_base64<std::string::const_iterator>,
    8, // get a view of 8 bit
    6  // from a sequence of 6 bit
    >;
} // ns detail

template <typename T> std::string encodeBase64(const T &x)
{
#if not defined __GNUC__ or __GNUC__ > 4
    static_assert(std::is_trivially_copyable<T>::value, "requires a trivially copyable type");
#endif

    std::vector<unsigned char> bytes{reinterpret_cast<const char *>(&x),
                                     reinterpret_cast<const char *>(&x) + sizeof(T)};
    BOOST_ASSERT(!bytes.empty());

    std::size_t bytes_to_pad{0};

    while (bytes.size() % 3 != 0)
    {
        bytes_to_pad += 1;
        bytes.push_back(0);
    }

    BOOST_ASSERT(bytes_to_pad == 0 || bytes_to_pad == 1 || bytes_to_pad == 2);
    BOOST_ASSERT_MSG(0 == bytes.size() % 3, "base64 input data size is not a multiple of 3");

    std::string encoded{detail::Base64FromBinary{bytes.data()},
                        detail::Base64FromBinary{bytes.data() + (bytes.size() - bytes_to_pad)}};

    std::replace(begin(encoded), end(encoded), '+', '-');
    std::replace(begin(encoded), end(encoded), '/', '_');

    return encoded;
}

template <typename T> T decodeBase64(std::string encoded)
{
#if not defined __GNUC__ or __GNUC__ > 4
    static_assert(std::is_trivially_copyable<T>::value, "requires a trivially copyable type");
#endif

    std::replace(begin(encoded), end(encoded), '-', '+');
    std::replace(begin(encoded), end(encoded), '_', '/');

    T rv;

    std::copy(detail::BinaryFromBase64{begin(encoded)},
              detail::BinaryFromBase64{begin(encoded) + encoded.length()},
              reinterpret_cast<char *>(&rv));

    return rv;
}

} // ns engine
} // ns osrm

#endif /* OBJECT_ENCODER_HPP */