#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/soundcard.h>
#include <sys/time.h>
#include <unistd.h>

typedef struct
{
    short l, r;
} stereoent;

struct task
{
    int fd, Bits, Stereo, Rate;
    struct task *next;
};

static char Device[256] = "/dev/dsp";

static unsigned ErecPort(void)
{
	unsigned p = 5582; /* magic */
	const char *s = Device;
	while(*s)
		p += toupper(*s++);

	/* /DEV/DSP:  6130 */
	/* /DEV/DSP1: 6179 */
	return p;
}

/* returns -1 if socket was closed. */
static int senddata(struct task *t, const void *p, unsigned c)
{
    const unsigned char *q = p;
    unsigned sent=0;
    while(c)
    {
        unsigned try = (sent&1) ? 1 : c;
        int n;
        n = send(t->fd, q, try, MSG_NOSIGNAL);
        if(n < 0 && errno != EAGAIN)return -1;
        if(n < 0)n = 0;
        sent += n;
        if(!(sent & 1))
        {
            struct timeval tv = {0, 100000};
            fd_set fds;
            FD_ZERO(&fds);
            FD_SET(t->fd, &fds);
            if(select(t->fd+1, NULL, &fds, NULL, &tv) != 1)
            {
                /* Don't keep other streams waiting */
                return 0;
            }
        }
        q += n;
        c -= n;
    }
    return 0;
}
static int sendtask(struct task *t, stereoent *buf, unsigned count, const int Rate)
{
    const double suhde = (double)t->Rate / Rate;
    const double dresultlen = count * suhde;
    const unsigned resultlen = dresultlen;
    
    stereoent dest[resultlen];
    
    double step = (double)count / resultlen;
    double pos = 0;
    unsigned x;
    for(x=0; x<resultlen; ++x)
    {
        dest[x] = buf[(unsigned)pos];
        pos += step;
    }
    if(t->Bits == 16)
    {
        if(t->Stereo)
        {
            return senddata(t, &dest, sizeof dest);
        }
        else
        {
            short tmp[resultlen];
            for(x=0; x<resultlen; ++x)
                tmp[x] = (dest[x].l + dest[x].r) / 2;
            return senddata(t, &tmp, sizeof tmp);
        }
    }
    else if(t->Bits == 8)
    {
        if(t->Stereo)
        {
            unsigned char tmp[resultlen*2];
            unsigned p=0;
            for(x=0; x<resultlen; ++x)
            {
                tmp[p++] = 128 + dest[x].l/256;
                tmp[p++] = 128 + dest[x].r/256;
            }
            return senddata(t, &tmp, sizeof tmp);
        }
        else
        {
            unsigned char tmp[resultlen];
            for(x=0; x<resultlen; ++x)
                tmp[x] = 128 + (dest[x].l + dest[x].r) / 512;
            return senddata(t, &tmp, sizeof tmp);
        }
    }
    return -1;
}

/* returns the number of entries read */
static unsigned getstereoents(int fd, stereoent *buf, unsigned maxnum)
{
    const unsigned entsize = sizeof(*buf);
    static unsigned char orphanbuf[sizeof(*buf)];
    static unsigned orphan = 0;
    unsigned char *dest = (unsigned char *)buf;
    unsigned maxbyte = maxnum * entsize;
    unsigned result, bytenum;
    int code;
    if(orphan)
    {
        memcpy(dest, orphanbuf, orphan);
        dest += orphan;
        maxbyte -= orphan;
    }
    code = read(fd, dest, maxbyte);
    if(code < 0) code = 0;
    
    bytenum = code + orphan;
    dest -= orphan;
    orphan = bytenum % entsize;
    result = bytenum / entsize;
    if(orphan)
    {
        bytenum -= orphan;
        memcpy(orphanbuf, &dest[bytenum], orphan);
    }
    return result;
}

