#include "ServiceManager.hpp"

// C++ system level
#include <vector>
#include <algorithm>
// #include <cstring>     // memset, strncmp
// #include <cstdlib>     // TODO: is this for memcpy?
// #include <functional>

// ESP32 specific
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// project specific
#include <Types.hpp>
#include <SpiderLib/Util.hpp>  // TODO: remove after startAlgo()-debuggin!!
// #include "SiliconTorch/NVSExplorer.hpp"

// qthing stuff
// #include <qthing>


static const char* TAG = "ServiceManager";


namespace SiliconTorch {

  namespace ServiceManager {


    void registerSiliconTorchServices(ServiceManager* mgr);


    ServiceManager::ServiceManager() {

      registerSiliconTorchServices(this);

      registrationLocked = true;


      for (auto& [name, service] : serviceMap)
        service->postInit();

      auto sers = startAlgo();

      ESP_LOGI(TAG, "=====   Services starting order   =====");

      for (const auto& s : sers) ESP_LOGI(TAG, "Service: %s", s->getFancyName().c_str());

      ESP_LOGI(TAG, "=======================================");

      return;


      printServiceTable();

      ESP_LOGI(TAG, "Starting services…");
      u32 started = startEnabledServices();
      ESP_LOGI(TAG, "Starting services#[ %d ] done.", started);
    }



    std::vector<Service*> ServiceManager::startAlgo() {
      
      std::vector<Service*> startOrder;  // our result


      std::map<Service*, ServiceSet> services;

      // prepare our data structures
      for (const auto& [_, service] : serviceMap) {

        if (service->isEnabled()) {

          bool reqsSatisfied = true;

          ServiceSet deps;
          ServiceSet wanted   = service->getWantedServices();
          ServiceSet required = service->getRequiredServices();

          // copy wanted services if enabled
          for (const auto& sw : wanted)
            if (sw->isEnabled()) deps.insert(sw);

          // copy required services
          for (const auto& sr : required)
            if (sr->isEnabled()) deps.insert(sr);
            else reqsSatisfied = false;

          if (reqsSatisfied)
            services[service] = deps;
        }

      }


      // Debugging stuff: print it!

      ESP_LOGI(TAG, "=====      Service structure      =====");

      for (const auto& [service, deps] : services) {

        std::set<std::string> depsnames;
        for (const auto& d : deps) depsnames.insert(d->getFancyName());

        std::string sdeps = SpiderLib::Util::join(depsnames, "  ");

        ESP_LOGI(TAG, "Service[ %s ] has deps{  %s  }", service->getFancyName().c_str(), sdeps.c_str());
      }

      ESP_LOGI(TAG, "=======================================");

      // end debugging stuff



      bool dslr = true;  // dslr = "did something last round" -> used for cycle detection

      while (dslr) {
        bool serviceCopied = false;

        auto it = services.begin();
        while (it != services.end()) {

          if (it->second.size() == 0) {
            startOrder.push_back(it->first);
            serviceCopied = true;
            it = services.erase(it);
          } else {
            it++;
          }

        }


        for (const auto& starting : startOrder)
          for (auto& [_, deps] : services)
            deps.erase(starting);


        dslr = serviceCopied;
      }

      return startOrder;
    }


    std::vector<Service*> ServiceManager::getServicesInStartOrder() const {
      std::vector<Service*> services;

      for (const auto& [_, service] : serviceMap)
        services.push_back(service);

      // std::sort(services.begin(), services.end(), [](const Service* x, const Service* y) {
      //   return x->getStartOrder() < y->getStartOrder();
      // });

      return services;
    }

    u32 ServiceManager::startEnabledServices() {
      std::vector<Service*> services = getServicesInStartOrder();
      u32 started = 0;

      for (const auto& service : services) {
        if (service->isEnabled()) {
          service->start();
          started++;
        }
      }
      
      return started;
    }


