#pragma once

#include "data_interface.hpp"

#include <libpldm/transport.h>
#include <stdint.h>

#include <phosphor-logging/lg2.hpp>
#include <sdeventplus/event.hpp>
#include <sdeventplus/source/io.hpp>

#include <chrono>
#include <functional>

namespace openpower
{
namespace pels
{

/**
 * @brief Return codes from sending a command
 */
enum class CmdStatus
{
    success,
    failure
};

/**
 * @brief Return codes from the command response
 */
enum class ResponseStatus
{
    success,
    failure
};

/**
 * @class HostInterface
 *
 * An abstract base class for sending the 'New PEL available' command
 * to the host.  Used so that the PLDM interfaces can be mocked for
 * testing the HostNotifier code.  The response to this command is
 * asynchronous, with the intent that other code registers a callback
 * function to run when the response is received.
 */
class HostInterface
{
  public:
    HostInterface() = delete;
    virtual ~HostInterface() = default;
    HostInterface(const HostInterface&) = default;
    HostInterface& operator=(const HostInterface&) = default;
    HostInterface(HostInterface&&) = default;
    HostInterface& operator=(HostInterface&&) = default;

    /**
     * @brief Constructor
     *
     * @param[in] event - The sd_event object pointer
     * @param[in] dataIface - The DataInterface object
     */
    HostInterface(sd_event* event, DataInterfaceBase& dataIface) :
        _event(event), _dataIface(dataIface)
    {}

    /**
     * @brief Pure virtual function for sending the 'new PEL available'
     *        asynchronous command to the host.
     *
     * @param[in] id - The ID of the new PEL
     * @param[in] size - The size of the new PEL
     *
     * @return CmdStatus - If the send was successful or not
     */
    virtual CmdStatus sendNewLogCmd(uint32_t id, uint32_t size) = 0;

    /**
     * @brief Returns the amount of time to wait before retrying after
     *        a failed send command.
     *
     * @return milliseconds - The amount of time to wait
     */
    virtual std::chrono::milliseconds getSendRetryDelay() const
    {
        return _defaultSendRetryDelay;
    }

    /**
     * @brief Returns the amount of time to wait before retrying after
     *        a command receive.
     *
     * @return milliseconds - The amount of time to wait
     */
    virtual std::chrono::milliseconds getReceiveRetryDelay() const
    {
        return _defaultReceiveRetryDelay;
    }

    /**
     * @brief Returns the amount of time to wait before retrying if the
     *        host firmware's PEL storage was full and it can't store
     *        any more logs until it is freed up somehow.
     *
     * In this class to help with mocking.
     *
     * @return milliseconds - The amount of time to wait
     */
    virtual std::chrono::milliseconds getHostFullRetryDelay() const
    {
        return _defaultHostFullRetryDelay;
    }

    /**
     * @brief Returns the amount of time to wait after the host is up
     *        before sending commands.
     *
     * In this class to help with mocking.
     *
     * @return milliseconds - The amount of time to wait
     */
    virtual std::chrono::milliseconds getHostUpDelay() const
    {
        return _defaultHostUpDelay;
    }

    using ResponseFunction = std::function<void(ResponseStatus)>;

    /**
     * @brief Sets the function to call on the command receive.
     *
     * The success/failure status is passed to the function.
     *
     * @param[in] func - The callback function
     */
    void setResponseFunction(ResponseFunction func)
    {
        _responseFunc = std::move(func);
    }

    /**
     * @brief Call the response function
     *
     * @param[in] status - The status given to the function
     */
    void callResponseFunc(ResponseStatus status)
    {
        if (_responseFunc)
        {
            try
            {
                (*_responseFunc)(status);
            }
            catch (const std::exception& e)
            {
                lg2::error(
                    "Host iface response callback threw an exception: {EX}",
                    "EX", e);
            }
        }
    }

    /**
     * @brief Returns the event object in use
     *
     * @return sdeventplus::Event& - The event object
     */
    sdeventplus::Event& getEvent()
    {
        return _event;
    }

    /**
     * @brief Pure virtual function to cancel an in-progress command
     *
     * 'In progress' means after the send but before the receive
     */
    virtual void cancelCmd() = 0;

    /**
     * @brief Says if the command is in progress (after send/before receive)
     *
     * @return bool - If command is in progress
     */
    bool cmdInProgress() const
    {
        return _inProgress;
    }

  protected:
    /**
     * @brief Pure virtual function for implementing the asynchronous
     *        command response callback.
     *
     * @param[in] io - The sdeventplus IO object that the callback is
     *                 invoked from.
     * @param[in] fd - The file descriptor being used
     * @param[in] revents - The event status bits
     * @param[in] transport - The transport data pointer
     */
    virtual void receive(sdeventplus::source::IO& io, int fd, uint32_t revents,
                         pldm_transport* transport) = 0;

    /**
     * @brief An optional function to call on a successful command response.
     */
    std::optional<ResponseFunction> _responseFunc;

    /**
     * @brief The sd_event wrapper object needed for response callbacks
     */
    sdeventplus::Event _event;

    /**
     * @brief The DataInterface object
     */
    DataInterfaceBase& _dataIface;

    /**
     * @brief Tracks status of after a command is sent and before the
     *        response is received.
     */
    bool _inProgress = false;

  private:
    /**
     * @brief The default amount of time to wait before retrying
     *        a failed send.
     */
    const std::chrono::milliseconds _defaultSendRetryDelay{1000};

    /**
     * @brief The default amount of time to wait
     *        before retrying after a failed receive.
     */
    const std::chrono::milliseconds _defaultReceiveRetryDelay{1000};

    /**
     * @brief The default amount of time to wait when the host said it
     *        was full before sending the PEL again.
     */
    const std::chrono::milliseconds _defaultHostFullRetryDelay{60000};

    /**
     * @brief The default amount of time to wait after the host is up
     *        before sending up the PELs.
     */
    const std::chrono::milliseconds _defaultHostUpDelay{60000};
};

} // namespace pels
} // namespace openpower