#define SOCK_DEBUG    /* Enables socket method tracing */
#include <socket.hh>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>

using namespace std;

using sw::sock::udp_socket; // The UDP socket class used
using sw::sock::ip_address; // IP address wrapper used

#ifdef OS_WIN
sw::sock::winsock_initializer wsinit; // Windows only
#endif

int main(int argc, char** argv)
{
  //--- CLI input
  string str_ip = "127.0.0.1";
  int sendport = 0;

  // Port to bind for receiving
  if(argc >=2 && argv[1]) {
    string s = argv[1];
    if(std::count_if(s.begin(), s.end(), ::isdigit) != (long)s.length()) {
      cerr << "Specify a numeric receive port as first argument." << endl;
      return 1;
    }
    str_ip += string(":") + s;
  } else {
    str_ip += ":1234";
  }

  // Port to send an echo / forward the message to
  if(argc >=3 && argv[2]) {
    // 0 on wrong input, will cause an error later
    sendport = (unsigned) ::atol(argv[2]);
  }

  // Create new UDP socket
  udp_socket sock;

  // Create an IP address with an initialiser string
  // This class also contains the port, type, family, and
  // methods to parse/compose ip address strings,
  // lookup host names, lookup service names/ports, etc.
  ip_address ip = str_ip;

  // Bind the socket to that IP
  if(!sock.bind(ip)) {
    // Methods error_text() and error() are used for error details
    cerr << "!!! Failed to bind UDP socket to address '" << ip.str() << "', port " << ip.port()
         << " (error: " << sock.error_text() << ")" << endl;
    return 1;
  } else {
    cerr << "Socket bound to address '" << ip.str() << "', port " << ip.port() << endl;
    if(sendport) cerr << "Echo send-to-port is " << sendport << endl;
    cerr << "CTRL-C to exit." << endl;
  }

  // The UDP socket class has no "integrated" thread, so this done here
  // in the main thread loop: We simply loop here until the socket is closed.
  while(!sock.closed()) {
    bool is_rx;

    // wait() makes the thread dormant until data were sent, received or
    // the specified timeout is exceeded. Wait is declared as:
    // bool wait(bool *is_recv = NULL, bool *is_sent = NULL, int timeout_ms=100),
    // so you can wait for a package been sent, too.
    // In the very end, this method is a wrapper for ::poll() or ::select().
    // wait() returns true if data were sent or received
    if(sock.wait(&is_rx) && is_rx) {
      char data[1025];
      // Receiving is similar to the standard ::recvfrom() function.
      long nreceived = sock.recvfrom(data, sizeof(data)-1, ip);
      data[sizeof(data)-1] = '\0';
      if(nreceived < 0) {
        sock.close();
      } else if(nreceived > 0) {
        data[nreceived] = '\0';
        cout << "received: " << data << endl;
        if(sendport) {
          ip.port(sendport);
          // Sending is similar to the standard ::sendto() function.
          long nsent = sock.sendto(data, nreceived, ip); // echo
          if(nsent >= 0) {
            cout << "send: " << data << endl;
          } else if(sock.error()) {
            cout << "!!! send error:" << sock.error() << endl;
          }
        }
        string s = data;
        if(s.find("close") != string::npos || s.find("stop") != string::npos) sock.close();
      }
    }
  }
  if(sock.error()) {
    cout << "!!! Error: " << sock.error_text() << endl;
    return 1;
  }
  return 0;
}
