/*********************************************************************
 *
 * AUTHORIZATION TO USE AND DISTRIBUTE
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: 
 *
 * (1) source code distributions retain this paragraph in its entirety, 
 *  
 * (2) distributions including binary code include this paragraph in
 *     its entirety in the documentation or other materials provided 
 *     with the distribution, and 
 *
 * (3) all advertising materials mentioning features or use of this 
 *     software display the following acknowledgment:
 * 
 *      "This product includes software written and developed 
 *       by Brian Adamson and Joe Macker of the Naval Research 
 *       Laboratory (NRL)." 
 *         
 *  The name of NRL, the name(s) of NRL  employee(s), or any entity
 *  of the United States Government may not be used to endorse or
 *  promote  products derived from this software, nor does the 
 *  inclusion of the NRL written and developed software  directly or
 *  indirectly suggest NRL or United States  Government endorsement
 *  of this product.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ********************************************************************/
 
#include "ip.h"  	// for hdr_ip def
#include "flags.h"  // for hdr_flags def

#include "nsProtoAgent.h" 

NsProtoAgent::NsProtoAgent()
{
}

NsProtoAgent::~NsProtoAgent()
{   
}

int NsProtoAgent::command(int argc, const char*const* argv)
{
   return Agent::command(argc, argv);
}  // end NsProtoAgent::command()


bool NsProtoAgent::OpenUdpSocket(UdpSocket* theSocket, unsigned short* thePort)
{
    // Make sure its not an already open socket (or port?)
    ASSERT(GetSocket() != theSocket);
    ASSERT(!udp_socket_agent_list.Find(theSocket));
    // Check to see if we can use our inherited implicit NsUdpSocketAgent
    if (!GetSocket() && ((0 == *thePort) || (port() == *thePort)))
    {
        // Use inherited implicit NsUdpSocketAgent
        AttachSocket(theSocket);
        // and Assume we're already attached to a node
        return true;
    }
    else
    {
        // Create a new NsUdpSocketAgent instance
        Tcl& tcl = Tcl::instance();	
	    tcl.evalf("eval new Agent/UdpSocket");
        char agentName[32];
        strncpy(agentName, tcl.result(), 32);
        NsUdpSocketAgent* udpSocketAgent = static_cast<NsUdpSocketAgent*>(tcl.lookup(agentName));
        if (udpSocketAgent)
        {
            tcl.evalf("%s set node_", this->name());
	        const char *nodeName = tcl.result();
            if (*thePort)
            {
                tcl.evalf("%s attach %s %hu", nodeName, agentName, *thePort);
            }
            else
            {
                tcl.evalf("%s attach %s", nodeName, agentName);
                *thePort = udpSocketAgent->port();
            }
            udpSocketAgent->AttachSocket(theSocket);
            udp_socket_agent_list.Append(udpSocketAgent);
            return true;
        }
        else
        {
            fprintf(stderr, "NsProtoAgent::OpenUdpSocket() "
                            "new Agent/UdpSocket failure\n");
            return false;
        }
    }
}  // end NsProtoAgent::OpenUdpSocket()

void NsProtoAgent::CloseUdpSocket(UdpSocket* theSocket)
{
    NsUdpSocketAgent* udpSocketAgent = (GetSocket() == theSocket) ?
            static_cast<NsUdpSocketAgent*>(this) : udp_socket_agent_list.Find(theSocket);
    ASSERT(udpSocketAgent);
    if (this != udpSocketAgent)
    {
        udp_socket_agent_list.Remove(udpSocketAgent);
        Tcl& tcl = Tcl::instance();	
        tcl.evalf("Simulator instance", this->name());
        char simName[32];
        strncpy(simName, tcl.result(), 32);
        tcl.evalf("%s set node_", this->name());
        const char* nodeName = tcl.result();
        tcl.evalf("%s detach-agent %s %s", simName, nodeName, udpSocketAgent->name());
        delete udpSocketAgent;
    }
    else
    {
        AttachSocket(NULL);   
    }
}  // end NsProtoAgent::CloseUdpSocket()

