/*
 * 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 "proton/codec/decoder.hpp"

#include "proton/annotation_key.hpp"
#include "proton/binary.hpp"
#include "proton/decimal.hpp"
#include "proton/message_id.hpp"
#include "proton/scalar.hpp"
#include "proton/symbol.hpp"
#include "proton/timestamp.hpp"
#include "proton/value.hpp"

#include "proton_bits.hpp"
#include "types_internal.hpp"
#include "msg.hpp"

#include <proton/codec.h>

namespace proton {
namespace codec {

/**@file
 *
 * Note the pn_data_t "current" node is always pointing *before* the next value
 * to be returned by the decoder.
 */
decoder::decoder(const internal::value_base& v, bool exact)
    : data(const_cast<internal::value_base&>(v).data()), exact_(exact)
{
    rewind();
}

namespace {
template <class T> T check(T result) {
    if (result < 0)
        throw conversion_error(error_str(result));
    return result;
}
}

void decoder::decode(const char* i, size_t size) {
    internal::state_guard sg(*this);
    const char* end = i + size;
    while (i < end)
        i += check(pn_data_decode(pn_object(), i, size_t(end - i)));
}

void decoder::decode(const std::string& s) { decode(s.data(), s.size()); }

bool decoder::more() {
    internal::state_guard sg(*this);
    return next();
}

type_id decoder::pre_get() {
    if (!next()) throw conversion_error("no more data");
    type_id t = type_id(pn_data_type(pn_object()));
    if (t < 0) throw conversion_error("invalid data");
    return t;
}

namespace {

template <class T, class U> void assign(T& x, const U& y) { x = y; }
void assign(uuid& x, const pn_uuid_t y) { byte_copy(x, y); }
void assign(decimal32& x, const pn_decimal32_t y) { byte_copy(x, y); }
void assign(decimal64& x, const pn_decimal64_t y)  { byte_copy(x, y); }
void assign(decimal128& x, const pn_decimal128_t y) { byte_copy(x, y); }
void assign(symbol& x, const pn_bytes_t y) { x = str(y); }
void assign(binary& x, const pn_bytes_t y) { x = bin(y); }

} // namespace


// Simple extract with no type conversion.
template <class T, class U> decoder& decoder::extract(T& x, U (*get)(pn_data_t*)) {
    internal::state_guard sg(*this);
    assert_type_equal(internal::type_id_of<T>::value, pre_get());
    assign(x, get(pn_object()));
    sg.cancel();                // No error, cancel the reset.
    return *this;
}

type_id decoder::next_type() {
    internal::state_guard sg(*this);
    return pre_get();
}

