/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
/* Open Source Software - may be modified and shared by FRC teams. The code   */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project.                                                               */
/*----------------------------------------------------------------------------*/

#include "wpi/future.h"

namespace wpi {
namespace detail {

PromiseFactoryBase::~PromiseFactoryBase() {
  m_active = false;
  m_resultCv.notify_all();  // wake up any waiters
}

void PromiseFactoryBase::IgnoreResult(uint64_t request) {
  std::unique_lock lock(m_resultMutex);
  EraseRequest(request);
}

uint64_t PromiseFactoryBase::CreateRequest() {
  std::unique_lock lock(m_resultMutex);
  uint64_t req = ++m_uid;
  m_requests.push_back(req);
  return req;
}

bool PromiseFactoryBase::EraseRequest(uint64_t request) {
  if (request == 0) return false;
  auto it = std::find_if(m_requests.begin(), m_requests.end(),
                         [=](auto r) { return r == request; });
  if (it == m_requests.end()) return false;  // no waiters
  m_requests.erase(it);
  return true;
}

}  // namespace detail

future<void> PromiseFactory<void>::MakeReadyFuture() {
  std::unique_lock lock(GetResultMutex());
  uint64_t req = CreateErasedRequest();
  m_results.emplace_back(req);
  return future<void>{this, req};
}

void PromiseFactory<void>::SetValue(uint64_t request) {
  std::unique_lock lock(GetResultMutex());
  if (!EraseRequest(request)) return;
  auto it = std::find_if(m_thens.begin(), m_thens.end(),
                         [=](const auto& x) { return x.request == request; });
  if (it != m_thens.end()) {
    uint64_t outRequest = it->outRequest;
    ThenFunction func = std::move(it->func);
    m_thens.erase(it);
    lock.unlock();
    return func(outRequest);
  }
  m_results.emplace_back(request);
  Notify();
}

void PromiseFactory<void>::SetThen(uint64_t request, uint64_t outRequest,
                                   ThenFunction func) {
  std::unique_lock lock(GetResultMutex());
  auto it = std::find_if(m_results.begin(), m_results.end(),
                         [=](const auto& r) { return r == request; });
  if (it != m_results.end()) {
    m_results.erase(it);
    lock.unlock();
    return func(outRequest);
  }
  m_thens.emplace_back(request, outRequest, func);
}

bool PromiseFactory<void>::IsReady(uint64_t request) noexcept {
  std::unique_lock lock(GetResultMutex());
  auto it = std::find_if(m_results.begin(), m_results.end(),
                         [=](const auto& r) { return r == request; });
  return it != m_results.end();
}

void PromiseFactory<void>::GetResult(uint64_t request) {
  // wait for response
  std::unique_lock lock(GetResultMutex());
  while (IsActive()) {
    // Did we get a response to *our* request?
    auto it = std::find_if(m_results.begin(), m_results.end(),
                           [=](const auto& r) { return r == request; });
    if (it != m_results.end()) {
      // Yes, remove it from the vector and we're done.
      m_results.erase(it);
      return;
    }
    // No, keep waiting for a response
    Wait(lock);
  }
}

void PromiseFactory<void>::WaitResult(uint64_t request) {
  // wait for response
  std::unique_lock lock(GetResultMutex());
  while (IsActive()) {
    // Did we get a response to *our* request?
    auto it = std::find_if(m_results.begin(), m_results.end(),
                           [=](const auto& r) { return r == request; });
    if (it != m_results.end()) return;
    // No, keep waiting for a response
    Wait(lock);
  }
}

PromiseFactory<void>& PromiseFactory<void>::GetInstance() {
  static PromiseFactory<void> inst;
  return inst;
}

}  // namespace wpi
