// Copyright 2006, Google Inc.

#include <complex>
#include <iostream>
#include <cassert>

#include "talk/base/thread.h"
#include "talk/base/virtualsocketserver.h"
#include "talk/base/testclient.h"
#include "talk/base/time.h"

#ifdef POSIX
extern "C" {
#include <errno.h>
}
#endif // POSIX

using namespace talk_base;

void test_basic(Thread* thread, VirtualSocketServer* ss) {
  std::cout << "basic: ";
  std::cout.flush();

  SocketAddress addr1(ss->GetNextIP(), 5000);
  AsyncUDPSocket* socket = CreateAsyncUDPSocket(ss);
  socket->Bind(addr1);

  TestClient* client1 = new TestClient(socket);
  TestClient* client2 = new TestClient(CreateAsyncUDPSocket(ss));

  SocketAddress addr2;
  client2->SendTo("foo", 3, addr1);
  client1->CheckNextPacket("foo", 3, &addr2);

  SocketAddress addr3;
  client1->SendTo("bizbaz", 6, addr2);
  client2->CheckNextPacket("bizbaz", 6, &addr3);
  assert(addr3 == addr1);

  for (int i = 0; i < 10; i++) {
    client2 = new TestClient(CreateAsyncUDPSocket(ss));

    SocketAddress addr4;
    client2->SendTo("foo", 3, addr1);
    client1->CheckNextPacket("foo", 3, &addr4);
    assert((addr4.ip() == addr2.ip()) && (addr4.port() == addr2.port() + 1));

    SocketAddress addr5;
    client1->SendTo("bizbaz", 6, addr4);
    client2->CheckNextPacket("bizbaz", 6, &addr5);
    assert(addr5 == addr1);

    addr2 = addr4;
  }

  std::cout << "PASS" << std::endl;
}

// Sends at a constant rate but with random packet sizes.
struct Sender : public MessageHandler {
  Sender(Thread* th, AsyncUDPSocket* s, uint32 rt)
      : thread(th), socket(s), done(false), rate(rt), count(0) {
    last_send = GetMillisecondCount();
    thread->PostDelayed(NextDelay(), this, 1);
  }

  uint32 NextDelay() {
    uint32 size = (rand() % 4096) + 1;
    return 1000 * size / rate;
  }

  void OnMessage(Message* pmsg) {
    assert(pmsg->message_id == 1);

    if (done)
      return;

    uint32 cur_time = GetMillisecondCount();
    uint32 delay = cur_time - last_send;
    uint32 size = rate * delay / 1000;
    size = std::min(size, uint32(4096));
    size = std::max(size, uint32(4));

    count += size;
    *reinterpret_cast<uint32*>(dummy) = cur_time;
    socket->Send(dummy, size);

    last_send = cur_time;
    thread->PostDelayed(NextDelay(), this, 1);
  }

  Thread* thread;
  AsyncUDPSocket* socket;
  bool done;
  uint32 rate; // bytes per second
  uint32 count;
  uint32 last_send;
  char dummy[4096];
};

struct Receiver : public MessageHandler, public sigslot::has_slots<> {
  Receiver(Thread* th, AsyncUDPSocket* s, uint32 bw)
      : thread(th), socket(s), bandwidth(bw), done(false), count(0),
        sec_count(0), sum(0), sum_sq(0), samples(0) {
    socket->SignalReadPacket.connect(this, &Receiver::OnReadPacket);
    thread->PostDelayed(1000, this, 1);
  }

  ~Receiver() {
    thread->Clear(this);
  }

  void OnReadPacket(
      const char* data, size_t size, const SocketAddress& remote_addr, 
      AsyncPacketSocket* s) {
    assert(s == socket);
    assert(size >= 4);

    count += size;
    sec_count += size;

    uint32 send_time = *reinterpret_cast<const uint32*>(data);
    uint32 recv_time = GetMillisecondCount();
    uint32 delay = recv_time - send_time;
    sum += delay;
    sum_sq += delay * delay;
    samples += 1;
  }

