#pragma once

#include "message.hpp"
#include "message_parsers.hpp"
#include "session.hpp"
#include "sol/console_buffer.hpp"

#include <memory>
#include <numeric>

namespace message
{

class Handler
{
  public:
    explicit Handler(
        std::shared_ptr<udpsocket::Channel> channel,
        uint32_t sessionID = message::Message::MESSAGE_INVALID_SESSION_ID) :
        sessionID(sessionID),
        channel(channel)
    {
    }

    Handler() = delete;
    ~Handler() = default;
    Handler(const Handler&) = default;
    Handler& operator=(const Handler&) = default;
    Handler(Handler&&) = default;
    Handler& operator=(Handler&&) = default;

    /**
     * @brief Receive the IPMI packet
     *
     * Read the data on the socket, get the parser based on the Session
     * header type and flatten the payload and generate the IPMI message
     *
     * @return IPMI Message on success and nullptr on failure
     *
     */
    std::shared_ptr<Message> receive();

    /**
     * @brief Process the incoming IPMI message
     *
     * The incoming message payload is handled and the command handler for
     * the Network function and Command is executed and the response message
     * is returned
     *
     * @param[in] inMessage - Incoming Message
     *
     * @return Outgoing message on success and nullptr on failure
     */
    std::shared_ptr<Message> executeCommand(std::shared_ptr<Message> inMessage);

    /** @brief Send the outgoing message
     *
     *  The payload in the outgoing message is flattened and sent out on the
     *  socket
     *
     *  @param[in] outMessage - Outgoing Message
     */
    void send(std::shared_ptr<Message> outMessage);

    /** @brief Set socket channel in session object */
    void setChannelInSession() const;

    /** @brief Send the SOL payload
     *
     *  The SOL payload is flattened and sent out on the socket
     *
     *  @param[in] input - SOL Payload
     */
    void sendSOLPayload(const std::vector<uint8_t>& input);

    /** @brief Send the unsolicited IPMI payload to the remote console.
     *
     *  This is used by commands like SOL activating, in which case the BMC
     *  has to notify the remote console that a SOL payload is activating
     *  on another channel.
     *
     *  @param[in] netfn - Net function.
     *  @param[in] cmd - Command.
     *  @param[in] input - Command request data.
     */
    void sendUnsolicitedIPMIPayload(uint8_t netfn, uint8_t cmd,
                                    const std::vector<uint8_t>& input);

    // BMC Session ID for the Channel
    session::SessionID sessionID;

  private:
    /** @brief Socket channel for communicating with the remote client.*/
    std::shared_ptr<udpsocket::Channel> channel;

    parser::SessionHeader sessionHeader = parser::SessionHeader::IPMI20;

    /**
     * @brief Create the response IPMI message
     *
     * The IPMI outgoing message is constructed out of payload and the
     * corresponding fields are populated.For the payload type IPMI, the
     * LAN message header and trailer are added.
     *
     * @tparam[in] T - Outgoing message payload type
     * @param[in] output - Payload for outgoing message
     * @param[in] inMessage - Incoming IPMI message
     *
     * @return Outgoing message on success and nullptr on failure
     */
    template <PayloadType T>
    std::shared_ptr<Message> createResponse(std::vector<uint8_t>& output,
                                            std::shared_ptr<Message> inMessage)
    {
        auto outMessage = std::make_shared<Message>();
        outMessage->payloadType = T;
        outMessage->payload = output;
        return outMessage;
    }

    /**
     * @brief Extract the command from the IPMI payload
     *
     * @param[in] message - Incoming message
     *
     * @return Command ID in the incoming message
     */
    uint32_t getCommand(std::shared_ptr<Message> message);

    /**
     * @brief Calculate 8 bit 2's complement checksum
     *
     * Initialize checksum to 0. For each byte, checksum = (checksum + byte)
     * modulo 256. Then checksum = - checksum. When the checksum and the
     * bytes are added together, modulo 256, the result should be 0.
     */
    uint8_t crc8bit(const uint8_t* ptr, const size_t len)
    {
        return (0x100 - std::accumulate(ptr, ptr + len, 0));
    }
};

} // namespace message