/*
 *
 * 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 "PnData.h"
#include "qpid/types/encodings.h"

namespace qpid {
namespace messaging {
namespace amqp {

using types::Variant;

void PnData::write(const Variant::Map& map)
{
    pn_data_put_map(data);
    pn_data_enter(data);
    for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) {
        pn_data_put_string(data, str(i->first));
        write(i->second);
    }
    pn_data_exit(data);
}
void PnData::write(const Variant::List& list)
{
    pn_data_put_list(data);
    pn_data_enter(data);
    for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) {
        write(*i);
    }
    pn_data_exit(data);
}
void PnData::write(const Variant& value)
{
    switch (value.getType()) {
      case qpid::types::VAR_VOID:
        pn_data_put_null(data);
        break;
      case qpid::types::VAR_BOOL:
        pn_data_put_bool(data, value.asBool());
        break;
      case qpid::types::VAR_UINT64:
        pn_data_put_ulong(data, value.asUint64());
        break;
      case qpid::types::VAR_INT64:
        pn_data_put_long(data, value.asInt64());
        break;
      case qpid::types::VAR_DOUBLE:
        pn_data_put_double(data, value.asDouble());
        break;
      case qpid::types::VAR_STRING:
        pn_data_put_string(data, str(value.asString()));
        break;
      case qpid::types::VAR_MAP:
        write(value.asMap());
        break;
      case qpid::types::VAR_LIST:
        write(value.asList());
        break;
      default:
        break;
    }
}

bool PnData::read(qpid::types::Variant& value)
{
    return read(pn_data_type(data), value);
}

void PnData::readList(qpid::types::Variant::List& value)
{
    size_t count = pn_data_get_list(data);
    pn_data_enter(data);
    for (size_t i = 0; i < count && pn_data_next(data); ++i) {
        qpid::types::Variant e;
        if (read(e)) value.push_back(e);
    }
    pn_data_exit(data);
}

void PnData::readMap(qpid::types::Variant::Map& value)
{
    size_t count = pn_data_get_list(data);
    pn_data_enter(data);
    for (size_t i = 0; i < (count/2) && pn_data_next(data); ++i) {
        std::string key = str(pn_data_get_symbol(data));
        pn_data_next(data);
        qpid::types::Variant e;
        if (read(e)) value[key]= e;
    }
    pn_data_exit(data);
}

void PnData::readArray(qpid::types::Variant::List& value)
{
    size_t count = pn_data_get_array(data);
    pn_type_t type = pn_data_get_array_type(data);
    pn_data_enter(data);
    for (size_t i = 0; i < count && pn_data_next(data); ++i) {
        qpid::types::Variant e;
        if (read(type, e)) value.push_back(e);
    }
    pn_data_exit(data);
}

bool PnData::read(pn_type_t type, qpid::types::Variant& value)
{
    switch (type) {
      case PN_NULL:
        if (value.getType() != qpid::types::VAR_VOID) value = qpid::types::Variant();
        return true;
      case PN_BOOL:
        value = pn_data_get_bool(data);
        return true;
      case PN_UBYTE:
        value = pn_data_get_ubyte(data);
        return true;
      case PN_BYTE:
        value = pn_data_get_byte(data);
        return true;
      case PN_USHORT:
        value = pn_data_get_ushort(data);
        return true;
      case PN_SHORT:
        value = pn_data_get_short(data);
        return true;
      case PN_UINT:
        value = pn_data_get_uint(data);
        return true;
      case PN_INT:
        value = pn_data_get_int(data);
        return true;
      case PN_CHAR:
        value = pn_data_get_char(data);
        return true;
      case PN_ULONG:
        value = pn_data_get_ulong(data);
        return true;
      case PN_LONG:
        value = pn_data_get_long(data);
        return true;
      case PN_TIMESTAMP:
        value = pn_data_get_timestamp(data);
        return true;
      case PN_FLOAT:
        value = pn_data_get_float(data);
        return true;
      case PN_DOUBLE:
        value = pn_data_get_double(data);
        return true;
      case PN_UUID:
        value = qpid::types::Uuid(pn_data_get_uuid(data).bytes);
        return true;
      case PN_BINARY:
        value = str(pn_data_get_binary(data));
        value.setEncoding(qpid::types::encodings::BINARY);
        return true;
      case PN_STRING:
        value = str(pn_data_get_string(data));
        value.setEncoding(qpid::types::encodings::UTF8);
        return true;
      case PN_SYMBOL:
        value = str(pn_data_get_string(data));
        value.setEncoding(qpid::types::encodings::ASCII);
        return true;
      case PN_LIST:
        value = qpid::types::Variant::List();
        readList(value.asList());
        return true;
        break;
      case PN_MAP:
        value = qpid::types::Variant::Map();
        readMap(value.asMap());
        return true;
      case PN_ARRAY:
        value = qpid::types::Variant::List();
        readArray(value.asList());
        return true;
      case PN_DESCRIBED:
      case PN_DECIMAL32:
      case PN_DECIMAL64:
      case PN_DECIMAL128:
      default:
        return false;
    }

}

pn_bytes_t PnData::str(const std::string& s)
{
    pn_bytes_t result;
    result.start = const_cast<char*>(s.data());
    result.size = s.size();
    return result;
}

std::string PnData::str(const pn_bytes_t& in)
{
    return std::string(in.start, in.size);
}

}}} // namespace qpid::messaging::amqp
