/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

#include "platform/consensus/execution/transaction_executor.h"

#include <glog/logging.h>

#include "common/utils/utils.h"

namespace resdb {

TransactionExecutor::TransactionExecutor(
    const ResDBConfig& config, PostExecuteFunc post_exec_func,
    SystemInfo* system_info,
    std::unique_ptr<TransactionManager> transaction_manager)
    : config_(config),
      post_exec_func_(post_exec_func),
      system_info_(system_info),
      transaction_manager_(std::move(transaction_manager)),
      commit_queue_("order"),
      execute_queue_("execute"),
      stop_(false),
      duplicate_manager_(nullptr) {
  memset(blucket_, 0, sizeof(blucket_));
  global_stats_ = Stats::GetGlobalStats();
  ordering_thread_ = std::thread(&TransactionExecutor::OrderMessage, this);
  for (int i = 0; i < execute_thread_num_; ++i) {
    execute_thread_.push_back(
        std::thread(&TransactionExecutor::ExecuteMessage, this));
  }

  for (int i = 0; i < 1; ++i) {
    prepare_thread_.push_back(
        std::thread(&TransactionExecutor::PrepareMessage, this));
  }

  if (transaction_manager_ && transaction_manager_->IsOutOfOrder()) {
    execute_OOO_thread_ =
        std::thread(&TransactionExecutor::ExecuteMessageOutOfOrder, this);
    LOG(ERROR) << " is out of order:" << transaction_manager_->IsOutOfOrder();
  }
}

TransactionExecutor::~TransactionExecutor() { Stop(); }

void TransactionExecutor::RegisterExecute(int64_t seq) {
  if (execute_thread_num_ == 1) return;
  int idx = seq % blucket_num_;
  std::unique_lock<std::mutex> lk(mutex_);
  // LOG(ERROR)<<"register seq:"<<seq<<" bluck:"<<blucket_[idx];
  assert(!blucket_[idx] || !(blucket_[idx] ^ 3));
  blucket_[idx] = 1;
  // LOG(ERROR)<<"register seq:"<<seq;
}

void TransactionExecutor::WaitForExecute(int64_t seq) {
  if (execute_thread_num_ == 1) return;
  int pre_idx = (seq - 1 + blucket_num_) % blucket_num_;
  // LOG(ERROR)<<"wait for:"<<seq<<" pre idx:"<<pre_idx<<"
  // thread:"<<execute_thread_num_;
  while (!IsStop()) {
    std::unique_lock<std::mutex> lk(mutex_);
    cv_.wait_for(lk, std::chrono::milliseconds(10000), [&] {
      return ((blucket_[pre_idx] & 2) || !blucket_[pre_idx]);
    });
    if ((blucket_[pre_idx] & 2) || !blucket_[pre_idx]) {
      break;
    }
  }
  // LOG(ERROR)<<"wait for :"<<seq<<" done";
}

void TransactionExecutor::FinishExecute(int64_t seq) {
  if (execute_thread_num_ == 1) return;
  int idx = seq % blucket_num_;
  std::unique_lock<std::mutex> lk(mutex_);
  // LOG(ERROR)<<"finish :"<<seq<<" done"<<" idx:"<<idx;
  blucket_[idx] = 3;
  cv_.notify_all();
}

void TransactionExecutor::Stop() {
  stop_ = true;
  if (ordering_thread_.joinable()) {
    ordering_thread_.join();
  }
  for (auto& th : execute_thread_) {
    if (th.joinable()) {
      th.join();
    }
  }
  for (auto& th : prepare_thread_) {
    if (th.joinable()) {
      th.join();
    }
  }
  if (execute_OOO_thread_.joinable()) {
    execute_OOO_thread_.join();
  }
}

Storage* TransactionExecutor::GetStorage() {
  assert(transaction_manager_);
  return transaction_manager_ ? transaction_manager_->GetStorage() : nullptr;
}

void TransactionExecutor::SetPreExecuteFunc(PreExecuteFunc pre_exec_func) {
  pre_exec_func_ = pre_exec_func;
}

void TransactionExecutor::SetSeqUpdateNotifyFunc(SeqUpdateNotifyFunc func) {
  seq_update_notify_func_ = func;
}

bool TransactionExecutor::IsStop() { return stop_; }

uint64_t TransactionExecutor::GetMaxPendingExecutedSeq() {
  return next_execute_seq_ - 1;
}

void TransactionExecutor::SetPendingExecutedSeq(int seq) {
  // LOG(ERROR)<<" seq next pending seq:"<<seq;
  next_execute_seq_ = seq;
}

bool TransactionExecutor::NeedResponse() {
  return transaction_manager_ == nullptr ||
         transaction_manager_->NeedResponse();
}

int TransactionExecutor::Commit(std::unique_ptr<Request> message) {
  global_stats_->IncPendingExecute();
  if (transaction_manager_ && transaction_manager_->IsOutOfOrder()) {
    // LOG(ERROR)<<"add out of order exe:"<<message->seq()<<" from
    // proxy:"<<message->proxy_id();
    std::unique_ptr<Request> msg = std::make_unique<Request>(*message);
    execute_OOO_queue_.Push(std::move(message));
    commit_queue_.Push(std::move(msg));
  } else {
    commit_queue_.Push(std::move(message));
  }
  return 0;
}

void TransactionExecutor::AddNewData(std::unique_ptr<Request> message) {
  candidates_.insert(std::make_pair(message->seq(), std::move(message)));
}

std::unique_ptr<Request> TransactionExecutor::GetNextData() {
  if (candidates_.empty() || candidates_.begin()->first != next_execute_seq_) {
    return nullptr;
  }
  auto res = std::move(candidates_.begin()->second);
  if (pre_exec_func_) {
    pre_exec_func_(res.get());
  }
  candidates_.erase(candidates_.begin());
  return res;
}

void TransactionExecutor::OrderMessage() {
  while (!IsStop()) {
    auto message = commit_queue_.Pop();
    if (message != nullptr) {
      global_stats_->IncExecute();
      uint64_t seq = message->seq();
      if (next_execute_seq_ > seq) {
        LOG(INFO) << "request seq:" << seq << " has been executed"
                  << " next seq:" << next_execute_seq_;
        continue;
      }

      AddNewData(std::move(message));
    }

    while (!IsStop()) {
      std::unique_ptr<Request> message = GetNextData();
      if (message == nullptr) {
        break;
      }
      execute_queue_.Push(std::move(message));
      next_execute_seq_++;
      if (seq_update_notify_func_) {
        seq_update_notify_func_(next_execute_seq_);
      }
    }
  }
  return;
}

void TransactionExecutor::AddExecuteMessage(std::unique_ptr<Request> message) {
  global_stats_->IncCommit();
  message->set_commit_time(GetCurrentTime());
  execute_queue_.Push(std::move(message));
}

void TransactionExecutor::ExecuteMessage() {
  while (!IsStop()) {
    std::unique_ptr<Request> message = nullptr;
    bool need_execute = true;
    {
      std::unique_lock<std::mutex> lk(e_mutex_);
      message = execute_queue_.Pop();
      if (message == nullptr) {
        continue;
      }
      if (transaction_manager_ && transaction_manager_->IsOutOfOrder()) {
        need_execute = false;
      }
      RegisterExecute(message->seq());
    }
    Execute(std::move(message), need_execute);
  }
}

void TransactionExecutor::ExecuteMessageOutOfOrder() {
  while (!IsStop()) {
    auto message = execute_OOO_queue_.Pop();
    if (message == nullptr) {
      continue;
    }
    OnlyExecute(std::move(message));
  }
}

void TransactionExecutor::OnlyExecute(std::unique_ptr<Request> request) {
  // Only Execute the request.
  BatchUserRequest batch_request;
  if (!batch_request.ParseFromString(request->data())) {
    LOG(ERROR) << "parse data fail";
  }
  batch_request.set_seq(request->seq());
  batch_request.set_hash(request->hash());
  batch_request.set_proxy_id(request->proxy_id());
  if (request->has_committed_certs()) {
    *batch_request.mutable_committed_certs() = request->committed_certs();
  }

  // LOG(INFO) << " get request batch size:"
  //          << batch_request.user_requests_size()<<" proxy
  //          id:"<<request->proxy_id();
  std::unique_ptr<BatchUserResponse> response;
  global_stats_->GetTransactionDetails(batch_request);
  if (transaction_manager_) {
    response = transaction_manager_->ExecuteBatchWithSeq(request->seq(),
                                                         batch_request);
  }

  // global_stats_->IncTotalRequest(batch_request.user_requests_size());
  // global_stats_->IncExecuteDone();
}

void TransactionExecutor::Execute(std::unique_ptr<Request> request,
                                  bool need_execute) {
  std::unique_ptr<BatchUserRequest> batch_request = nullptr;
  std::unique_ptr<std::vector<std::unique_ptr<google::protobuf::Message>>> data;
  std::vector<std::unique_ptr<google::protobuf::Message>>* data_p = nullptr;
  BatchUserRequest* batch_request_p = nullptr;

  // Execute the request, then send the response back to the user.
  if (batch_request_p == nullptr) {
    batch_request = std::make_unique<BatchUserRequest>();
    if (!batch_request->ParseFromString(request->data())) {
      LOG(ERROR) << "parse data fail";
    }
    batch_request->set_hash(request->hash());
    if (request->has_committed_certs()) {
      *batch_request->mutable_committed_certs() = request->committed_certs();
    }
    batch_request->set_seq(request->seq());
    batch_request->set_proxy_id(request->proxy_id());
    batch_request_p = batch_request.get();
    // LOG(ERROR)<<"get data from req:";
  } else {
    assert(batch_request_p);
    batch_request_p->set_seq(request->seq());
    batch_request_p->set_proxy_id(request->proxy_id());
    // LOG(ERROR)<<" get from cache:"<<uid;
  }
  assert(batch_request_p);

  // LOG(ERROR) << " get request batch size:"
  // << batch_request_p->user_requests_size()<<" proxy id:"
  //  <<request->proxy_id()<<" need execute:"<<need_execute<<"
  //  seq:"<<request->seq();

  std::unique_ptr<BatchUserResponse> response;
  global_stats_->GetTransactionDetails(*batch_request_p);
  if (transaction_manager_ && need_execute) {
    if (execute_thread_num_ == 1) {
      response = transaction_manager_->ExecuteBatchWithSeq(request->seq(),
                                                           *batch_request_p);
    } else {
      std::vector<std::unique_ptr<std::string>> response_v;

      if (data_p == nullptr) {
        int64_t start_time = GetCurrentTime();
        data = std::move(transaction_manager_->Prepare(*batch_request_p));
        int64_t end_time = GetCurrentTime();
        if (end_time - start_time > 10) {
          // LOG(ERROR)<<"exec data done:"<<uid<<" wait
          // time:"<<(end_time-start_time);
        }
        data_p = data.get();
      }

      WaitForExecute(request->seq());
      if (last_seq_ == 0) {
        last_seq_ = request->seq();
      } else {
        assert(last_seq_ + 1 == request->seq());
        last_seq_++;
      }
      if (data_p->empty() || (*data_p)[0] == nullptr) {
        response = transaction_manager_->ExecuteBatchWithSeq(request->seq(),
                                                             *batch_request_p);
      } else {
        response_v = transaction_manager_->ExecuteBatchDataWithSeq(
            request->seq(), *data_p);
      }
      FinishExecute(request->seq());

      if (response == nullptr) {
        response = std::make_unique<BatchUserResponse>();
        for (auto& s : response_v) {
          response->add_response()->swap(*s);
        }
      }
    }
  }
  // LOG(ERROR)<<" CF = :"<<(cf==1)<<" uid:"<<uid;

  if (duplicate_manager_ && batch_request_p) {
    duplicate_manager_->AddExecuted(batch_request_p->hash(),
                                    batch_request_p->seq());
  }

  if (response == nullptr) {
    response = std::make_unique<BatchUserResponse>();
  }
  global_stats_->IncTotalRequest(batch_request_p->user_requests_size());
  response->set_proxy_id(batch_request_p->proxy_id());
  response->set_createtime(batch_request_p->createtime() +
                           request->queuing_time());
  response->set_local_id(batch_request_p->local_id());

  response->set_seq(request->seq());
  if (post_exec_func_) {
    post_exec_func_(std::move(request), std::move(response));
  }

  global_stats_->IncExecuteDone();
}

void TransactionExecutor::SetDuplicateManager(DuplicateManager* manager) {
  duplicate_manager_ = manager;
}

bool TransactionExecutor::SetFlag(uint64_t uid, int f) {
  std::unique_lock<std::mutex> lk(f_mutex_[uid % mod]);
  auto it = flag_[uid % mod].find(uid);
  if (it == flag_[uid % mod].end()) {
    flag_[uid % mod][uid] |= f;
    // LOG(ERROR)<<"NO FUTURE uid:"<<uid;
    return true;
  }
  assert(it != flag_[uid % mod].end());
  if (f == Start_Prepare) {
    if (flag_[uid % mod][uid] & Start_Execute) {
      return false;
    }
  } else if (f == Start_Execute) {
    if (flag_[uid % mod][uid] & End_Prepare) {
      // if (flag_[uid % mod][uid] & Start_Prepare) {
      return false;
    }
  }
  flag_[uid % mod][uid] |= f;
  return true;
}

void TransactionExecutor::ClearPromise(uint64_t uid) {
  std::unique_lock<std::mutex> lk(f_mutex_[uid % mod]);
  auto it = pre_[uid % mod].find(uid);
  if (it == pre_[uid % mod].end()) {
    return;
  }
  // LOG(ERROR)<<"CLEAR UID:"<<uid;
  assert(it != pre_[uid % mod].end());
  assert(flag_[uid % mod].find(uid) != flag_[uid % mod].end());
  // assert(data_[uid%mod].find(uid) != data_[uid%mod].end());
  // assert(req_[uid%mod].find(uid) != req_[uid%mod].end());
  // data_[uid%mod].erase(data_[uid%mod].find(uid));
  // req_[uid%mod].erase(req_[uid%mod].find(uid));
  pre_[uid % mod].erase(it);
  flag_[uid % mod].erase(flag_[uid % mod].find(uid));
}

std::promise<int>* TransactionExecutor::GetPromise(uint64_t uid) {
  std::unique_lock<std::mutex> lk(f_mutex_[uid % mod]);
  auto it = pre_[uid % mod].find(uid);
  if (it == pre_[uid % mod].end()) {
    return nullptr;
  }
  return it->second.get();
}

std::unique_ptr<std::future<int>> TransactionExecutor::GetFuture(uint64_t uid) {
  std::unique_lock<std::mutex> lk(f_mutex_[uid % mod]);
  auto it = pre_[uid % mod].find(uid);
  if (it == pre_[uid % mod].end()) {
    return nullptr;
  }
  // return std::move(it->second);
  // LOG(ERROR)<<"add future:"<<uid;
  return std::make_unique<std::future<int>>(it->second->get_future());
}

bool TransactionExecutor::AddFuture(uint64_t uid) {
  std::unique_lock<std::mutex> lk(f_mutex_[uid % mod]);
  auto it = pre_[uid % mod].find(uid);
  if (it == pre_[uid % mod].end()) {
    // LOG(ERROR)<<"add future:"<<uid;
    std::unique_ptr<std::promise<int>> p =
        std::make_unique<std::promise<int>>();
    // auto f = std::make_unique<std::future<int>>(p->get_future());
    pre_[uid % mod][uid] = std::move(p);
    // pre_f_[uid % mod][uid] = std::move(f);
    flag_[uid % mod][uid] = 0;
    return true;
  }
  return false;
}

void TransactionExecutor::Prepare(std::unique_ptr<Request> request) {
  if (AddFuture(request->uid())) {
    prepare_queue_.Push(std::move(request));
  }
}

void TransactionExecutor::PrepareMessage() {
  while (!IsStop()) {
    std::unique_ptr<Request> request = prepare_queue_.Pop();
    if (request == nullptr) {
      continue;
    }

    uint64_t uid = request->uid();
    int current_f = SetFlag(uid, Start_Prepare);
    if (current_f == 0) {
      // commit has done
      // LOG(ERROR)<<" want prepare, commit started:"<<uid;
      //      ClearPromise(uid);
      continue;
    }

    std::promise<int>* p = GetPromise(uid);
    assert(p);
    // LOG(ERROR)<<" prepare started:"<<uid;

    // LOG(ERROR)<<" prepare uid:"<<uid;

    // Execute the request, then send the response back to the user.
    std::unique_ptr<BatchUserRequest> batch_request =
        std::make_unique<BatchUserRequest>();
    if (!batch_request->ParseFromString(request->data())) {
      LOG(ERROR) << "parse data fail";
    }
    // batch_request = std::make_unique<BatchUserRequest>();
    batch_request->set_seq(request->seq());
    batch_request->set_hash(request->hash());
    batch_request->set_proxy_id(request->proxy_id());
    if (request->has_committed_certs()) {
      *batch_request->mutable_committed_certs() = request->committed_certs();
    }

    // LOG(ERROR)<<"prepare seq:"<<batch_request->seq()<<" proxy
    // id:"<<request->proxy_id()<<" local id:"<<batch_request->local_id();

    std::unique_ptr<std::vector<std::unique_ptr<google::protobuf::Message>>>
        request_v = transaction_manager_->Prepare(*batch_request);
    {
      std::unique_lock<std::mutex> lk(fd_mutex_[uid % mod]);
      //   assert(request_v);
      // assert(data_[uid%mod].find(uid) == data_[uid%mod].end());
      data_[uid % mod][uid] = std::move(request_v);
      req_[uid % mod][uid] = std::move(batch_request);
    }
    // LOG(ERROR)<<"set promise:"<<uid;
    p->set_value(1);
    {
      int set_ret = SetFlag(uid, End_Prepare);
      if (set_ret == 0) {
        // LOG(ERROR)<<"commit interrupt:"<<uid;
        // ClearPromise(uid);
      } else {
        // LOG(ERROR)<<"prepare done:"<<uid;
      }
    }
  }
}

}  // namespace resdb
