/*************************************************************************
 *
 * Copyright 2016 Realm 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.
 *
 **************************************************************************/

#ifndef REALM_IMPL_TRANSACT_LOG_HPP
#define REALM_IMPL_TRANSACT_LOG_HPP

#include <realm/binary_data.hpp>
#include <realm/collection.hpp>
#include <realm/data_type.hpp>
#include <realm/group.hpp>
#include <realm/string_data.hpp>
#include <realm/util/buffer.hpp>
#include <realm/util/input_stream.hpp>

namespace realm {

struct GlobalKey;

namespace _impl {

/// Transaction log instruction encoding
/// NOTE: Any change to this enum is a file-format breaking change.
enum Instruction {
    instr_InsertGroupLevelTable = 1,
    instr_EraseGroupLevelTable = 2, // Remove columnless table from group
    instr_RenameGroupLevelTable = 3,

    instr_SelectTable = 10,
    instr_CreateObject = 11,
    instr_RemoveObject = 12,
    instr_Set = 13,
    instr_SetDefault = 14,
    // instr_ClearTable = 15, Remove all rows in selected table  (unused from file format 11)

    instr_InsertColumn = 20, // Insert new column into to selected descriptor
    instr_EraseColumn = 21,  // Remove column from selected descriptor
    instr_RenameColumn = 22, // Rename column in selected descriptor
    // instr_SetLinkType = 23,  Strong/weak (unused from file format 11)

    instr_SelectCollection = 30,
    instr_CollectionInsert = 31, // Insert collection entry
    instr_CollectionSet = 32,    // Assign to collection entry
    instr_CollectionMove = 33,   // Move an entry within an ordered collection
    // instr_ListSwap = 34,   Swap two entries within a list (unused from file format 11)
    instr_CollectionErase = 35, // Remove an entry from a collection
    instr_CollectionClear = 36, // Remove all entries from a collection

    // No longer emitted, but supported for a file shared with an older version.
    // Treated identically to the Collection versions.
    instr_DictionaryInsert = 37,
    instr_DictionarySet = 38,
    instr_DictionaryErase = 39,
    instr_SetInsert = 40,
    instr_SetErase = 41,
    instr_SetClear = 42,

    // An action involving TypedLinks has occurred which caused
    // the number of backlink columns to change. This can happen
    // when a TypedLink is created for the first time to a Table.
    instr_TypedLinkChange = 43,

    // dictionary clear should be moved up with the other instructions once we
    // release the next file format breaking change
    instr_DictionaryClear = 44,

    // This instruction includes a path to the collection
    instr_SelectCollectionByPath = 45,
};

class TransactLogStream {
public:
    virtual ~TransactLogStream() {}

    /// Ensure contiguous free space in the transaction log
    /// buffer. This method must update `out_free_begin`
    /// and `out_free_end` such that they refer to a chunk
    /// of free space whose size is at least \a n.
    ///
    /// \param size The required amount of contiguous free space. Must be
    /// small (probably not greater than 1024)
    /// \param out_free_begin must point to current write position which must be inside earlier
    /// allocated area. Will be updated to point to new writing position.
    /// \param out_free_end Will be updated to point to end of allocated area.
    virtual void transact_log_reserve(size_t size, char** out_free_begin, char** out_free_end) = 0;

    /// Copy the specified data into the transaction log buffer. This
    /// function should be called only when the specified data does
    /// not fit inside the chunk of free space currently referred to
    /// by `out_free_begin` and `out_free_end`.
    ///
    /// This method must update `out_begin` and
    /// `out_end` such that, upon return, they still
    /// refer to a (possibly empty) chunk of free space.
    virtual void transact_log_append(const char* data, size_t size, char** out_free_begin, char** out_free_end) = 0;
};

class TransactLogBufferStream : public TransactLogStream {
public:
    void transact_log_reserve(size_t size, char** out_free_begin, char** out_free_end) override;
    void transact_log_append(const char* data, size_t size, char** out_free_begin, char** out_free_end) override;

