#include "qgrexsocket.h"

#include <QUdpSocket>
#include <QNetworkDatagram>
#include <QHostAddress>

#include "qgrexnetwork.h"

#include <QDebug>

#include <qglogger.h>

#include "rex_udp_tunnelling.h"
#include "rex_udp_manipulator.h"

typedef RexUdpMessageHelper MessageManipulator;

#define RIDOUDP "RIDUDP"

typedef unsigned short mrk_net_ip_port_t;
typedef QHostAddress mrk_net_ip_raw_address_t;

enum rtlog_severity_t
{
    rtlog_info,
    rtlog_warning,
    rtlog_error,

    rtlog_breaking_news
};

#if 0
static void rtlog_printf(const char* nickname, ...)
{
    QDebug()<<
}
#endif
#define rtlog_printf(n_, l_, args...) qDebug()<<n_<<QString::asprintf(args)

class RexUdpPeerMap
{
public:
    enum {max_peer=64};

    struct ip_map_t
    {
        QHostAddress ip[max_peer];
    };
    static ip_map_t peer_ip;

    struct port_map_t
    {
        unsigned short port[max_peer];
    };

    static const port_map_t peer_ports;

    static void updateArp(unsigned char src, QHostAddress sender_ip, unsigned short /*sender_port*/)
    {
        if (peer_ip.ip[src]!=sender_ip)
        {
            if (peer_ip.ip[src].isNull())
                rtlog_printf(RIDOUDP, rtlog_info|rtlog_breaking_news, "ARP: peer mapped: %u->%X", src, sender_ip.toIPv4Address());
            else
                rtlog_printf(RIDOUDP, rtlog_warning|rtlog_breaking_news, "ARP: peer %u map changed? %X->%X", src, peer_ip.ip[src].toIPv4Address(), sender_ip.toIPv4Address());

            peer_ip.ip[src]=sender_ip;
        }
    }

    static QHostAddress arp(unsigned char src)
    {
        return peer_ip.ip[src];
    }

    static QHostAddress unmappedPeer()
    {
        return QgRexNetwork().networkBroadcast();
    }

    static unsigned short peerPort(unsigned char src)
    {
        return peer_ports.port[src];
    }
};

#include "grifo_inet_ports.h"

RexUdpPeerMap::ip_map_t RexUdpPeerMap::peer_ip;
const RexUdpPeerMap::port_map_t RexUdpPeerMap::peer_ports=
{
   0,//     NONE,
   grifo_inet::rex_eif,
   grifo_inet::rex_dfe,
   grifo_inet::rex_dsp0,
   grifo_inet::rex_dsp1,
   grifo_inet::rex_dsp2,
   grifo_inet::rex_dsp3,
   grifo_inet::rex_dsp4,
   grifo_inet::rex_corefpga,
   grifo_inet::rex_ext_tgt_sim
};

class QgRexSocket::Implementation
{
public:
    QgRexSocket* parent;
    QgLogger logger;

    QUdpSocket qs;

    enum magic_numbers_t
    {
        mtu_max=8*1024,	//Support jumbo frame
        rcv_synch_timeout_ticks=100	//
    };

    unsigned int myId;
    unsigned int port;
    const char* name;
    int err_state;
    //unsigned int pending;
    //mrk_mutex_t mutex;
    //mrk_event_t* evn_asynch;
    unsigned int evn_flag;
    unsigned int rcv_timeout;
    bool asynch_mode;
    //unsigned int sender_ip;
    //unsigned short sender_port;
    //unsigned int received_len;
    unsigned int fcs_counter;
    static unsigned int default_windows;
    //unsigned char datagram[mtu_max];

    //stat_t stat;

    typedef RexNetUdpTxTransactionManager<QByteArray, QHostAddress> TxTransactionManager;

    TxTransactionManager txCx;
    typedef TxTransactionManager::transaction_t TxTransaction;

    RexUdpMsgHeader reply;

    struct mmap_t
    {
        uint8_t* base;
        uintptr_t logical;
        unsigned long size;
        uint32_t end;
    };

    mmap_t mmap;

