#include "opnetProtoSimProcess.h"

OpnetProtoSimProcess::OpnetProtoSimProcess() 
{
    
}

OpnetProtoSimProcess::~OpnetProtoSimProcess(void)
{
  
}

ProtoSimAgent::SocketProxy* OpnetProtoSimProcess::OpenSocket(ProtoSocket& theSocket)
{
    SocketAgent* socketAgent = new SocketAgent(udp_process_id);
    if (socketAgent)
    {
        socketAgent->AttachSocket(theSocket);
        socket_proxy_list.Prepend(*socketAgent);
        return socketAgent;             
    }    
    else
    {
        fprintf(stderr, "OpnetProtoSimProcess::OpenSocket() new SocketAgent error\n");
        return NULL;   
    }
}  // end OpnetProtoSimProcess::OpenSocket()

void OpnetProtoSimProcess::CloseSocket(ProtoSocket& theSocket)
{
    SocketProxy* socketProxy = static_cast<SocketProxy*>(theSocket.GetHandle());
    // (TBD) Do we to do any OPNET cleanup here???
    socket_proxy_list.Remove(*socketProxy);
}  // end OpnetProtoSimProcess::CloseSocket()

void OpnetProtoSimProcess::OnReceive()
{
    // Get packet from stream ZERO
    Ici* cmd = op_intrpt_ici();
    if (OPC_NIL != cmd)
    {
        // 1) Get packet dest port and find which local socket it goes to
        int localPort;
        op_ici_attr_get(cmd, "local_port", &localPort);
        SocketProxy* socketProxy = socket_proxy_list.FindProxyByPort(localPort);
        if (socketProxy)
        {
            Packet* pkt = op_pk_get(0);
            // Get recv packet payload
            char* recvData;
            op_pk_fd_get(pkt, 0, &recvData);
            // Get recv packet length
            int recvLen  = op_pk_bulk_size_get(pkt) / 8;
            // Get source addr/port
            IpT_Address remoteAddr;
            op_ici_attr_get(cmd, "rem_addr", &remoteAddr);
            int remotePort;
            op_ici_attr_get(cmd, "rem_port", &remotePort);
            IpT_Address localAddr;
            op_ici_attr_get(cmd, "local_addr", &localAddr);
            ProtoAddress srcAddr;
            srcAddr.SimSetAddress(remoteAddr);
            srcAddr.SetPort(remotePort);
            ProtoAddress dstAddr;
            dstAddr.SimSetAddress(localAddr);
            dstAddr.SetPort(localPort);
            // Pass packet content/info to socket proxy
            static_cast<UdpSocketProxy*>(socketProxy)->OnReceive(recvData, recvLen, srcAddr, dstAddr);
            op_prg_mem_free(recvData);
            op_pk_destroy(pkt); 
        }
    }
    else
    {
        // Generate an error and end simulation.  
	    op_sim_end ("Error: DoReceive() OPC_NIL cmd", "", "", "");
    }
}  // end OpnetProtoSimProcess::OnReceive()


OpnetProtoSimProcess::UdpSocketProxy::UdpSocketProxy(Objid udpProcessId)
 : udp_process_id(udpProcessId),
   mcast_ttl(255), mcast_loopback(false), 
   recv_data(NULL), recv_data_len(0)
{    
}

OpnetProtoSimProcess::UdpSocketProxy::~UdpSocketProxy()
{
    // (TBD) cleanup ???
}

bool OpnetProtoSimProcess::UdpSocketProxy::Bind(UINT16& thePort)
{
    // (TBD) What happens if (0 == thePort) ???
    Ici* cmd = op_ici_create("udp_command_v3");
    op_ici_attr_set(cmd, "local_port", (int)thePort);
    op_ici_install(cmd);
    op_intrpt_force_remote(UDPC_COMMAND_CREATE_PORT, udp_process_id);   
    return true;       
}  // end OpnetProtoSimProcess::SocketAgent::Bind()

bool OpnetProtoSimProcess::UdpSocketProxy::SendTo(const char*         buffer, 
                                                  unsigned int&       buflen,
                                                  const ProtoAddress& dstAddr)
{
    // (TBD) error check Opnet API calls ...
    Packet* pkt = op_pk_create(len*8);
    char* payload = (char*) op_prg_mem_copy_create(buffer, buflen);
    op_pk_fd_set(pkt, 0, OPC_FIELD_TYPE_STRUCT, payload, 0,
                 op_prg_mem_copy_create, op_prg_mem_free, buflen);
    Ici* cmd = op_ici_create("udp_command_v3");
    op_ici_attr_set(cmd, "local_port", proto_socket->GetPort());
    op_ici_attr_set(cmd, "rem_port", dstAddr.GetPort());
    op_ici_attr_set(cmd, "rem_addr", dstAddr.SimGetAddress());
    op_ici_install(cmd);
    op_pk_send_forced(pkt, 0);
    return true;
}  // end OpnetProtoSimProcess::UdpSocketProxy::SendTo()

bool OpnetProtoSimProcess::UdpSocketProxy::RecvFrom(char*         buffer, 
                                                    unsigned int& numBytes, 
                                                    ProtoAddress& srcAddr)
{
    if (recv_data)
    {
        if (numBytes >= recv_data_len)
        {
            numBytes = recv_data_len;
            memcpy(buffer, recv_data, numBytes);
            srcAddr = src_addr;
            recv_data = NULL;
            recv_data_len = 0;
            return true;
        }
        else
        {
            fprintf(stderr, "OpnetProtoSimProcess::UdpSocketProxy::RecvFrom buffer tool small\n");
            numBytes = 0;
            return false;    
        }        
    }
    else
    {
        fprintf(stderr, "OpnetProtoSimProcess::UdpSocketProxy::RecvFrom no data ready\n");
        numBytes = 0;
        return false;       
    }   
}  // end OpnetProtoSimProcess::UdpSocketProxy::RecvFrom()

bool OpnetProtoSimProcess::UdpSocketProxy::JoinGroup(const ProtoAddress& groupAddr)
{
    // (TBD)
	return true;
}  // end NsProtoSimAgent::SocketAgent::JoinGroup()

bool OpnetProtoSimProcess::UdpSocketProxy::LeaveGroup(const ProtoAddress& groupAddr)
{
    // (TBD)
	return true;
}  // end NsProtoSimAgent::SocketAgent::LeaveGroup()

void OpnetProtoSimProcess::UdpSocketProxy::OnReceive(char*               recvData, 
                                                     unsigned int        recvLen,
                                                     const ProtoAddress& srcAddr,
                                                     const ProtoAddress& dstAddr)
{
    // (TBD) filter out loopback multicast traffic
    recv_data = recvData;
    recv_data_len = recvLen;
    src_addr = srcAddr;
    if (proto_socket)
        proto_socket->OnNotify(ProtoSocket::NOTIFY_INPUT);
}  // end OpnetProtoSimProcess::UdpSocketProxy::OnReceive()