static void Serveri(int fd, int notificationfd)
{
    struct task *tasks = NULL;
    struct sockaddr_in madr;
    const int Bits = 16;
    const int Stereo = 1;
    const int Rate = 44100;
    unsigned radrsize = sizeof madr;
    int Plug;
    
    if(ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &Bits) < 0
    || ioctl(fd, SNDCTL_DSP_STEREO,     &Stereo) < 0
    || ioctl(fd, SNDCTL_DSP_SPEED,      &Rate) < 0)
    {
        exit(-1);
    }
    
    fclose(stdin);
    fclose(stdout);
    fclose(stderr);
    chdir("/");
    
    if(fork() > 0)_exit(0);
    setsid();
    
    memset(&madr, 0, sizeof madr);
    madr.sin_port = htons(ErecPort());
    madr.sin_family = AF_INET;
    madr.sin_addr.s_addr = 0x0100007F; /* localhost */
    Plug = socket(madr.sin_family, SOCK_STREAM, 0);
    { int dummy;
    setsockopt(Plug, SOL_SOCKET, SO_REUSEADDR, &dummy, sizeof dummy); }
    bind(Plug, (struct sockaddr *)&madr, sizeof(struct sockaddr));
    listen(Plug, 10);
    fcntl(Plug, F_SETFL, fcntl(Plug, F_GETFL) | O_NONBLOCK);
    fcntl(fd,   F_SETFL, fcntl(fd,   F_GETFL) & ~O_NONBLOCK);
    
    /* Inform the child that we're ready by just writing one byte */
    write(notificationfd, &Plug, 1);
    close(notificationfd);
    
    for(;;)
    {
        struct sockaddr_in radr;
        stereoent buf[512];
        unsigned entnum;
        int Sock=0;
        struct task **tmp;
        fd_set fds;
        struct timeval tv = {0, 100000};
        FD_ZERO(&fds);
        FD_SET(Plug, &fds); if(Plug>=Sock)Sock = Plug+1;
        FD_SET(fd,   &fds); if(fd  >=Sock)Sock = fd+1;
        
        select(Sock, &fds, NULL, NULL, &tv);
        
        if(FD_ISSET(Plug, &fds))
        {
            int Sock = accept(Plug, (struct sockaddr *)&radr, &radrsize);
            if(Sock >= 0)
            {
                unsigned char c;
                struct task *tmp = (struct task *)malloc(sizeof(*tmp));
                tmp->fd = Sock;
                read(Sock, &c, 1); tmp->Bits = c;
                read(Sock, &c, 1); tmp->Stereo = c;
                read(Sock, &c, 1); tmp->Rate = c;
                read(Sock, &c, 1); tmp->Rate |= c<<8;
                fcntl(Sock, F_SETFL, fcntl(Sock, F_GETFL) | O_NONBLOCK);
                tmp->next = tasks;
                tasks = tmp;
            }
        }
        if(FD_ISSET(fd, &fds))
        {
            int c=0;
            entnum = getstereoents(fd, buf, (sizeof(buf) / sizeof(buf[0])));
            for(tmp = &tasks; *tmp; )
            {
                if(sendtask(*tmp, buf, entnum, Rate) < 0)
                {
                    struct task *t = (*tmp)->next;
                    close((*tmp)->fd);
                    free(*tmp);
                    *tmp = t;
                    ++c;
                }
                else
                    tmp = &(*tmp)->next;
            }
            if(c && !tasks)
            {
                /* All clients are gone. I'm dead.
                 * In a typical case, nobody even
                 * realizes that this was process
                 * here... Unless they don't have
                 * TCP/IP, which is exceptional.
                 */
                _exit(0);
            }
        }
    }
}