    const char* get_data() const;
    char* get_data();
    size_t get_size();

private:
    util::Buffer<char> m_buffer;
};


// LCOV_EXCL_START (because the NullInstructionObserver is trivial)
class NullInstructionObserver {
public:
    /// The following methods are also those that TransactLogParser expects
    /// to find on the `InstructionHandler`.

    // No selection needed:
    bool select_table(TableKey)
    {
        return true;
    }
    bool select_collection(ColKey, ObjKey, const StablePath&)
    {
        return true;
    }
    bool insert_group_level_table(TableKey)
    {
        return true;
    }
    bool erase_class(TableKey)
    {
        return true;
    }
    bool rename_class(TableKey)
    {
        return true;
    }
    bool typed_link_change(ColKey, TableKey)
    {
        return true;
    }

    // Must have table selected:
    bool create_object(ObjKey)
    {
        return true;
    }
    bool remove_object(ObjKey)
    {
        return true;
    }
    bool modify_object(ColKey, ObjKey)
    {
        return true;
    }

    // Must have descriptor selected:
    bool insert_column(ColKey)
    {
        return true;
    }
    bool erase_column(ColKey)
    {
        return true;
    }
    bool rename_column(ColKey)
    {
        return true;
    }
    bool set_link_type(ColKey)
    {
        return true;
    }

    // Must have collection selected:
    bool collection_set(size_t)
    {
        return true;
    }
    bool collection_insert(size_t)
    {
        return true;
    }
    bool collection_move(size_t, size_t)
    {
        return true;
    }
    bool collection_erase(size_t)
    {
        return true;
    }
    bool collection_clear(size_t)
    {
        return true;
    }

    void parse_complete() {}
};
// LCOV_EXCL_STOP (NullInstructionObserver)


/// See Replication for information about the meaning of the
/// arguments of each of the functions in this class.
class TransactLogEncoder {
public:
    /// The following methods are also those that TransactLogParser expects
    /// to find on the `InstructionHandler`.

    // No selection needed:
    bool select_table(TableKey key);
    bool insert_group_level_table(TableKey table_key);
    bool erase_class(TableKey table_key);
    bool rename_class(TableKey table_key);

    /// Must have table selected.
    bool create_object(ObjKey key)
    {
        append_simple_instr(instr_CreateObject, key); // Throws
        return true;
    }

    bool remove_object(ObjKey key)
    {
        append_simple_instr(instr_RemoveObject, key); // Throws
        return true;
    }
    bool modify_object(ColKey col_key, ObjKey key);

    // Must have descriptor selected:
    bool insert_column(ColKey col_key);
    bool erase_column(ColKey col_key);
    bool rename_column(ColKey col_key);
    bool set_link_type(ColKey col_key);

    // Must have collection selected:
    bool select_collection(ColKey col_key, ObjKey key, const StablePath& path);
    bool collection_set(size_t collection_ndx);
    bool collection_insert(size_t ndx);
    bool collection_move(size_t from_ndx, size_t to_ndx);
    bool collection_erase(size_t collection_ndx);
    bool collection_clear(size_t old_size);

    bool typed_link_change(ColKey col, TableKey dest);


    /// End of methods expected by parser.


    TransactLogEncoder(TransactLogStream& out_stream);
    void set_buffer(char* new_free_begin, char* new_free_end);
    char* write_position() const
    {
        return m_transact_log_free_begin;
    }

private:
    // Make sure this is in agreement with the actual integer encoding
    // scheme (see encode_int()).
    static constexpr int max_enc_bytes_per_int = 10;
// Space is reserved in chunks to avoid excessive over allocation.
#ifdef REALM_DEBUG
    static constexpr int max_numbers_per_chunk = 2; // Increase the chance of chunking in debug mode
#else
    static constexpr int max_numbers_per_chunk = 8;
#endif

    TransactLogStream& m_stream;