bool NsProtoAgent::SendTo(UdpSocket* theSocket, 
                          SIMADDR theAddr, 
                          unsigned short thePort,
                          const char* buffer, unsigned int len)
{
    NsUdpSocketAgent* udpSocketAgent = (GetSocket() == theSocket) ?
            static_cast<NsUdpSocketAgent*>(this) : udp_socket_agent_list.Find(theSocket);
    return udpSocketAgent->SendTo(theAddr, thePort, buffer, len);
}  // end NsProtoAgent::SendTo()   
        
bool NsProtoAgent::RecvFrom(UdpSocket* theSocket,
                            char* buffer, unsigned int* buflen,
                            SIMADDR* srcAddr, unsigned short* srcPort)
{
    NsUdpSocketAgent* udpSocketAgent = (GetSocket() == theSocket) ?
            static_cast<NsUdpSocketAgent*>(this) : udp_socket_agent_list.Find(theSocket);
    return udpSocketAgent->RecvFrom(buffer, buflen, srcAddr, srcPort);
}  // end NsProtoAgent::RecvFrom()    
        
bool NsProtoAgent::JoinGroup(UdpSocket* theSocket, SIMADDR theGroup)
{
    NsUdpSocketAgent* udpSocketAgent = (GetSocket() == theSocket) ?
            static_cast<NsUdpSocketAgent*>(this) : udp_socket_agent_list.Find(theSocket);
    ASSERT(udpSocketAgent);
    return udpSocketAgent->JoinGroup(theGroup);
}  // end NsProtoAgent::JoinGroup()

void NsProtoAgent::LeaveGroup(UdpSocket* theSocket, SIMADDR theGroup)
{
    NsUdpSocketAgent* udpSocketAgent = (GetSocket() == theSocket) ?
            static_cast<NsUdpSocketAgent*>(this) : udp_socket_agent_list.Find(theSocket);
    ASSERT(udpSocketAgent);
    return udpSocketAgent->LeaveGroup(theGroup);
}  // end NsProtoAgent::LeaveGroup()

void NsProtoAgent::SetSocketTTL(UdpSocket* theSocket, unsigned char theTTL)
{
    NsUdpSocketAgent* udpSocketAgent = (GetSocket() == theSocket) ?
        static_cast<NsUdpSocketAgent*>(this) : udp_socket_agent_list.Find(theSocket);
    ASSERT(udpSocketAgent);
    return udpSocketAgent->SetTTL(theTTL);
}  // end NsProtoAgent::SetSocketTTL()

void NsProtoAgent::SetSocketLoopback(UdpSocket* theSocket, bool state)
{
    NsUdpSocketAgent* udpSocketAgent = (GetSocket() == theSocket) ?
            static_cast<NsUdpSocketAgent*>(this) : udp_socket_agent_list.Find(theSocket);
    ASSERT(udpSocketAgent);
    return udpSocketAgent->SetLoopback(state);
}  // end NsProtoAgent::SetSocketLoopback()
        
bool NsProtoAgent::InstallTimer(ProtocolTimerMgr* timerMgr, double delay)
{
    Timer* timer = timer_list.GetTimer(timerMgr);
    if (timer)
    {
        timer->resched(delay); 
        return true;  
    }
    else
    {
        return false;
    }
}  // end NsProtoAgent::InstallTimer()


bool NsProtoAgent::RemoveTimer(ProtocolTimerMgr* timerMgr)
{
    Timer* timer = timer_list.GetTimer(timerMgr);
    if (timer)
    {
        timer->force_cancel();   
        return true;
    }
    else
    {
        return false;
    }
}  // end NsProtoAgent::RemoveTimer()
   


