#include <VWeb.h>
#include <cstring>

namespace VWeb {
Server::Server() {
  m_Router = CreateRef<Router>();
  m_ServerConfig = CreateRef<ServerConfig>();
  m_ServerConfig->EPoll = CreateRef<EPollManager>();
  m_ServerConfig->Socket = CreateRef<SocketManager>(m_ServerConfig);
  m_RequestHandler = CreateRef<RequestHandler>(m_ServerConfig->Socket);
  m_RequestHandler->m_Server = this;
};
void Server::LoadSharedLibs() {
  // @TODO: LOAD .so files inside sub-folder
}
void Server::Start() {
  m_ServerConfig->Socket->Init();
  if (m_ServerConfig->Socket->IsErrored())
    return;
  m_RequestHandler->SetRouter(m_Router);
  m_RequestHandler->InitThreads(m_ServerConfig->WorkerThreads);
  m_WorkerThread = CreateRef<std::thread>(&Server::Execute, this);
  fprintf(stdout, "[VWeb] Running Server On: 0.0.0.0:%d\n",
          m_ServerConfig->Port);
}
void Server::AddRoute(const std::string &path, const Ref<Route> &route) {
  m_Router->AddRoute(path, route);
}
void Server::RemoveRoute(const std::string &path) {
  m_Router->DeleteRoute(path);
}
void Server::Execute() {
  constexpr size_t MAX_EVENTS = 5000;
  struct epoll_event events[MAX_EVENTS];
  int sockID = m_ServerConfig->Socket->ID();
  for (;;) {
    if (m_IsExit)
      break;
    int eventCount = m_ServerConfig->EPoll->Wait(MAX_EVENTS, events);
    for (int i = 0; i < eventCount; ++i) {
      auto &event = events[i];
      if ((event.events & EPOLLERR) || (event.events & EPOLLHUP)) {
        m_RawRequest.Remove(event.data.fd);
        SocketUtils::Close(event.data.fd);
        continue;
      }
      if (event.events & EPOLLOUT) {
        OutgoingExecute(events[i]);
      }
      if (event.data.fd == sockID) {
        IncomingExecute(event);
      } else if (event.events & EPOLLIN) {
        HandleRequestReading(event);
      }
    }
  }
}

void Server::OutgoingExecute(epoll_event &event) {
  if (!m_OutRequest.Has(event.data.fd)) {
    m_ServerConfig->Socket->SetReadListen(event.data.fd);
    return;
  }
  auto &sendData = m_OutRequest.Get(event.data.fd);
  auto returnVal = SocketUtils::Write(sendData);
  if (returnVal == WriteState::ERRORED || returnVal == WriteState::OK) {
    SocketUtils::WriteDone(sendData.SocketID);
    m_OutRequest.Remove(event.data.fd);
  }
}

void Server::IncomingExecute(epoll_event &event) {
  for (;;) {
    auto req = m_ServerConfig->Socket->Handle();
    if (req.ReturnValue == EPollReturns::BREAK ||
        req.ReturnValue == EPollReturns::FAILURE)
      break;
    else if (req.ReturnValue == EPollReturns::OK) {
      if (!m_RawRequest.Add(req.SockId, req))
        SocketUtils::Close(req.SockId);
    } else
      continue;
  }
}
void Server::HandleRequestReading(epoll_event &event) {
  int fd = event.data.fd;
  if (!m_RawRequest.Has(fd)) {
    SocketUtils::Close(fd);
    return;
  }
  auto &data = m_RawRequest.Get(fd);
  char buf[m_ServerConfig->BufferSize];
  bool isFinished = false;
  for (;;) {
    memset(buf, 0, sizeof(buf));
    ssize_t nBytes = recv(fd, buf, m_ServerConfig->BufferSize, 0);
    if (nBytes < 0) {
      if (errno == EAGAIN || errno == EWOULDBLOCK) {
        if (isFinished)
          CreateRequest(fd);
        break;
      } else {
        m_RawRequest.Remove(fd);
        SocketUtils::Close(fd);
        break;
      }
    } else if (nBytes == 0) {
      m_RawRequest.Remove(fd);
      SocketUtils::Close(fd);
    } else {
      if (m_ServerConfig->MaxBufferSize != -1 &&
          data.CurrentBytes + nBytes > m_ServerConfig->MaxBufferSize) {
        data.Data.clear();
        data.Data << "GET @431 HTTP/1.1";
        CreateRequest(fd);
        break;
      }
      data.Data << buf;
      data.CurrentBytes += nBytes;
      if (nBytes < m_ServerConfig->BufferSize - 1) {
        isFinished = true;
        continue;
      }
    }
  }
}
void Server::CreateRequest(int sockID) {
  Accept &meAccept = m_RawRequest.Get(sockID);
  auto request = CreateRef<Request>();
  request->ID = meAccept.SockId;
  request->Body = meAccept.Data.str();
  m_RawRequest.Remove(sockID);
  m_RequestHandler->AddRequest(request);
}
} // namespace VWeb