/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#ifndef OCEANBASE_CLOG_OB_ILOG_STORE_H_
#define OCEANBASE_CLOG_OB_ILOG_STORE_H_
#include "lib/container/ob_se_array.h"
#include "lib/lock/ob_tc_rwlock.h"
#include "lib/task/ob_timer.h"
#include "common/ob_member_list.h"
#include "ob_file_id_cache.h"
#include "ob_log_common.h"
#include "ob_log_define.h"
#include "ob_ilog_memstore.h"

namespace oceanbase {
namespace storage {
class ObPartitionService;
}
namespace common {
class ObILogFileStore;
}
namespace clog {
class ObIlogMemstore;
class ObFileIdCache;
class ObLogDirectReader;
class ObILogDir;
class ObIlogFileBuilder;

class PinnedMemory {
public:
  PinnedMemory();
  ~PinnedMemory();
  int init(const int64_t size);
  void destroy();
  char* get_ptr();
  bool is_valid(const int64_t size) const;
  TO_STRING_KV(KP(pinned_memory_), K(size_));

private:
  char* pinned_memory_;
  int64_t size_;
  bool is_inited_;
  DISALLOW_COPY_AND_ASSIGN(PinnedMemory);
};

class ObIlogStore {
public:
  ObIlogStore();
  ~ObIlogStore();

public:
  int init(const file_id_t next_ilog_file_id, common::ObILogFileStore* file_store, ObFileIdCache* file_id_cache,
      ObLogDirectReader* direct_reader, storage::ObPartitionService* partition_service);
  void destroy();
  int start();
  void stop();
  void wait();

public:
  // retry inside the function
  // caller guarantees that the same partition_key is called from
  // a single thread, log_id is monotonically increasing.
  int submit_cursor(
      const common::ObPartitionKey& partition_key, const uint64_t log_id, const ObLogCursorExt& log_cursor_ext);
  int submit_cursor(const common::ObPartitionKey& partition_key, const uint64_t log_id,
      const ObLogCursorExt& log_cursor_ext, const common::ObMemberList& memberlist, const int64_t replica_num,
      const int64_t memberlist_version);
  // return value
  // 1) OB_SUCCESS, query success
  // 2) OB_ENTRY_NOT_EXIST, the log_id is not exist
  // 3) OB_ERR_OUT_OF_UPPER_BOUND, the log_id is greater than max_log_id
  //
  // support batch query
  int get_cursor_from_memstore(
      const common::ObPartitionKey& partition_key, const uint64_t log_id, ObGetCursorResult& result) const;
  // return value
  // 1) OB_SUCCESS, query success
  // 2) OB_ENTRY_NOT_EXIST, the log_id is not exist, only return this
  //    error code when file was reclaimed
  //
  // support batch query
  int get_cursor_from_ilog_file(const common::ObPartitionKey& partition_key, const uint64_t log_id,
      const Log2File& log2file_item, ObGetCursorResult& result) const;
  // return value
  // 1) OB_SUCCESS, query success
  // 2) OB_PARTITION_NOT_EXIST, partition not exist
  // query the max_log_id and max_log_ts in the partition
  int get_max_ilog_from_memstore(const common::ObPartitionKey& partition_key, uint64_t& ret_max_log_id) const;
  //
  // return value
  // 1) OB_SUCCESS, query success
  // 2) OB_PARTITION_NOT_EXIST, partition not exist
  //
  // query the min_log_id and min_log_ts in the partition
  int get_memstore_min_log_id_and_ts(
      const common::ObPartitionKey& partition_key, uint64_t& ret_min_log_id, int64_t& ret_min_log_ts) const;

  file_id_t get_next_ilog_file_id() const
  {
    return next_ilog_file_id_;
  }
  // ilog writer worker
  void runTimerTask();

private:
  const static int64_t DEFAULT_MEMSTORE_COUNT = 16;
  const static int64_t TIMER_TASK_INTERVAL = 1000;
  // PINNED_MEMORY_SIZE should set as ObIlogMemstore::CURSOR_SIZE_TRIGGER(32MB)
  // however, in concurrent scenarios, it will causes that the size of
  // ObIlogMemstore will exceed than ObIlogMemstore::CURSOR_SIZE_TRIGGER.
  // therefore, set PINNED_MEMORY_SIZE as double ObIlogMemstore::CURSOR_SIZE_TRIGGER
  const static int64_t PINNED_MEMORY_SIZE = 2 * ObIlogMemstore::CURSOR_SIZE_TRIGGER;
  struct FrozenMemstore;
  typedef common::ObSEArray<ObIlogMemstore*, DEFAULT_MEMSTORE_COUNT> IlogMemstoreArray;
  typedef common::ObSEArray<FrozenMemstore, DEFAULT_MEMSTORE_COUNT> FrozenMemstoreArray;

  int get_memstore_min_log_id_and_ts_unlock_(
      const common::ObPartitionKey& partition_key, uint64_t& ret_min_log_id, int64_t& ret_min_log_ts) const;
  int get_cursor_from_memstore_(
      const common::ObPartitionKey& partition_key, const uint64_t log_id, ObLogCursorExt& log_cursor_ext) const;
  int get_cursor_from_frozen_memstore_(
      const common::ObPartitionKey& partition_key, const uint64_t log_id, ObLogCursorExt& log_cursor_ext) const;
  int get_max_log_from_frozen_memstore_(const common::ObPartitionKey& partition_key, uint64_t& ret_max_log_id) const;
  int get_min_log_from_frozen_memstore_(
      const common::ObPartitionKey& partition_key, uint64_t& ret_min_log_id, int64_t& ret_min_log_ts) const;
  int update_max_flushed_ilog_id_(IndexInfoBlockMap& index_info_block_map);