static class NsUdpSocketAgentClass : public TclClass
{
	public:
		NsUdpSocketAgentClass() : TclClass("Agent/UdpSocket") {}
	 	TclObject* create(int argc, const char*const* argv) 
			{return (new NsUdpSocketAgent());}
} class_udp_socket_agent;	

NsUdpSocketAgent::NsUdpSocketAgent()
  : Agent(PT_UDP), udp_socket(NULL), 
    mcast_ttl(255), mcast_loopback(false),
    recv_data(NULL), prev(NULL), next(NULL)   
{
}

NsUdpSocketAgent::~NsUdpSocketAgent()
{
    
}

bool NsUdpSocketAgent::SendTo(SIMADDR           theAddr, 
                              unsigned short    thePort,
                              const char*       buffer, 
                              unsigned int      len)
{	
    ASSERT(len);
    // Allocate _and_ init packet
	Packet* p = allocpkt(len);    
	// Set packet destination addr/port
	hdr_ip* iph = hdr_ip::access(p);
	hdr_flags::access(p)->ect() = 1;   
	iph->daddr() = theAddr;
	iph->dport() = thePort;   
    if (theAddr & 0x80000000)
        iph->ttl() = mcast_ttl;
	else
        iph->ttl() = 255;
	hdr_cmn* ch = hdr_cmn::access(p);  
    // Other packet types my be desired
    ch->ptype() = PT_UDP;
	// Copy data to be sent into packet
	char* data = (char*) p->accessdata();
	memcpy(data, buffer, len);
	// Set packet length field to UDP payload size for now
    // (TBD) Should we add UDP/IP header overhead?
	ch->size() = len;
	send(p, 0);
    return true;
}  // end NsUdpSocketAgent::SendTo()

bool NsUdpSocketAgent::RecvFrom(char*           buffer, 
                                unsigned int*   buflen,
                                SIMADDR*        srcAddr, 
                                unsigned short* srcPort)
{
    if (recv_data && (*buflen >= recv_len))
    {
        memcpy(buffer, recv_data, recv_len);
        *buflen = recv_len;
        *srcAddr = recv_addr;
        *srcPort = recv_port;
        recv_data = NULL;
        return true;
    }
    else
    {
        return false;   
    }
}  // end NsUdpSocketAgent::RecvFrom()


bool NsUdpSocketAgent::JoinGroup(SIMADDR theGroup)
{
    Tcl& tcl = Tcl::instance();	
	tcl.evalf("%s set node_", this->name());
	const char* result = tcl.result();
    Tcl::instance().evalf("%s join-group %s 0x%x", result, 
                          this->name(), theGroup);	
	return true;
}  // end sProtoAgent::NsUdpSocketAgent::JoinGroup() 

void NsUdpSocketAgent::LeaveGroup(SIMADDR theGroup)
{
	Tcl& tcl = Tcl::instance();	
	tcl.evalf("%s set node_", this->name());
	const char* result = tcl.result();
	Tcl::instance().evalf("%s leave-group %s 0x%x", result, 
                          this->name(), theGroup);	
}  // end NsUdpSocketAgent::LeaveGroup() 

void NsUdpSocketAgent::recv(Packet *p, Handler */*h*/)
{   
    // Give the packet to the socket given mcast/loopback/packet source
	nsaddr_t srcAddr = hdr_ip::access(p)->src().addr_;
    unsigned short srcPort = hdr_ip::access(p)->src().port_;
	nsaddr_t dstAddr = hdr_ip::access(p)->dst().addr_;    
    bool isUnicast = (0 == (dstAddr & 0x80000000));    
    if ((srcAddr != addr()) || isUnicast || mcast_loopback)
	{		
		// Note: In ns, we have to be careful that our "udp_socket"
        // was not closed before the packet was received    
        if (udp_socket)
        {
            recv_data = (char*)p->accessdata();
            recv_len = ((PacketData*)(p->userdata()))->size();
            recv_addr = srcAddr;
            recv_port = srcPort;
            udp_socket->OnReceive();
            recv_data = NULL;
            recv_len = 0;            
        }
    } 
	Packet::free(p);
}  // end NsUdpSocketAgent::recv()