    Implementation(QgRexSocket* p):
        parent(p),
        logger("REXSOCKET", p),
        myId(0),
        port(0),
        name("?"),
        err_state(1),
        //pending(0),
        //evn_asynch(0),
        evn_flag(0),
        rcv_timeout(rcv_synch_timeout_ticks),
        asynch_mode(false),
        fcs_counter(0)
    {
        memset(&mmap, 0, sizeof mmap);
    }

    void mapMemory(void* physical_address, uintptr_t logical_address, unsigned int size_bytes)
    {
        mmap.size=size_bytes;
        mmap.base=reinterpret_cast<uint8_t*>(physical_address);
        mmap.logical=(uintptr_t)logical_address;
        mmap.end=mmap.logical+mmap.size;

        unsigned long iptr=(unsigned long)(physical_address);
        rtlog_printf(RIDOUDP, rtlog_info|rtlog_breaking_news, "MAP: 0x%lX -> 0x%lX x %u (%s)", (unsigned long)logical_address, iptr, size_bytes, name);
    }

    void* memoryMap(uint32_t address, unsigned int size)
    {
        if (mmap.base==0)
            return 0;
        unsigned long e=address+size;
        if ((address<mmap.logical) || (e>=mmap.end))
            return 0;
        return (address-mmap.logical)+mmap.base;
    }

    typedef RexUdpMsgHeaderT<uint8_t[2048]> pkt_t;

