// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

#include "ConnectionList.hpp"

#include <string>
#include <vector>

#include "IListenerStorage.hpp"
#include "wpi/nt/ntcore_c.h"
#include "wpi/nt/ntcore_cpp.hpp"
#include "wpi/util/SmallVector.hpp"
#include "wpi/util/json.hpp"
#include "wpi/util/raw_ostream.hpp"

using namespace wpi::nt;

static std::string ConnInfoToJson(bool connected, const ConnectionInfo& info) {
  std::string str;
  wpi::util::raw_string_ostream os{str};
  os << "{\"connected\":";
  wpi::util::json::stringify_bool(os, connected);
  os << ",\"remote_id\":";
  wpi::util::json::stringify_string(os, info.remote_id);
  os << ",\"remote_ip\":";
  wpi::util::json::stringify_string(os, info.remote_ip);
  os << ",\"remote_port\":";
  wpi::util::json::stringify_int(os, info.remote_port);
  os << ",\"protocol_version\":";
  wpi::util::json::stringify_int(os, info.protocol_version);
  os << "}";
  return str;
}

ConnectionList::ConnectionList(int inst, IListenerStorage& listenerStorage)
    : m_inst{inst}, m_listenerStorage{listenerStorage} {}

ConnectionList::~ConnectionList() = default;

int ConnectionList::AddConnection(const ConnectionInfo& info) {
  std::scoped_lock lock{m_mutex};
  m_connected = true;
  m_listenerStorage.Notify({}, NT_EVENT_CONNECTED, &info);
  if (!m_dataloggers.empty()) {
    auto now = Now();
    for (auto&& datalogger : m_dataloggers) {
      datalogger->entry.Append(ConnInfoToJson(true, info), now);
    }
  }
  return m_connections.emplace_back(info);
}

void ConnectionList::RemoveConnection(int handle) {
  std::scoped_lock lock{m_mutex};
  auto val = m_connections.erase(handle);
  if (m_connections.empty()) {
    m_connected = false;
  }
  if (val) {
    m_listenerStorage.Notify({}, NT_EVENT_DISCONNECTED, &(*val));
    if (!m_dataloggers.empty()) {
      auto now = Now();
      for (auto&& datalogger : m_dataloggers) {
        datalogger->entry.Append(ConnInfoToJson(false, *val), now);
      }
    }
  }
}

void ConnectionList::ClearConnections() {
  std::scoped_lock lock{m_mutex};
  m_connected = false;
  for (auto&& conn : m_connections) {
    m_listenerStorage.Notify({}, NT_EVENT_DISCONNECTED, &(*conn));
  }
  m_connections.clear();
}

std::vector<ConnectionInfo> ConnectionList::GetConnections() const {
  std::scoped_lock lock{m_mutex};
  std::vector<ConnectionInfo> info;
  info.reserve(m_connections.size());
  for (auto&& conn : m_connections) {
    info.emplace_back(*conn);
  }
  return info;
}

bool ConnectionList::IsConnected() const {
  return m_connected;
}

void ConnectionList::AddListener(NT_Listener listener, unsigned int eventMask) {
  std::scoped_lock lock{m_mutex};
  eventMask &= (NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE);
  m_listenerStorage.Activate(listener, eventMask);
  if ((eventMask & (NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE)) ==
          (NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE) &&
      !m_connections.empty()) {
    wpi::util::SmallVector<const ConnectionInfo*, 16> infos;
    infos.reserve(m_connections.size());
    for (auto&& conn : m_connections) {
      infos.emplace_back(&(*conn));
    }
    m_listenerStorage.Notify({&listener, 1},
                             NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE, infos);
  }
}

NT_ConnectionDataLogger ConnectionList::StartDataLog(wpi::log::DataLog& log,
                                                     std::string_view name) {
  std::scoped_lock lock{m_mutex};
  auto now = Now();
  auto datalogger = m_dataloggers.Add(m_inst, log, name, now);
  for (auto&& conn : m_connections) {
    datalogger->entry.Append(ConnInfoToJson(true, *conn), now);
  }
  return datalogger->handle;
}

void ConnectionList::StopDataLog(NT_ConnectionDataLogger logger) {
  std::scoped_lock lock{m_mutex};
  if (auto datalogger = m_dataloggers.Remove(logger)) {
    datalogger->entry.Finish(Now());
  }
}