    void ServiceManager::printServiceTable() const {

      // burn a few cycles because Espressivs logging is mehr :/
      // while (xTaskGetTickCount() * portTICK_PERIOD_MS < 1000) {
      //   vTaskDelay(42 / portTICK_PERIOD_MS);
      // }
      vTaskDelay(100 / portTICK_PERIOD_MS);

      // helper function, never used outside of this
      // TODO: should we move it to utils…?
      auto strmul = [](const str& target, u32 count) {
        str out = "";

        for (u32 n = 0; n < count; n++)
          out += target;

        return out;
      };


      u8 nameColSize = 4;  // size of "name"

      for (const auto& [name, _] : serviceMap)
        if (name.length() > nameColSize) nameColSize = name.length();

      nameColSize += 2;  // padding


      u8 startOrderColSize = 10 + 2;  // size of "StartOrder" + padding
      u8 enabledColSize    =  8 + 2;  // size of "Enabled " + padding (must be even)


      ESP_LOGI(TAG, "┌%s┬%s┬%s┐",
        strmul("─", nameColSize).c_str(),
        strmul("─", startOrderColSize).c_str(),
        strmul("─", enabledColSize).c_str()
      );

      ESP_LOGI(TAG, "│ Name %s│ StartOrder %s│ Enabled  %s│",
        strmul(" ", nameColSize - 4 - 2).c_str(),
        strmul(" ", startOrderColSize - 10 - 2).c_str(),
        strmul(" ", enabledColSize - 8 - 2).c_str()
      );

      ESP_LOGI(TAG, "╞%s╪%s╪%s╡",
        strmul("═", nameColSize).c_str(),
        strmul("═", startOrderColSize).c_str(),
        strmul("═", enabledColSize).c_str()
      );

      std::vector<Service*> services = getServicesInStartOrder();

      for (const auto& service : services) {
        u8 nameSize = service->getFancyName().length();
        ESP_LOGI(TAG, "│ %s %s│ % 5d %s│%s%s%s│",
          service->getFancyName().c_str(),
          strmul(" ", nameColSize - 2 - nameSize).c_str(),
          1337,                                                   // service->getStartOrder(),
          strmul(" ", startOrderColSize - 2 - 5).c_str(),
          strmul(" ", (enabledColSize - 2) / 2).c_str(),
          service->isEnabled() ? "✅" : "❌",
          strmul(" ", (enabledColSize - 2) / 2).c_str()
        );
      }

      ESP_LOGI(TAG, "└%s┴%s┴%s┘",
        strmul("─", nameColSize).c_str(),
        strmul("─", startOrderColSize).c_str(),
        strmul("─", enabledColSize).c_str()
      );

    }



    void ServiceManager::setServiceEnabled(const str& name, bool enabled) {
      if (serviceMap.count(name) > 0) {
        serviceMap.at(name)->setEnabled(enabled);
      } else {
        // Service not found
        // TODO: logging…?
      }
    }

    bool ServiceManager::getServiceEnabled(const str& name) const {
      if (serviceMap.count(name) > 0) {
        return serviceMap.at(name)->isEnabled();
      } else {
        // Service not found
        // TODO: logging…?
        return false;
      }
    }


    Service* ServiceManager::byName(const str& name) {
      if (serviceMap.count(name) > 0)
        return serviceMap.at(name);
      else
        return NULL;
    }


    void ServiceManager::registerService(Service* s) {
      if (!registrationLocked) {

        auto serviceLookup = [&](const str& name) {
          return byName(name);          
        };

        s->preInit(serviceLookup);
        s->init();
        // only do init() here to avoid name-lookup cycles in postInit() !

        serviceMap[s->getName()] = s;

        ESP_LOGI(TAG, "Registered service[ %s ]", s->getFancyName().c_str());
      } else {
        ESP_LOGW(TAG, "Service registration is locked");
      }
    }



  }
}