    bool processDatagram(QNetworkDatagram& d)
    {
        //const RexUdpMsgHeader& h=*reinterpret_cast<const RexUdpMsgHeader*>(d.data().constData());

        unsigned int received_len=d.data().size();

        //unsigned int src=h.src;
        //RexUdpPeerMap::updateArp(h.src, d.senderAddress(), d.senderPort());

        if (received_len<sizeof(RexUdpMsgHeader))
        {
            //++stat.datagram_too_short;
            rtlog_printf(RIDOUDP, rtlog_info|rtlog_breaking_news, "RX(%s:%u%u): datagram too short (%u)", name, myId, port, received_len);
            return true;
        }

        const void* datagram=d.data().constData();

        unsigned int sender_ip=d.senderAddress().toIPv4Address();
        unsigned int sender_port=d.senderPort();

        const pkt_t* pkt=reinterpret_cast<const pkt_t*>(datagram);

        const RexUdpMsgHeader& h=(pkt->header);
        const void* payload=&(pkt->payload);

        if (!h.markerValid())
        {
            //++stat.marker_invalid;
            rtlog_printf(RIDOUDP, rtlog_warning, "RX(%s:%u:%u): invalid marker %u", name, myId, port, h.marker_value);
            return true;
        }

        unsigned int expected_len=h.extsize_bytes+h.payload_size_bytes+(sizeof h);
        if (received_len<expected_len)
        {
            //&&++stat.size_error;
            rtlog_printf(RIDOUDP, rtlog_warning, "RX(%s:%u:%u): size error: %u <> %u+%u+%u", name, myId, port, received_len, sizeof h, h.payload_size_bytes, h.extsize_bytes);
            return true;
        }

        //OK, its seems to be a valid packet
        //unsigned int src=h.src;
        //RexUdpPeerMap::updateArp(src, sender_ip, sender_port);
        RexUdpPeerMap::updateArp(h.src, d.senderAddress(), d.senderPort());

        RexUdpMsgHeader::flow_control_t cf=h.flow_control;

        if (h.marker()==RexUdpMsgHeader::marker_ack) //An ACK: manage tx context
        {
            if (cf.code.ack_frag)
            {
                //mrk_mutex_acquire(&mutex);
                TxTransaction* txt=txCx.lookup(h);
                if (txt && txt->valid)
                {
                    transmitFrags(*txt);
                }
                else
                {
                    rtlog_printf(RIDOUDP, rtlog_warning|rtlog_breaking_news, "RX(%s:%u:%u): invalid tx ack: %u from %X:%u", name, myId, port, h.transaction_id, sender_ip, sender_port);
                }
            }
            return true;
        }

        if (cf.code.arp_required || cf.code.ack_frag_required)
        {
            //WARNING: reuse the incoming packet
            reply=pkt->header;
            reply.dst=reply.src;
            reply.src=myId;
            reply.flow_control.raw=0;
            reply.flow_control.code.ack_frag=cf.code.ack_frag_required;
            reply.flow_control.code.arp_ack=cf.code.arp_required;
            reply.flow_control.code.jumbo=0; //mtu_encode(phy_mtu);
            reply.flow_control.code.ack_window=1;
            reply.marker_value=RexUdpMsgHeader::marker_ack;
            reply.extsize_bytes=0;
            reply.payload_size_bytes=0;
            //mrk_net_udp_send(&mrk_socket, &reply, sizeof reply, sender_ip, sender_port, 0);
            qs.writeDatagram(reinterpret_cast<const char*>(&reply), sizeof reply, d.senderAddress(), d.senderPort());

        }

        if (h.marker()==RexUdpMsgHeader::marker_begin)	//A normal message
        {
            if (h.current_frags!=0)
            {
                rtlog_printf(RIDOUDP, rtlog_warning, "RX(%s:%u:%u): invalid frags", name, myId, port);
                return false;
            }

            if (h.messageClass()==RexUdpMsgHeader::t_msg)
            {
                //process mailbox
                QByteArray msg;
                msg.reserve(4096);

                uint32_t* p=reinterpret_cast<uint32_t*>(msg.data()); //memAlloc<uint32_t>();
                if (!p)
                {
                    rtlog_printf(RIDOUDP, rtlog_warning, "RX(%s:%u:%u): Pool dry", name, myId, port);
                    return true;
                }
                memcpy(p, payload, h.payload_size_bytes);
                {
                    IRexNetListener::msg_descriptor_t md;

                    md.dstId=h.dst;
                    md.srcId=h.src;
                    md.mailbox=h.mailbox>>2;
                    md.letter=h.mailbox & 0x03;
                    md.size=h.total_message_size;
                    md.payload=p;
                    md.ba=msg;
                    Q_EMIT parent->messageReceived(md);
#if 0
                    IRexNetListener::release_policy_t r=theListener->onMessageReceived(d);
                    if (r==IRexNetListener::release_now)
                    {
                        memRelease(p);
                    }
#endif
                }
                return true;
            }
        }
        else if (h.messageClass()!=RexUdpMsgHeader::t_memory_write)
        {
            rtlog_printf(RIDOUDP, rtlog_warning, "RX(%s:%u:%u): invalid frags", name, myId, port);
            return true;
        }

        if (h.messageClass()==RexUdpMsgHeader::t_memory_write)
        {
            //In this case ignore the frags! mailbox is a memory address

            void* p=memoryMap(h.mailbox, h.payload_size_bytes); //this is an address
            if (!p)
            {
                //TODO: garbage collect, in case of chronic broken messages
                rtlog_printf(RIDOUDP, rtlog_trace|rtlog_breaking_news, "RX(): invalid address 0x%X", h.mailbox);
                return true;
            }
            memcpy(p, payload, h.payload_size_bytes);
            if (h.doorbell)
            {
#if 0
                IRexNetListener::doorbell_descriptor_t d(h.doorbell);
                d.dstId=h.dst;
                d.srcId=h.src;
                d.errors=0;
                theListener->onDoorbell(d);
#endif
                Q_EMIT parent->doorbellReceived(h.dst, h.doorbell);
            }
        }
        return true;
    }

    void processData()
    {
        while(qs.hasPendingDatagrams())
        {
            QNetworkDatagram d=qs.receiveDatagram();

            processDatagram(d);
        }
    }

    bool transmitDatagram(const void* data, unsigned int size, QHostAddress host, unsigned short port)
    {
        qint64 res=qs.writeDatagram(reinterpret_cast<const char*>(data), size, host, port);
        return res>0;
    }    

