#pragma once
#include "file.hpp"

#include <libevdev/libevdev.h>
#include <systemd/sd-event.h>

#include <sdbusplus/message.hpp>

#include <map>
#include <memory>
#include <string>

namespace phosphor
{
namespace gpio
{

/* Need a custom deleter for freeing up sd_event */
struct EventDeleter
{
    void operator()(sd_event* event) const
    {
        sd_event_unref(event);
    }
};
using EventPtr = std::unique_ptr<sd_event, EventDeleter>;

/* Need a custom deleter for freeing up sd_event_source */
struct EventSourceDeleter
{
    void operator()(sd_event_source* eventSource) const
    {
        sd_event_source_unref(eventSource);
    }
};
using EventSourcePtr = std::unique_ptr<sd_event_source, EventSourceDeleter>;

/* Need a custom deleter for freeing up evdev struct */
struct FreeEvDev
{
    void operator()(struct libevdev* device) const
    {
        libevdev_free(device);
    }
};
using EvdevPtr = std::unique_ptr<struct libevdev, FreeEvDev>;

/** @class Evdev
 *  @brief Responsible for catching GPIO state changes conditions and taking
 *  actions
 */
class Evdev
{
    using Property = std::string;
    using Value = std::variant<bool, std::string>;
    // Association between property and its value
    using PropertyMap = std::map<Property, Value>;
    using Interface = std::string;
    // Association between interface and the D-Bus property
    using InterfaceMap = std::map<Interface, PropertyMap>;
    using Object = sdbusplus::message::object_path;
    // Association between object and the interface
    using ObjectMap = std::map<Object, InterfaceMap>;

  public:
    Evdev() = delete;
    ~Evdev() = default;
    Evdev(const Evdev&) = delete;
    Evdev& operator=(const Evdev&) = delete;
    Evdev(Evdev&&) = delete;
    Evdev& operator=(Evdev&&) = delete;

    /** @brief Constructs Evdev object.
     *
     *  @param[in] path      - Device path to read for GPIO pin state
     *  @param[in] key       - GPIO key to monitor
     *  @param[in] event     - sd_event handler
     *  @param[in] handler   - IO callback handler.
     *  @param[in] useEvDev  - Whether to use EvDev to retrieve events
     */
    Evdev(const std::string& path, const unsigned int key, EventPtr& event,
          sd_event_io_handler_t handler, bool useEvDev = true) :
        path(path), key(key), event(event), callbackHandler(handler),
        fd(openDevice())

    {
        if (useEvDev)
        {
            // If we are asked to use EvDev, do that initialization.
            initEvDev();
        }

        // Register callback handler when FD has some data
        registerCallback();
    }

  protected:
    /** @brief Device path to read for GPIO pin state */
    const std::string path;

    /** @brief GPIO key to monitor */
    const unsigned int key;

    /** @brief Event structure */
    EvdevPtr devicePtr;

    /** @brief Monitor to sd_event */
    EventPtr& event;

    /** @brief Callback handler when the FD has some data */
    sd_event_io_handler_t callbackHandler;

    /** @brief event source */
    EventSourcePtr eventSource;

    /** @brief Opens the device and populates the descriptor */
    int openDevice();

    /** @brief attaches FD to events and sets up callback handler */
    void registerCallback();

    /** @brief File descriptor manager */
    FileDescriptor fd;

    /** @brief Initializes evdev handle with the fd */
    void initEvDev();
};

} // namespace gpio
} // namespace phosphor