    // These two delimit a contiguous region of free space in a
    // transaction log buffer following the last written data. It may
    // be empty.
    char* m_transact_log_free_begin = nullptr;
    char* m_transact_log_free_end = nullptr;

    char* reserve(size_t size);
    /// \param ptr Must be in the range [m_transact_log_free_begin, m_transact_log_free_end]
    void advance(char* ptr) noexcept;

    template <class T>
    size_t max_size(T);

    size_t max_size_list()
    {
        return 0;
    }

    template <class T, class... Args>
    size_t max_size_list(T val, Args... args)
    {
        return max_size(val) + max_size_list(args...);
    }

    template <class T>
    char* encode(char* ptr, T value);

    char* encode_list(char* ptr)
    {
        advance(ptr);
        return ptr;
    }

    template <class T, class... Args>
    char* encode_list(char* ptr, T value, Args... args)
    {
        return encode_list(encode(ptr, value), args...);
    }

    template <class... L>
    void append_simple_instr(L... numbers);

    template <typename... L>
    void append_string_instr(Instruction, StringData);

    template <class T>
    static char* encode_int(char*, T value);

    void encode_string(StringData string);

    friend class TransactLogParser;
};


class TransactLogParser {
public:
    /// See `TransactLogEncoder` for a list of methods that the `InstructionHandler` must define.
    template <class InstructionHandler>
    void parse(util::InputStream&, InstructionHandler&);

private:
    util::Buffer<char> m_input_buffer{1024};

    // The input stream is assumed to consist of chunks of memory organised such that
    // every instruction resides in a single chunk only.
    util::InputStream* m_input;
    // pointer into transaction log, each instruction is parsed from m_input_begin and onwards.
    // Each instruction are assumed to be contiguous in memory.
    const char* m_input_begin;
    // pointer to one past current instruction log chunk. If m_input_begin reaches m_input_end,
    // a call to next_input_buffer will move m_input_begin and m_input_end to a new chunk of
    // memory. Setting m_input_end to 0 disables this check, and is used if it is already known
    // that all of the instructions are in memory.
    const char* m_input_end;
    std::string m_string_buffer;

    REALM_COLD REALM_NORETURN void parser_error() const;

    template <class InstructionHandler>
    void parse_one(InstructionHandler&);
    bool has_next() noexcept;

    template <class T>
    T read_int();

    void read_bytes(char* data, size_t size);
    BinaryData read_buffer(std::string&, size_t size);

    StringData read_string(std::string&);

    // Advance m_input_begin and m_input_end to reflect the next block of instructions
    // Returns false if no more input was available
    bool next_input_buffer();

