import std.socket, std.string, std.conv, std.date;

const BUFFER_SIZE = 256;  // data buffer of 256 bytes, read buffer of 128 bytes

class Client
{
  Socket socket;
  char[] inBuffer;
  char[] outBuffer;
  long lastActive;
  
  this(Socket argSocket)
  {
    updateTime();
    socket = argSocket;
  }
  
  int readData()
  {
    if(inBuffer.length > BUFFER_SIZE / 2)
    {
      return 0;  // we don't want clients that send off this much data
    }
    
    char[BUFFER_SIZE / 2] buf;
    int rcvd = socket.receive(buf);
    
    if(rcvd >= 0)
    {
      updateTime();
      inBuffer ~= buf[0 .. rcvd];
      this.handleData();
    }

    return rcvd;
  }
  
  void handleData()
  {
    int s;
    for(;;)
    {
      s = find(inBuffer, "\r\n");
      if(s == -1)
        break;
      
      char[] request = inBuffer[0 .. s];
      inBuffer = inBuffer[s + 1 .. inBuffer.length];
      
      char[][] rawPorts = split(request, ",");
      
      if(rawPorts.length != 2)
      {
        this.pushData(":ERROR:UNKNOWN-ERROR\r\n");
        continue;
      }
      
      ushort port1, port2;
      try
      {
        port1 = toUshort(strip(rawPorts[0]));
        port2 = toUshort(strip(rawPorts[1]));
      } catch(Error e)
      {
        this.pushData("0,0:ERROR:INVALID-PORT\r\n");
        continue;
      }
      
      this.pushData(format("%d,%d:USERID:UNIX:jantore\r\n", port1, port2));
    }
  }
  
  int sendData()
  {
    int sent = socket.send(outBuffer);
    outBuffer = outBuffer[sent .. outBuffer.length];
    updateTime();
    return sent;
  }
  
  void pushData(char[] data)
  {
    outBuffer ~= data;
    this.sendData();
  }
  
  void tearDown()
  {
    socket.close();
  }
  
  void updateTime() {
    lastActive = getUTCtime();
  }
}

void deleteClient(inout Client[] list, int i)
{
  if(i != list.length - 1)
    list[i] = list[list.length - 1];
  list = list[0 .. list.length - 1];
}

int main(char[][] args)
{
  Socket listener = new TcpSocket();
  listener.bind(new InternetAddress(113));
  listener.blocking = false;
  listener.listen(10);
  
  Socket[] sockets;
  Client[] clients;
  
  SocketSet readSet = new SocketSet();
  SocketSet writeSet = new SocketSet();
  
  for(;; readSet.reset(), writeSet.reset())
  {
    readSet.add(listener);
    
    foreach(Client c; clients)
    {
      readSet.add(c.socket);
      if(c.outBuffer.length > 0)
        writeSet.add(c.socket);
    }
    
    int num = Socket.select(readSet, writeSet, null, 1000000);
    
    int i;
    
    if(num == 0) // let's check for timeouts
    {
      long timeNow = getUTCtime();
      for(i = 0;; i++)
      {
        if(i >= clients.length)
          break;
        
        Client c = clients[i];
        if(timeNow - c.lastActive > 60000)
        {
          c.tearDown();
          deleteClient(clients, i);
        }
      }
      continue;
    }
    
    for(i = 0;; i++)
    {
      if(i >= clients.length) break;
      
      Client c = clients[i];
      
      if(readSet.isSet(c.socket))
      {
        int read = c.readData();
        if(read == 0 || read == Socket.ERROR)
        {
          c.tearDown();
          deleteClient(clients, i);          
          continue;
        }
      }
      
      if(writeSet.isSet(c.socket))
      {
        int sent = c.sendData();
        if(sent == Socket.ERROR)
        {
          c.tearDown;
          deleteClient(clients, i);          
          continue;
        }
      }
    }
    
    if(readSet.isSet(listener))
    {
      Socket inSocket = listener.accept();
      Client newClient = new Client(inSocket);
      clients ~= newClient;
    }
  }
  
  return 0;
}
