/**
 * @brief read tuple record from log file and add them tuples to recovery txn
 * @param recovery txn
 */
void AriesFrontendLogger::DeleteTuple(concurrency::Transaction *recovery_txn) {
  TupleRecord tuple_record(LOGRECORD_TYPE_ARIES_TUPLE_DELETE);

  // Check for torn log write
  if (ReadTupleRecordHeader(tuple_record, log_file, log_file_size) == false) {
    return;
  }

  auto txn_id = tuple_record.GetTransactionId();
  if (recovery_txn_table.find(txn_id) == recovery_txn_table.end()) {
    LOG_TRACE("Delete txd id %d not found in recovery txn table", (int)txn_id);
    return;
  }

  auto table = GetTable(tuple_record);

  ItemPointer delete_location = tuple_record.GetDeleteLocation();

  // Try to delete the tuple
  bool status = table->DeleteTuple(recovery_txn, delete_location);
  if (status == false) {
    // TODO: We need to abort on failure !
    recovery_txn->SetResult(Result::RESULT_FAILURE);
    return;
  }

  auto txn = recovery_txn_table.at(txn_id);
  txn->RecordDelete(delete_location);
}
/**
 * @brief read tuple record from log file and add them tuples to recovery txn
 * @param recovery txn
 */
void AriesFrontendLogger::InsertTuple(concurrency::Transaction *recovery_txn) {
  TupleRecord tuple_record(LOGRECORD_TYPE_ARIES_TUPLE_INSERT);

  // Check for torn log write
  if (ReadTupleRecordHeader(tuple_record, log_file, log_file_size) == false) {
    LOG_ERROR("Could not read tuple record header.");
    return;
  }

  auto txn_id = tuple_record.GetTransactionId();
  if (recovery_txn_table.find(txn_id) == recovery_txn_table.end()) {
    LOG_ERROR("Insert txd id %d not found in recovery txn table", (int)txn_id);
    return;
  }

  auto table = GetTable(tuple_record);

  // Read off the tuple record body from the log
  auto tuple = ReadTupleRecordBody(table->GetSchema(), recovery_pool, log_file,
                                   log_file_size);

  // Check for torn log write
  if (tuple == nullptr) {
    return;
  }

  auto target_location = tuple_record.GetInsertLocation();
  auto tile_group_id = target_location.block;
  auto tuple_slot = target_location.offset;

  auto &manager = catalog::Manager::GetInstance();
  auto tile_group = manager.GetTileGroup(tile_group_id);

  auto txn = recovery_txn_table.at(txn_id);

  // Create new tile group if table doesn't already have that tile group
  if (tile_group == nullptr) {
    table->AddTileGroupWithOid(tile_group_id);
    tile_group = manager.GetTileGroup(tile_group_id);
    if (max_oid < tile_group_id) {
      max_oid = tile_group_id;
    }
  }

  // Do the insert !
  auto inserted_tuple_slot = tile_group->InsertTuple(
      recovery_txn->GetTransactionId(), tuple_slot, tuple);

  if (inserted_tuple_slot == INVALID_OID) {
    // TODO: We need to abort on failure !
    recovery_txn->SetResult(Result::RESULT_FAILURE);
  } else {
    txn->RecordInsert(target_location);
    table->IncreaseNumberOfTuplesBy(1);
  }

  delete tuple;
}
/**
 * @brief read tuple record from log file and add them tuples to recovery txn
 * @param recovery txn
 */