NsUdpSocketAgentList::NsUdpSocketAgentList()
 : head(NULL), tail(NULL)
{
}  

NsUdpSocketAgentList::~NsUdpSocketAgentList()
{
    Destroy();   
}

void NsUdpSocketAgentList::Destroy()
{
    while (head)
    {
        NsUdpSocketAgent* next = head;
        head = next->next;
        delete next; 
    }    
    head = tail = NULL;
}  // end NsUdpSocketAgentList::Destroy()

void NsUdpSocketAgentList::Append(NsUdpSocketAgent* agent)
{
    agent->next = NULL;
    if ((agent->prev = tail))
        tail->next = agent;
    else head = agent;
    tail = agent;
}  // end NsUdpSocketAgentList::Append()
      
void NsUdpSocketAgentList::Remove(NsUdpSocketAgent* agent)
{
    if (agent->prev)
        agent->prev->next = agent->next;
    else
        head = agent->next;
    if (agent->next)
        agent->next->prev = agent->prev;
    else
        tail = agent->prev;
}  // end NsUdpSocketAgentList::Append()

NsUdpSocketAgent* NsUdpSocketAgentList::Find(const UdpSocket* theSocket)
{
    NsUdpSocketAgent* next = head;
    while (next)
    {
        if (next->GetSocket() == theSocket) 
            return next;
        else
            next = next->next;
    }
    return NULL;    
}  // end NsUdpSocketAgentList::Find()
        


NsProtoAgent::Timer::Timer(ProtocolTimerMgr* timerMgr)
 : timer_mgr(timerMgr), prev(NULL), next(NULL)
{   
}

NsProtoAgent::Timer::~Timer()
{
}

NsProtoAgent::TimerList::TimerList()
 : head(NULL), tail(NULL)
{
}

NsProtoAgent::TimerList::~TimerList()
{
    Destroy();
}

void NsProtoAgent::TimerList::Destroy()
{
    while (head)
    {
        Timer* next = head;
        head = next->next;
        delete next;
    }
    head = tail = NULL;
}  // end NsProtoAgent::TimerList::Destroy()

NsProtoAgent::Timer* NsProtoAgent::TimerList::GetTimer(ProtocolTimerMgr* timerMgr)
{
    Timer* next = head;
    while (next)
    {
        if (next->timer_mgr == timerMgr)
            return next;
        else
            next = next->next;   
    }
    if ((next = new Timer(timerMgr)))
    {
        next->next = NULL;
        if ((next->prev = tail))
            tail->next = next;
        else
            head = next;
        tail = next;
        return next;
    }
    else
    {
        perror("NsProtoAgent::TimerList::GetTimer() new Timer error");
        return NULL;   
    }
}  // end NsProtoAgent::TimerList::GetTimer()

//////////////////////////////////////////////////////
//  Simple example ns-2 agent class based on ProtoLib

static class NsProtoExampleClass : public TclClass
{
	public:
		NsProtoExampleClass() : TclClass("Agent/ProtoExample") {}
	 	TclObject *create(int argc, const char*const* argv) 
			{return (new NsProtoExample());}
} class_protean_example;	


NsProtoExample::NsProtoExample()
{  
    udp_socket.Init((UdpSocketOwner*)this, 
                    (UdpSocketRecvHandler)&NsProtoExample::OnReceive,
                    ProtoSim::SocketInstaller, 
                    static_cast<ProtoSim*>(this));
    
    timer_mgr.SetInstaller(ProtoSim::TimerInstaller, 
                           static_cast<ProtoSim*>(this));
    
    tx_timer.Init(1.0, -1, (ProtocolTimerOwner*)this, 
                  (ProtocolTimeoutFunc)&NsProtoExample::OnTxTimeout);
}