  void OnMessage(Message* pmsg) {
    assert(pmsg->message_id == 1);
    // It is always possible for us to receive more than expected because
    // packets can be further delayed in delivery.
    if (bandwidth > 0)
      assert(sec_count <= 5 * bandwidth / 4);
    sec_count = 0;
    thread->PostDelayed(1000, this, 1);
  }  

  Thread* thread;
  AsyncUDPSocket* socket;
  uint32 bandwidth;
  bool done;
  uint32 count;
  uint32 sec_count;
  double sum;
  double sum_sq;
  uint32 samples;
};

void test_bandwidth(Thread* thread, VirtualSocketServer* ss) {
  std::cout << "bandwidth: ";
  std::cout.flush();

  AsyncUDPSocket* send_socket = CreateAsyncUDPSocket(ss);
  AsyncUDPSocket* recv_socket = CreateAsyncUDPSocket(ss);
  assert(send_socket->Bind(SocketAddress(ss->GetNextIP(), 1000)) >= 0);
  assert(recv_socket->Bind(SocketAddress(ss->GetNextIP(), 1000)) >= 0);
  assert(send_socket->Connect(recv_socket->GetLocalAddress()) >= 0);

  uint32 bandwidth = 64 * 1024;
  ss->set_bandwidth(bandwidth);

  Sender sender(thread, send_socket, 80 * 1024);
  Receiver receiver(thread, recv_socket, bandwidth);

  Thread* pthMain = Thread::Current();
  pthMain->ProcessMessages(5000);
  sender.done = true;
  pthMain->ProcessMessages(5000);

  assert(receiver.count >= 5 * 3 * bandwidth / 4);
  assert(receiver.count <= 6 * bandwidth); // queue could drain for 1 sec

  delete send_socket;
  delete recv_socket;

  ss->set_bandwidth(0);

  std::cout << "PASS" << std::endl;
}

void test_delay(Thread* thread, VirtualSocketServer* ss) {
  std::cout << "delay: ";
  std::cout.flush();

  uint32 mean = 2000;
  uint32 stddev = 500;

  ss->set_delay_mean(mean);
  ss->set_delay_stddev(stddev);
  ss->UpdateDelayDistribution();

  AsyncUDPSocket* send_socket = CreateAsyncUDPSocket(ss);
  AsyncUDPSocket* recv_socket = CreateAsyncUDPSocket(ss);
  assert(send_socket->Bind(SocketAddress(ss->GetNextIP(), 1000)) >= 0);
  assert(recv_socket->Bind(SocketAddress(ss->GetNextIP(), 1000)) >= 0);
  assert(send_socket->Connect(recv_socket->GetLocalAddress()) >= 0);

  Sender sender(thread, send_socket, 64 * 1024);
  Receiver receiver(thread, recv_socket, 0);

  Thread* pthMain = Thread::Current();
  pthMain->ProcessMessages(5000);
  sender.done = true;
  pthMain->ProcessMessages(5000);

  double sample_mean = receiver.sum / receiver.samples;
  double num = receiver.sum_sq - 2 * sample_mean * receiver.sum +
       receiver.samples * sample_mean * sample_mean;
  double sample_stddev = std::sqrt(num / (receiver.samples - 1));
std::cout << "mean=" << sample_mean << " dev=" << sample_stddev << std::endl;

  assert(0.9 * mean <= sample_mean);
  assert(sample_mean <= 1.1 * mean);
  assert(0.9 * stddev <= sample_stddev);
  assert(sample_stddev <= 1.1 * stddev);

  delete send_socket;
  delete recv_socket;

  ss->set_delay_mean(0);
  ss->set_delay_stddev(0);
  ss->UpdateDelayDistribution();

  std::cout << "PASS" << std::endl;
}

int main(int argc, char** argv) {
  Thread *pthMain = Thread::Current(); 
  VirtualSocketServer* ss = new VirtualSocketServer();
  pthMain->set_socketserver(ss);

  test_basic(pthMain, ss);
  test_bandwidth(pthMain, ss);
  test_delay(pthMain, ss);

  return 0;
}
