#ifndef PROTON_CODEC_ENCODER_HPP
#define PROTON_CODEC_ENCODER_HPP

/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

#include "../internal/data.hpp"
#include "../internal/type_traits.hpp"
#include "../types_fwd.hpp"
#include "./common.hpp"

#include <proton/type_compat.h>

/// @file
/// @copybrief proton::codec::encoder

namespace proton {
class scalar_base;

namespace internal{
class value_base;
}

namespace codec {

/// **Unsettled API** - A stream-like encoder from C++ values to AMQP
/// bytes.
///
/// For internal use only.
///
/// @see @ref types_page for the recommended ways to manage AMQP data
class encoder : public internal::data {
  public:
    /// Wrap Proton-C data object.
    explicit encoder(const data& d) : data(d) {}

    /// Encoder into v. Clears any current value in v.
    PN_CPP_EXTERN explicit encoder(internal::value_base& v);

    /// Encode the current values into buffer and update size to reflect the
    /// number of bytes encoded.
    ///
    /// Clears the encoder.
    ///
    /// @return if buffer == 0 or size is too small, then return false
    /// and size to the required size.  Otherwise, return true and set
    /// size to the number of bytes encoded.
    PN_CPP_EXTERN bool encode(char* buffer, size_t& size);

    /// Encode the current values into a std::string and resize the
    /// string if necessary. Clears the encoder.
    PN_CPP_EXTERN void encode(std::string&);

    /// Encode the current values into a std::string. Clears the
    /// encoder.
    PN_CPP_EXTERN std::string encode();

    /// @name Insert built-in types
    /// @{
    PN_CPP_EXTERN encoder& operator<<(bool);
    PN_CPP_EXTERN encoder& operator<<(uint8_t);
    PN_CPP_EXTERN encoder& operator<<(int8_t);
    PN_CPP_EXTERN encoder& operator<<(uint16_t);
    PN_CPP_EXTERN encoder& operator<<(int16_t);
    PN_CPP_EXTERN encoder& operator<<(uint32_t);
    PN_CPP_EXTERN encoder& operator<<(int32_t);
    PN_CPP_EXTERN encoder& operator<<(wchar_t);
    PN_CPP_EXTERN encoder& operator<<(uint64_t);
    PN_CPP_EXTERN encoder& operator<<(int64_t);
    PN_CPP_EXTERN encoder& operator<<(timestamp);
    PN_CPP_EXTERN encoder& operator<<(float);
    PN_CPP_EXTERN encoder& operator<<(double);
    PN_CPP_EXTERN encoder& operator<<(decimal32);
    PN_CPP_EXTERN encoder& operator<<(decimal64);
    PN_CPP_EXTERN encoder& operator<<(decimal128);
    PN_CPP_EXTERN encoder& operator<<(const uuid&);
    PN_CPP_EXTERN encoder& operator<<(const std::string&);
    PN_CPP_EXTERN encoder& operator<<(const symbol&);
    PN_CPP_EXTERN encoder& operator<<(const binary&);
    PN_CPP_EXTERN encoder& operator<<(const scalar_base&);
    PN_CPP_EXTERN encoder& operator<<(const null&);
    PN_CPP_EXTERN encoder& operator<<(decltype(nullptr));
    /// @}

    /// Insert a proton::value.
    ///
    /// @internal NOTE insert value_base, not value to avoid recursive
    /// implicit conversions.
    PN_CPP_EXTERN encoder& operator<<(const internal::value_base&);

    /// Start a complex type
    PN_CPP_EXTERN encoder& operator<<(const start&);

    /// Finish a complex type
    PN_CPP_EXTERN encoder& operator<<(const finish&);

    /// @cond INTERNAL

    // Undefined template to  prevent pointers being implicitly converted to bool.
    template <class T> void* operator<<(const T*);

    template <class T> struct list_cref { T& ref; list_cref(T& r) : ref(r) {} };
    template <class T> struct map_cref { T& ref;  map_cref(T& r) : ref(r) {} };

    template <class T> struct array_cref {
        start array_start;
        T& ref;
        array_cref(T& r, type_id el, bool described) : array_start(ARRAY, el, described), ref(r) {}
    };

    template <class T> static list_cref<T> list(T& x) { return list_cref<T>(x); }
    template <class T> static map_cref<T> map(T& x) { return map_cref<T>(x); }
    template <class T> static array_cref<T> array(T& x, type_id element, bool described=false) {
        return array_cref<T>(x, element, described);
    }

    template <class T> encoder& operator<<(const map_cref<T>& x) {
        internal::state_guard sg(*this);
        *this << start::map();
        for (typename T::const_iterator i = x.ref.begin(); i != x.ref.end(); ++i)
            *this << i->first << i->second;
        *this << finish();
        return *this;
    }

    template <class T> encoder& operator<<(const list_cref<T>& x) {
        internal::state_guard sg(*this);
        *this << start::list();
        for (typename T::const_iterator i = x.ref.begin(); i != x.ref.end(); ++i)
            *this << *i;
        *this << finish();
        return *this;
    }

    template <class T> encoder& operator<<(const array_cref<T>& x) {
        internal::state_guard sg(*this);
        *this << x.array_start;
        for (typename T::const_iterator i = x.ref.begin(); i != x.ref.end(); ++i)
            *this << *i;
        *this << finish();
        return *this;
    }
    /// @endcond

  private:
    template<class T, class U> encoder& insert(const T& x, int (*put)(pn_data_t*, U));
    void check(long result);
};

/// Treat char* as string
inline encoder& operator<<(encoder& e, const char* s) { return e << std::string(s); }

/// operator << for integer types that are not covered by the standard overrides.
template <class T> typename std::enable_if<internal::is_unknown_integer<T>::value, encoder&>::type
operator<<(encoder& e, T i)  {
    using namespace internal;
    return e << static_cast<typename integer_type<sizeof(T), std::is_signed<T>::value>::type>(i);
}

/// @cond INTERNAL

namespace is_encodable_impl {   // Protect the world from fallback operator<<

using namespace internal;

sfinae::no operator<<(encoder const&, const sfinae::any_t &); // Fallback

template<typename T> struct is_encodable : public sfinae {
    static yes test(encoder&);
    static no test(...);         // Failed test, no match.
    static encoder* e;
    static const T* t;
    static bool const value = sizeof(test(*e << *t)) == sizeof(yes);
};

// Avoid recursion
template <> struct is_encodable<value> : public std::true_type {};

} // is_encodable_impl

using is_encodable_impl::is_encodable;

// Metafunction to test if a class looks like an encodable map from K to T.
template <class M, class K, class T, class Enable = void>
struct is_encodable_map : public std::false_type {};

template <class M, class K, class T> struct is_encodable_map<
    M, K, T, typename std::enable_if<
                 std::is_same<K, typename M::key_type>::value &&
                 std::is_same<T, typename M::mapped_type>::value &&
                 is_encodable<M>::value
                 >::type
    > : public std::true_type {};


/// @endcond

} // codec
} // proton

#endif /// PROTON_CODEC_ENCODER_HPP
