/*
 * 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 "alloc.h"
#include <qpid/dispatch/ctools.h>
#include <qpid/dispatch/parse.h>
#include <qpid/dispatch/amqp.h>

DEQ_DECLARE(qd_parsed_field_t, qd_parsed_field_list_t);

struct qd_parsed_field_t {
    DEQ_LINKS(qd_parsed_field_t);
    qd_parsed_field_t      *parent;
    qd_parsed_field_list_t  children;
    uint8_t                 tag;
    qd_field_iterator_t    *raw_iter;
    const char             *parse_error;
};

ALLOC_DECLARE(qd_parsed_field_t);
ALLOC_DEFINE(qd_parsed_field_t);


static char *get_type_info(qd_field_iterator_t *iter, uint8_t *tag, uint32_t *length, uint32_t *count, uint32_t *clen)
{
    if (qd_field_iterator_end(iter))
        return "Insufficient Data to Determine Tag";
    *tag      = qd_field_iterator_octet(iter);
    *count    = 0;
    *length   = 0;
    *clen     = 0;

    switch (*tag & 0xF0) {
    case 0x40: *length = 0;  break;
    case 0x50: *length = 1;  break;
    case 0x60: *length = 2;  break;
    case 0x70: *length = 4;  break;
    case 0x80: *length = 8;  break;
    case 0x90: *length = 16; break;
    case 0xB0:
    case 0xD0:
    case 0xF0:
        *length += ((unsigned int) qd_field_iterator_octet(iter)) << 24;
        *length += ((unsigned int) qd_field_iterator_octet(iter)) << 16;
        *length += ((unsigned int) qd_field_iterator_octet(iter)) << 8;
        // fall through to the next case

    case 0xA0:
    case 0xC0:
    case 0xE0:
        if (qd_field_iterator_end(iter))
            return "Insufficient Data to Determine Length";
        *length += (unsigned int) qd_field_iterator_octet(iter);
        break;

    default:
        return "Invalid Tag - No Length Information";
    }

    switch (*tag & 0xF0) {
    case 0xD0:
    case 0xF0:
        *count += ((unsigned int) qd_field_iterator_octet(iter)) << 24;
        *count += ((unsigned int) qd_field_iterator_octet(iter)) << 16;
        *count += ((unsigned int) qd_field_iterator_octet(iter)) << 8;
        *clen = 3;
        // fall through to the next case

    case 0xC0:
    case 0xE0:
        if (qd_field_iterator_end(iter))
            return "Insufficient Data to Determine Count";
        *count += (unsigned int) qd_field_iterator_octet(iter);
        *clen += 1;
        break;
    }

    if ((*tag == QD_AMQP_MAP8 || *tag == QD_AMQP_MAP32) && (*count & 1))
        return "Odd Number of Elements in a Map";

    if (*clen > *length)
        return "Insufficient Length to Determine Count";

    return 0;
}


static qd_parsed_field_t *qd_parse_internal(qd_field_iterator_t *iter, qd_parsed_field_t *p)
{
    qd_parsed_field_t *field = new_qd_parsed_field_t();
    if (!field)
        return 0;

    DEQ_ITEM_INIT(field);
    DEQ_INIT(field->children);
    field->parent   = p;
    field->raw_iter = 0;

    uint32_t length;
    uint32_t count;
    uint32_t length_of_count;

    field->parse_error = get_type_info(iter, &field->tag, &length, &count, &length_of_count);

    if (!field->parse_error) {
        field->raw_iter = qd_field_iterator_sub(iter, length);
        qd_field_iterator_advance(iter, length - length_of_count);
        for (uint32_t idx = 0; idx < count; idx++) {
            qd_parsed_field_t *child = qd_parse_internal(field->raw_iter, field);
            DEQ_INSERT_TAIL(field->children, child);
            if (!qd_parse_ok(child)) {
                field->parse_error = child->parse_error;
                break;
            }
        }
    }

    return field;
}


qd_parsed_field_t *qd_parse(qd_field_iterator_t *iter)
{
    return qd_parse_internal(iter, 0);
}


void qd_parse_free(qd_parsed_field_t *field)
{
    if (!field)
        return;

    assert(field->parent == 0);
    if (field->raw_iter)
        qd_field_iterator_free(field->raw_iter);

    qd_parsed_field_t *sub_field = DEQ_HEAD(field->children);
    while (sub_field) {
        qd_parsed_field_t *next = DEQ_NEXT(sub_field);
        DEQ_REMOVE_HEAD(field->children);
        sub_field->parent = 0;
        qd_parse_free(sub_field);
        sub_field = next;
    }

    free_qd_parsed_field_t(field);
}


int qd_parse_ok(qd_parsed_field_t *field)
{
    return field->parse_error == 0;
}


const char *qd_parse_error(qd_parsed_field_t *field)
{
    return field->parse_error;
}


uint8_t qd_parse_tag(qd_parsed_field_t *field)
{
    return field->tag;
}


qd_field_iterator_t *qd_parse_raw(qd_parsed_field_t *field)
{
    return field->raw_iter;
}


uint32_t qd_parse_as_uint(qd_parsed_field_t *field)
{
    uint32_t result = 0;

    qd_field_iterator_reset(field->raw_iter);

    switch (field->tag) {
    case QD_AMQP_UINT:
        result |= ((uint32_t) qd_field_iterator_octet(field->raw_iter)) << 24;
        result |= ((uint32_t) qd_field_iterator_octet(field->raw_iter)) << 16;
        // fallthrough

    case QD_AMQP_USHORT:
        result |= ((uint32_t) qd_field_iterator_octet(field->raw_iter)) << 8;
        // Fall Through...

    case QD_AMQP_UBYTE:
    case QD_AMQP_SMALLUINT:
    case QD_AMQP_BOOLEAN:
        result |= (uint32_t) qd_field_iterator_octet(field->raw_iter);
        break;

    case QD_AMQP_TRUE:
        result = 1;
        break;
    }

    return result;
}


uint64_t qd_parse_as_ulong(qd_parsed_field_t *field)
{
    uint64_t result = 0;

    qd_field_iterator_reset(field->raw_iter);

    switch (field->tag) {
    case QD_AMQP_ULONG:
    case QD_AMQP_TIMESTAMP:
        result |= ((uint64_t) qd_field_iterator_octet(field->raw_iter)) << 56;
        result |= ((uint64_t) qd_field_iterator_octet(field->raw_iter)) << 48;
        result |= ((uint64_t) qd_field_iterator_octet(field->raw_iter)) << 40;
        result |= ((uint64_t) qd_field_iterator_octet(field->raw_iter)) << 32;
        result |= ((uint64_t) qd_field_iterator_octet(field->raw_iter)) << 24;
        result |= ((uint64_t) qd_field_iterator_octet(field->raw_iter)) << 16;
        result |= ((uint64_t) qd_field_iterator_octet(field->raw_iter)) << 8;
        // Fall Through...

    case QD_AMQP_SMALLULONG:
        result |= (uint64_t) qd_field_iterator_octet(field->raw_iter);
        // Fall Through...

    case QD_AMQP_ULONG0:
        break;
    }

    return result;
}


int32_t qd_parse_as_int(qd_parsed_field_t *field)
{
    int32_t result = 0;

    qd_field_iterator_reset(field->raw_iter);

    switch (field->tag) {
    case QD_AMQP_INT:
        result |= ((int32_t) qd_field_iterator_octet(field->raw_iter)) << 24;
        result |= ((int32_t) qd_field_iterator_octet(field->raw_iter)) << 16;
        // Fall Through...

    case QD_AMQP_SHORT:
        result |= ((int32_t) qd_field_iterator_octet(field->raw_iter)) << 8;
        // Fall Through...

    case QD_AMQP_BYTE:
    case QD_AMQP_BOOLEAN:
        result |= (int32_t) qd_field_iterator_octet(field->raw_iter);
        break;

    case QD_AMQP_SMALLINT:
        result = (int8_t) qd_field_iterator_octet(field->raw_iter);
        break;

    case QD_AMQP_TRUE:
        result = 1;
        break;
    }

    return result;
}


int64_t qd_parse_as_long(qd_parsed_field_t *field)
{
    int64_t result = 0;

    qd_field_iterator_reset(field->raw_iter);

    switch (field->tag) {
    case QD_AMQP_LONG:
        result |= ((int64_t) qd_field_iterator_octet(field->raw_iter)) << 56;
        result |= ((int64_t) qd_field_iterator_octet(field->raw_iter)) << 48;
        result |= ((int64_t) qd_field_iterator_octet(field->raw_iter)) << 40;
        result |= ((int64_t) qd_field_iterator_octet(field->raw_iter)) << 32;
        result |= ((int64_t) qd_field_iterator_octet(field->raw_iter)) << 24;
        result |= ((int64_t) qd_field_iterator_octet(field->raw_iter)) << 16;
        result |= ((int64_t) qd_field_iterator_octet(field->raw_iter)) << 8;
        result |= (uint64_t) qd_field_iterator_octet(field->raw_iter);
        break;

    case QD_AMQP_SMALLLONG:
        result = (int8_t) qd_field_iterator_octet(field->raw_iter);
        break;
    }

    return result;
}


uint32_t qd_parse_sub_count(qd_parsed_field_t *field)
{
    uint32_t count = DEQ_SIZE(field->children);

    if (field->tag == QD_AMQP_MAP8 || field->tag == QD_AMQP_MAP32)
        count = count >> 1;

    return count;
}


qd_parsed_field_t *qd_parse_sub_key(qd_parsed_field_t *field, uint32_t idx)
{
    if (field->tag != QD_AMQP_MAP8 && field->tag != QD_AMQP_MAP32)
        return 0;

    idx = idx << 1;
    qd_parsed_field_t *key = DEQ_HEAD(field->children);
    while (idx && key) {
        idx--;
        key = DEQ_NEXT(key);
    }

    return key;
}


qd_parsed_field_t *qd_parse_sub_value(qd_parsed_field_t *field, uint32_t idx)
{
    if (field->tag == QD_AMQP_MAP8 || field->tag == QD_AMQP_MAP32)
        idx = (idx << 1) + 1;

    qd_parsed_field_t *key = DEQ_HEAD(field->children);
    while (idx && key) {
        idx--;
        key = DEQ_NEXT(key);
    }

    return key;
}


int qd_parse_is_map(qd_parsed_field_t *field)
{
    return field->tag == QD_AMQP_MAP8 || field->tag == QD_AMQP_MAP32;
}


int qd_parse_is_list(qd_parsed_field_t *field)
{
    return field->tag == QD_AMQP_LIST8 || field->tag == QD_AMQP_LIST32;
}


int qd_parse_is_scalar(qd_parsed_field_t *field)
{
    return DEQ_SIZE(field->children) == 0;
}


qd_parsed_field_t *qd_parse_value_by_key(qd_parsed_field_t *field, const char *key)
{
    uint32_t count = qd_parse_sub_count(field);

    for (uint32_t idx = 0; idx < count; idx++) {
        qd_parsed_field_t *sub  = qd_parse_sub_key(field, idx);
        if (!sub)
            return 0;

        qd_field_iterator_t *iter = qd_parse_raw(sub);
        if (!iter)
            return 0;

        if (qd_field_iterator_equal(iter, (const unsigned char*) key)) {
            return qd_parse_sub_value(field, idx);
        }
    }

    return 0;
}