    // return true if input was available
    bool read_char(char&); // throws
};


/// Implementation:

inline void TransactLogBufferStream::transact_log_reserve(size_t size, char** inout_new_begin, char** out_new_end)
{
    char* data = m_buffer.data();
    REALM_ASSERT(*inout_new_begin >= data);
    REALM_ASSERT(*inout_new_begin <= (data + m_buffer.size()));
    size_t used_size = *inout_new_begin - data;
    m_buffer.reserve_extra(used_size, size);
    data = m_buffer.data(); // May have changed
    *inout_new_begin = data + used_size;
    *out_new_end = data + m_buffer.size();
}

inline void TransactLogBufferStream::transact_log_append(const char* data, size_t size, char** out_new_begin,
                                                         char** out_new_end)
{
    transact_log_reserve(size, out_new_begin, out_new_end);
    *out_new_begin = realm::safe_copy_n(data, size, *out_new_begin);
}

inline const char* TransactLogBufferStream::get_data() const
{
    return m_buffer.data();
}

inline char* TransactLogBufferStream::get_data()
{
    return m_buffer.data();
}

inline size_t TransactLogBufferStream::get_size()
{
    return m_buffer.size();
}

inline TransactLogEncoder::TransactLogEncoder(TransactLogStream& stream)
    : m_stream(stream)
{
}

inline void TransactLogEncoder::set_buffer(char* free_begin, char* free_end)
{
    REALM_ASSERT(free_begin <= free_end);
    m_transact_log_free_begin = free_begin;
    m_transact_log_free_end = free_end;
}

inline char* TransactLogEncoder::reserve(size_t n)
{
    if (size_t(m_transact_log_free_end - m_transact_log_free_begin) < n) {
        m_stream.transact_log_reserve(n, &m_transact_log_free_begin, &m_transact_log_free_end);
    }
    return m_transact_log_free_begin;
}

inline void TransactLogEncoder::advance(char* ptr) noexcept
{
    REALM_ASSERT_DEBUG(m_transact_log_free_begin <= ptr);
    REALM_ASSERT_DEBUG(ptr <= m_transact_log_free_end);
    m_transact_log_free_begin = ptr;
}


// The integer encoding is platform independent. Also, it does not
// depend on the type of the specified integer. Integers of any type
// can be encoded as long as the specified buffer is large enough (see
// below). The decoding does not have to use the same type. Decoding
// will fail if, and only if the encoded value falls outside the range
// of the requested destination type.
//
// The encoding uses one or more bytes. It never uses more than 8 bits
// per byte. The last byte in the sequence is the first one that has
// its 8th bit set to zero.
//
// Consider a particular non-negative value V. Let W be the number of
// bits needed to encode V using the trivial binary encoding of
// integers. The total number of bytes produced is then
// ceil((W+1)/7). The first byte holds the 7 least significant bits of
// V. The last byte holds at most 6 bits of V including the most
// significant one. The value of the first bit of the last byte is
// always 2**((N-1)*7) where N is the total number of bytes.
//
// A negative value W is encoded by setting the sign bit to one and
// then encoding the positive result of -(W+1) as described above. The
// advantage of this representation is that it converts small negative
// values to small positive values which require a small number of
// bytes. This would not have been true for 2's complements
// representation, for example. The sign bit is always stored as the
// 7th bit of the last byte.
//
//               value bits    value + sign    max bytes
//     --------------------------------------------------
//     int8_t         7              8              2
//     uint8_t        8              9              2
//     int16_t       15             16              3
//     uint16_t      16             17              3
//     int32_t       31             32              5
//     uint32_t      32             33              5
//     int64_t       63             64             10
//     uint64_t      64             65             10
//
template <class T>
char* TransactLogEncoder::encode_int(char* ptr, T value)
{
    static_assert(std::numeric_limits<T>::is_integer, "Integer required");
    bool negative = value < 0;
    if (negative) {
        // The following conversion is guaranteed by C++11 to never
        // overflow (contrast this with "-value" which indeed could
        // overflow). See C99+TC3 section 6.2.6.2 paragraph 2.
        REALM_DIAG_PUSH();
        REALM_DIAG_IGNORE_UNSIGNED_MINUS();
        value = -(value + 1);
        REALM_DIAG_POP();
    }
    // At this point 'value' is always a positive number. Also, small
    // negative numbers have been converted to small positive numbers.
    REALM_ASSERT(value >= 0);
    // One sign bit plus number of value bits
    const int num_bits = 1 + std::numeric_limits<T>::digits;
    // Only the first 7 bits are available per byte. Had it not been
    // for the fact that maximum guaranteed bit width of a char is 8,
    // this value could have been increased to 15 (one less than the
    // number of value bits in 'unsigned').
    const int bits_per_byte = 7;
    const int max_bytes = (num_bits + (bits_per_byte - 1)) / bits_per_byte;
    static_assert(max_bytes <= max_enc_bytes_per_int, "Bad max_enc_bytes_per_int");
    // An explicit constant maximum number of iterations is specified
    // in the hope that it will help the optimizer (to do loop
    // unrolling, for example).
    typedef unsigned char uchar;
    for (int i = 0; i < max_bytes; ++i) {
        if (value >> (bits_per_byte - 1) == 0)
            break;
        *reinterpret_cast<uchar*>(ptr) = uchar((1U << bits_per_byte) | unsigned(value & ((1U << bits_per_byte) - 1)));
        ++ptr;
        value >>= bits_per_byte;
    }
    *reinterpret_cast<uchar*>(ptr) = uchar(negative ? (1U << (bits_per_byte - 1)) | unsigned(value) : value);
    return ++ptr;
}

template <class T>
inline char* TransactLogEncoder::encode(char* ptr, T inst)
{
    return encode_int<T>(ptr, inst);
}

template <>
inline char* TransactLogEncoder::encode<TableKey>(char* ptr, TableKey key)
{
    return encode_int<int64_t>(ptr, key.value);
}

template <>
inline char* TransactLogEncoder::encode<ColKey>(char* ptr, ColKey key)
{
    return encode_int<int64_t>(ptr, key.value);
}

template <>
inline char* TransactLogEncoder::encode<ObjKey>(char* ptr, ObjKey key)
{
    return encode_int<int64_t>(ptr, key.value);
}

template <>
inline char* TransactLogEncoder::encode<Instruction>(char* ptr, Instruction inst)
{
    return encode_int<int64_t>(ptr, inst);
}

template <class T>
size_t TransactLogEncoder::max_size(T)
{
    return max_enc_bytes_per_int;
}

template <>
inline size_t TransactLogEncoder::max_size(Instruction)
{
    return 1;
}

template <class... L>
void TransactLogEncoder::append_simple_instr(L... numbers)
{
    size_t max_required_bytes = max_size_list(numbers...);
    char* ptr = reserve(max_required_bytes); // Throws
    encode_list(ptr, numbers...);
}

template <typename... L>
void TransactLogEncoder::append_string_instr(Instruction instr, StringData string)
{
    size_t max_required_bytes = 1 + max_enc_bytes_per_int + string.size();
    char* ptr = reserve(max_required_bytes); // Throws
    *ptr++ = char(instr);
    ptr = encode(ptr, int(type_String));
    ptr = encode(ptr, size_t(string.size()));
    ptr = std::copy(string.data(), string.data() + string.size(), ptr);
    advance(ptr);
}

inline bool TransactLogEncoder::insert_group_level_table(TableKey table_key)
{
    append_simple_instr(instr_InsertGroupLevelTable, table_key); // Throws
    return true;
}

inline bool TransactLogEncoder::erase_class(TableKey table_key)
{
    append_simple_instr(instr_EraseGroupLevelTable, table_key); // Throws
    return true;
}

inline bool TransactLogEncoder::rename_class(TableKey table_key)
{
    append_simple_instr(instr_RenameGroupLevelTable, table_key); // Throws
    return true;
}

inline bool TransactLogEncoder::insert_column(ColKey col_key)
{
    append_simple_instr(instr_InsertColumn, col_key); // Throws
    return true;
}

inline bool TransactLogEncoder::erase_column(ColKey col_key)
{
    append_simple_instr(instr_EraseColumn, col_key); // Throws
    return true;
}

inline bool TransactLogEncoder::rename_column(ColKey col_key)
{
    append_simple_instr(instr_RenameColumn, col_key); // Throws
    return true;
}

inline bool TransactLogEncoder::modify_object(ColKey col_key, ObjKey key)
{
    append_simple_instr(instr_Set, col_key, key); // Throws
    return true;
}


/************************************ Collections ***********************************/

inline bool TransactLogEncoder::collection_set(size_t ndx)
{
    append_simple_instr(instr_CollectionSet, ndx); // Throws
    return true;
}

inline bool TransactLogEncoder::collection_insert(size_t ndx)
{
    append_simple_instr(instr_CollectionInsert, ndx); // Throws
    return true;
}


inline bool TransactLogEncoder::collection_move(size_t from_ndx, size_t to_ndx)
{
    // This test is to prevent some fuzzy testing on the server to crash
    if (from_ndx != to_ndx) {
        append_simple_instr(instr_CollectionMove, from_ndx, to_ndx); // Throws
    }
    return true;
}

inline bool TransactLogEncoder::collection_erase(size_t ndx)
{
    append_simple_instr(instr_CollectionErase, ndx); // Throws
    return true;
}


inline bool TransactLogEncoder::collection_clear(size_t old_size)
{
    append_simple_instr(instr_CollectionClear, old_size); // Throws
    return true;
}

inline bool TransactLogEncoder::typed_link_change(ColKey col, TableKey dest)
{
    append_simple_instr(instr_TypedLinkChange, col, dest);
    return true;
}


template <class InstructionHandler>
void TransactLogParser::parse(util::InputStream& in, InstructionHandler& handler)
{
    m_input = &in;
    m_input_begin = m_input_end = nullptr;

    while (has_next())
        parse_one(handler); // Throws
}

inline bool TransactLogParser::has_next() noexcept
{
    return m_input_begin != m_input_end || next_input_buffer();
}

template <class InstructionHandler>
void TransactLogParser::parse_one(InstructionHandler& handler)
{
    char instr_ch = 0; // silence a warning
    if (!read_char(instr_ch))
        parser_error(); // Throws
    Instruction instr = Instruction(instr_ch);
    switch (instr) {
        case instr_Set: {
            ColKey col_key = ColKey(read_int<int64_t>()); // Throws
            ObjKey key(read_int<int64_t>());              // Throws
            if (!handler.modify_object(col_key, key))     // Throws
                parser_error();
            return;
        }
        case instr_SetDefault:
            // Should not appear in the transaction log
            parser_error();
        case instr_CreateObject: {
            ObjKey key(read_int<int64_t>()); // Throws
            if (!handler.create_object(key)) // Throws
                parser_error();
            return;
        }
        case instr_RemoveObject: {
            ObjKey key(read_int<int64_t>()); // Throws
            if (!handler.remove_object(key)) // Throws
                parser_error();
            return;
        }
        case instr_SelectTable: {
            int levels = read_int<int>(); // Throws
            REALM_ASSERT(levels == 0);
            TableKey key = TableKey(read_int<uint32_t>());
            if (!handler.select_table(key)) // Throws
                parser_error();
            return;
        }
        case instr_CollectionSet: {
            size_t ndx = read_int<size_t>();
            if (!handler.collection_set(ndx)) // Throws
                parser_error();
            return;
        }
        case instr_SetInsert:
        case instr_CollectionInsert: {
            size_t ndx = read_int<size_t>();
            if (!handler.collection_insert(ndx)) // Throws
                parser_error();
            return;
        }
        case instr_CollectionMove: {
            size_t from_ndx = read_int<size_t>();           // Throws
            size_t to_ndx = read_int<size_t>();             // Throws
            if (!handler.collection_move(from_ndx, to_ndx)) // Throws
                parser_error();
            return;
        }
        case instr_SetErase:
        case instr_CollectionErase: {
            size_t ndx = read_int<size_t>();    // Throws
            if (!handler.collection_erase(ndx)) // Throws
                parser_error();
            return;
        }
        case instr_SetClear:
        case instr_DictionaryClear:
        case instr_CollectionClear: {
            size_t old_size = read_int<size_t>();    // Throws
            if (!handler.collection_clear(old_size)) // Throws
                parser_error();
            return;
        }
        case instr_DictionaryInsert: {
            int type = read_int<int>(); // Throws
            REALM_ASSERT(type == int(type_String));
            read_string(m_string_buffer);             // skip key
            size_t dict_ndx = read_int<size_t>();     // Throws
            if (!handler.collection_insert(dict_ndx)) // Throws
                parser_error();
            return;
        }
        case instr_DictionarySet: {
            int type = read_int<int>(); // Throws
            REALM_ASSERT(type == int(type_String));
            read_string(m_string_buffer);          // skip key
            size_t dict_ndx = read_int<size_t>();  // Throws
            if (!handler.collection_set(dict_ndx)) // Throws
                parser_error();
            return;
        }
        case instr_DictionaryErase: {
            int type = read_int<int>(); // Throws
            REALM_ASSERT(type == int(type_String));
            read_string(m_string_buffer);            // skip key
            size_t dict_ndx = read_int<size_t>();    // Throws
            if (!handler.collection_erase(dict_ndx)) // Throws
                parser_error();
            return;
        }
        case instr_SelectCollection:
        case instr_SelectCollectionByPath: {
            ColKey col_key = ColKey(read_int<int64_t>()); // Throws
            ObjKey key = ObjKey(read_int<int64_t>());     // Throws
            size_t nesting_level = instr == instr_SelectCollectionByPath ? read_int<uint32_t>() : 0;
            StablePath path;
            path.push_back(StableIndex(col_key, 0));
            for (size_t l = 0; l < nesting_level; l++) {
                auto ndx = read_int<int64_t>();
                path.emplace_back(ndx);
            }
            if (!handler.select_collection(col_key, key, path)) // Throws
                parser_error();
            return;
        }
        case instr_InsertColumn: {
            ColKey col_key = ColKey(read_int<int64_t>()); // Throws
            if (!handler.insert_column(col_key))          // Throws
                parser_error();
            return;
        }
        case instr_EraseColumn: {
            ColKey col_key = ColKey(read_int<int64_t>()); // Throws
            if (!handler.erase_column(col_key))           // Throws
                parser_error();
            return;
        }
        case instr_RenameColumn: {
            ColKey col_key = ColKey(read_int<int64_t>()); // Throws
            if (!handler.rename_column(col_key))          // Throws
                parser_error();
            return;
        }
        case instr_InsertGroupLevelTable: {
            TableKey table_key = TableKey(read_int<uint32_t>()); // Throws
            if (!handler.insert_group_level_table(table_key))    // Throws
                parser_error();
            return;
        }
        case instr_EraseGroupLevelTable: {
            TableKey table_key = TableKey(read_int<uint32_t>()); // Throws
            if (!handler.erase_class(table_key))                 // Throws
                parser_error();
            return;
        }
        case instr_RenameGroupLevelTable: {
            TableKey table_key = TableKey(read_int<uint32_t>()); // Throws
            if (!handler.rename_class(table_key))                // Throws
                parser_error();
            return;
        }
        case instr_TypedLinkChange: {
            ColKey col_key = ColKey(read_int<int64_t>());         // Throws
            TableKey dest_table = TableKey(read_int<uint32_t>()); // Throws
            if (!handler.typed_link_change(col_key, dest_table))
                parser_error();
            return;
        }
    }

    parser_error();
}


template <class T>
T TransactLogParser::read_int()
{
    T value = 0;
    int part = 0;
    const int max_bytes = (std::numeric_limits<T>::digits + 7) / 7;
    for (int i = 0; i <= max_bytes; ++i) {
        char c;
        if (!read_char(c))
            parser_error(); // Input ended early
        part = static_cast<unsigned char>(c);
        if (0xFF < part)
            parser_error(); // Only the first 8 bits may be used in each byte
        if ((part & 0x80) == 0) {
            T p = part & 0x3F;
            if (util::int_shift_left_with_overflow_detect(p, i * 7))
                parser_error();
            value |= p;
            break;
        }
        if (i == max_bytes - 1)
            parser_error(); // Too many bytes
        value |= T(part & 0x7F) << (i * 7);
    }
    if (part & 0x40) {
        // The real value is negative. Because 'value' is positive at
        // this point, the following negation is guaranteed by C++11
        // to never overflow. See C99+TC3 section 6.2.6.2 paragraph 2.
        REALM_DIAG_PUSH();
        REALM_DIAG_IGNORE_UNSIGNED_MINUS();
        value = -value;
        REALM_DIAG_POP();
        if (util::int_subtract_with_overflow_detect(value, 1))
            parser_error();
    }
    return value;
}

inline void TransactLogParser::read_bytes(char* data, size_t size)
{
    for (;;) {
        const size_t avail = m_input_end - m_input_begin;
        if (size <= avail)
            break;
        const char* to = m_input_begin + avail;
        std::copy(m_input_begin, to, data);
        if (!next_input_buffer())
            parser_error();
        data += avail;
        size -= avail;
    }
    const char* to = m_input_begin + size;
    std::copy(m_input_begin, to, data);
    m_input_begin = to;
}

inline BinaryData TransactLogParser::read_buffer(std::string& buf, size_t size)
{
    const size_t avail = m_input_end - m_input_begin;
    if (avail >= size) {
        m_input_begin += size;
        return BinaryData(m_input_begin - size, size);
    }

    buf.clear();
    buf.resize(size); // Throws
    read_bytes(buf.data(), size);
    return BinaryData(buf.data(), size);
}

inline StringData TransactLogParser::read_string(std::string& buf)
{
    size_t size = read_int<size_t>(); // Throws

    if (size > Table::max_string_size)
        parser_error();

    BinaryData buffer = read_buffer(buf, size);
    return StringData{buffer.data(), size};
}

inline bool TransactLogParser::next_input_buffer()
{
    auto buffer = m_input->next_block();
    m_input_begin = buffer.begin();
    m_input_end = buffer.end();
    return m_input_begin != m_input_end;
}


inline bool TransactLogParser::read_char(char& c)
{
    if (m_input_begin == m_input_end && !next_input_buffer())
        return false;
    c = *m_input_begin++;
    return true;
}

template <typename Handler>
void parse_transact_log(util::InputStream& is, Handler& handler)
{
    TransactLogParser parser;
    parser.parse(is, handler);
    handler.parse_complete();
}

// A base class for transaction log parsers so that tests which want to test
// just a single part of the transaction log handling don't have to implement
// the entire interface
class NoOpTransactionLogParser {
public:
    TableKey get_current_table() const
    {
        return m_current_table;
    }

