// 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.

// iobuf - A non-continuous zero-copied buffer

// Author: Ge,Jun (gejun@baidu.com)
// Date: Thu Nov 22 13:57:56 CST 2012

// Inlined implementations of some methods defined in iobuf.h

#ifndef BUTIL_IOBUF_INL_H
#define BUTIL_IOBUF_INL_H

void* fast_memcpy(void *__restrict dest, const void *__restrict src, size_t n);

namespace butil {

inline ssize_t IOBuf::cut_into_file_descriptor(int fd, size_t size_hint) {
    return pcut_into_file_descriptor(fd, -1, size_hint);
}

inline ssize_t IOBuf::cut_multiple_into_file_descriptor(
    int fd, IOBuf* const* pieces, size_t count) {
    return pcut_multiple_into_file_descriptor(fd, -1, pieces, count);
}

inline ssize_t IOPortal::append_from_file_descriptor(int fd, size_t max_count) {
    return pappend_from_file_descriptor(fd, -1, max_count);
}

inline void IOPortal::return_cached_blocks() {
    if (_block) {
        return_cached_blocks_impl(_block);
        _block = NULL;
    }
}

inline void reset_block_ref(IOBuf::BlockRef& ref) {
    ref.offset = 0;
    ref.length = 0;
    ref.block = NULL;
}

inline IOBuf::IOBuf() {
    reset_block_ref(_sv.refs[0]);
    reset_block_ref(_sv.refs[1]);
}

inline IOBuf::IOBuf(const Movable& rhs) {
    _sv = rhs.value()._sv;
    new (&rhs.value()) IOBuf;
}

inline void IOBuf::operator=(const Movable& rhs) {
    clear();
    _sv = rhs.value()._sv;
    new (&rhs.value()) IOBuf;
}

inline void IOBuf::operator=(const char* s) {
    clear();
    append(s);
}

inline void IOBuf::operator=(const std::string& s) {
    clear();
    append(s);
}

inline void IOBuf::swap(IOBuf& other) {
    const SmallView tmp = other._sv;
    other._sv = _sv;
    _sv = tmp;
}

inline int IOBuf::cut_until(IOBuf* out, char const* delim) {
    if (*delim) {
        if (!*(delim+1)) {
            return _cut_by_char(out, *delim);
        } else {
            return _cut_by_delim(out, delim, strlen(delim));
        }
    }
    return -1;
}

inline int IOBuf::cut_until(IOBuf* out, const std::string& delim) {
    if (delim.length() == 1UL) {
        return _cut_by_char(out, delim[0]);
    } else if (delim.length() > 1UL) {
        return _cut_by_delim(out, delim.data(), delim.length());
    } else {
        return -1;
    }
}

inline int IOBuf::append(const std::string& s) {
    return append(s.data(), s.length());
}

inline std::string IOBuf::to_string() const {
    std::string s;
    copy_to(&s);
    return s;
}

inline bool IOBuf::empty() const {
    return _small() ? !_sv.refs[0].block : !_bv.nbytes;
}

inline size_t IOBuf::length() const {
    return _small() ?
        (_sv.refs[0].length + _sv.refs[1].length) : _bv.nbytes;
}

inline bool IOBuf::_small() const {
    return _bv.magic >= 0;
}

inline size_t IOBuf::_ref_num() const {
    return _small()
        ? (!!_sv.refs[0].block + !!_sv.refs[1].block) : _bv.nref;
}

inline IOBuf::BlockRef& IOBuf::_front_ref() {
    return _small() ? _sv.refs[0] : _bv.refs[_bv.start];
}

inline const IOBuf::BlockRef& IOBuf::_front_ref() const {
    return _small() ? _sv.refs[0] : _bv.refs[_bv.start];
}

inline IOBuf::BlockRef& IOBuf::_back_ref() {
    return _small() ? _sv.refs[!!_sv.refs[1].block] : _bv.ref_at(_bv.nref - 1);
}

inline const IOBuf::BlockRef& IOBuf::_back_ref() const {
    return _small() ? _sv.refs[!!_sv.refs[1].block] : _bv.ref_at(_bv.nref - 1);
}

inline IOBuf::BlockRef& IOBuf::_ref_at(size_t i) {
    return _small() ? _sv.refs[i] : _bv.ref_at(i);
}

inline const IOBuf::BlockRef& IOBuf::_ref_at(size_t i) const {
    return _small() ? _sv.refs[i] : _bv.ref_at(i);
}

inline const IOBuf::BlockRef* IOBuf::_pref_at(size_t i) const {
    if (_small()) {
        return i < (size_t)(!!_sv.refs[0].block + !!_sv.refs[1].block) ? &_sv.refs[i] : NULL;
    } else {
        return i < _bv.nref ? &_bv.ref_at(i) : NULL;
    }
}

inline bool operator==(const IOBuf::BlockRef& r1, const IOBuf::BlockRef& r2) {
    return r1.offset == r2.offset && r1.length == r2.length &&
        r1.block == r2.block;
}
        
inline bool operator!=(const IOBuf::BlockRef& r1, const IOBuf::BlockRef& r2) {
    return !(r1 == r2);
}

inline void IOBuf::_push_back_ref(const BlockRef& r) {
    if (_small()) {
        return _push_or_move_back_ref_to_smallview<false>(r);
    } else {
        return _push_or_move_back_ref_to_bigview<false>(r);
    }
}

inline void IOBuf::_move_back_ref(const BlockRef& r) {
    if (_small()) {
        return _push_or_move_back_ref_to_smallview<true>(r);
    } else {
        return _push_or_move_back_ref_to_bigview<true>(r);
    }
}

////////////////  IOBufCutter ////////////////
inline size_t IOBufCutter::remaining_bytes() const {
    if (_block) {
        return (char*)_data_end - (char*)_data + _buf->size() - _buf->_front_ref().length;
    } else {
        return _buf->size();
    }
}

inline bool IOBufCutter::cut1(void* c) {
    if (_data == _data_end) {
        if (!load_next_ref()) {
            return false;
        }
    }
    *(char*)c = *(const char*)_data;
    _data = (char*)_data + 1;
    return true;
}

inline const void* IOBufCutter::fetch1() {
    if (_data == _data_end) {
        if (!load_next_ref()) {
            return NULL;
        }
    }
    return _data;
}

inline size_t IOBufCutter::copy_to(void* out, size_t n) {
    size_t size = (char*)_data_end - (char*)_data;
    if (n <= size) {
        memcpy(out, _data, n);
        return n;
    }
    return slower_copy_to(out, n);
}

inline size_t IOBufCutter::pop_front(size_t n) {
    const size_t saved_n = n;
    do {
        const size_t size = (char*)_data_end - (char*)_data;
        if (n <= size) {
            _data = (char*)_data + n;
            return saved_n;
        }
        if (size != 0) {
            n -= size;
        }
        if (!load_next_ref()) {
            return saved_n;
        }
    } while (true);
}

inline size_t IOBufCutter::cutn(std::string* out, size_t n) {
    if (n == 0) {
        return 0;
    }
    const size_t len = remaining_bytes();
    if (n > len) {
        n = len;
    }
    const size_t old_size = out->size();
    out->resize(out->size() + n);
    return cutn(&(*out)[old_size], n);
}

/////////////// IOBufAppender /////////////////
inline int IOBufAppender::append(const void* src, size_t n) {
    do {
        const size_t size = (char*)_data_end - (char*)_data;
        if (n <= size) {
            memcpy(_data, src, n);
            _data = (char*)_data + n;
            return 0;
        }
        if (size != 0) {
            memcpy(_data, src, size);
            src = (const char*)src + size;
            n -= size;
        }
        if (add_block() != 0) {
            return -1;
        }
    } while (true);
}

inline int IOBufAppender::append(const StringPiece& str) {
    return append(str.data(), str.size());
}

inline int IOBufAppender::push_back(char c) {
    if (_data == _data_end) {
        if (add_block() != 0) {
            return -1;
        }
    }
    char* const p = (char*)_data;
    *p = c;
    _data = p + 1;
    return 0;
}

inline int IOBufAppender::add_block() {
    int size = 0;
    if (_zc_stream.Next(&_data, &size)) {
        _data_end = (char*)_data + size;
        return 0;
    }
    _data = NULL;
    _data_end = NULL;
    return -1;
}

inline void IOBufAppender::shrink() {
    const size_t size = (char*)_data_end - (char*)_data;
    if (size != 0) {
        _zc_stream.BackUp(size);
        _data = NULL;
        _data_end = NULL;
    }
}

inline IOBufBytesIterator::IOBufBytesIterator(const butil::IOBuf& buf)
    : _block_begin(NULL), _block_end(NULL), _block_count(0), 
      _bytes_left(buf.length()), _buf(&buf) {
    try_next_block();
}

inline IOBufBytesIterator::IOBufBytesIterator(const IOBufBytesIterator& it)
    : _block_begin(it._block_begin)
    , _block_end(it._block_end)
    , _block_count(it._block_count)
    , _bytes_left(it._bytes_left)
    , _buf(it._buf) {
}

inline IOBufBytesIterator::IOBufBytesIterator(
    const IOBufBytesIterator& it, size_t bytes_left)
    : _block_begin(it._block_begin)
    , _block_end(it._block_end)
    , _block_count(it._block_count)
    , _bytes_left(bytes_left)
    , _buf(it._buf) {
    //CHECK_LE(_bytes_left, it._bytes_left);
    if (_block_end > _block_begin + _bytes_left) {
        _block_end = _block_begin + _bytes_left;
    }
}

inline void IOBufBytesIterator::try_next_block() {
    if (_bytes_left == 0) {
        return;
    }
    butil::StringPiece s = _buf->backing_block(_block_count++);
    _block_begin = s.data();
    _block_end = s.data() + std::min(s.size(), (size_t)_bytes_left);
}

inline void IOBufBytesIterator::operator++() {
    ++_block_begin;
    --_bytes_left;
    if (_block_begin == _block_end) {
        try_next_block();
    }
}

inline size_t IOBufBytesIterator::copy_and_forward(void* buf, size_t n) {
    size_t nc = 0;
    while (nc < n && _bytes_left != 0) {
        const size_t block_size = _block_end - _block_begin;
        const size_t to_copy = std::min(block_size, n - nc);
        memcpy((char*)buf + nc, _block_begin, to_copy);
        _block_begin += to_copy;
        _bytes_left -= to_copy;
        nc += to_copy;
        if (_block_begin == _block_end) {
            try_next_block();
        }
    }
    return nc;
}

inline size_t IOBufBytesIterator::copy_and_forward(std::string* s, size_t n) {
    bool resized = false;
    if (s->size() < n) {
        resized = true;
        s->resize(n);
    }
    const size_t nc = copy_and_forward(const_cast<char*>(s->data()), n);
    if (nc < n && resized) {
        s->resize(nc);
    }
    return nc;
}

inline size_t IOBufBytesIterator::forward(size_t n) {
    size_t nc = 0;
    while (nc < n && _bytes_left != 0) {
        const size_t block_size = _block_end - _block_begin;
        const size_t to_copy = std::min(block_size, n - nc);
        _block_begin += to_copy;
        _bytes_left -= to_copy;
        nc += to_copy;
        if (_block_begin == _block_end) {
            try_next_block();
        }
    }
    return nc;
}

}  // namespace butil

#endif  // BUTIL_IOBUF_INL_H