#include "projectManager.h"

#include "requirementItem.h"
#include "requirementManager.h"

using namespace std;
using namespace core::project;
//--------------------------------------------------------------
/* Constructor */
ProjectManager::ProjectManager(dBus::Bus &bus)
  : Node(bus)
{
  // Initialize the bus listener thread
  runBusListener();
}
//--------------------------------------------------------------
/* Default destructor */
ProjectManager::~ProjectManager()
{
  // Stop the bus listener thread if it's running
  stopBusListener();
}
//--------------------------------------------------------------
/* Open a project file and load the requirements */
void ProjectManager::openFile(const std::filesystem::path &filePath)
{
}
//--------------------------------------------------------------
/* Save the current requirements to a project file */
void ProjectManager::saveFile(const std::filesystem::path &filePath)
{
}
//--------------------------------------------------------------
/* Run the bus listener thread */
void ProjectManager::runBusListener()
{
  // Subscribe to the bus for messages related to project file and requirement operations
  subscribe(dBus::makeID("project.file.operation"));
  subscribe(dBus::makeID("requirement.get"));
  subscribe(dBus::makeID("requirement.requirement.getChildren"));
  subscribe(dBus::makeID("requirement.add"));
  subscribe(dBus::makeID("requirement.update"));
  subscribe(dBus::makeID("requirement.delete"));

  /* Start the bus listener thread to process incoming messages related to requirements.
   * The thread will continuously wait for messages and process them until a stop is requested. */
  // clang-format off
  m_busListenerThread = std::jthread([this](const std::stop_token &stopToken)
  {
    while (!stopToken.stop_requested())
    {
      // Wait for a message to be received
      syncWaitForMessage();

      // Process all received messages
      while (getMessageCount() > 0)
      {
        auto message = popNextMessage();
        if (message)
          processMessageBus(message);
      }
    } 
  });
  // clang-format on
}
//--------------------------------------------------------------
/* Stop the bus listener thread */
void ProjectManager::stopBusListener()
{
  // Stop event thread loop
  m_busListenerThread.request_stop();

  // Wake up event thread if waiting
  notifyMessageQueue();

  /* No need to join the thread explicitly as std::jthread will
   * automatically join in its destructor.
   * Subscribe to the bus will be automatically cleaned up in the
   * Node destructor. */
}
//--------------------------------------------------------------
/* Process a message received from the bus */
void ProjectManager::processMessageBus(const Message_ptr &message)
{
  const auto messageType = message->getMessageTypeID();
  switch (messageType)
  {
    // Process project events
    case dBus::makeID("project.file.operation"):
    {
      on_projectOperationEvent(message);
      break;
    }

    // Process requirement events
    case dBus::makeID("requirement.get"):
    {
      on_requirementGetEvent(message->as<dBus::RequestMessage<api::requirement::GetRequirementEvent, api::requirement::Requirement>>());
      break;
    }
    case dBus::makeID("requirement.getChildren"):
    {
      on_requirementGetChildrenEvent(message->as<dBus::RequestMessage<api::requirement::GetChildrenRequirementEvent, std::vector<api::requirement::Requirement>>>());
      break;
    }
    case dBus::makeID("requirement.add"):
    {
      on_requirementAddEvent(message->as<dBus::RequestMessage<api::requirement::AddRequirementEvent, void>>());
      break;
    }
    case dBus::makeID("requirement.update"):
    {
      on_requirementUpdateEvent(message->as<dBus::RequestMessage<api::requirement::UpdateRequirementEvent, void>>());
      break;
    }
    case dBus::makeID("requirement.delete"):
    {
      on_requirementDeleteEvent(message->as<dBus::RequestMessage<api::requirement::DeleteRequirementEvent, void>>());
      break;
    }

    default:
      break;
  }
}
//--------------------------------------------------------------
/* Process a file operation event */
void ProjectManager::on_projectOperationEvent(const Message_ptr &message)
{
  const auto specializedMessage = message->as<dBus::RequestMessage<api::project::ProjectOperationEvent, void>>();

  // Sanity check
  if (!specializedMessage)
    return;

  switch (specializedMessage->value.operationType)
  {
    using enum api::project::OperationType;
    case Create:
      on_projectCreateFileEvent(specializedMessage);
      break;
    case Open:
      on_projectOpenFileEvent(specializedMessage);
      break;
    case Save:
    case SaveAs:
      on_projectSaveFileEvent(specializedMessage);
      break;
    case Close:
      on_projectCloseFileEvent(specializedMessage);
      break;
  }
}
//--------------------------------------------------------------
/* Process a file creation event */
void ProjectManager::on_projectCreateFileEvent(const api::project::ProjectOperationMessage_ptr &message)
{
  m_requirementManager = make_shared<RequirementManager>();

  message->responsePromise.set_value();
}
//--------------------------------------------------------------
/* Process a file open event */
void ProjectManager::on_projectOpenFileEvent(const api::project::ProjectOperationMessage_ptr &message)
{
  try
  {
    openFile(message->value.filePath);

    message->responsePromise.set_value();
  }
  catch (const std::exception &e)
  {
    message->responsePromise.set_exception(std::make_exception_ptr(e));
  }
}
//--------------------------------------------------------------
/* Process a file save event */
void ProjectManager::on_projectSaveFileEvent(const api::project::ProjectOperationMessage_ptr &message)
{
  try
  {
    if (message->value.operationType == api::project::OperationType::SaveAs)
    {
      if (message->value.filePath.empty())
        throw std::runtime_error("File path is empty for SaveAs operation");
      saveFile(message->value.filePath);
    }
    else if (message->value.operationType == api::project::OperationType::Save)
    {
      if (m_filePath.empty())
        throw std::runtime_error("File path is empty for Save operation");
      saveFile();
    }

    message->responsePromise.set_value();
  }
  catch (const std::exception &e)
  {
    message->responsePromise.set_exception(std::make_exception_ptr(e));
  }
}
//--------------------------------------------------------------
/* Process a file close event */
void ProjectManager::on_projectCloseFileEvent(const api::project::ProjectOperationMessage_ptr &message)
{
  m_requirementManager = {};
  message->responsePromise.set_value();
}
//--------------------------------------------------------------
/* Process a requirement get event */
void ProjectManager::on_requirementGetEvent(const api::requirement::GetRequirementMessage_ptr &message) const
{
  // Sanity check
  if (!message)
    return;

  try
  {
    // Sanity check
    if (!m_requirementManager)
      throw std::runtime_error("No project file is currently opened");

    // Get requirement handle
    const auto requirement = m_requirementManager->getRequirement(message->value.uuid);
    if (!requirement)
      throw std::runtime_error("Requirement not found");

    message->responsePromise.set_value(requirement->toRequirement());
  }
  catch (const std::exception &e)
  {
    message->responsePromise.set_exception(std::make_exception_ptr(e));
  }
}
//--------------------------------------------------------------
/* Process a requirement get children event */
void ProjectManager::on_requirementGetChildrenEvent(const api::requirement::GetChildrenRequirementMessage_ptr &message) const
{
  // Sanity check
  if (!message)
    return;

  try
  {
    // Sanity check
    if (!m_requirementManager)
      throw std::runtime_error("No project file is currently opened");

    // Get requirement handle
    const auto requirement = m_requirementManager->getRequirement(message->value.uuid);
    if (!requirement)
      throw std::runtime_error("Requirement not found");

    // Get children requirements
    const auto                           &children = requirement->getChildren();
    vector<api::requirement::Requirement> childrenData;
    childrenData.reserve(children.size());
    for (const auto &child : children)
      childrenData.push_back(child->toRequirement());

    message->responsePromise.set_value(childrenData);
  }
  catch (const std::exception &e)
  {
    message->responsePromise.set_exception(std::make_exception_ptr(e));
  }
}
//--------------------------------------------------------------
/* Process a requirement addition event */
void ProjectManager::on_requirementAddEvent(const api::requirement::AddRequirementMessage_ptr &message) const
{
  // Sanity check
  if (!message)
    return;

  try
  {
    // Sanity check
    if (!m_requirementManager)
      throw std::runtime_error("No project file is currently opened");

    // Get parent requirement handle
    const auto parentRequirement = message->value.parentUuid.empty()
                                     ? m_requirementManager->getRootRequirement()
                                     : m_requirementManager->getRequirement(message->value.parentUuid);
    if (!parentRequirement)
      throw std::runtime_error("Parent requirement not found");

    // Append the new requirement to the parent requirement
    parentRequirement->appendChild(message->value.requirementData);

    message->responsePromise.set_value();
  }
  catch (const std::exception &e)
  {
    message->responsePromise.set_exception(std::make_exception_ptr(e));
  }
}
//--------------------------------------------------------------
/* Process a requirement update event */
void ProjectManager::on_requirementUpdateEvent(const api::requirement::UpdateRequirementMessage_ptr &message) const
{
  // Sanity check
  if (!message)
    return;

  try
  {
    // Sanity check
    if (!m_requirementManager)
      throw std::runtime_error("No project file is currently opened");

    // Get requirement handle
    const auto requirement = m_requirementManager->getRequirement(message->value.requirementData.metadata.uuid);
    if (!requirement)
      throw std::runtime_error("Requirement not found");

    // Update the requirement data
    requirement->update(message->value.requirementData);

    message->responsePromise.set_value();
  }
  catch (const std::exception &e)
  {
    message->responsePromise.set_exception(std::make_exception_ptr(e));
  }
}
//--------------------------------------------------------------
/* Process a requirement deletion event */
void ProjectManager::on_requirementDeleteEvent(const api::requirement::DeleteRequirementMessage_ptr &message) const
{
  // Sanity check
  if (!message)
    return;

  try
  {
    // Sanity check
    if (!m_requirementManager)
      throw std::runtime_error("No project file is currently opened");

    // Get requirement handle
    const auto requirement = m_requirementManager->getRequirement(message->value.requirementData.metadata.uuid);
    if (!requirement)
      throw std::runtime_error("Requirement not found");

    // Get parent requirement handle
    const auto parentRequirement = requirement->getParent();
    if (!parentRequirement)
    {
      // If the requirement to be deleted is the root requirement,
      // we cannot delete it as it is the base of the requirements hierarchy
      throw std::runtime_error("Cannot delete root requirement");
    }

    // Remove the requirement from its parent
    parentRequirement->removeChild(requirement->metadata.uuid);

    message->responsePromise.set_value();
  }
  catch (const std::exception &e)
  {
    message->responsePromise.set_exception(std::make_exception_ptr(e));
  }
}
//--------------------------------------------------------------
