#pragma once

#include "guid.h"

#include <algorithm>
#include <array>

#include <util/generic/strbuf.h>

namespace NYT {

////////////////////////////////////////////////////////////////////////////////

//! A dead-simple string formatter.
/*!
 *  This formatter is intended to be as simple as possible and async signal safe.
 *  This is the reason we do not use printf(): it does not meet signal-safety
 *  requirements.
 */

class TBaseFormatter
{
public:
    TBaseFormatter(char* buffer, int length)
        : Begin_(buffer)
        , Cursor_(buffer)
        , End_(buffer + length)
    { }

    //! Returns an underlying cursor.
    char* GetCursor()
    {
        return Cursor_;
    }

    //! Returns an pointer to the underlying buffer.
    const char* GetData() const
    {
        return Begin_;
    }

    //! Returns the number of bytes written in the buffer.
    int GetBytesWritten() const
    {
        return Cursor_ - Begin_;
    }

    //! Returns the number of bytes available in the buffer.
    int GetBytesRemaining() const
    {
        return End_ - Cursor_;
    }

    //! Advances the internal cursor #count symbols forward (assuming the data is already present).
    void Advance(int count)
    {
        Cursor_ += count;

        if (Cursor_ > End_) {
            Cursor_ = End_;
        }
    }

    //! Drops trailing #count symbols (assuming these are present).
    void Revert(int count)
    {
        Cursor_ -= count;
    }

    //! Appends the string and updates the internal cursor.
    void AppendString(const char* string)
    {
        while (*string != '\0' && Cursor_ < End_) {
            *Cursor_++ = *string++;
        }
    }

    //! Appends the string and updates the internal cursor.
    void AppendString(TStringBuf string)
    {
        size_t position = 0;
        while (position < string.length() && Cursor_ < End_) {
            *Cursor_++ = string[position++];
        }
    }

    //! Appends a single character given number of times and updates the internal cursor.
    void AppendChar(char ch, int count = 1)
    {
        while (Cursor_ < End_ && count > 0) {
            *Cursor_++ = ch;
            count--;
        }
    }

    //! Formats |number| in base |radix| and updates the internal cursor.
    void AppendNumber(uintptr_t number, int radix = 10, int width = 0, char ch = ' ')
    {
        int digits = 0;

        width = std::min(width, GetBytesRemaining());

        if (radix == 16) {
            // Optimize output of hex numbers.

            uintptr_t reverse = 0;
            int length = 0;
            do {
                reverse <<= 4;
                reverse |= number & 0xf;
                number >>= 4;
                ++length;
            } while (number > 0);

            for (int index = 0; index < length && Cursor_ + digits < End_; ++index) {
                unsigned int modulus = reverse & 0xf;
                Cursor_[digits] = (modulus < 10 ? '0' + modulus : 'a' + modulus - 10);
                ++digits;
                reverse >>= 4;
            }
        } else {
            while (Cursor_ + digits < End_) {
                const int modulus = number % radix;
                number /= radix;
                Cursor_[digits] = (modulus < 10 ? '0' + modulus : 'a' + modulus - 10);
                ++digits;
                if (number == 0) {
                    break;
                }
            }

            // Reverse the bytes written.
            std::reverse(Cursor_, Cursor_ + digits);
        }

        if (digits < width) {
            auto delta = width - digits;
            std::copy(Cursor_, Cursor_ + digits, Cursor_ + delta);
            std::fill(Cursor_, Cursor_ + delta, ch);
            Cursor_ += width;
        } else {
            Cursor_ += digits;
        }
    }

    //! Formats |guid| and updates the internal cursor.
    void AppendGuid(TGuid guid)
    {
        if (Y_LIKELY(End_ - Cursor_ >= MaxGuidStringSize)) {
            // Fast path.
            Cursor_ = WriteGuidToBuffer(Cursor_, guid);
        } else {
            // Slow path.
            std::array<char, MaxGuidStringSize> buffer;
            auto* end = WriteGuidToBuffer(buffer.data(), guid);
            AppendString(TStringBuf(buffer.data(), end));
        }
    }

    //! Resets the underlying cursor.
    void Reset()
    {
        Cursor_ = Begin_;
    }

    TStringBuf GetBuffer() const
    {
        return {Begin_, Cursor_};
    }

private:
    char* const Begin_;
    char* Cursor_;
    char* const End_;
};

template <size_t N>
class TRawFormatter
    : public TBaseFormatter
{
public:
    TRawFormatter()
        : TBaseFormatter(Buffer_, N)
    { }

    TRawFormatter(char* buffer, int length)
        : TBaseFormatter(buffer, length)
    { }

private:
    char Buffer_[N];
};

////////////////////////////////////////////////////////////////////////////////

} // namespace NYT