NsProtoExample::~NsProtoExample()
{
}

int NsProtoExample::command(int argc, const char*const* argv)
{
    if (4 == argc)
    {
        if (!strcmp(argv[1], "send"))
        {
            if (UDP_SOCKET_ERROR_NONE != udp_socket.Open())
            {
                fprintf(stderr, "NsProtoExample::command(send) socket open error\n");
                return TCL_ERROR;
            }        
            dst_addr.LookupHostAddress(argv[2]);
            dst_addr.SetPort(atoi(argv[3]));
            OnTxTimeout();  // transmit first packet right away
            timer_mgr.InstallTimer(&tx_timer);
            return TCL_OK; 
        }
        else if (!strcmp(argv[1], "recv"))  // recv <groupAddr> <port>
        {
            unsigned short recvPort = atoi(argv[3]);
            if (udp_socket.IsOpen())
            {
                if (recvPort != udp_socket.Port())
                {
                    fprintf(stderr, "NsProtoExample::command(recv <addr> <port>) socket already "
                                    "open on different port\n");
                    return TCL_ERROR;
                }
            }
            else if (UDP_SOCKET_ERROR_NONE != udp_socket.Open(recvPort))
            {
                fprintf(stderr, "NsProtoExample::command(start) socket open error\n");
                return TCL_ERROR;
            }  
            NetworkAddress groupAddr;
            groupAddr.LookupHostAddress(argv[2]);
            if (groupAddr.IsMulticast())
            {
                udp_socket.JoinGroup(&groupAddr);   
            }
            else
            {
                fprintf(stderr, "NsProtoExample::command(recv <addr> <port>) not a multicast address\n");   
            }
            return TCL_OK;
        }
    }
    else if (3 == argc)
    {
        if (!strcmp(argv[1], "recv"))
        {
            unsigned short recvPort = atoi(argv[2]);
            if (udp_socket.IsOpen())
            {
                if (recvPort != udp_socket.Port())
                {
                    fprintf(stderr, "NsProtoExample::command(recv <port>) socket already "
                                    "open on different port\n");
                    return TCL_ERROR;
                }
                else
                {
                    return TCL_OK;
                }
            }
            if (UDP_SOCKET_ERROR_NONE != udp_socket.Open(recvPort))
            {
                fprintf(stderr, "NsProtoExample::command(start) socket open error\n");
                return TCL_ERROR;
            }        
            return TCL_OK; 
        }
    }
    else if (2 == argc)
    {
        if (!strcmp(argv[1], "stop"))
        {
            if (tx_timer.IsActive()) tx_timer.Deactivate();
            udp_socket.Close();
            return TCL_OK;   
        }
        else if (!strcmp(argv[1], "recv"))
        {
            if (UDP_SOCKET_ERROR_NONE != udp_socket.Open())
            {
                fprintf(stderr, "NsProtoExample::command(start) socket open error\n");
                return TCL_ERROR;
            }        
            return TCL_OK; 
        }
    }
    return NsProtoAgent::command(argc, argv);
}  // end NsProtoExample::command()

bool NsProtoExample::OnTxTimeout()
{
    char* buffer = "Hello, Proteus";
    unsigned int len = strlen(buffer) + 1;
    udp_socket.SendTo(&dst_addr, buffer, len);
    return true;
}  // end NsProtoExample::OnTxTimeout()

bool NsProtoExample::OnReceive()
{
    char buffer[512];
    unsigned int buflen = 512;
    NetworkAddress src;
    udp_socket.RecvFrom(buffer, &buflen, &src);
    fprintf(stderr, "NsProtoExample::OnReceive() %d.%hu received \"%s\" from %s.%hu\n",
            addr(), udp_socket.Port(), buffer, 
            src.HostAddressString(), src.Port());
    return true;  
}  // end NsProtoExample::OnReceive()



