#pragma once

#include "button_config.hpp"
#include "common.hpp"
#include "xyz/openbmc_project/Chassis/Common/error.hpp"

#include <phosphor-logging/elog-errors.hpp>
// This is the base class for all the button interface types
//
class ButtonIface
{
  public:
    ButtonIface(sdbusplus::bus_t& bus, EventPtr& event, ButtonConfig& buttonCfg,
                sd_event_io_handler_t handler = ButtonIface::EventHandler) :
        bus(bus), event(event), config(buttonCfg), callbackHandler(handler)
    {
        int ret = -1;
        std::string configType;

        // config group gpio or cpld based on the defs read from the json file
        if (buttonCfg.type == ConfigType::gpio)
        {
            configType = "GPIO";
            ret = configGroupGpio(config);
        }
        else if (buttonCfg.type == ConfigType::cpld)
        {
            configType = "CPLD";
            ret = configCpld(config);
        }

        if (ret < 0)
        {
            phosphor::logging::log<phosphor::logging::level::ERR>(
                (getFormFactorType() + " : failed to config " + configType)
                    .c_str());
            throw sdbusplus::xyz::openbmc_project::Chassis::Common::Error::
                IOError();
        }
    }
    virtual ~ButtonIface() {}

    /**
     * @brief This method is called from sd-event provided callback function
     * callbackHandler if platform specific event handling is needed then a
     * derived class instance with its specific evend handling logic along with
     * init() function can be created to override the default event handling.
     */

    virtual void handleEvent(sd_event_source* es, int fd, uint32_t revents) = 0;
    static int EventHandler(sd_event_source* es, int fd, uint32_t revents,
                            void* userdata)
    {
        if (userdata)
        {
            ButtonIface* buttonIface = static_cast<ButtonIface*>(userdata);
            buttonIface->handleEvent(es, fd, revents);
        }

        return 0;
    }

    std::string getFormFactorType() const
    {
        return config.formFactorName;
    }

  protected:
    /**
     * @brief oem specific initialization can be done under init function.
     * if platform specific initialization is needed then
     * a derived class instance with its own init function to override the
     * default init() method can be added.
     */

    virtual void init()
    {
        // initialize the button io fd from the ButtonConfig
        // which has fd stored when configGroupGpio or configCpld is called
        for (auto fd : config.fds)
        {
            char buf;

            int ret = ::read(fd, &buf, sizeof(buf));
            if (ret < 0)
            {
                phosphor::logging::log<phosphor::logging::level::ERR>(
                    (getFormFactorType() + " : read error!").c_str());
            }

            ret = sd_event_add_io(event.get(), nullptr, fd, EPOLLPRI,
                                  callbackHandler, this);
            if (ret < 0)
            {
                phosphor::logging::log<phosphor::logging::level::ERR>(
                    (getFormFactorType() + " : failed to add to event loop")
                        .c_str());
                if (fd > 0)
                {
                    ::close(fd);
                }
                throw sdbusplus::xyz::openbmc_project::Chassis::Common::Error::
                    IOError();
            }
        }
    }
    /**
     * @brief similar to init() oem specific deinitialization can be done under
     * deInit function. if platform specific deinitialization is needed then a
     * derived class instance with its own init function to override the default
     * deinit() method can be added.
     */
    virtual void deInit()
    {
        for (auto fd : config.fds)
        {
            if (fd > 0)
            {
                ::close(fd);
            }
        }
    }

    sdbusplus::bus_t& bus;
    EventPtr& event;
    ButtonConfig config;
    sd_event_io_handler_t callbackHandler;
};