#include <unistd.h>

#include <spdlog/spdlog.h>

#include "common.h"
#include "telegram_client.h"

TelegramClient::TelegramClient(uv_loop_t *eventLoop)
  : m_eventLoop(eventLoop) {}

bool TelegramClient::start() {
  spdlog::debug("tg: start");
  if (m_running) return false;
  m_running = true;

  spdlog::debug("tg: creating uv handle");
  m_uvHandle = new uv_async_t;
  m_uvHandle->data = (void*)this;
  uv_async_init(m_eventLoop, m_uvHandle, TelegramClient::uv_callback);

  spdlog::debug("tg: starting telegram thread");
  m_thread = std::thread([this](){
    run_thread();
  });
  return true;
}

TelegramClient::~TelegramClient() {
  if (m_thread.joinable()) {
    send_query(td_api::make_object<td_api::close>(), {});
    m_thread.join();

    if (m_uvHandle)
      uv_close((uv_handle_t*)m_uvHandle, TelegramClient::uv_close_handle_callback);
  }
}

void TelegramClient::run_thread() {
  spdlog::debug("tg: i am telegram thread");
  td::ClientManager::execute(td_api::make_object<td_api::setLogVerbosityLevel>(0));
  m_clientManager = std::make_unique<td::ClientManager>();
  m_clientId = m_clientManager->create_client_id();
  send_query(td_api::make_object<td_api::getOption>("version"), {});
  run_telegram_main_loop();
}

void TelegramClient::run_telegram_main_loop() {
  spdlog::info("tg: begin telegram main loop");
  while (m_running) {
    if (!process_response(m_clientManager->receive(500))) {
      spdlog::info("tg: closed");
      return;
    }
  }
}

void TelegramClient::send_query(td_api::object_ptr<td_api::Function> f, std::function<void(TgObject)> callback) {
  std::uint64_t query_id = m_nextQueryId++;
  if (callback) {
    m_handlers.emplace(query_id, std::move(callback));
  }
  m_clientManager->send(m_clientId, query_id, std::move(f));
}

bool TelegramClient::process_response(td::ClientManager::Response response) {
  if (!response.object) {
    return true;
  }
  if (response.object->get_id() == td_api::updateAuthorizationState::ID) {
    auto authUpdate = (td_api::updateAuthorizationState*) response.object.get();
    if (authUpdate->authorization_state_->get_id() == td_api::authorizationStateClosed::ID) {
      return false;
    }
  }
  m_responseQueueMutex.lock();
  m_responseQueue.push_back(std::move(response));
  m_responseQueueMutex.unlock();

  uv_async_send(m_uvHandle);
  return true;
}

void TelegramClient::uv_callback(uv_async_t *h) {
  //spdlog::debug("tg: libuv callback");
  TelegramClient *self = (TelegramClient*)h->data;

  self->m_responseQueueMutex.lock();
  while (self->m_responseQueue.size() > 0) {
    auto response = std::move(self->m_responseQueue.front());
    self->m_responseQueue.pop_front();
    if (response.request_id == 0) {
      self->dispatch_update(response.object);
    } else {
      self->dispatch_response(response.request_id, std::move(response.object));
    }
  }
  self->m_responseQueueMutex.unlock();
}

void TelegramClient::dispatch_update(TgObject &update) {
  //spdlog::debug("tg: update");
  for (auto i = m_updateHandlers.begin(); i != m_updateHandlers.end(); i++) {
    i->first(i->second, *update.get());
  }
}

void TelegramClient::dispatch_response(std::uint64_t queryId, TgObject response) {
  spdlog::debug("tg: response to {}", queryId);
  auto queryHandler = m_handlers.find(queryId);
  if (queryHandler != m_handlers.end()) {
    queryHandler->second(std::move(response));
  }
}

void TelegramClient::uv_close_handle_callback(uv_handle_t *h) {
  spdlog::debug("tg: closing uv handle");
  delete h;
}

void TelegramClient::add_update_handler(std::function<void(void*, td_api::Object&)> handler, void *context) {
  m_updateHandlers.push_back({std::move(handler), context});
}
