#include <ctype.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
#include <map>
#include <algorithm>
#include <cassert>

#include "ns3/address.h"
#include "ns3/log.h"
#include "ns3/inet-socket-address.h"
#include "ns3/node.h"
#include "ns3/socket.h"
#include "ns3/simulator.h"
#include "ns3/socket-factory.h"
#include "ns3/packet.h"
#include "ns3/trace-source-accessor.h"
#include "ns3/application.h"
#include "ns3/event-id.h"
#include "ns3/ptr.h"
#include "ns3/traced-callback.h"
#include "ns3/address.h"
#include "ns3/tcp-socket-factory.h"

//old
#include "ns3/core-module.h"
#include "ns3/helper-module.h"
#include "ns3/node-module.h"
#include "ns3/global-route-manager.h"
#include "ns3/simulator-module.h"

using namespace ns3;

//-------------  Application ------------------------
class TalkerApp : public Application {
public:
  TalkerApp();
  virtual ~TalkerApp ();
  void ConfRecv(Ptr<Node>, const Address,int);
  void ConfSend(Ptr<Node>, const Address,int);
protected:
  virtual void DoDispose (void);
private:
  virtual void StartApplication (void);
  virtual void StartApplicationRecv (void);
  virtual void StartApplicationSend (void);
  virtual void StopApplication (void);
  void HandleRead(Ptr<Socket>);

  void SendPacket(std::string,char,int);

  virtual void CloseConnection (Ptr<Socket> socket);
  void ConnectionSucceeded(Ptr<Socket>);
  void ConnectionFailed(Ptr<Socket>);
  void ConnectionHalfClosed(Ptr<Socket>);
  bool ConnectionRequested(Ptr<Socket>, const Address &);
  void ConnectionCreated(Ptr<Socket>, const Address &);
  void ConnectionCloseRequested(Ptr<Socket>);


  Ptr<Socket>     m_socket;
  Ptr<Socket>     m_servsocket;
  Address         m_local;
  Address         m_remote;
  int             i_am_listener;
  int             recv_cnt;
  int             m_send_size;
  int             m_recv_size;
};

TalkerApp::TalkerApp () : m_socket(0), m_servsocket(0),
			  recv_cnt(0),
			  m_send_size(0), m_recv_size(0) {}

TalkerApp::~TalkerApp() {}

void TalkerApp::DoDispose (void) {
  m_socket = 0;
  m_servsocket = 0;
  Application::DoDispose ();
}


void TalkerApp::CloseConnection (Ptr<Socket> sock) {
  std::cout << "TalkerApp::CloseConnection("
	    << sock
	    << "): "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
  sock->Close ();
}
void TalkerApp::ConnectionSucceeded(Ptr<Socket> sock) {
  std::cout << "TalkerApp::ConnectionSucceeded("
	    << sock
	    << "): "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
}
void TalkerApp::ConnectionFailed(Ptr<Socket> sock) {
  std::cout << "TalkerApp::ConnectionFailed("
	    << sock
	    << "): "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
}
void TalkerApp::ConnectionHalfClosed(Ptr<Socket> sock) {
  std::cout << "TalkerApp::ConnectionHalfClosed("
	    << sock
	    << "): "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
}
bool TalkerApp::ConnectionRequested(Ptr<Socket> sock, const Address &addr) {
  std::cout << "TalkerApp::ConnectionRequested("
	    << sock
	    << ","
	    << addr
	    << "): "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
  return(1);
}
void TalkerApp::ConnectionCreated(Ptr<Socket> sock, const Address &addr) {
  std::cout << "TalkerApp::ConnectionCreated("
	    << sock
	    << ","
	    << addr
	    << "): "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
  m_socket = sock;
  m_socket->SetRecvCallback(MakeCallback(&TalkerApp::HandleRead, this));
}
void TalkerApp::ConnectionCloseRequested(Ptr<Socket> sock) {
  std::cout << "TalkerApp::ConnectionCloseRequested("
	    << sock
	    << "): "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
}

void TalkerApp::StartApplication() {
  std::cout << "TalkerApp::StartApplication() "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
  if (i_am_listener)
    StartApplicationRecv();
  else
    StartApplicationSend();
}

void TalkerApp::StartApplicationSend() {
  std::cout << "TalkerApp::StartApplicationSend() "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;

  if (!m_socket) {
    m_socket = Socket::CreateSocket(GetNode(),
				    TcpSocketFactory::GetTypeId());
    m_socket->Bind ();
  }

  m_socket->Connect (m_remote);
  m_socket->SetConnectCallback
    (
     MakeCallback(&TalkerApp::ConnectionSucceeded,this),
     MakeCallback(&TalkerApp::ConnectionFailed,this),
     MakeCallback(&TalkerApp::ConnectionHalfClosed,this)
     );
  m_socket->SetRecvCallback(MakeCallback(&TalkerApp::HandleRead, this));
  SendPacket(std::string("Hello"),'S',m_send_size);
}