    std::pair<ColKey, ObjKey> get_current_linkview() const
    {
        return {m_current_linkview_col, m_current_linkview_obj};
    }

    const StablePath& get_path() const
    {
        return m_path;
    }

private:
    TableKey m_current_table;
    ColKey m_current_linkview_col;
    ObjKey m_current_linkview_obj;
    StablePath m_path;

public:
    void parse_complete() {}

    bool select_table(TableKey t)
    {
        m_current_table = t;
        return true;
    }

    bool select_collection(ColKey col_key, ObjKey obj_key, const StablePath& path)
    {
        m_current_linkview_col = col_key;
        m_current_linkview_obj = obj_key;
        m_path = path;
        return true;
    }

    // Default no-op implementations of all of the mutation instructions
    bool insert_group_level_table(TableKey)
    {
        return false;
    }
    bool erase_class(TableKey)
    {
        return false;
    }
    bool rename_class(TableKey)
    {
        return false;
    }
    bool insert_column(ColKey)
    {
        return false;
    }
    bool erase_column(ColKey)
    {
        return false;
    }
    bool rename_column(ColKey)
    {
        return false;
    }
    bool set_link_type(ColKey)
    {
        return false;
    }
    bool create_object(ObjKey)
    {
        return false;
    }
    bool remove_object(ObjKey)
    {
        return false;
    }
    bool collection_set(size_t)
    {
        return false;
    }
    bool collection_clear(size_t)
    {
        return false;
    }
    bool collection_erase(size_t)
    {
        return false;
    }
    bool collection_insert(size_t)
    {
        return false;
    }
    bool collection_move(size_t, size_t)
    {
        return false;
    }
    bool modify_object(ColKey, ObjKey)
    {
        return false;
    }
    bool typed_link_change(ColKey, TableKey)
    {
        return true;
    }
};

} // namespace _impl
} // namespace realm

#endif // REALM_IMPL_TRANSACT_LOG_HPP
