#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>

#ifndef WIN32

#include <netdb.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>

#else

#define WINVER 0x0501
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <fcntl.h>


#endif

#include <errno.h>
#include <signal.h>
#include <sys/time.h>

#define BUFSIZE 4096
#define QUEUESIZE 4000
#define STAT_RECONSIDERATION_PERIOD 2 // seconds

int s_recv = -1;
int s_send = -1;

int start = 0;
int end = 0;

unsigned char queue[QUEUESIZE][BUFSIZE];
int lengths[QUEUESIZE];

int current_queuesize = 0;
    
int main(int argc, char* argv[]) {
    if (argc<6 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-?")) {
        fprintf (stderr, 
        "Usage:\n"
        "\tudpsmooth recv_host recv_port send_host send_port initial_delay\n"
        "\tCreated by Vitaly \"_Vi\" Shukela\n"
        );
        return 1;
    }
    
    struct addrinfo hints;
    struct addrinfo *recv_addr;
    struct addrinfo *send_addr;
    
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
    hints.ai_flags = AI_PASSIVE;
    
    char* recv_host = argv[1];
    char* recv_port = argv[2];
    char* send_host = argv[3];
    char* send_port = argv[4];
    long long int conf_delay = atoll(argv[5]);
    
    if (!strcmp(recv_host, "NULL")) recv_host=NULL;
    if (!strcmp(recv_port, "NULL")) recv_port=NULL; 
    if (!strcmp(send_host, "NULL")) send_host=NULL;
    if (!strcmp(send_port, "NULL")) send_port=NULL;     
    
    
    int gai_error;
    gai_error=getaddrinfo(recv_host,recv_port, &hints, &recv_addr);
    if (gai_error) { fprintf(stderr, "getaddrinfo 1: %s\n",gai_strerror(gai_error)); return 4; }
    if (!recv_addr) { fprintf(stderr, "getaddrinfo returned no addresses\n");   return 6;  }
    if (recv_addr->ai_next) {
        fprintf(stderr, "Warning: using only one of addresses retuned by getaddrinfo\n");
    }
    
    hints.ai_flags &= !AI_PASSIVE;
    gai_error=getaddrinfo(send_host, send_port, &hints, &send_addr);
    if (gai_error) { fprintf(stderr, "getaddrinfo 2: %s\n",gai_strerror(gai_error));   return 13;  }
    if (!send_addr) { fprintf(stderr, "getaddrinfo 2 returned no addresses\n");  return 15;  }
    if (send_addr->ai_next) {
        fprintf(stderr, "Warning: using only one of addresses retuned by getaddrinfo 2\n");
    }
    
    s_recv = socket(recv_addr->ai_family, recv_addr->ai_socktype, recv_addr->ai_protocol);
    if (s_recv == -1) { perror("socket"); return 7; }
    {
        int one = 1;
        setsockopt(s_recv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
    }
    if (bind(s_recv, recv_addr->ai_addr, recv_addr->ai_addrlen)) {  perror("bind");  return 5;  }
    
    s_send = socket(send_addr->ai_family, send_addr->ai_socktype, send_addr->ai_protocol);
    if (s_send == -1) { perror("socket 2"); return 12; }
        
    struct timeval last_packet_timestamp = {0,0};
    int first_packet = 1;
    
    double averaged_delay = conf_delay;
    long long int packets_received = 0;
    
    int last_stat_print_second = 0;
    int stat_min_queue = QUEUESIZE;
    int stat_max_queue = 0;
    int overqueue_event_count = 0;
    int stat_packets = 0;
    
    for (;;) {
        fd_set rfds;
        FD_ZERO(&rfds);
        
        int maxfd = 0;
        
         
        FD_SET(s_recv, &rfds);
        if (s_recv >= maxfd) maxfd = s_recv+1;
        
        struct timeval *timeout = NULL;
        struct timeval tv;
            
        if (current_queuesize > stat_max_queue) stat_max_queue = current_queuesize;
        if (current_queuesize < stat_min_queue) stat_min_queue = current_queuesize;
        if (start!=end) {
            timeout = &tv;
            int delay = averaged_delay;
            
            if (current_queuesize > QUEUESIZE/2) {
                delay = delay * (QUEUESIZE - current_queuesize)/QUEUESIZE;
                averaged_delay = 0.9999 * averaged_delay;
            }
            
            if(overqueue_event_count)delay=delay/overqueue_event_count;
                
            if(delay<0)delay=0;
                
            tv.tv_sec = delay / 1000000;
            tv.tv_usec = delay % 1000000;
        }
        
        if (last_stat_print_second != (last_packet_timestamp.tv_sec/STAT_RECONSIDERATION_PERIOD)) {
            last_stat_print_second = (last_packet_timestamp.tv_sec/STAT_RECONSIDERATION_PERIOD);
            fprintf(stderr, "queue min= %d max = %d \taveraged_delay= %g\tpackets_this_time= %d\tpackets_received= %lld\n", 
                    stat_min_queue, stat_max_queue, averaged_delay, stat_packets, packets_received);
            
            if (stat_min_queue==0) {
               averaged_delay = 1.01 * averaged_delay;
               overqueue_event_count = 0;
            } else if (stat_min_queue > QUEUESIZE/8 ) {
                ++overqueue_event_count;
                if(overqueue_event_count == 1) { 
                    averaged_delay = 0.8 * averaged_delay;
                }
            } else if (stat_min_queue > 20 ) {
               averaged_delay = 0.99 * averaged_delay;
               overqueue_event_count = 0;
            } else {
               overqueue_event_count = 0;
            }
            
            if (stat_packets > 50) {
                double stat_delay = 1000000.0 * STAT_RECONSIDERATION_PERIOD / stat_packets;
                if (averaged_delay > stat_delay) {
                    averaged_delay = 0.5*averaged_delay + 0.5*stat_delay;
                }
                if (averaged_delay < 0.7*stat_delay) {
                    averaged_delay = 0.8*averaged_delay + 0.2*stat_delay;
                }
            }
            
            stat_max_queue = 0;
            stat_min_queue = QUEUESIZE;
            stat_packets = 0;
        }
        
        int select_ret = select(maxfd, &rfds, NULL, NULL, timeout);
        
        if (select_ret==-1) {
            if (errno==EAGAIN || errno==EINTR) continue;
            perror("select");
            return 91;
        }
        
        
        if (FD_ISSET(s_recv, &rfds)) {
            //write(2, "r", 1);
            if(  (end + 1) % QUEUESIZE == start) {                
                write(2, "!", 1);
                sendto(s_send, queue[start], lengths[start], 0,  send_addr->ai_addr,  send_addr->ai_addrlen);
                ++start; start %= QUEUESIZE;
                --current_queuesize;
            }
            int ret = recv(s_recv, queue[end], sizeof queue[end], 0);
            if (ret >= 0) {
                lengths[end] = ret;
                ++end; end %= QUEUESIZE;
                ++current_queuesize;
                ++packets_received;
                ++stat_packets;
                
                struct timeval tv;
                gettimeofday(&tv, NULL);
                if (first_packet) {
                    first_packet = 0;
                } else {
                    long int this_packet_delay = 
                        (tv.tv_sec - last_packet_timestamp.tv_sec) * 1000000 +
                        (tv.tv_usec - last_packet_timestamp.tv_usec);
                    
                    if (this_packet_delay < 5000000) {
                        averaged_delay = 0.99995  * averaged_delay + 0.00005 * this_packet_delay;
                        if(averaged_delay<0)averaged_delay=0;
                    }
                }
                last_packet_timestamp = tv;
            }
        }
        
        if (select_ret == 0 && start!=end) {            
            //write(2, "s", 1);
            sendto(s_send, queue[start], lengths[start], 0,  send_addr->ai_addr,  send_addr->ai_addrlen);
            ++start; start %= QUEUESIZE;
            --current_queuesize;
        }
    }
}
