/*
 * Read limited subset of matroska files (generated by udp2mkv) and stream them to networks as UDP packets
 *
 * Limitations:
 *  1. cluster size is 5 bytes: 08 NN NN NN NN
 *  2. Each cluster have only two elements: Timecode and SimpleBlock
 *  3. Timecode have size 8 bytes and it is microseconds (timecode scale = 1000)
 *  4. SimpleBlock have size as 5 bytes: 08 NN NN NN NN
 *  5. There is only one track and it is track number 1
 *  6. No lacing
 *  i.e. frame data begins at fixed offset after the start of cluster
 *
 * For similar program, but without these limitations see HsMkv's ExampleUdpSend
 *    source: https://github.com/vi/HsMkv
 *    binary windows: http://vi-server.org/pub/HsMkv.exe
 *    binary linux: http://vi-server.org/pub/HsMkv
 * 
 * License=MIT; Vitaly "_Vi" Shukela; 2012.
 *
 * i586-mingw32msvc-gcc  mkv2udp.c -O2 -lws2_32 -o mkv2udp.exe
 * gcc -O2 mkv2udp.c -o mkv2udp
 */


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

#include <string.h>

#ifndef WIN32
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <endian.h>
#else
    #include <winsock2.h>

    // Remove these things if they conflict with already defined ones
    #if __BYTE_ORDER == __LITTLE_ENDIAN
        // http://www.linux.org.ru/forum/development/4087771
        uint16_t htobe16(uint16_t x) {
            union { uint16_t u16; uint8_t v[2]; } ret;
            ret.v[0] = (uint8_t)(x >> 8);
            ret.v[1] = (uint8_t) x;
            return ret.u16;
        }

        uint32_t htobe32(uint32_t x) {
            union { uint32_t u32; uint8_t v[4]; } ret;
            ret.v[0] = (uint8_t)(x >> 24);
            ret.v[1] = (uint8_t)(x >> 16);
            ret.v[2] = (uint8_t)(x >> 8);
            ret.v[3] = (uint8_t) x;
            return ret.u32;
        }

        uint64_t htobe64(uint64_t x) {
            union { uint64_t u64; uint8_t v[8]; } ret;
            ret.v[0] = (uint8_t)(x >> 56);
            ret.v[1] = (uint8_t)(x >> 48);
            ret.v[2] = (uint8_t)(x >> 40);
            ret.v[3] = (uint8_t)(x >> 32);
            ret.v[4] = (uint8_t)(x >> 24);
            ret.v[5] = (uint8_t)(x >> 16);
            ret.v[6] = (uint8_t)(x >> 8);
            ret.v[7] = (uint8_t) x;
            return ret.u64;
        }
    #else
        #define htobe16(x) (x)
        #define htobe32(x) (x)
        #define htobe64(x) (x)
    #endif

#endif


char buf[240*1024];

int udp_socket() {
    int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket == -1) {
        perror("socket");
        return -1;
    }
    
  /*  
    int rxsockbufsz = 64 * 1024;
    if (setsockopt (udp_socket, SOL_SOCKET, SO_SNDBUF,
                &rxsockbufsz, sizeof (rxsockbufsz))) {
        perror("setsockopt SO_RCVBUF");
    }*/

    return udp_socket;

}


// length must be at least 16
// debt must be initialized to 0 initially
// @return 0 - no frame, call again; length - frame in buffer with this length; -1 - EOF
int resync_and_receive_one_frame(FILE* f, unsigned char* buffer, size_t length, size_t* debt, unsigned long long *timecode) {
    
    int initial_recv = 16;
    int r = fread(buffer + *debt, initial_recv - *debt, 1, f);
    if (!r) return -1;
    int i;
    int found=0;
    for(i=0; i<initial_recv - 3; ++i) {
        if(
                buffer[i+0]==0x1F &&
                buffer[i+1]==0x43 &&
                buffer[i+2]==0xB6 &&
                buffer[i+3]==0x75) {
            found=1;
            break;
        }
    }
        
    if(found) {
        memmove(buffer, buffer+i, initial_recv-i);
        // Now cluster is at the beginning of buffer
        
        long int cluster_length;
        memcpy(&cluster_length, buffer+5, 4);
        cluster_length = htobe32(cluster_length);

        size_t total_length = cluster_length + 9;
        if(total_length > length) {
            total_length = length;
        }
        if(total_length < 29) {
            *debt = 0;
            return 0;
        }
        fread(buffer+(initial_recv-i), total_length-(initial_recv-i), 1, f);

        memcpy(timecode, buffer+11, 8);
        *timecode = htobe64(*timecode);

        memmove(buffer, buffer+29, total_length-29);
        // Now content at the beginning of buffer

        *debt = 0;
        return total_length-29;
    } else {
        memmove(buffer, buffer+i, initial_recv-i);
        *debt = initial_recv - i;
        return 0;
    }

}

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

    if(argc<3) {
        fprintf(stderr, 
                "mkv2udp v0.1 by Vitaly \"_Vi\" Shukela; License=MIT\n"
                "\n"
                "Usage: mkv2udp host port < file.mkv\n"
                "    Read mkv file generated by mkv2udp and\n"
                "    send frames as UDP packets to host:port\n"
                "    See also HsMkv's ExampleUdpSend: https://github.com/vi/HsMkv/\n");
        return 2;
    }
    #ifdef WIN32
        WSADATA wsaData;
        int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
        if ( iResult != NO_ERROR ) {
            fprintf(stderr,"Error at WSAStartup()\n");
            exit(1);
        }
        _setmode(_fileno(stdin), _O_BINARY);
    #endif

    struct sockaddr_in sa = {AF_INET, htons(atoi(argv[2])), {inet_addr(argv[1])}};
    sa.sin_addr.s_addr = inet_addr(argv[1]);

    int sock = udp_socket();
    if (sock == -1) {
        return 1;
    }

    unsigned long long timecode;
    size_t tmp = 0;

    int ret;

    unsigned long long first_frame_timecode=0;
    unsigned long long timebase=0;

    for(;;) {
        ret = resync_and_receive_one_frame(stdin, buf, sizeof buf, &tmp, &timecode);
        if(ret==-1) break;
        if(ret>0) {
            if(timebase==0) {
                struct timeval tv;
                gettimeofday(&tv, NULL);
                timebase = tv.tv_sec * 1000000LL + tv.tv_usec;
                first_frame_timecode = timecode;
            }

            for(;;) {
                struct timeval tv;
                gettimeofday(&tv, NULL);
                unsigned long long int now = tv.tv_sec * 1000000LL + tv.tv_usec;

                long long int delay = (timecode - first_frame_timecode) - (now - timebase);
                if(delay<0) delay=0;
                if(delay>999999) {
                    usleep(900000);
                    continue;
                } else {
                    usleep(delay);
                    break;
                }
            }

            if(sendto(sock, buf, ret, 0, &sa, sizeof sa) == -1) {
                perror("sendto");
                return 3;
            }
            fputc('.', stdout); fflush(stdout);
        }
    }
    fputc('\n', stdout); fflush(stdout);

    return 0;
}