void AriesFrontendLogger::UpdateTuple(concurrency::Transaction *recovery_txn) {
  TupleRecord tuple_record(LOGRECORD_TYPE_ARIES_TUPLE_UPDATE);

  // Check for torn log write
  if (ReadTupleRecordHeader(tuple_record, log_file, log_file_size) == false) {
    return;
  }

  auto txn_id = tuple_record.GetTransactionId();
  if (recovery_txn_table.find(txn_id) == recovery_txn_table.end()) {
    LOG_TRACE("Update txd id %d not found in recovery txn table", (int)txn_id);
    return;
  }

  auto txn = recovery_txn_table.at(txn_id);

  auto table = GetTable(tuple_record);

  auto tuple = ReadTupleRecordBody(table->GetSchema(), recovery_pool, log_file,
                                   log_file_size);

  // Check for torn log write
  if (tuple == nullptr) {
    return;
  }

  // First, redo the delete
  ItemPointer delete_location = tuple_record.GetDeleteLocation();

  bool status = table->DeleteTuple(recovery_txn, delete_location);
  if (status == false) {
    recovery_txn->SetResult(Result::RESULT_FAILURE);
  } else {
    txn->RecordDelete(delete_location);

    auto target_location = tuple_record.GetInsertLocation();
    auto tile_group_id = target_location.block;
    auto tuple_slot = target_location.offset;
    auto &manager = catalog::Manager::GetInstance();
    auto tile_group = manager.GetTileGroup(tile_group_id);

    // Create new tile group if table doesn't already have that tile group
    if (tile_group == nullptr) {
      table->AddTileGroupWithOid(tile_group_id);
      tile_group = manager.GetTileGroup(tile_group_id);
      if (max_oid < tile_group_id) {
        max_oid = tile_group_id;
      }
    }

    // Do the insert !
    auto inserted_tuple_slot = tile_group->InsertTuple(
        recovery_txn->GetTransactionId(), tuple_slot, tuple);
    if (inserted_tuple_slot == INVALID_OID) {
      recovery_txn->SetResult(Result::RESULT_FAILURE);
    } else {
      txn->RecordInsert(target_location);
    }
  }

  delete tuple;
}
/**
 * @brief Recovery system based on log file
 */
void WriteBehindFrontendLogger::DoRecovery() {
  // Set log file size
  log_file_size = GetLogFileSize(log_file_fd);

  // Go over the log size if needed
  if (log_file_size > 0) {
    bool reached_end_of_file = false;

    // check whether first item is LOGRECORD_TYPE_TRANSACTION_COMMIT
    // if not, no need to do recovery.
    // if yes, need to replay all log records before we hit
    // LOGRECORD_TYPE_TRANSACTION_DONE
    bool need_recovery = NeedRecovery();
    if (need_recovery == true) {
      TransactionRecord dummy_transaction_record(LOGRECORD_TYPE_INVALID);
      cid_t current_commit_id = INVALID_CID;

      // Go over each log record in the log file
      while (reached_end_of_file == false) {
        // Read the first byte to identify log record type
        // If that is not possible, then wrap up recovery
        LogRecordType log_type = GetNextLogRecordType(log_file, log_file_size);

        switch (log_type) {
          case LOGRECORD_TYPE_TRANSACTION_DONE:
          case LOGRECORD_TYPE_TRANSACTION_COMMIT: {
            // read but do nothing
            ReadTransactionRecordHeader(dummy_transaction_record, log_file,
                                        log_file_size);
          } break;

          case LOGRECORD_TYPE_WBL_TUPLE_INSERT: {
            TupleRecord insert_record(LOGRECORD_TYPE_WBL_TUPLE_INSERT);
            ReadTupleRecordHeader(insert_record, log_file, log_file_size);

            auto insert_location = insert_record.GetInsertLocation();
            auto info = SetInsertCommitMark(insert_location);
            current_commit_id = info.first;
          } break;

          case LOGRECORD_TYPE_WBL_TUPLE_DELETE: {
            TupleRecord delete_record(LOGRECORD_TYPE_WBL_TUPLE_DELETE);
            ReadTupleRecordHeader(delete_record, log_file, log_file_size);

            auto delete_location = delete_record.GetDeleteLocation();
            auto info = SetDeleteCommitMark(delete_location);
            current_commit_id = info.first;
          } break;

          case LOGRECORD_TYPE_WBL_TUPLE_UPDATE: {
            TupleRecord update_record(LOGRECORD_TYPE_WBL_TUPLE_UPDATE);
            ReadTupleRecordHeader(update_record, log_file, log_file_size);

            auto delete_location = update_record.GetDeleteLocation();
            SetDeleteCommitMark(delete_location);

            auto insert_location = update_record.GetInsertLocation();
            auto info = SetInsertCommitMark(insert_location);
            current_commit_id = info.first;
          } break;

          default:
            reached_end_of_file = true;
            break;
        }
      }

      // Update latest commit id
      if (latest_commit_id < current_commit_id) {
        latest_commit_id = current_commit_id;
      }

      // write out a trasaction done log record to file
      // to avoid redo next time during recovery
      WriteTransactionLogRecord(
          TransactionRecord(LOGRECORD_TYPE_TRANSACTION_DONE));
    }

    // After finishing recovery, set the next oid with maximum oid
    // observed during the recovery
    auto &manager = catalog::Manager::GetInstance();
    manager.SetNextOid(max_oid);
  }
}