#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>

#include <array>
#include <file.hpp>
#include <sbe_chipOp_handler.hpp>
namespace openpower
{
namespace sbe
{
namespace internal
{

constexpr uint16_t MAGIC_CODE = 0xC0DE;
constexpr auto SBE_OPERATION_SUCCESSFUL = 0;
constexpr auto LENGTH_OF_DISTANCE_HEADER_IN_WORDS = 0x1;
constexpr auto LENGTH_OF_RESP_HEADER_IN_WORDS = 0x2;
constexpr auto DISTANCE_TO_RESP_CODE = 0x1;
constexpr auto MAX_FFDC_LEN_IN_WORDS = 5120;
constexpr auto WORD_SIZE = 4;
constexpr auto MAGIC_CODE_BITS = 16;
std::vector<sbe_word_t> writeToFifo(const char* devPath,
                                    const sbe_word_t* cmdBuffer,
                                    size_t cmdBufLen, size_t respBufLen)
{
    ssize_t len = 0;
    std::vector<sbe_word_t> response;
    std::ostringstream errMsg;

    // Open the device and obtain the file descriptor associated with it.
    FileDescriptor fileFd(devPath, (O_RDWR | O_NONBLOCK));

    // Wait for FIFO device and perform write operation
    struct pollfd poll_fd = {};
    poll_fd.fd = fileFd();
    poll_fd.events = POLLOUT | POLLERR;

    int rc = 0;
    if ((rc = poll(&poll_fd, 1, -1)) < 0)
    {
        // TODO:use elog infrastructure
        errMsg << "Waiting for FIFO device:" << devPath << "to write failed"
               << "rc=" << rc << "errno=" << errno;
        throw std::runtime_error(errMsg.str().c_str());
    }
    if (poll_fd.revents & POLLERR)
    {
        // TODO:use elog infrastructure
        errMsg << "POLLERR while waiting for writeable FIFO,errno:" << errno;
        throw std::runtime_error(errMsg.str().c_str());
    }
    auto bytesToWrite = (cmdBufLen * WORD_SIZE);
    // Perform the write operation
    len = write(fileFd(), cmdBuffer, bytesToWrite);
    if (len < 0)
    {
        // TODO:use elog infrastructure
        errMsg << "Failed to write to FIFO device:" << devPath
               << " Length "
                  "returned= "
               << len << " errno=" << errno;
        throw std::runtime_error(errMsg.str().c_str());
    }
    // Wait for FIFO device and perform read operation
    poll_fd.fd = fileFd();
    poll_fd.events = POLLIN | POLLERR;
    if ((rc = poll(&poll_fd, 1, -1) < 0))
    {
        // TODO:use elog infrastructure
        errMsg << "Waiting for FIFO device:" << devPath << "to read failed"
               << " rc=" << rc << " and errno=" << errno;
        throw std::runtime_error(errMsg.str().c_str());
    }
    if (poll_fd.revents & POLLERR)
    {
        // TODO:use elog infrastructure
        errMsg << "POLLERR while waiting for readable FIFO,errno:" << errno;
        throw std::runtime_error(errMsg.str().c_str());
    }
    // Derive the total read length which should include the FFDC, which SBE
    // returns in case of failure.
    size_t totalReadLen = respBufLen + MAX_FFDC_LEN_IN_WORDS;
    // Create a temporary buffer
    std::vector<sbe_word_t> buffer(totalReadLen);

    ssize_t bytesToRead = (totalReadLen * WORD_SIZE);
    len = read(fileFd(), buffer.data(), bytesToRead);
    if (len < 0)
    {
        // TODO:use elog infrastructure
        errMsg << "Failed to read the FIFO device:" << devPath
               << "bytes read =" << len << " errno=" << errno;
        throw std::runtime_error(errMsg.str().c_str());
    }

    // Extract the valid number of words read.
    for (auto i = 0; i < (len / WORD_SIZE); ++i)
    {
        response.push_back(be32toh(buffer[i]));
    }

    // Closing of the file descriptor will be handled when the FileDescriptor
    // object will go out of scope.
    return response;
}

void parseResponse(std::vector<sbe_word_t>& sbeDataBuf)
{
    // Number of 32-bit words obtained from the SBE
    size_t lengthObtained = sbeDataBuf.size();

    // Fetch the SBE header and SBE chiop primary and secondary status
    // Last value in the buffer will have the offset for the SBE header
    size_t distanceToStatusHeader = sbeDataBuf[sbeDataBuf.size() - 1];

    if (lengthObtained < distanceToStatusHeader)
    {
        // TODO:use elog infrastructure
        std::ostringstream errMsg;
        errMsg << "Distance to SBE status header value "
               << distanceToStatusHeader
               << " is greater then total length of "
                  "response buffer "
               << lengthObtained;
        throw std::runtime_error(errMsg.str().c_str());
    }

    // Fetch the response header contents
    auto iter = sbeDataBuf.begin();
    std::advance(iter, (lengthObtained - distanceToStatusHeader));

    // First header word will have 2 bytes of MAGIC CODE followed by
    // Command class and command type
    //|  MAGIC BYTES:0xCODE | COMMAND-CLASS | COMMAND-TYPE|
    sbe_word_t l_magicCode = (*iter >> MAGIC_CODE_BITS);

    // Fetch the primary and secondary response code
    std::advance(iter, DISTANCE_TO_RESP_CODE);
    auto l_priSecResp = *iter;

    // Validate the magic code obtained in the response
    if (l_magicCode != MAGIC_CODE)
    {
        // TODO:use elog infrastructure
        std::ostringstream errMsg;
        errMsg << "Invalid MAGIC keyword in the response header ("
               << l_magicCode << "),expected keyword " << MAGIC_CODE;
        throw std::runtime_error(errMsg.str().c_str());
    }

    // Validate the Primary and Secondary response value
    if (l_priSecResp != SBE_OPERATION_SUCCESSFUL)
    {
        // Extract the SBE FFDC and throw it to the caller
        size_t ffdcLen =
            (distanceToStatusHeader - LENGTH_OF_RESP_HEADER_IN_WORDS -
             LENGTH_OF_DISTANCE_HEADER_IN_WORDS);
        if (ffdcLen)
        {
            std::vector<sbe_word_t> ffdcData(ffdcLen);
            // Fetch the offset of FFDC data
            auto ffdcOffset = (lengthObtained - distanceToStatusHeader) +
                              LENGTH_OF_RESP_HEADER_IN_WORDS;
            std::copy_n((sbeDataBuf.begin() + ffdcOffset), ffdcLen,
                        ffdcData.begin());
        }

        // TODO:use elog infrastructure to return the SBE and Hardware procedure
        // FFDC container back to the caller.
        std::ostringstream errMsg;
        errMsg << "Chip operation failed with SBE response code:"
               << l_priSecResp
               << ".Length of FFDC data of obtained:" << ffdcLen;
        throw std::runtime_error(errMsg.str().c_str());
    }

    // In case of success, remove the response header content and send only the
    // data.Response header will be towards the end of the buffer.
    auto respLen = (lengthObtained - distanceToStatusHeader);
    iter = sbeDataBuf.begin();
    std::advance(iter, respLen);
    sbeDataBuf.erase(iter, sbeDataBuf.end());
}

} // namespace internal
} // namespace sbe
} // namespace openpower