    void transmitFrags(TxTransaction& txt, bool unknown_peer_address=false)
    {
        RexUdpMessageHelper& hm=txt.msg;

        for(;;)
        {
            int fsize=hm.preparNextFrag();
            if (fsize==0)
                break;

            if (unknown_peer_address)
            {
                hm.hdr->flow_control.code.arp_required=1;
                unknown_peer_address=false;
            }
            hm.hdr->fcs=++fcs_counter;
            bool res=transmitDatagram(hm.hdr, hm.frag_size, txt.ip, txt.port);
            hm.hdr->flow_control.code.arp_required=0;
            if (!res)
            {
                rtlog_printf(RIDOUDP, rtlog_error|rtlog_breaking_news, "tx(): error=%d, a=%X:%u", res, txt.ip.toIPv4Address(), txt.port);
                //Force an abort
                hm.hdr->current_frags=hm.hdr->total_frags-1;
            }

            if (hm.fragsLeft()<=0)
            {

                txCx.release(&txt);
                //packetRelease(hm.hdr);
                Q_EMIT parent->messageCompleted();
                break;
            }

            if (fsize<0)
                break;
        }
    }

    void transmit(unsigned int dst, unsigned int mailbox, const void* data, unsigned int len_bytes, unsigned int evnCode=0, RexUdpMsgHeader::msg_class_t mc=RexUdpMsgHeader::t_msg)
    {
        const unsigned int msg_max_size=4*1024;

        unsigned int t_size=len_bytes; //+sizeof(RexUdpMsgHeader);
        if (mc==RexUdpMsgHeader::t_msg)
        {
            if (t_size>msg_max_size)
            {
                rtlog_printf(RIDOUDP, rtlog_error|rtlog_breaking_news, "TX(%s:%u:%u): too big message %u:%u:%u",  name, myId, port, dst, mailbox, len_bytes);
                return;
            }
        }

        mrk_net_ip_port_t rx_port=RexUdpPeerMap::peerPort(dst);

        if (!rx_port)
        {
            rtlog_printf(RIDOUDP, rtlog_warning, "tx(%s:%d): unmapped peer %u", name, port, rx_port);
            return;
        }

        int frags=MessageManipulator::calcFrags(len_bytes);
        if (frags<1)
        {
            rtlog_printf(RIDOUDP, rtlog_warning|rtlog_breaking_news, "TX(%s:%d): null message", name, port);
            return;
        }

        mrk_net_ip_raw_address_t address=RexUdpPeerMap::arp(dst);
        bool ack_required=false;
        if (address.isNull())
        {
            ack_required=true;
            address=RexUdpPeerMap::unmappedPeer();
        }

        RexUdpMsgHeader* pkt=0;

        TxTransaction* txt=0;
        {
            //mrk_mutex_acquire(&mutex, mrk_wait_forever);

            txt=txCx.allocate();
            if (txt->valid)
            {
                rtlog_printf(RIDOUDP, rtlog_error|rtlog_breaking_news, "TX(%s:%d): warning - aborting old txt %u", name, port, txt->pid);
                pkt=txt->msg.hdr;
            }
            else
                txt->valid=1;
            //mrk_mutex_release(&mutex);
            txt->pkt_.resize(4096);
        }

        pkt=reinterpret_cast<RexUdpMsgHeader*>(txt->pkt_.data());

        if (!pkt)
        {
            rtlog_printf(RIDOUDP, rtlog_error|rtlog_breaking_news, "TX(%s:%d): pool exhausted", name, port);
            return;
        }

        MessageManipulator* hm=&txt->msg;

        txt->msg.frags_window=default_windows;

        hm->initializeMessage(pkt, myId, dst, mc, mailbox, evnCode, len_bytes, data);

        if ((ack_required) && (frags<int(hm->frags_window)))
        {
            hm->hdr->flow_control.code.arp_required=1;
        }
        txt->port=rx_port;
        txt->ip=address;
        hm->setTId(txt->id);


        transmitFrags(*txt, ack_required);

        return;
    }

};

unsigned int QgRexSocket::Implementation::default_windows=2;


QgRexSocket::QgRexSocket(const char* name, QObject* parent):
    QObject(parent),
    p_(*new Implementation(this))
{
    if (name)
        setObjectName(name);

    connect(&p_.qs, &QUdpSocket::readyRead, this,
            [=]()
    {
        p_.processData();
    }
            );
}

