#include <array>
#include <atomic>
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <memory>
#include <random>
#include <string>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <poll.h>
#include <signal.h>
#include <sys/eventfd.h>
#include <sys/socket.h>
#include <unistd.h>

extern "C" {
#include <asn_random_fill.h>
}

#include <MessageFrame.h>

constexpr ::std::size_t MTU = 1024;

template<typename MessageType>
using MessagePtr = ::std::unique_ptr<MessageType, void (*)(MessageType *)>;

template<typename MessageType>
MessageType *allocMessage()
{
    return reinterpret_cast<MessageType *>(::calloc(1, sizeof(MessageType)));
}

template<typename MessageType>
MessageType *allocMessage(::std::size_t size)
{
    return reinterpret_cast<MessageType *>(::calloc(1, size));
}

#define releaseMessage(MessageType)                          \
    [](MessageType *message) -> void {                       \
        if (message != nullptr) {                            \
            ASN_STRUCT_FREE(asn_DEF_##MessageType, message); \
        }                                                    \
    }

/**
 * @brief       Print data.
 */
::std::ostream &operator<<(::std::ostream &stream, const MessageFrame &message)
{
    auto flags = stream.flags();
    auto fill  = stream.fill();
    auto width = stream.width();

    ::std::unique_ptr<char[]> buffer;
    ::std::size_t             bufferSize = 1024;
    while (true) {
        buffer = ::std::unique_ptr<char[]>(new char[bufferSize]);
        ::asn_enc_rval_t encodeResult = asn_encode_to_buffer(
            nullptr, ATS_NONSTANDARD_PLAINTEXT, &asn_DEF_MessageFrame,
            reinterpret_cast<const void *>(&message), buffer.get(), bufferSize);
        if (encodeResult.encoded < 0) {
            stream << "ENCODE_ERROR";
        } else if (static_cast<::std::size_t>(encodeResult.encoded)
                   <= bufferSize) {
            stream.write(buffer.get(), encodeResult.encoded);
            break;
        } else {
            bufferSize *= 2;
        }
    }

    stream.flags(flags);
    stream.fill(fill);
    stream.width(width);

    return stream;
}

/**
 * @brief       Fill random string.
 *
 * @param[out]  output      String to fill.
 * @param[in]   minSize     Minimum size.
 * @param[in]   maxSize     Maximum size.
 */
void fillRandomString(OCTET_STRING &output,
                      ::std::size_t minSize,
                      ::std::size_t maxSize)
{
    ::std::random_device         randomDev;
    ::std::default_random_engine engine(randomDev());
    ::std::size_t                size = minSize == maxSize
                                            ? minSize
                                            : ::std::uniform_int_distribution<::std::size_t>(
                                 minSize, maxSize)(engine);
    output.buf                        = allocMessage<::std::uint8_t>(size);
    output.size                       = size;

    ::std::uniform_int_distribution<::std::uint8_t> generator(0x20, 0x7E);
    for (::std::size_t i = 0; i < size; ++i) {
        output.buf[i] = generator(engine);
    }
}

/**
 * @brief       Fill random number.
 *
 * @tparam      NumType     Number type.
 *
 * @param[out]  output      Output value to fill.
 * @param[in]   minSize     Minimum size.
 * @param[in]   maxSize     Maximum size.
 */
template<typename NumType>
void fillRandomNumber(NumType &output, ::std::int64_t min, ::std::int64_t max)
{
    ::std::random_device                     randomDev;
    ::std::default_random_engine             engine(randomDev());
    ::std::uniform_int_distribution<NumType> generator(
        static_cast<NumType>(min), static_cast<NumType>(max));
    output = generator(engine);
    return;
}

/**
 * @brief   Get message count.
 *
 * @return  Message count.
 */
::std::uint64_t getCount()
{
    static ::std::atomic<::std::uint64_t> count(0);
    return count.fetch_add(1);
}

/**
 * @brief   Make BSM message.
 *
 * @return  Fake message made.
 */
MessagePtr<MessageFrame> makeBSMMessage()
{
    MessagePtr<MessageFrame> ret(allocMessage<MessageFrame>(),
                                 releaseMessage(MessageFrame));

    ret->present                = MessageFrame_PR::MessageFrame_PR_bsmFrame;
    ret->choice.bsmFrame.msgCnt = getCount();
    fillRandomString(ret->choice.bsmFrame.id, 8, 8);
    fillRandomNumber(ret->choice.bsmFrame.size.width, 3, 5);
    fillRandomNumber(ret->choice.bsmFrame.size.length, 10, 20);
    ret->choice.bsmFrame.size.height = allocMessage<VehicleHeight_t>();
    fillRandomNumber(*(ret->choice.bsmFrame.size.height), 3, 5);
    ret->choice.bsmFrame.driveMode = allocMessage<DriveModeType_t>();
    fillRandomNumber(*(ret->choice.bsmFrame.driveMode), 0, 3);
    ret->choice.bsmFrame.loadStatus = allocMessage<LoadStatus_t>();
    fillRandomNumber(*(ret->choice.bsmFrame.loadStatus), 0, 4);
    ret->choice.bsmFrame.vehicleId = allocMessage<OCTET_STRING>();
    fillRandomString(*ret->choice.bsmFrame.vehicleId, 17, 17);
    ret->choice.bsmFrame.priorityRequest = allocMessage<PriorityRequest>();
    fillRandomNumber(ret->choice.bsmFrame.priorityRequest->priority, 0, 10);
    ret->choice.bsmFrame.priorityRequest->node.region
        = allocMessage<RoadRegulatorID_t>();
    fillRandomNumber(*(ret->choice.bsmFrame.priorityRequest->node.region), 0,
                     1024);
    fillRandomNumber(ret->choice.bsmFrame.priorityRequest->node.id, 0, 1024);
    fillRandomNumber(ret->choice.bsmFrame.priorityRequest->id, 0, 255);
    fillRandomNumber(ret->choice.bsmFrame.priorityRequest->distance, 0, 255);

    return ret;
}

/**
 * @brief   Make map message.
 *
 * @return  Fake message made.
 */
MessagePtr<MessageFrame> makeMapMessage()
{
    MessagePtr<MessageFrame> ret(allocMessage<MessageFrame>(),
                                 releaseMessage(MessageFrame));
    ret->present                   = MessageFrame_PR::MessageFrame_PR_mapFrame;
    ret->choice.mapFrame.msgCnt    = getCount();
    ret->choice.mapFrame.timeStamp = allocMessage<MinuteOfTheYear_t>();
    fillRandomNumber(*ret->choice.mapFrame.timeStamp, 0, 3000);

    Node_t *node = allocMessage<Node_t>();
    fillRandomNumber(node->id.id, 0, 65535);
    fillRandomNumber(node->refPos.Long, 0, 10000);
    fillRandomNumber(node->refPos.lat, 0, 10000);
    asn_sequence_add(&(ret->choice.mapFrame.nodes.list), node);

    return ret;
}

/**
 * @brief   Make spat message.
 *
 * @return  Fake message made.
 */
MessagePtr<MessageFrame> makeSpatMessage()
{
    MessagePtr<MessageFrame> ret(allocMessage<MessageFrame>(),
                                 releaseMessage(MessageFrame));

    ret->present                 = MessageFrame_PR::MessageFrame_PR_spatFrame;
    ret->choice.spatFrame.msgCnt = getCount();
    ret->choice.spatFrame.timeStamp = allocMessage<DSecond_t>();
    fillRandomNumber(*ret->choice.spatFrame.timeStamp, 0, 65535);

    IntersectionState_t *intersectionState
        = allocMessage<IntersectionState_t>();
    fillRandomNumber(intersectionState->intersectionId.id, 0, 65535);
    intersectionState->status.bits_unused = 0;
    intersectionState->status.buf         = allocMessage<::std::uint8_t>(2);
    intersectionState->status.buf[0]      = 0x01;
    intersectionState->status.buf[1]      = 0x00;
    intersectionState->status.size        = 2;
    intersectionState->timeStamp          = allocMessage<MinuteOfTheYear_t>();
    fillRandomNumber(*intersectionState->timeStamp, 0, 3000);

    Phase_t *phase = allocMessage<Phase_t>();
    fillRandomNumber(phase->id, 0, 255);

    PhaseState_t *phaseState = allocMessage<PhaseState_t>();
    fillRandomNumber(phaseState->light, 0, 8);

    asn_sequence_add(&(phase->phaseStates.list), phaseState);

    asn_sequence_add(&(intersectionState->phases.list), phase);

    asn_sequence_add(&(ret->choice.spatFrame.intersections.list),
                     intersectionState);

    return ret;
}

/**
 * @brief       Read data.
 *
 * @param[in]   fd      File descriptor to read.
 */
void readData(int fd)
{
    // Receive data.
    char      buffer[MTU];
    ::ssize_t sizeRead = ::recv(fd, buffer, sizeof(buffer), 0);
    if (sizeRead < 0) {
        int err = errno;
        ::std::cerr << "Failed to receive data: " << ::strerror(err);
        return;
    }

    // Decode data.
    MessageFrame    *message = nullptr;
    ::asn_dec_rval_t result  = ::asn_decode(
        nullptr, ATS_UNALIGNED_CANONICAL_PER, &::asn_DEF_MessageFrame,
        reinterpret_cast<void **>(&message), buffer, sizeRead);
    if (result.code != RC_OK) {
        ::std::cerr << "Decode error." << ::std::endl;
        return;
    }
    MessagePtr<MessageFrame> autoRelease(message, releaseMessage(MessageFrame));

    // Print data.
    ::std::cout << ::std::chrono::duration_cast<::std::chrono::seconds>(
                       ::std::chrono::system_clock::now().time_since_epoch())
                       .count()
                << " Data received: " << *message << ::std::endl;
}

/**
 * @brief       Write data.
 *
 * @param[in]   fd      File descriptor to write.
 */
void writeData(int fd)
{
    using Generator        = MessagePtr<MessageFrame> (*)();
    Generator generators[] = {makeBSMMessage, makeMapMessage, makeSpatMessage};
    for (auto &generator : generators) {
        auto message = generator();
        if (message == nullptr) {
            return;
        }
        ::std::cout << "Message generated." << ::std::endl;
        char             buffer[MTU];
        ::asn_enc_rval_t encodeResult = asn_encode_to_buffer(
            nullptr, ATS_UNALIGNED_CANONICAL_PER, &asn_DEF_MessageFrame,
            reinterpret_cast<const void *>(message.get()), buffer,
            sizeof(buffer));

        if (encodeResult.encoded > 0) {
            ssize_t sizeSent = ::send(fd, buffer, encodeResult.encoded, 0);
            if (sizeSent >= 0) {
                ::std::cout
                    << ::std::chrono::duration_cast<::std::chrono::seconds>(
                           ::std::chrono::system_clock::now()
                               .time_since_epoch())
                           .count()
                    << " Data sent: " << *message << ::std::endl;
            } else {
                int err = errno;
                ::std::cerr << "Failed to send data: " << ::strerror(err)
                            << ::std::endl;
                return;
            }
        } else {
            int err = errno;
            switch (err) {
                case EINVAL: {
                    ::std::cerr << "Encode error: Incorrect parameters to the "
                                   "function, such as NULLs."
                                << ::std::endl;
                } break;
                case ENOENT: {
                    ::std::cerr << "Encode error: Encoding transfer syntax is "
                                   "not defined (for this type)."
                                << ::std::endl;
                } break;
                case EBADF: {
                    ::std::cerr << "Encode error: The structure has invalid "
                                   "form or content constraint "
                                   "failed."
                                << ::std::endl;
                } break;
                default:
                    ::std::cerr << "Encode error: " << ::strerror(err)
                                << ::std::endl;
            }
        }
    }
}

/**
 * @brief       Entery.
 *
 * @param[in]   argc    Count of arguments.
 * @param[in]   argv    Valuse of arguments.
 *
 * @return      Exit code.
 */
int main(int argc, char *argv[])
{
    // Get arguments.
    if (argc != 5) {
        ::std::cerr << "Illegal arguments." << ::std::endl;
        return -1;
    }

    ::std::string   dmd3aIP = argv[1];
    ::std::uint16_t targetPort
        = static_cast<::std::uint16_t>(::std::strtoul(argv[2], nullptr, 10));
    ::std::string   bindIP = argv[3];
    ::std::uint16_t listenPort
        = static_cast<::std::uint16_t>(::std::strtoul(argv[4], nullptr, 10));

    // Create receive socket.
    int recvFd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (recvFd < 0) {
        int err = errno;
        ::std::cerr << "Failed to create socket: " << ::strerror(err)
                    << ::std::endl;
        return -1;
    }
    ::std::unique_ptr<int, void (*)(int *)> autoCloseRecvFd(
        &recvFd, [](int *fd) -> void {
            ::close(*fd);
            *fd = -1;
        });

    // Bind.
    ::sockaddr_in bindAddress;
    ::memset(&bindAddress, 0, sizeof(bindAddress));
    bindAddress.sin_family = AF_INET;
    if (! ::inet_pton(AF_INET, bindIP.c_str(), &bindAddress.sin_addr)) {
        ::std::cerr << "Illegal local IP address \"" << bindIP << "\".";
        return -1;
    }
    bindAddress.sin_port = htons(listenPort);
    auto bindResult
        = ::bind(recvFd, reinterpret_cast<::sockaddr *>(&bindAddress),
                 sizeof(bindAddress));
    if (bindResult < 0) {
        int err = errno;
        ::std::cerr << "Failed to bind listen port: " << ::strerror(err)
                    << ::std::endl;
        return -1;
    }

    // Reuse.
    int opt = 1;
    ::setsockopt(recvFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    opt = 1;
    ::setsockopt(recvFd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

    // Create send socket.
    int sendFd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sendFd < 0) {
        int err = errno;
        ::std::cerr << "Failed to create socket: " << ::strerror(err)
                    << ::std::endl;
        return -1;
    }
    ::std::unique_ptr<int, void (*)(int *)> autoCloseSendFd(
        &sendFd, [](int *fd) -> void {
            ::close(*fd);
            *fd = -1;
        });

    // Make send address.
    ::sockaddr_in targetAddress;
    ::memset(&targetAddress, 0, sizeof(targetAddress));
    targetAddress.sin_family = AF_INET;
    if (! ::inet_pton(AF_INET, dmd3aIP.c_str(), &targetAddress.sin_addr)) {
        ::std::cerr << "Illegal DMD3A IP address \"" << dmd3aIP << "\".";
        return -1;
    }
    targetAddress.sin_port = htons(targetPort);
    auto connectResult
        = ::connect(sendFd, reinterpret_cast<::sockaddr *>(&targetAddress),
                    sizeof(targetAddress));
    if (connectResult < 0) {
        int err = errno;
        ::std::cerr << "Failed to connect to DMD3A port: " << ::strerror(err)
                    << ::std::endl;
        return -1;
    }

    // Reuse.
    opt = 1;
    ::setsockopt(sendFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    opt = 1;
    ::setsockopt(sendFd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

    // Create quit event.
    static int quitEvent = -1;
    quitEvent            = ::eventfd(0, 0);
    if (quitEvent < 0) {
        int err = errno;
        ::std::cerr << "Failed to create quit event: " << ::strerror(err)
                    << ::std::endl;
        return -1;
    }
    ::std::unique_ptr<int, void (*)(int *)> autoCloseQuitEvent(
        &quitEvent, [](int *fd) -> void {
            ::close(*fd);
            *fd = -1;
        });

    // Handle signal.
    auto onSignal = [](int) -> void {
        // Emit quit event.
        ::std::uint64_t v = 1;
        ::write(quitEvent, &v, sizeof(v));
    };
    signal(SIGINT, onSignal);
    signal(SIGTERM, onSignal);
    signal(SIGHUP, onSignal);

    // Main loop.
    auto nextTimestamp = ::std::chrono::steady_clock::now();
    while (true) {
        auto now = ::std::chrono::steady_clock::now();
        if (now - nextTimestamp > ::std::chrono::seconds(1)) {
            // Send.
            writeData(sendFd);
            nextTimestamp += ::std::chrono::seconds(1);
            if (now > nextTimestamp) {
                nextTimestamp = now + ::std::chrono::seconds(1);
            }
        }

        // Wait for data.
        ::std::array<::pollfd, 2> fds {
            ::pollfd {.fd = recvFd, .events = POLLIN, .revents = 0},
            ::pollfd {.fd = quitEvent, .events = POLLIN, .revents = 0}};
        now             = ::std::chrono::steady_clock::now();
        auto pollResult = ::poll(
            fds.data(), fds.size(),
            (now < nextTimestamp
                 ? ::std::chrono::duration_cast<::std::chrono::milliseconds>(
                       nextTimestamp - now)
                       .count()
                 : 0));
        if (pollResult > 0) {
            for (auto &status : fds) {
                if (status.revents & POLLIN) {
                    if (status.fd == recvFd) {
                        // New data to read.
                        ::readData(recvFd);
                    } else if (status.fd == quitEvent) {
                        // Need quit.
                        ::std::cout << "Quit event received." << ::std::endl;
                        return 0;
                    }
                }
            }
        }
    }

    return 0;
}
