#include "config.h"

#include "control.h"

#include <unistd.h>
#include <sys/stat.h>
#include <torrent/net/http_stack.h>
#include <torrent/runtime/network_manager.h>
#include <torrent/runtime/runtime.h>
#include <torrent/utils/directory_events.h>

#include "core/dht_manager.h"
#include "core/http_queue.h"
#include "core/manager.h"
#include "core/view_manager.h"
#include "display/canvas.h"
#include "display/window.h"
#include "display/window_http_queue.h"
#include "display/window_input.h"
#include "display/window_statusbar.h"
#include "display/window_title.h"
#include "display/manager.h"
#include "input/manager.h"
#include "input/input_event.h"
#include "rpc/command_scheduler.h"
#include "rpc/lua.h"
#include "rpc/parse_commands.h"
#include "rpc/object_storage.h"
#include "session/session_manager.h"
#include "ui/root.h"
#include "utils/watch_ready_queue.h"

Control::Control()
  : m_ui(new ui::Root()),
    m_display(new display::Manager()),
    m_input(new input::Manager()),
    m_inputStdin(new input::InputEvent(STDIN_FILENO)),
    m_commandScheduler(new rpc::CommandScheduler()),
    m_objectStorage(new rpc::object_storage()),
    m_lua_engine(new rpc::LuaEngine()),
    m_directory_events(new torrent::directory_events()),
    m_watch_ready_queue(new utils::WatchReadyQueue()) {

  m_core         = std::make_unique<core::Manager>();
  m_view_manager = std::make_unique<core::ViewManager>();
  m_dht_manager  = std::make_unique<core::DhtManager>();

  m_inputStdin->slot_pressed(std::bind(&input::Manager::pressed, m_input.get(), std::placeholders::_1));

  m_task_shutdown.slot()                = [this] { handle_shutdown(); };
  m_task_shutdown_clear_requests.slot() = [this] { handle_shutdown_clear_requests(); };

  m_commandScheduler->set_slot_error_message([this](const std::string& msg) { m_core->push_log_std(msg); });
}

Control::~Control() {
  m_view_manager.reset();

  m_ui.reset();
  m_display.reset();
}

void
Control::initialize() {
  session_thread::thread()->start_thread();
  scgi_thread::thread()->start_thread();

  display::Canvas::initialize();
  display::Window::slot_schedule([this](display::Window* w, std::chrono::microseconds t) { m_display->schedule(w, t); });
  display::Window::slot_unschedule([this](display::Window* w) { m_display->unschedule(w); });
  display::Window::slot_adjust([this]() { m_display->adjust_layout(); });

  m_core->set_hashing_view(*m_view_manager->find_throw("hashing"));

  m_ui->init(this);

  if(!display::Canvas::daemon())
    m_inputStdin->insert();
}

void
Control::cleanup() {
  rpc::rpc.cleanup();

  torrent::this_thread::scheduler()->erase(&m_task_shutdown);
  torrent::this_thread::scheduler()->erase(&m_task_shutdown_clear_requests);

  if(!display::Canvas::daemon())
    m_inputStdin->remove();

  m_directory_events->close();

  if (scgi_thread::thread()->is_active())
    scgi_thread::thread()->stop_thread_wait();

  // Wait for all session files to be written.
  session_thread::manager()->flush_all_pending_builds();
  session_thread::thread()->stop_thread_wait();

  m_ui->cleanup();
  m_core->cleanup();

  display::Canvas::erase_std();
  display::Canvas::refresh_std();
  display::Canvas::do_update();
  display::Canvas::cleanup();
}

void
Control::cleanup_exception() {
  display::Canvas::cleanup();
}

bool
Control::is_shutdown_completed() {
  if (!m_shutdown_quick)
    return false;

  // Tracker requests can be disowned, so wait for these to
  // finish. The edge case of torrent http downloads may delay
  // shutdown.

  // TODO: We keep http requests in the queue for a while after, so improve this check to ignore
  // those.
  if (torrent::net_thread::http_stack()->size() != 0 || !core()->http_queue()->empty())
    return false;

  return core()->is_download_shutdown_completed();
}

void
Control::handle_shutdown() {
  m_watch_ready_queue->shutdown();

  rpc::commands.call_catch("event.system.shutdown", rpc::make_target(), "shutdown", "System shutdown event action failed: ");

  if (scgi_thread::thread()->is_active())
    scgi_thread::thread()->stop_thread_wait();

  if (!m_shutdown_quick) {
    torrent::runtime::network_manager()->listen_close();
    torrent::runtime::shutdown();

    m_directory_events->close();
    m_core->shutdown(false);

    if (!m_task_shutdown.is_scheduled())
      torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_shutdown, 5s);

  } else {
    torrent::runtime::quick_shutdown();
    m_core->shutdown(true);
  }

  if (!m_task_shutdown_clear_requests.is_scheduled())
    torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_shutdown_clear_requests, 10s);

  m_shutdown_quick    = true;
  m_shutdown_received = false;
}

void
Control::handle_shutdown_clear_requests() {
  torrent::net_thread::http_stack()->clear_requests();

  // Use 5s for the initial wait to ensure trackers get a chance to finish both IPv4 and IPv6 requests.
  if (m_clear_requests_count++ == 0)
    torrent::this_thread::scheduler()->wait_for(&m_task_shutdown_clear_requests, 5s);
  else
    torrent::this_thread::scheduler()->wait_for(&m_task_shutdown_clear_requests, 1s);
}