void QgRexSocket::setDefaultWindow(unsigned int num_msg)
{
    if (num_msg<1)
        num_msg=1;
    if (num_msg>128)
        num_msg=128;
    Implementation::default_windows=num_msg;
}

void QgRexSocket::setId(unsigned int id, const char* name)
{
    p_.myId=id;
    p_.name=name;
}

bool QgRexSocket::localBind(unsigned int port)
{
    bool ok=p_.qs.bind(QgRexNetwork().networkInterface(), port);

    QString localMsg=QString("Local Bind %1:%2 @%3").arg(objectName()).arg(port).arg(QgRexNetwork().networkInterface().toString());
    if (ok)
    {
        int rx_size=128*1024;
        int tx_size=60*1024;

        //logger.success(QString("local Bind %1:%2").arg(objectName()).arg(port));
        p_.qs.setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, tx_size);
        p_.qs.setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, rx_size);
        int rxsize=p_.qs.socketOption(QAbstractSocket::ReceiveBufferSizeSocketOption).toInt();
        int txsize=p_.qs.socketOption(QAbstractSocket::SendBufferSizeSocketOption).toInt();
        bool sizeok=(rxsize==rx_size) && (txsize==tx_size);
        if (sizeok)
            p_.logger.info(localMsg, QString("rx=%1, tx=%2").arg(rxsize).arg(txsize));
        else
            p_.logger.warning(localMsg, QString("Set size error. RX %1!=%2, TX %3!=%4")
                           .arg(rxsize).arg(rx_size)
                           .arg(txsize).arg(tx_size));
    }
    else
        p_.logger.error(QString("local Bind %1:%2").arg(objectName()).arg(port), p_.qs.errorString());

    //remoteAddress=QgRexNetwork().networkBroadcast();
    //remotePort=50001;
    return ok;
}

bool QgRexSocket::remoteBind(const QHostAddress& ha, unsigned int port)
{
  QHostAddress a=ha;

  if (ha==QHostAddress::Any)
  {
      a=QgRexNetwork().networkBroadcast();
/*
    quint32 ip=s.localAddress().toIPv4Address();
    if (port==0)
        port=s.localPort();
    ip|=0xFF;   //TODO: depend on subnet mask!
    QHostAddress badr(ip);
    p_.autoSocket.bind(badr, port);
    connect(&p_.autoSocket, &QUdpSocket::readyRead, this,
            [this]()
            {
                QNetworkDatagram d=p_.autoSocket.receiveDatagram();
                remoteAddress=d.senderAddress();
                remotePort=d.senderPort();
                //s.connectToHost(d.senderAddress(), d.senderPort());
                //p_.autoSocket.disconnect(this);
                Q_EMIT connectedToHost(true, &d);
                m_connected=true;
            }

       );
*/
  }
  //else
  {
     if (a==QHostAddress::Null)
     {
         a=QgRexNetwork().networkInterface();
     }
     //remoteAddress=a;
     //remotePort=port;
     //m_connected=true;
  }
  return true;
}

void QgRexSocket::sendMessage(unsigned int dst, unsigned int mailbox, const void* data, unsigned int len_bytes)
{
    p_.transmit(dst, mailbox, data, len_bytes, 0, RexUdpMsgHeader::t_msg);
}
void QgRexSocket::sendDoorBell(unsigned int dst, unsigned int code)
{
    p_.transmit(dst, 0, 0, code, RexUdpMsgHeader::t_memory_write);
}
void QgRexSocket::memoryWrite(unsigned int dst, unsigned int destination_address, const void* data, unsigned int len_bytes,unsigned int event_code)
{
    p_.transmit(dst, destination_address, data, len_bytes, event_code, RexUdpMsgHeader::t_memory_write);
}

void QgRexSocket::mapMemory(void* physical_address, uintptr_t logical_address, unsigned int size_bytes)
{
    p_.mapMemory(physical_address, logical_address, size_bytes);
}