static int ServeriYhteys(int Bits, int Stereo, int Rate)
{
    struct sockaddr_in madr;
    char c;
    int sock;
    int fd = open(Device, O_RDONLY);
    fcntl(fd,   F_SETFL, fcntl(fd,   F_GETFL) & ~O_NONBLOCK);
    int err = errno;
    if(fd >= 0)
    {
    	int waitpipe[2], p;
    	if(pipe(waitpipe) < 0)
    	{
    		perror("pipe");
    		return -1;
    	}
    	p = fork();
    	if(!p)
    	{
    		close(waitpipe[0]);
    		/* Doesn't return */
	    	Serveri(fd, waitpipe[1]);
	    	return -1;
	    }
	    close(waitpipe[1]);
    	close(fd);
    	
    	/* Wait until the server says it's ready */
    	read(waitpipe[0], &p, 1);
    	close(waitpipe[0]);
    }
    
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0) { perror("socket"); return -1; }
    memset(&madr, 0, sizeof madr);
    madr.sin_port   = htons(ErecPort());
    madr.sin_family = AF_INET;
    madr.sin_addr.s_addr = 0x0100007F; /* localhost */
    if(connect(sock, (struct sockaddr *)&madr, sizeof madr) < 0)
    {
        if(err) /* Error code of opening /dev/dsp */
            fprintf(stderr, "%s open failed (%s), and erec is not already running.\n",
                            Device, strerror(err));
        close(sock);
        return -1;
    }
    c = Bits;     send(sock, &c, 1, MSG_NOSIGNAL);
    c = Stereo;   send(sock, &c, 1, MSG_NOSIGNAL);
    c = Rate&255; send(sock, &c, 1, MSG_NOSIGNAL);
    c = Rate>>8;  send(sock, &c, 1, MSG_NOSIGNAL);
    return sock;
}

static int stereo=0;
static int Bits  =16;
static int Rate  =44100;

static void PrintVersionInfo(void)
{
    printf("erec - shared recording server v"VERSION" (C) 1992,2002 Bisqwit\n");
}

#include "cat.h"

#include <argh.h>

int main(int argc, const char *const *argv)
{
    int fd;
    int Heelp = 0;
    
    argh_init();
    
    argh_add_long("stereo", '2'); argh_add_bool('2'); argh_add_desc('2', "Specifies stereo sound. Default is mono.", NULL);
    argh_add_long("mono",   'm'); argh_add_bool('m'); argh_add_desc('m', "Redundant. It's here for esd compatibility.", NULL);
    argh_add_long("byte",   'b'); argh_add_bool('b'); argh_add_desc('b', "8-bit mode. Normally 16-bit mode.", NULL);
    argh_add_long("word",   '6'); argh_add_bool('6'); argh_add_desc('6', "16-bit mode. Redundant.", NULL);
    argh_add_long("rate",   'r'); argh_add_int('r',18,999999); argh_add_desc('r', "Specifies recording rate. 44100 is default.", "<num>");
    argh_add_long("device", 'd'); argh_add_string('d',1,1023); argh_add_desc('d', "Specify device.", "<file>");
    argh_add_long("help",   'h'); argh_add_bool('h'); argh_add_desc('h', "Help", NULL);
    argh_add_long("version",'V'); argh_add_bool('V'); argh_add_desc('V', "Version information", NULL);
    
    argh_start_parse(argc, argv);
    for(;;)
    {
    	int c = argh_get_param();
    	if(c == -1)break;
    	switch(c)
    	{
            case 'V':
                PrintVersionInfo();
                return 0;
            case 'h':
                Heelp = 1;
                break;
            case '2':
            	if(argh_get_bool())++stereo;else stereo=0;
                break;
            case 'b':
                Bits = argh_get_bool() ? 8 : 16;
                break;
            case '6':
                Bits = argh_get_bool() ? 16 : 8;
                break;
            case 'm':
                if(argh_get_bool())stereo = 0;else ++stereo;
                break;
            case 'r':
                Rate = argh_get_int();
                break;
            case 'd':
            	strncpy(Device, argh_get_string(), sizeof Device);
            	Device[sizeof(Device)-1] = 0;
                break;
            default:
            {
            	/*
                */
            }
    	}
    }
    
    if(!argh_ok())return -1;
    
    if(Heelp)
    {
        PrintVersionInfo();
        printf(
            "\nAllows multiple applications request recorded data"
            "\nat the same time with different stream attributes.\n");
        printf(
            "Usage: erec [<options> [...]]\n"
            "Options:\n");
        argh_list_options();
        return 0;
    }
    
    fd = ServeriYhteys(Bits, stereo, Rate);
    if(fd < 0)
    {
        perror(*argv);
        return errno;
    }
    
    argh_done();
    
    dup2(fd, 0);
    
    cat(); /* doesn't return */

    return -1;
}
