/**
 * 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 OB_PARTITION_MERGE_TASK_H_
#define OB_PARTITION_MERGE_TASK_H_

#include "storage/ob_storage_struct.h"
#include "share/scheduler/ob_dag_scheduler.h"
#include "lib/utility/ob_print_utils.h"
#include "common/rowkey/ob_rowkey.h"
#include "storage/ob_i_partition_report.h"
#include "storage/compaction/ob_partition_merge_util.h"
#include "storage/compaction/ob_column_checksum_calculator.h"
#include "storage/ob_partition_service.h"
#include "storage/ob_pg_partition.h"
#include "storage/ob_partition_parallel_merge_ctx.h"

namespace oceanbase {

namespace common {
class ObStoreRowkey;
}

namespace storage {
class ObSSTableMergeDag;

class ObMacroBlockMergeTask : public share::ObITask {
public:
  ObMacroBlockMergeTask();
  virtual ~ObMacroBlockMergeTask();
  int init(const int64_t idx, storage::ObSSTableMergeCtx& ctx);
  virtual int process();
  int generate_next_task(ObITask*& next_task);

private:
  int process_iter_to_complement(transaction::ObTransService* txs);

private:
  int64_t idx_;
  storage::ObSSTableMergeCtx* ctx_;
  compaction::ObMacroBlockBuilder builder_;
  compaction::ObMacroBlockEstimator estimator_;
  compaction::ObIStoreRowProcessor* processor_;
  bool is_inited_;

private:
  DISALLOW_COPY_AND_ASSIGN(ObMacroBlockMergeTask);
};

// used for record output macro blocks
class ObSSTableMergeContext {
public:
  ObSSTableMergeContext();
  virtual ~ObSSTableMergeContext();

  int init(const int64_t array_count, const bool has_lob, common::ObIArray<common::ObColumnStat*>* column_stats,
      const bool merge_complement);
  int add_macro_blocks(const int64_t idx, blocksstable::ObMacroBlocksWriteCtx* blocks_ctx,
      blocksstable::ObMacroBlocksWriteCtx* lob_blocks_ctx, const ObSSTableMergeInfo& sstable_merge_info);
  int add_bloom_filter(blocksstable::ObMacroBlocksWriteCtx& bloom_filter_blocks_ctx);
  int add_column_stats(const common::ObIArray<common::ObColumnStat*>& column_stats);
  int create_sstable(storage::ObCreateSSTableParamWithTable& param, storage::ObIPartitionGroupGuard& pg_guard,
      ObTableHandle& table_handle);
  int create_sstables(ObIArray<storage::ObCreateSSTableParamWithTable>& params,
      storage::ObIPartitionGroupGuard& pg_guard, ObTablesHandle& tables_handle);
  int64_t get_finish_count() const
  {
    return finish_count_;
  }
  ObSSTableMergeInfo& get_sstable_merge_info()
  {
    return sstable_merge_info_;
  }
  int64_t get_file_id() const
  {
    return file_id_;
  }
  void destroy();
  int get_data_macro_block_count(int64_t& macro_block_count);
  TO_STRING_KV(K_(is_inited), K_(concurrent_cnt), K_(finish_count), K_(sstable_merge_info));

private:
  int add_lob_macro_blocks(const int64_t idx, blocksstable::ObMacroBlocksWriteCtx* blocks_ctx);
  int new_block_write_ctx(blocksstable::ObMacroBlocksWriteCtx*& ctx);
  OB_INLINE bool need_lob_macro_blocks()
  {
    return !lob_block_ctxs_.empty();
  }

private:
  bool is_inited_;
  common::ObSpinLock lock_;
  ObArray<blocksstable::ObMacroBlocksWriteCtx*> block_ctxs_;
  // index sstable should not have lob blocks
  ObArray<blocksstable::ObMacroBlocksWriteCtx*> lob_block_ctxs_;
  blocksstable::ObMacroBlocksWriteCtx* bloom_filter_block_ctx_;
  ObSSTableMergeInfo sstable_merge_info_;
  common::ObIArray<common::ObColumnStat*>* column_stats_;
  common::ObArenaAllocator allocator_;
  int64_t finish_count_;
  int64_t concurrent_cnt_;
  bool merge_complement_;
  int64_t file_id_;
};

struct ObSSTableScheduleMergeParam {
  ObSSTableScheduleMergeParam();
  bool is_valid() const;

  OB_INLINE bool is_major_merge() const
  {
    return storage::is_major_merge(merge_type_);
  }
  OB_INLINE bool is_history_mini_minor_merge() const
  {
    return storage::is_history_mini_minor_merge(merge_type_);
  }
  OB_INLINE bool is_mini_merge() const
  {
    return storage::is_mini_merge(merge_type_);
  }
  OB_INLINE bool is_multi_version_minor_merge() const
  {
    return storage::is_multi_version_minor_merge(merge_type_);
  }
  OB_INLINE bool is_memtable_merge() const
  {
    return MINI_MERGE == schedule_merge_type_;
  }
  OB_INLINE bool is_mini_minor_merge() const
  {
    return storage::is_mini_minor_merge(merge_type_);
  }
  OB_INLINE bool is_minor_merge() const
  {
    return MINI_MINOR_MERGE == merge_type_ || MINOR_MERGE == merge_type_;
  }
  TO_STRING_KV(K_(merge_type), K_(merge_version), K_(pkey), K_(index_id), K_(schedule_merge_type), K_(pg_key));

  ObMergeType merge_type_;
  common::ObVersion merge_version_;
  common::ObPartitionKey pkey_;
  uint64_t index_id_;
  ObMergeType schedule_merge_type_;
  ObPartitionKey pg_key_;
};

class ObMergeParameter;
//TODO such a massive context!
struct ObSSTableMergeCtx {
  ObSSTableMergeCtx();
  virtual ~ObSSTableMergeCtx();
  virtual bool is_valid() const;
  bool need_incremental_checksum() const
  {
    return checksum_method_ == blocksstable::CCM_TYPE_AND_VALUE;
  }
  bool need_full_checksum() const
  {
    return is_in_progressive_new_checksum_ || checksum_method_ == blocksstable::CCM_VALUE_ONLY || is_full_merge_;
  }
  bool need_rewrite_macro_block(const ObMacroBlockDesc& block_desc) const;
  int get_storage_format_version(int64_t& storage_format_version) const;
  int get_storage_format_work_version(int64_t& storage_format_work_version) const;
  int init_parallel_merge();
  int get_merge_range(int64_t parallel_idx, common::ObExtStoreRange& merge_range, common::ObIAllocator& allocator);
  OB_INLINE int64_t get_concurrent_cnt() const
  {
    return parallel_merge_ctx_.get_concurrent_cnt();
  }
  ObITable::TableType get_merged_table_type() const;
  storage::ObSSTableMergeContext& get_merge_context(const bool iter_complement)
  {
    return iter_complement ? merge_context_for_complement_minor_sstable_ : merge_context_;
  }

  // 1. init in dag
  ObSSTableScheduleMergeParam param_;
  storage::ObIPartitionReport* report_;

  // 2. filled in ObPartitionStore::get_merge_tables
  ObVersionRange sstable_version_range_;  // version range for new sstable
  int64_t create_snapshot_version_;
  int64_t dump_memtable_timestamp_;
  storage::ObTablesHandle tables_handle_;
  storage::ObTableHandle base_table_handle_;

  // 3. filled in ObPartitionStorage::get_schemas_to_merge
  int64_t base_schema_version_;
  const share::schema::ObTableSchema* mv_dep_table_schema_;  // not null means materialized_view
  const share::schema::ObTableSchema* data_table_schema_;
  common::ObSEArray<share::schema::ObIndexTableStat, OB_MAX_INDEX_PER_TABLE> index_stats_;
  int64_t schema_version_;
  const share::schema::ObTableSchema* table_schema_;  // table's schema need merge or split
  share::schema::ObSchemaGetterGuard schema_guard_;
  int64_t bf_rowkey_prefix_;

  // 4. filled in ObPartitionStorage::cal_merge_param
  bool is_full_merge_;  // full merge or increment merge
  int64_t stat_sampling_ratio_;
  storage::ObMergeLevel merge_level_;
  storage::ObMultiVersionRowInfo multi_version_row_info_;
  storage::ObSSTableMergeContext merge_context_;
  storage::ObSSTableMergeContext merge_context_for_complement_minor_sstable_;
  int64_t progressive_merge_num_;
  int64_t progressive_merge_start_version_;
  // progressive_merge_end_version_ = progressive_merge_start_version_ + progressive_merge_num_
  // current_round = merge_version - progressive_merge_start_version_
  // int64_t concurrent_count_;
  ObParallelMergeCtx parallel_merge_ctx_;

  storage::ObIPartitionGroupGuard pg_guard_;
  storage::ObPGPartitionGuard partition_guard_;

  int64_t checksum_method_;

  // 5. filled in ObPartitionStorage::prepare_merge_mv_depend_sstable
  storage::ObTablesHandle mv_dep_tables_handle_;  // obsoleted

  // 6. inited in ObSSTableMergePrepareTask::init_estimate
  common::ObArray<common::ObColumnStat*, ObIAllocator&> column_stats_;

  // 7. filled in ObSSTableMergeFinishTask::update_partition_store
  storage::ObTableHandle merged_table_handle_;
  storage::ObTableHandle merged_complement_minor_table_handle_;

  // no need init
  common::ObArenaAllocator allocator_;
  int32_t result_code_;
  compaction::ObSSTableColumnChecksum column_checksum_;
  bool is_in_progressive_new_checksum_;
  int64_t backup_progressive_merge_num_;
  int64_t backup_progressive_merge_start_version_;
  bool is_created_index_first_merge_;
  bool store_column_checksum_in_micro_;
  int64_t progressive_merge_round_;
  int64_t progressive_merge_step_;
  bool use_new_progressive_;
  bool create_sstable_for_large_snapshot_;
  int64_t logical_data_version_;
  common::ObLogTsRange log_ts_range_;
  int64_t merge_log_ts_;
  int64_t trans_table_end_log_ts_;
  int64_t trans_table_timestamp_;
  // we would push up last_replay_log_ts if the correspoding memtable has been merged,
  // but this memtable may not be released due to the warming-up table_store
  // if a new index is created, the schedule will also trigger a mini merge for it with the old frozen memtable
  // now we get a table store with old end_log_ts within the pg which has a larger last_replay_log_ts
  // so we need use last_replay_log_ts to prevent such uselsess mini merge happenning
  int64_t pg_last_replay_log_ts_;
  int64_t read_base_version_; // use for major merge

  TO_STRING_KV(K_(param), K_(sstable_version_range), K_(create_snapshot_version),
               K_(base_schema_version), K_(schema_version), K_(dump_memtable_timestamp),
               KP_(table_schema), K_(is_full_merge), K_(stat_sampling_ratio), K_(merge_level),
               K_(progressive_merge_num), K_(progressive_merge_start_version),
               K_(parallel_merge_ctx), K_(checksum_method), K_(result_code),
               KP_(data_table_schema), KP_(mv_dep_table_schema),
               K_(index_stats),
               "tables_handle count", tables_handle_.get_count(), K_(index_stats),
               K_(is_in_progressive_new_checksum), K_(store_column_checksum_in_micro),
               K_(progressive_merge_round),
               K_(progressive_merge_step), K_(use_new_progressive),
               K_(tables_handle), K_(base_table_handle), K_(create_sstable_for_large_snapshot),
               K_(logical_data_version), K_(log_ts_range), K_(merge_log_ts), K_(trans_table_end_log_ts),
               K_(trans_table_timestamp), K_(pg_last_replay_log_ts), K_(read_base_version));
private:
  DISALLOW_COPY_AND_ASSIGN(ObSSTableMergeCtx);
};

// ObMergeParamater is a sub part of ObSSTableMergeCtx
struct ObMergeParameter {
  ObMergeParameter();
  ~ObMergeParameter()
  {
    reset();
  }
  bool is_valid() const;
  void reset();
  int init(ObSSTableMergeCtx& merge_ctx, const int64_t idx, const bool iter_complement);
  storage::ObTablesHandle* tables_handle_;
  bool is_full_merge_;  // full merge or increment merge
  ObMergeType merge_type_;
  int64_t checksum_method_;
  ObMergeLevel merge_level_;
  const share::schema::ObTableSchema* table_schema_;         // table's schema need merge
  const share::schema::ObTableSchema* mv_dep_table_schema_;  // not null means materialized_view
  ObVersionRange version_range_;
  compaction::ObColumnChecksumCalculator* checksum_calculator_;
  bool is_iter_complement_;

  OB_INLINE bool is_major_merge() const
  {
    return storage::is_major_merge(merge_type_);
  }
  OB_INLINE bool is_multi_version_minor_merge() const
  {
    return storage::is_multi_version_minor_merge(merge_type_);
  }
  OB_INLINE bool need_checksum() const
  {
    return storage::is_major_merge(merge_type_);
  }
  TO_STRING_KV(K_(is_full_merge), K_(merge_type), K_(checksum_method), K_(merge_level), KP_(table_schema),
      KP_(mv_dep_table_schema), K_(version_range), KP_(checksum_calculator), KP_(tables_handle),
      K_(is_iter_complement));

private:
  DISALLOW_COPY_AND_ASSIGN(ObMergeParameter);
};

class ObSSTableMergePrepareTask : public share::ObITask {
public:
  ObSSTableMergePrepareTask();
  virtual ~ObSSTableMergePrepareTask();
  int init();
  virtual int process() override;

private:
  int generate_merge_sstable_task();
  int init_estimate(storage::ObSSTableMergeCtx& ctx);
  int create_sstable_for_large_snapshot(storage::ObSSTableMergeCtx& ctx);

private:
  bool is_inited_;
  ObSSTableMergeDag* merge_dag_;
  DISALLOW_COPY_AND_ASSIGN(ObSSTableMergePrepareTask);
};

class ObSSTableMergeFinishTask : public share::ObITask {
public:
  ObSSTableMergeFinishTask();
  virtual ~ObSSTableMergeFinishTask();
  int init();
  virtual int process() override;

private:
  int get_merged_sstable_(const ObITable::TableType& type, const uint64_t table_id,
      storage::ObSSTableMergeContext& merge_context, storage::ObTableHandle& table_handle, ObSSTable*& sstable);
  int check_data_checksum();
  int check_empty_merge_valid(storage::ObSSTableMergeCtx& ctx);
  int check_macro_cnt_of_merge_table(const ObITable::TableType& type, storage::ObSSTableMergeContext& merge_context);

private:
  bool is_inited_;
  ObSSTableMergeDag* merge_dag_;
  DISALLOW_COPY_AND_ASSIGN(ObSSTableMergeFinishTask);
};

class ObWriteCheckpointTask : public share::ObITask {
public:
  ObWriteCheckpointTask();
  virtual ~ObWriteCheckpointTask();
  int init(int64_t frozen_version);
  virtual int process() override;

private:
  static const int64_t FAIL_WRITE_CHECKPOINT_ALERT_INTERVAL = 1000L * 1000L * 3600LL;  // 6h
  static const int64_t RETRY_WRITE_CHECKPOINT_MIN_INTERVAL = 1000L * 1000L * 300L;     // 5 minutes
  // Average replay time of 1 slog is 100us. Total replay time should less than 1 minute.
  // So once log count exceed 50% * (60000000 / 100) = 300000, try to write a checkpoint.
  static const int64_t MIN_WRITE_CHECKPOINT_LOG_CNT = 300000;

  bool is_inited_;
  int64_t frozen_version_;
  DISALLOW_COPY_AND_ASSIGN(ObWriteCheckpointTask);
};

class ObWriteCheckpointDag : public share::ObIDag {
public:
  ObWriteCheckpointDag();
  virtual ~ObWriteCheckpointDag();
  virtual bool operator==(const ObIDag& other) const override;
  virtual int64_t hash() const override;
  virtual int init(int64_t frozen_version);
  virtual int64_t get_tenant_id() const override;
  virtual int fill_comment(char* buf, const int64_t buf_len) const override;
  virtual int64_t get_compat_mode() const override
  {
    return static_cast<int64_t>(share::ObWorker::CompatMode::MYSQL);
  }

protected:
  bool is_inited_;
  int64_t frozen_version_;
  DISALLOW_COPY_AND_ASSIGN(ObWriteCheckpointDag);
};

class ObSSTableMergeDag : public share::ObIDag {
public:
  ObSSTableMergeDag(const ObIDagType type, const ObIDagPriority priority);
  virtual ~ObSSTableMergeDag();
  storage::ObSSTableMergeCtx& get_ctx()
  {
    return ctx_;
  }
  virtual bool operator==(const ObIDag& other) const override;
  virtual int64_t hash() const override;
  virtual int init(const ObSSTableScheduleMergeParam& param, ObIPartitionReport* report) = 0;
  virtual int64_t get_tenant_id() const override
  {
    return ctx_.param_.pkey_.get_tenant_id();
  }
  virtual int fill_comment(char* buf, const int64_t buf_len) const override;
  virtual int64_t get_compat_mode() const override
  {
    return static_cast<int64_t>(compat_mode_);
  }
  bool ignore_warning() override
  {
    return OB_NO_NEED_MERGE == dag_ret_ || OB_TABLE_IS_DELETED == dag_ret_ || OB_TENANT_HAS_BEEN_DROPPED == dag_ret_ ||
           OB_PARTITION_NOT_EXIST == dag_ret_;
  }
  INHERIT_TO_STRING_KV("ObIDag", ObIDag, "param", ctx_.param_, "sstable_version_range", ctx_.sstable_version_range_,
      "log_ts_range", ctx_.log_ts_range_);

protected:
  int inner_init(const ObSSTableScheduleMergeParam& param, ObIPartitionReport* report);

  bool is_inited_;
  share::ObWorker::CompatMode compat_mode_;
  storage::ObSSTableMergeCtx ctx_;
  // merge_type\pkey\index_id is used to check if dag is equal
  ObMergeType merge_type_;
  common::ObPartitionKey pkey_;
  uint64_t index_id_;
  DISALLOW_COPY_AND_ASSIGN(ObSSTableMergeDag);
};

class ObSSTableMajorMergeDag : public ObSSTableMergeDag {
public:
  ObSSTableMajorMergeDag();
  virtual ~ObSSTableMajorMergeDag();
  int init(const ObSSTableScheduleMergeParam& param, ObIPartitionReport* report) override;

private:
  DISALLOW_COPY_AND_ASSIGN(ObSSTableMajorMergeDag);
};

class ObSSTableMiniMergeDag : public ObSSTableMergeDag {
public:
  ObSSTableMiniMergeDag();
  virtual ~ObSSTableMiniMergeDag();
  int init(const ObSSTableScheduleMergeParam& param, ObIPartitionReport* report) override;

private:
  DISALLOW_COPY_AND_ASSIGN(ObSSTableMiniMergeDag);
};

class ObSSTableMinorMergeDag : public ObSSTableMergeDag {
public:
  ObSSTableMinorMergeDag();
  virtual ~ObSSTableMinorMergeDag();
  int init(const ObSSTableScheduleMergeParam& param, ObIPartitionReport* report) override;

private:
  DISALLOW_COPY_AND_ASSIGN(ObSSTableMinorMergeDag);
};

class ObTransTableMergeDag : public share::ObIDag {
public:
  ObTransTableMergeDag() : ObIDag(ObIDagType::DAG_TYPE_TRANS_TABLE_MERGE, ObIDagPriority::DAG_PRIO_TRANS_TABLE_MERGE){};
  ~ObTransTableMergeDag(){};
  int init(const ObPartitionKey& pg_key)
  {
    this->pg_key_ = pg_key;
    return OB_SUCCESS;
  }
  virtual bool operator==(const ObIDag& other) const override
  {
    bool is_same = true;
    if (this == &other) {
    } else if (get_type() != other.get_type()) {
      is_same = false;
    } else {
      const ObTransTableMergeDag& other_dag = static_cast<const ObTransTableMergeDag&>(other);
      is_same = pg_key_ == other_dag.pg_key_;
    }
    return is_same;
  }
  virtual int64_t hash() const override
  {
    return pg_key_.hash();
  }
  virtual int64_t get_tenant_id() const override
  {
    return pg_key_.get_tenant_id();
  }
  virtual int fill_comment(char* buf, const int64_t buf_len) const override;
  virtual int64_t get_compat_mode() const override
  {
    return static_cast<int64_t>(share::ObWorker::CompatMode::MYSQL);
  }
  INHERIT_TO_STRING_KV("ObIDag", ObIDag, "pg_key", pg_key_);

private:
  ObPartitionKey pg_key_;
  DISALLOW_COPY_AND_ASSIGN(ObTransTableMergeDag);
};

class ObTransTableMergeTask : public share::ObITask {
private:
  enum class SOURCE { NONE = 0, FROM_LOCAL, FROM_REMOTE, FROM_BOTH };

public:
  ObTransTableMergeTask();
  ~ObTransTableMergeTask(){};
  int init(const ObPartitionKey& pg_key, const int64_t end_log_ts, const int64_t trans_table_seq);
  int init(const ObPartitionKey& pg_key, ObSSTable* sstable);
  virtual int process();
  int init_schema_and_writer(blocksstable::ObMacroBlockWriter& writer);
  int merge_trans_table(blocksstable::ObMacroBlockWriter& writer);
  int get_merged_trans_sstable(ObTableHandle& table_handle, blocksstable::ObMacroBlockWriter& writer);

private:
  const int64_t SCHEMA_VERSION = 1;

private:
  int update_partition_store(ObTableHandle& table_handle);
  int merge_remote_with_local(blocksstable::ObMacroBlockWriter& writer);

private:
  ObPartitionKey pg_key_;
  common::ObArenaAllocator allocator_;
  ObPartitionKey trans_table_pkey_;
  blocksstable::ObDataStoreDesc desc_;
  int64_t end_log_ts_;
  ObTablesHandle tables_handle_;
  share::schema::ObTableSchema table_schema_;
  ObIPartitionGroupGuard pg_guard_;
  int64_t trans_table_seq_;
  bool is_inited_;
  DISALLOW_COPY_AND_ASSIGN(ObTransTableMergeTask);
};

} /* namespace storage */
} /* namespace oceanbase */

#endif /* OB_PARTITION_MERGE_TASK_H_ */
