// mcpack2pb - Make protobuf be front-end of mcpack/compack
// Copyright (c) 2015 Baidu, Inc.
// 
// Licensed 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.

// Author: Ge,Jun (gejun@baidu.com)
// Date: Mon Oct 19 17:17:36 CST 2015

#ifndef MCPACK2PB_MCPACK_PARSER_INL_H
#define MCPACK2PB_MCPACK_PARSER_INL_H

namespace mcpack2pb {

// Binary head before items of array/object except isomorphic array.
struct ItemsHead {
    uint32_t item_count;
} __attribute__((__packed__));

inline size_t InputStream::popn(size_t n) {
    const size_t saved_n = n;
    do {
        if (_size >= (int64_t)n) {
            _data = (const char*)_data + n;
            _size -= n;
            _popped_bytes += saved_n;
            return saved_n;
        }
        n -= _size;
    } while (_zc_stream->Next(&_data, &_size));
    _data = NULL;
    _size = 0;
    _popped_bytes += saved_n - n;
    return saved_n - n;
}
    
inline size_t InputStream::cutn(void* out, size_t n) {
    const size_t saved_n = n;
    do {
        if (_size >= (int64_t)n) {
            memcpy(out, _data, n);
            _data = (const char*)_data + n;
            _size -= n;
            _popped_bytes += saved_n;
            return saved_n;
        }
        if (_size) {
            memcpy(out, _data, _size);
            out = (char*)out + _size;
            n -= _size;
        }
    } while (_zc_stream->Next(&_data, &_size));
    _data = NULL;
    _size = 0;
    _popped_bytes += saved_n - n;
    return saved_n - n;
}

template <typename T>
inline size_t InputStream::cut_packed_pod(T* packed_pod) {
    if (_size >= (int)sizeof(T)) {
        *packed_pod = *(T*)_data;
        _data = (const char*)_data + sizeof(T);
        _size -= sizeof(T);
        _popped_bytes += sizeof(T);
        return sizeof(T);
    }
    return cutn(packed_pod, sizeof(T));
}

template <typename T>
inline T InputStream::cut_packed_pod() {
    T packed_pod;
    if (_size >= (int)sizeof(T)) {
        packed_pod = *(T*)_data;
        _data = (const char*)_data + sizeof(T);
        _size -= sizeof(T);
        _popped_bytes += sizeof(T);
        return packed_pod;
    }
    cutn(&packed_pod, sizeof(T));
    return packed_pod;
}
    
inline butil::StringPiece InputStream::ref_cut(std::string* aux, size_t n) {
    if (_size >= (int64_t)n) {
        butil::StringPiece ret((const char*)_data, n);
        _data = (const char*)_data + n;
        _size -= n;
        _popped_bytes += n;
        return ret;
    }
    aux->resize(n);
    size_t m = cutn(&(*aux)[0], n);
    if (m != n) {
        aux->resize(m);
    }
    return *aux;
}

inline uint8_t InputStream::peek1() {
    if (_size > 0) {
        return *(const uint8_t*)_data;
    }
    while (_zc_stream->Next(&_data, &_size)) {
        if (_size > 0) {
            return *(const uint8_t*)_data;
        }
    }
    return 0;
}

// Binary head before items of isomorphic array.
struct IsoItemsHead {
    uint8_t type;
} __attribute__((__packed__));

inline ObjectIterator UnparsedValue::as_object() {
    return ObjectIterator(_stream, _size);
}

inline ArrayIterator UnparsedValue::as_array() {
    return ArrayIterator(_stream, _size);
}

inline ISOArrayIterator UnparsedValue::as_iso_array() {
    return ISOArrayIterator(_stream, _size);
}

inline void ObjectIterator::init(InputStream* stream, size_t size) {
    _field_count = 0;
    _stream = stream;
    _expected_popped_bytes = _stream->popped_bytes() + sizeof(ItemsHead);
    _expected_popped_end = _stream->popped_bytes() + size;
    ItemsHead items_head;
    if (_stream->cut_packed_pod(&items_head) != sizeof(ItemsHead)) {
        CHECK(false) << "buffer(size=" << size << ") is not enough";
        return set_bad();
    }
    _field_count = items_head.item_count;
    operator++();
}

inline void ArrayIterator::init(InputStream* stream, size_t size) {
    _item_count = 0;
    _stream = stream;
    _expected_popped_bytes = _stream->popped_bytes() + sizeof(ItemsHead);
    _expected_popped_end = _stream->popped_bytes() + size;
    ItemsHead items_head;
    if (_stream->cut_packed_pod(&items_head) != sizeof(ItemsHead)) {
        CHECK(false) << "buffer(size=" << size << ") is not enough";
        return set_bad();
    }
    _item_count = items_head.item_count;
    operator++();
}

inline void ISOArrayIterator::init(InputStream* stream, size_t size) {
    _buf_index = 0;
    _buf_count = 0;
    _stream = stream;
    _item_type = (PrimitiveFieldType)0;
    _item_size = 0;
    _item_count = 0;
    _left_item_count = 0;
    IsoItemsHead items_head;
    if (_stream->cut_packed_pod(&items_head) != sizeof(IsoItemsHead)) {
        CHECK(false) << "Not enough data";
        return set_bad();
    }
    _item_type = (PrimitiveFieldType)items_head.type;
    _item_size = get_primitive_type_size(_item_type);
    if (!_item_size) {
        CHECK(false) << "type=" << type2str(_item_type)
                   << " in primitive isoarray is not primitive";
        return set_bad();
    }
    const size_t items_full_size = size - sizeof(IsoItemsHead);
    _item_count = items_full_size / _item_size;
    if (_item_count * _item_size != items_full_size) {
        CHECK(false) << "inconsistent item_count(" << _item_count
                   << ") and value_size(" << items_full_size
                   << "), item_size=" << _item_size;
        return set_bad();
    }
    _left_item_count = _item_count;
    operator++();
}

inline void ISOArrayIterator::operator++() {
    if (_buf_index + 1 < _buf_count) {
        ++_buf_index;
        return;
    }
    // Iterate all items in isomorphic array. We have to do this
    // right here because the items are lacking of headings.
    // Call on_primitives in batch to reduce overhead.
    if (_left_item_count == 0) {
        set_end();
        return;
    }
    _buf_count = std::min((uint32_t)sizeof(_item_buf) / _item_size, _left_item_count);
    _buf_index = 0;
    if (_stream->cutn(_item_buf, _buf_count * _item_size) !=
        _buf_count * _item_size) {
        CHECK(false) << "Not enough data";
        return set_bad();
    }
    _left_item_count -= _buf_count;
}

template <typename T>
inline T ISOArrayIterator::as_integer() const {
    const void* ptr = (_item_buf + _buf_index * _item_size);
    switch ((PrimitiveFieldType)_item_type) {
    case PRIMITIVE_FIELD_INT8:
        return *static_cast<const int8_t*>(ptr);
    case PRIMITIVE_FIELD_INT16:
        return *static_cast<const int16_t*>(ptr);
    case PRIMITIVE_FIELD_INT32:
        return *static_cast<const int32_t*>(ptr);
    case PRIMITIVE_FIELD_INT64:
        return *static_cast<const int64_t*>(ptr);
    case PRIMITIVE_FIELD_UINT8:
        return *static_cast<const uint8_t*>(ptr);
    case PRIMITIVE_FIELD_UINT16:
        return *static_cast<const uint16_t*>(ptr);
    case PRIMITIVE_FIELD_UINT32:
        return *static_cast<const uint32_t*>(ptr);
    case PRIMITIVE_FIELD_UINT64:
        return *static_cast<const uint64_t*>(ptr);
    case PRIMITIVE_FIELD_BOOL:
        return *static_cast<const bool*>(ptr);
    case PRIMITIVE_FIELD_FLOAT:
        return 0;
    case PRIMITIVE_FIELD_DOUBLE:
        return 0;
    }
    return 0;
}

template <typename T>
inline T ISOArrayIterator::as_fp() const {
    const void* ptr = (_item_buf + _buf_index * _item_size);
    if (_item_type == PRIMITIVE_FIELD_FLOAT) {
        return *static_cast<const float*>(ptr);
    } else if (_item_type == PRIMITIVE_FIELD_DOUBLE) {
        return *static_cast<const double*>(ptr);
    }
    return T();
}

}  // namespace mcpack2pb

#endif  // MCPACK2PB_MCPACK_PARSER_INL_H