void TalkerApp::StartApplicationRecv() {
  std::cout << "TalkerApp::StartApplicationRecv() "
            << (i_am_listener ? "Server" : "Client")
            << std::endl;
  if (!m_servsocket) {
    m_servsocket = Socket::CreateSocket(GetNode(),
					TcpSocketFactory::GetTypeId());
    m_servsocket->Bind (m_local);
    m_servsocket->Listen (0);
  }

  m_servsocket->SetAcceptCallback
    (
     MakeCallback(&TalkerApp::ConnectionRequested,this),
     MakeCallback(&TalkerApp::ConnectionCreated,this),
     MakeCallback(&TalkerApp::ConnectionCloseRequested,this)
     );
}
 
void TalkerApp::ConfRecv(Ptr<Node> node, const Address addr,int size) {
  i_am_listener = 1;
  std::cout << "TalkerApp::ConfRecv("
            << node << ","
            << addr << ","
            << size << ") "
            << (i_am_listener ? "Server" : "Client")
	    << std::endl;
  m_local = addr;
  m_recv_size = size;
  node->AddApplication(this);
}

void TalkerApp::ConfSend(Ptr<Node> node, const Address addr,int size) {
  i_am_listener = 0;
  std::cout << "TalkerApp::ConfSend("
            << node << ","
            << addr << ","
            << size << ") "
            << (i_am_listener ? "Server" : "Client")
	    << std::endl;
  m_remote = addr;
  m_send_size = size;
  node->AddApplication(this);
}

void TalkerApp::SendPacket(std::string label,char fill,int payloadsize) {
  int size = payloadsize;
  std::cout << "TalkerApp::SendPacket("
	    << label << ","
	    << fill << ","
	    << payloadsize << ") "
	    << (i_am_listener ? "Server" : "Client")
            << std::endl;
  Ptr<Packet> p = Create<Packet>(payloadsize);
  p->PrintTags(std::cout);
  m_socket->Send (p);
}

void TalkerApp::StopApplication() {
  if (m_socket)
    m_socket->SetRecvCallback (MakeNullCallback<void, Ptr<Socket> > ());
}

void TalkerApp::HandleRead(Ptr<Socket> socket) {
  std::cout << "TalkerApp::HandleRead("
	    << socket
	    << "): "
	    << (i_am_listener ? "Server" : "Client")
	    << std::endl;

  if (i_am_listener && recv_cnt++ == 0) {
    SendPacket(std::string("BackAtYou"),'R', m_recv_size);
  }

  Ptr<Packet> packet;
  while (packet = socket->Recv()){
    SocketRxAddressTag rx_addr_tag;
    bool found;
    found = packet->FindFirstMatchingTag (rx_addr_tag);
    NS_ASSERT (found);
    Address from = rx_addr_tag.GetAddress ();
    // XXX packet->RemoveTag (tag);
    if (InetSocketAddress::IsMatchingType (from)) {
      InetSocketAddress address = InetSocketAddress::ConvertFrom (from);
      std::cout
	<< std::setw(4) << packet->GetSize() << " bytes from "
	<< address.GetIpv4() << " [" << address << "] ";
      packet->Print(std::cout << " ");
      packet->PrintTags(std::cout << "|");
      std::cout << std::endl;
    } else {
      std::cout << "not a match in HandleRead" << std::endl;
    }
  }
}


int main (int argc, char *argv[]) {

  int send_size = 600;
  int recv_size = 700;
  if (argc == 3) {
    send_size = atoi(argv[1]);
    recv_size = atoi(argv[2]);
  }

  RandomVariable::UseGlobalSeed (1, 1, 2, 3, 5, 8);
  Packet::EnableMetadata();
  NodeContainer c0;
  c0.Create (2);

  // We create the channels first without any IP addressing information
  PointToPointHelper p2p;
  p2p.SetDeviceParameter("DataRate",DataRateValue(DataRate(10000000)));
  p2p.SetChannelParameter ("Delay",TimeValue(MilliSeconds(10)));
  NetDeviceContainer dev0 = p2p.Install(c0);

  // add ip/tcp stack to nodes.
  InternetStackHelper internet;
  internet.Install (c0);

  // Later, we add IP addresses.  
  Ipv4AddressHelper ipv4;
  ipv4.SetBase ("10.1.0.0", "255.255.255.0");
  Ipv4InterfaceContainer ifaces = ipv4.Assign (dev0);

  // Configure the apps.
  uint16_t servPort = 32;
  TalkerApp source;
  source.ConfSend(c0.Get(0),
                  InetSocketAddress (ifaces.GetAddress (1),
                                     servPort),send_size);
  source.Start(Seconds(0.0));

  TalkerApp sink;
  sink.ConfRecv(c0.Get(1),
		InetSocketAddress (Ipv4Address::GetAny(), servPort),
		recv_size);
  sink.Start (Seconds (0.0));

  //Simulator::Stop (Seconds(10));

  Simulator::Run ();
  Simulator::Destroy ();
}
