#ifndef StatefulService_h
#define StatefulService_h

#include <Arduino.h>
#include <ArduinoJson.h>

#include <list>
#include <functional>
#ifdef ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#endif

#ifndef DEFAULT_BUFFER_SIZE
#define DEFAULT_BUFFER_SIZE 1024
#endif

enum class StateUpdateResult {
  CHANGED = 0,  // The update changed the state and propagation should take place if required
  UNCHANGED,    // The state was unchanged, propagation should not take place
  ERROR         // There was a problem updating the state, propagation should not take place
};

template <typename T>
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject& root, T& settings)>;

template <typename T>
using JsonStateReader = std::function<void(T& settings, JsonObject& root)>;

typedef size_t update_handler_id_t;
typedef std::function<void(const String& originId)> StateUpdateCallback;

typedef struct StateUpdateHandlerInfo {
  static update_handler_id_t currentUpdatedHandlerId;
  update_handler_id_t _id;
  StateUpdateCallback _cb;
  bool _allowRemove;
  StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove) :
      _id(++currentUpdatedHandlerId), _cb(cb), _allowRemove(allowRemove){};
} StateUpdateHandlerInfo_t;

template <class T>
class StatefulService {
 public:
  template <typename... Args>
#ifdef ESP32
  StatefulService(Args&&... args) :
      _state(std::forward<Args>(args)...), _accessMutex(xSemaphoreCreateRecursiveMutex()) {
  }
#else
  StatefulService(Args&&... args) : _state(std::forward<Args>(args)...) {
  }
#endif

  update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
    if (!cb) {
      return 0;
    }
    StateUpdateHandlerInfo_t updateHandler(cb, allowRemove);
    _updateHandlers.push_back(updateHandler);
    return updateHandler._id;
  }

  void removeUpdateHandler(update_handler_id_t id) {
    for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) {
      if ((*i)._allowRemove && (*i)._id == id) {
        i = _updateHandlers.erase(i);
      } else {
        ++i;
      }
    }
  }

  StateUpdateResult update(std::function<StateUpdateResult(T&)> stateUpdater, const String& originId) {
    beginTransaction();
    StateUpdateResult result = stateUpdater(_state);
    endTransaction();
    if (result == StateUpdateResult::CHANGED) {
      callUpdateHandlers(originId);
    }
    return result;
  }

  StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T&)> stateUpdater) {
    beginTransaction();
    StateUpdateResult result = stateUpdater(_state);
    endTransaction();
    return result;
  }

  StateUpdateResult update(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater, const String& originId) {
    beginTransaction();
    StateUpdateResult result = stateUpdater(jsonObject, _state);
    endTransaction();
    if (result == StateUpdateResult::CHANGED) {
      callUpdateHandlers(originId);
    }
    return result;
  }

  StateUpdateResult updateWithoutPropagation(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater) {
    beginTransaction();
    StateUpdateResult result = stateUpdater(jsonObject, _state);
    endTransaction();
    return result;
  }

  void read(std::function<void(T&)> stateReader) {
    beginTransaction();
    stateReader(_state);
    endTransaction();
  }

  void read(JsonObject& jsonObject, JsonStateReader<T> stateReader) {
    beginTransaction();
    stateReader(_state, jsonObject);
    endTransaction();
  }

  void callUpdateHandlers(const String& originId) {
    for (const StateUpdateHandlerInfo_t& updateHandler : _updateHandlers) {
      updateHandler._cb(originId);
    }
  }

 protected:
  T _state;

  inline void beginTransaction() {
#ifdef ESP32
    xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
#endif
  }

  inline void endTransaction() {
#ifdef ESP32
    xSemaphoreGiveRecursive(_accessMutex);
#endif
  }

 private:
#ifdef ESP32
  SemaphoreHandle_t _accessMutex;
#endif
  std::list<StateUpdateHandlerInfo_t> _updateHandlers;
};

#endif  // end StatefulService_h