decoder& decoder::operator>>(start& s) {
    internal::state_guard sg(*this);
    s.type = pre_get();
    switch (s.type) {
      case ARRAY:
        s.size = pn_data_get_array(pn_object());
        s.element = type_id(pn_data_get_array_type(pn_object())); s.is_described = pn_data_is_array_described(pn_object());
        break;
      case LIST:
        s.size = pn_data_get_list(pn_object());
        break;
      case MAP:
        s.size = pn_data_get_map(pn_object());
        break;
      case DESCRIBED:
        s.is_described = true;
        s.size = 1;
        break;
      default:
        throw conversion_error(MSG("" << s.type << " is not a container type"));
    }
    pn_data_enter(pn_object());
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(const finish&) {
    pn_data_exit(pn_object());
    return *this;
}

decoder& decoder::operator>>(null&) {
    internal::state_guard sg(*this);
    assert_type_equal(NULL_TYPE, pre_get());
    return *this;
}

#if PN_CPP_HAS_NULLPTR
decoder& decoder::operator>>(decltype(nullptr)&) {
    internal::state_guard sg(*this);
    assert_type_equal(NULL_TYPE, pre_get());
    return *this;
}
#endif

decoder& decoder::operator>>(internal::value_base& x) {
    if (*this == x.data_)
        throw conversion_error("extract into self");
    data d = x.data();
    d.clear();
    narrow();
    try {
        check(d.appendn(*this, 1));
        widen();
    } catch(...) {
        widen();
        throw;
    }
    next();
    return *this;
}

decoder& decoder::operator>>(message_id& x) {
    internal::state_guard sg(*this);
    type_id got = pre_get();
    if (got != ULONG && got != UUID && got != BINARY && got != STRING)
        throw conversion_error(
            msg() << "expected one of ulong, uuid, binary or string but found " << got);
    x.set(pn_data_get_atom(pn_object()));
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(annotation_key& x) {
    internal::state_guard sg(*this);
    type_id got = pre_get();
    if (got != ULONG && got != SYMBOL)
        throw conversion_error(msg() << "expected one of ulong or symbol but found " << got);
    x.set(pn_data_get_atom(pn_object()));
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(scalar& x) {
    internal::state_guard sg(*this);
    type_id got = pre_get();
    if (!type_id_is_scalar(got))
        throw conversion_error("expected scalar, found "+type_name(got));
    x.set(pn_data_get_atom(pn_object()));
    sg.cancel();                // No error, no rewind
    return *this;
}

decoder& decoder::operator>>(bool &x) { return extract(x, pn_data_get_bool); }

decoder& decoder::operator>>(uint8_t &x)  { return extract(x, pn_data_get_ubyte); }

decoder& decoder::operator>>(int8_t &x) { return extract(x, pn_data_get_byte); }

decoder& decoder::operator>>(uint16_t &x) {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(USHORT, tid);
    switch (tid) {
      case UBYTE: x = pn_data_get_ubyte(pn_object()); break;
      case USHORT: x = pn_data_get_ushort(pn_object()); break;
      default: assert_type_equal(USHORT, tid);
    }
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(int16_t &x) {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(SHORT, tid);
    switch (tid) {
      case BYTE: x = pn_data_get_byte(pn_object()); break;
      case SHORT: x = pn_data_get_short(pn_object()); break;
      default: assert_type_equal(SHORT, tid);
    }
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(uint32_t &x) {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(UINT, tid);
    switch (tid) {
      case UBYTE: x = pn_data_get_ubyte(pn_object()); break;
      case USHORT: x = pn_data_get_ushort(pn_object()); break;
      case UINT: x = pn_data_get_uint(pn_object()); break;
      default: assert_type_equal(UINT, tid);
    }
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(int32_t &x) {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(INT, tid);
    switch (tid) {
      case BYTE: x = pn_data_get_byte(pn_object()); break;
      case SHORT: x = pn_data_get_short(pn_object()); break;
      case INT: x = pn_data_get_int(pn_object()); break;
      default: assert_type_equal(INT, tid);
    }
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(uint64_t &x) {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(ULONG, tid);
    switch (tid) {
      case UBYTE: x = pn_data_get_ubyte(pn_object()); break;
      case USHORT: x = pn_data_get_ushort(pn_object()); break;
      case UINT: x = pn_data_get_uint(pn_object()); break;
      case ULONG: x = pn_data_get_ulong(pn_object()); break;
      default: assert_type_equal(ULONG, tid);
    }
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(int64_t &x) {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(LONG, tid);
    switch (tid) {
      case BYTE: x = pn_data_get_byte(pn_object()); break;
      case SHORT: x = pn_data_get_short(pn_object()); break;
      case INT: x = pn_data_get_int(pn_object()); break;
      case LONG: x = pn_data_get_long(pn_object()); break;
      default: assert_type_equal(LONG, tid);
    }
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(wchar_t &x) { return extract(x, pn_data_get_char); }

decoder& decoder::operator>>(timestamp &x) { return extract(x, pn_data_get_timestamp); }

decoder& decoder::operator>>(float &x) {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(FLOAT, tid);
    switch (tid) {
      case FLOAT: x = pn_data_get_float(pn_object()); break;
      case DOUBLE: x = float(pn_data_get_double(pn_object())); break;
      default: assert_type_equal(FLOAT, tid);
    }
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(double &x) {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(DOUBLE, tid);
    switch (tid) {
      case FLOAT: x = static_cast<double>(pn_data_get_float(pn_object())); break;
      case DOUBLE: x = pn_data_get_double(pn_object()); break;
      default: assert_type_equal(DOUBLE, tid);
    }
    sg.cancel();
    return *this;
}

decoder& decoder::operator>>(decimal32 &x) { return extract(x, pn_data_get_decimal32); }
decoder& decoder::operator>>(decimal64 &x) { return extract(x, pn_data_get_decimal64); }
decoder& decoder::operator>>(decimal128 &x)  { return extract(x, pn_data_get_decimal128); }

decoder& decoder::operator>>(uuid &x)  { return extract(x, pn_data_get_uuid); }
decoder& decoder::operator>>(binary &x)  { return extract(x, pn_data_get_binary); }
decoder& decoder::operator>>(symbol &x)  { return extract(x, pn_data_get_symbol); }

decoder& decoder::operator>>(std::string &x)  {
    internal::state_guard sg(*this);
    type_id tid = pre_get();
    if (exact_) assert_type_equal(STRING, tid);
    switch (tid) {
      case STRING: x = str(pn_data_get_string(pn_object())); break;
      case SYMBOL: x = str(pn_data_get_symbol(pn_object())); break;
      default: assert_type_equal(STRING, tid);
    }
    sg.cancel();
    return *this;
}

} // codec
} // proton
