// ============================================================= //
//                                                               //
//   File      : arb_strbuf.h                                    //
//   Purpose   : "unlimited" output buffer                       //
//                                                               //
//   Institute of Microbiology (Technical University Munich)     //
//   http://www.arb-home.de/                                     //
//                                                               //
// ============================================================= //

#ifndef ARB_STRBUF_H
#define ARB_STRBUF_H

#ifndef ARBTOOLS_H
#include <arbtools.h>
#endif
#ifndef ARB_ASSERT_H
#include <arb_assert.h>
#endif
#ifndef ARB_MEM_H
#include "arb_mem.h"
#endif
#ifndef ATTRIBUTES_H
#include <attributes.h>
#endif
#ifndef ARB_STRING_H
#include "arb_string.h"
#endif
#ifndef _GLIBCXX_ALGORITHM
#include <algorithm>
#endif


// -----------------------
//      String streams

class GBS_strstruct : virtual Noncopyable {
    char   *data;
    size_t  buffer_size;
    size_t  pos;

    void set_pos(size_t toPos) {
        pos = toPos;
        if (data) data[pos] = 0;
    }
    void inc_pos(size_t inc) { set_pos(pos+inc); }

    char *release_mem(size_t& size) {
        /*! take ownership of buffer and retrieve size of buffer (not size of content!!!) */
        char *result = data;
        size         = buffer_size;
        buffer_size  = 0;
        data         = NULp;
        return result;
    }
    void assign_mem(char *block, size_t blocksize) {
        free(data);

        arb_assert(block && blocksize>0);

        data      = block;
        buffer_size = blocksize;

        erase();
    }

    void alloc_mem(size_t blocksize) {
        arb_assert(blocksize>0);
        arb_assert(!data);

        assign_mem(ARB_alloc<char>(blocksize), blocksize);
    }
    void realloc_mem(size_t newsize) {
        if (!data) alloc_mem(newsize);
        else {
            // cppcheck-suppress memleakOnRealloc
            ARB_realloc(data, newsize);
            buffer_size = newsize;

            arb_assert(pos<newsize);
        }
    }
    void ensure_mem(size_t needed_size) {
        // ensures insertion of 'needed_size' bytes is ok
        size_t whole_needed_size = pos+needed_size+1;
        if (buffer_size<whole_needed_size) {
            size_t next_size = (whole_needed_size * 3) >> 1;
            realloc_mem(next_size);
        }
    }

public:

    GBS_strstruct() :
        data(NULp),
        buffer_size(0),
        pos(0)
    {}
    GBS_strstruct(size_t buffersize) :
        data(NULp),
        buffer_size(0),
        pos(0)
    {
        alloc_mem(buffersize);
    }
    ~GBS_strstruct() { free(data); }

    size_t get_buffer_size() const {
        /*! returns the current buffer size. */
        return buffer_size;
    }
    size_t get_position() const {
        /*! returns the current content size. */
        return pos;
    }

    bool filled() const { return get_position()>0; }
    bool empty() const { return !filled(); }

    const char *get_data() const {
        /*! get content as const char*.
         * Result is valid as long as 'this' exists and no reallocation takes place. */
        return null2empty(data);
    }
    char *get_copy() const {
        /*! return a heap copy of the current content of the buffer. */
        return ARB_strndup(get_data(), get_position());
    }
    char *release() {
        /*! take ownership of current buffer. might be oversized. */
        size_t s; return release_mem(s);
    }
    char *release_memfriendly() {
        /*! like release(), but shrink memory block if too oversized. */
        if (buffer_size > (pos*2) && buffer_size>1000) {
            realloc_mem(pos+1);
        }
        return release();
    }

    void erase() {
        /*! erase buffer */
        set_pos(0);
    }
    void cut_tail(size_t byte_count) {
        /*! Removes 'byte_count' characters from the tail of the content */
        set_pos(pos<byte_count ? 0 : pos-byte_count);
    }

    void cut(const size_t at, const size_t byte_count) {
        if (byte_count>0) {
            const size_t keepFrom = at+byte_count;

            if (keepFrom <= get_position()) {
                const size_t restlen = get_position()-keepFrom;
                memmove(data+at, data+keepFrom, restlen+1);

                set_pos(get_position()-byte_count);
            }
            else if (at < get_position()) {
                set_pos(at);
            }
        }
    }

    void swap_content(GBS_strstruct& other) {
        std::swap(data, other.data);
        std::swap(buffer_size, other.buffer_size);
        std::swap(pos, other.pos);
    }

    // --------------------

    void put(char c) {
        /*! append single character to content */
        ensure_mem(1);
        data[pos] = c;
        inc_pos(1);
    }
    void nput(char c, size_t count) {
        /*! append a character repeatedly to content */
        ensure_mem(count);
        if (count) {
            memset(data+pos, c, count);
            inc_pos(count);
        }
    }

    void ncat(const char *from, size_t count) {
        /*! append 'count' bytes of 'from' to content.
         * (caution : copies zero byte and mem behind if used with wrong len!)
         */
        if (count) {
            ensure_mem(count);
            memcpy(data+pos, from, count);
            inc_pos(count);
        }
    }
    void cat(const char *from) {
        /*! append whole string to content */
        ncat(from, strlen(from));
    }

    int ncatPadded(const char *from, size_t count, size_t paddedWidth) {
        /*! like catPadded(), when strlen is known or when only a prefix of 'from' shall be concatenated. */
        ensure_mem(std::max(count, paddedWidth));
        ncat(from, count);
        int toPad = long(paddedWidth)-long(count);
        if (toPad>0) nput(' ', toPad);
        return toPad;
    }
    int catPadded(const char *from, size_t paddedWidth) {
        /*! concatenate 'from' (will not be truncated if paddedWidth is smaller than 'strlen(from)').
         * Append spaces to pad to 'paddedWidth'.
         * Return number of spaces appended or a negative number ('strlen(from)-paddedWidth').
         */
        return ncatPadded(from, strlen(from), paddedWidth);
    }

    void npaste_at(size_t at, const char *from, size_t count) {
        /*! copy 'count' bytes from 'from' over existing content starting at offset 'at'.
         * Not meant to operate over end of content. Will fail assertion in that case.
         */
        size_t till = at+count-1;
        if (get_position() <= till) {
            arb_assert(0); // attempt to paste over end-of-buffer. this is most-likely by mistake
            // fallback code:
            nput(' ', till+1-get_position()); // increase string to allow paste
        }
        memcpy(data+at, from, count);
    }
    void paste_at(size_t at, const char *from) {
        /*! like npaste_at(), but always use complete string 'from' */
        npaste_at(at, from, strlen(from));
    }

    void vnprintf(size_t maxlen, const char *templat, va_list& parg) __ATTR__VFORMAT_MEMBER(2);
    void nprintf(size_t maxlen, const char *templat, ...) __ATTR__FORMAT_MEMBER(2);

    void putlong(long l) { nprintf(100, "%li", l); }
    void putfloat(float f) { nprintf(100, "%f", f); }

    // wrapped cats:
    void cat_wrapped(const char *in, const char *from) {
        /*! cat string 'from' wrapped into the 2 characters provided in 'in'. */
        arb_assert(in[0] && in[1] && !in[2]); // 'in' has to be exactly 2 chars
        put(in[0]);
        cat(from);
        put(in[1]);
    }
    void cat_sQuoted(const char *from) { cat_wrapped("''", from); }
    void cat_dQuoted(const char *from) { cat_wrapped("\"\"", from); }
};

#else
#error arb_strbuf.h included twice
#endif // ARB_STRBUF_H
