/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Microsoft Corporation
 *
 * -=- Robust Distributed System Nucleus (rDSN) -=-
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#pragma once

#include "log_block.h"

#include <dsn/tool-api/zlocks.h>

namespace dsn {
namespace replication {

// each log file has a log_file_header stored at the beginning of the first block's data content
struct log_file_header
{
    int32_t magic;   // 0xdeadbeef
    int32_t version; // current 0x1
    int64_t
        start_global_offset; // start offset in the global space, equals to the file name's postfix
};

// a structure to record replica's log info
struct replica_log_info
{
    int64_t max_decree;
    int64_t valid_start_offset; // valid start offset in global space
    replica_log_info(int64_t d, int64_t o)
    {
        max_decree = d;
        valid_start_offset = o;
    }
    replica_log_info()
    {
        max_decree = 0;
        valid_start_offset = 0;
    }
    bool operator==(const replica_log_info &o) const
    {
        return max_decree == o.max_decree && valid_start_offset == o.valid_start_offset;
    }
};

typedef std::unordered_map<gpid, replica_log_info> replica_log_info_map;

class log_file;
typedef dsn::ref_ptr<log_file> log_file_ptr;

//
// the log file is structured with sequences of log_blocks,
// each block consists of the log_block_header + log_content,
// and the first block contains the log_file_header at the beginning
//
// the class is not thread safe
//
class log_file : public ref_counter
{
public:
    ~log_file();

    //
    // file operations
    //

    // open the log file for read
    // 'path' should be in format of log.{index}.{start_offset}, where:
    //   - index: the index of the log file, start from 1
    //   - start_offset: start offset in the global space
    // returns:
    //   - non-null if open succeed
    //   - null if open failed
    static log_file_ptr open_read(const char *path, /*out*/ error_code &err);

    // open the log file for write
    // the file path is '{dir}/log.{index}.{start_offset}'
    // returns:
    //   - non-null if open succeed
    //   - null if open failed
    static log_file_ptr create_write(const char *dir, int index, int64_t start_offset);

    // close the log file
    void close();

    // flush the log file
    void flush() const;

    //
    // read routines
    //

    // sync read the next log entry from the file
    // the entry data is start from the 'local_offset' of the file
    // the result is passed out by 'bb', not including the log_block_header
    // return error codes:
    //  - ERR_OK
    //  - ERR_HANDLE_EOF
    //  - ERR_INCOMPLETE_DATA
    //  - ERR_INVALID_DATA
    //  - other io errors caused by file read operator
    error_code read_next_log_block(/*out*/ ::dsn::blob &bb);

    //
    // write routines
    //

    // async write log entry into the file
    // 'block' is the date to be written
    // 'offset' is start offset of the entry in the global space
    // 'evt' is to indicate which thread pool to execute the callback
    // 'callback_host' is used to get tracer
    // 'callback' is to indicate the callback handler
    // 'hash' helps to choose which thread in the thread pool to execute the callback
    // returns:
    //   - non-null if io task is in pending
    //   - null if error
    dsn::aio_task_ptr commit_log_block(log_block &block,
                                       int64_t offset,
                                       dsn::task_code evt,
                                       dsn::task_tracker *tracker,
                                       aio_handler &&callback,
                                       int hash);
    dsn::aio_task_ptr commit_log_blocks(log_appender &pending,
                                        dsn::task_code evt,
                                        dsn::task_tracker *tracker,
                                        aio_handler &&callback,
                                        int hash);

    //
    // others
    //

    // Reset file_streamer to point to `offset`.
    // offset=0 means the start of this log file.
    void reset_stream(size_t offset = 0);
    // end offset in the global space: end_offset = start_offset + file_size
    int64_t end_offset() const { return _end_offset.load(); }
    // start offset in the global space
    int64_t start_offset() const { return _start_offset; }
    // file index
    int index() const { return _index; }
    // file path
    const std::string &path() const { return _path; }
    // previous decrees
    const replica_log_info_map &previous_log_max_decrees() { return _previous_log_max_decrees; }
    // previous decree for speicified gpid
    decree previous_log_max_decree(const gpid &pid);
    // file header
    log_file_header &header() { return _header; }

    // read file header from reader, return byte count consumed
    int read_file_header(binary_reader &reader);
    // write file header to writer, return byte count written
    int write_file_header(binary_writer &writer, const replica_log_info_map &init_max_decrees);
    // get serialized size of current file header
    int get_file_header_size() const;
    // if the file header is valid
    bool is_right_header() const;

    // set & get last write time, used for gc
    void set_last_write_time(uint64_t last_write_time) { _last_write_time = last_write_time; }
    uint64_t last_write_time() const { return _last_write_time; }

    const disk_file *file_handle() const { return _handle; }

private:
    // make private, user should create log_file through open_read() or open_write()
    log_file(const char *path, disk_file *handle, int index, int64_t start_offset, bool is_read);

private:
    friend class mock_log_file;

    uint32_t _crc32;
    int64_t _start_offset; // start offset in the global space
    std::atomic<int64_t>
        _end_offset; // end offset in the global space: end_offset = start_offset + file_size
    class file_streamer;
    std::unique_ptr<file_streamer> _stream;
    disk_file *_handle;        // file handle
    const bool _is_read;       // if opened for read or write
    std::string _path;         // file path
    int _index;                // file index
    log_file_header _header;   // file header
    uint64_t _last_write_time; // seconds from epoch time

    mutable zlock _write_lock;

    // this data is used for garbage collection, and is part of file header.
    // for read, the value is read from file header.
    // for write, the value is set by write_file_header().
    replica_log_info_map _previous_log_max_decrees;
};

} // namespace replication
} // namespace dsn