  int write_file_(const file_id_t next_ilog_file_id, char* buffer, const int64_t buffer_size);

  int froze_memstore_(const ObIlogFreezeTriggerType& trigger_type);
  int raise_memstore_();
  void free_memstore_(IlogMemstoreArray& free_memstore_array);
  void free_memstore_(ObIlogMemstore* memstore);
  void free_memstore_();
  void timer_check_need_freeze_();

  // this function return the range of ObIlogMemstore which can be merged
  // end_idx is the last index of ObIlogMemstore which can be merged
  // need_switch_file means that whether we need switch ilog file
  int get_merge_range_(int64_t& end_idx, bool& need_switch_file);

  // merge all ObIlogMemstore in [0, end_idx] to merge_after_memstore
  // end_idx is the last index of frozen_memstore_array_
  // memstore_after_merge is the container to merge frozen_memstore_array_
  int merge_frozen_memstore_(int64_t& end_idx, FrozenMemstore& memstore_after_merge);
  int build_and_write_file_(ObIlogFileBuilder& builder);
  int check_need_dump_(bool& need_dump, int64_t curr_memstore_seq);

  // according to trigger_type, execute different operation
  // memstore_after_merge is the memstore after merge
  // end_idx means that the last index of frozen_memstore_array_
  // free_memstore_array_ records the memstore need to release memory
  // builder is used to write memstore_after_merge into file
  // 1) if trigger_type is OB_TIMER_TRIGGER_TYPE or OB_CLOG_SIZE_TRIGGER_TYPE,
  //    don't need append info_block into file_id_cache, free_memstore_array records the
  //    memstore located in [0, end_idx).
  // 2) if trigger_type is OB_ILOG_NOT_CONTINOUS_TYPE or OB_MERGE_NEED_SWITCH_FILE_TRIGGER_TYPE,
  //    need append info_block into file_id_cache and switch ilog file, free_memstore_array
  //    records the memstore located in [0, end_idx]
  int handle_different_trigger_type_(const FrozenMemstore& memstore_after_merge, const int64_t& end_idx,
      IlogMemstoreArray& free_memstore_array, ObIlogFileBuilder& builder);

  // according to trigger_type, determines whether need to do merge
  // 1. if call this function before merge, can determine whether need to do merge
  // 2. if call this function after merge, can determine where need to switch file
  //    after doing merge
  bool need_merge_frozen_memstore_array_by_trigger_type_(const ObIlogFreezeTriggerType& trigger_type) const;

  int do_merge_frozen_memstore_(
      const FrozenMemstoreArray& frozen_memstore_array, bool need_switch_file, FrozenMemstore& memstore_after_merge);

  void alloc_memstore_(ObIlogMemstore*& memstore);

private:
  class IlogWriterTask : public common::ObTimerTask {
  public:
    IlogWriterTask() : ilog_store_(NULL)
    {}
    ~IlogWriterTask()
    {}

  public:
    int init(ObIlogStore* ilog_store);
    virtual void runTimerTask();

  private:
    ObIlogStore* ilog_store_;
  };

  class MergeIlogMemstoreContainer;

  struct FrozenMemstore {
    FrozenMemstore() : trigger_type_(OB_INVALID_TRIGGER_TYPE), memstore_(NULL), seq_(-1)
    {}
    FrozenMemstore(const ObIlogFreezeTriggerType& trigger_type, ObIlogMemstore* memstore, int64_t seq)
        : trigger_type_(trigger_type), memstore_(memstore), seq_(seq)
    {}
    bool is_valid() const
    {
      return memstore_ != NULL && trigger_type_ != OB_INVALID_TRIGGER_TYPE && seq_ != -1;
    }
    int set_frozen_memstore(ObIlogFreezeTriggerType trigger_type, ObIlogMemstore* memstore, int64_t seq)
    {
      int ret = common::OB_SUCCESS;
      if (is_valid()) {
        ret = common::OB_ERR_UNEXPECTED;
        CLOG_LOG(ERROR, "want to set valid frozen memstore", K(*this), K(trigger_type), K(memstore), K(seq));
      } else {
        trigger_type_ = trigger_type;
        memstore_ = memstore;
        seq_ = seq;
      }
      return ret;
    }
    ObIlogFreezeTriggerType trigger_type_;
    ObIlogMemstore* memstore_;
    int64_t seq_;
    TO_STRING_KV(K(trigger_type_), K(memstore_), KP(memstore_), K(seq_));
  };

  class UpdateMaxFlushedIlogFunctor;
  typedef common::RWLock RWLock;
  typedef RWLock::RLockGuard RLockGuard;
  typedef RWLock::WLockGuard WLockGuard;

private:
  mutable RWLock lock_;
  bool is_inited_;
  file_id_t next_ilog_file_id_;
  common::ObILogFileStore* file_store_;
  ObFileIdCache* file_id_cache_;
  ObLogDirectReader* direct_reader_;
  storage::ObPartitionService* partition_service_;
  ObIlogMemstore* active_memstore_;
  FrozenMemstoreArray frozen_memstore_array_;
  int64_t memstore_cnt_;
  // active ObIlogMemstore's sequence number
  int64_t curr_memstore_seq_;
  // the sequence number corresponding to the last
  // memstore that has been merged
  int64_t prev_consumed_memstore_seq_;
  PinnedMemory pinned_memory_;
  IlogWriterTask task_;

private:
  DISALLOW_COPY_AND_ASSIGN(ObIlogStore);
};
}  // namespace clog
}  // namespace oceanbase

#endif  // OCEANBASE_CLOG_OB_ILOG_STORE_H_
