#include <alsa/asoundlib.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "podule_api.h"
#include "sound_in.h"

typedef struct wave_in_t
{
        snd_pcm_t *device;
        
        pthread_t in_thread;
        volatile int in_thread_term;

        int16_t buffer[4800*2];
        
        int running;
        int buffers_pending;

	void (*sound_in_buffer)(void *p, void *buffer, int samples);
        void *p;
} wave_in_t;

void *wave_in_callback(void *p);

#define MAX_IN_DEVICES 50
static struct
{
	int card;
	int device;
	int sub;
	char name[50];
} in_devices[MAX_IN_DEVICES];

static int in_device_count = 0;

static int in_queried = 0;

static void in_query()
{
	int status;
	int card = -1;

	in_queried = 1;

	if ((status = snd_card_next(&card)) < 0)
		return;

	if (card < 0)
		return; /*No cards*/

	while (card >= 0)
	{
		char *shortname;

		if ((status = snd_card_get_name(card, &shortname)) >= 0)
		{
			snd_ctl_t *ctl;
			char name[32];

			sprintf(name, "hw:%i", card);

			if ((status = snd_ctl_open(&ctl, name, 0)) >= 0)
			{
				int device = -1;

				do
				{
					status = snd_ctl_pcm_next_device(ctl, &device);
					if (status >= 0 && device != -1)
					{
						snd_pcm_info_t *info;
						int sub_nr, sub;

						snd_pcm_info_alloca(&info);
						snd_pcm_info_set_device(info, device);
						snd_ctl_pcm_info(ctl, info);
                                                snd_pcm_info_set_stream(info, SND_PCM_STREAM_CAPTURE);
						sub_nr = snd_pcm_info_get_subdevices_count(info);
//						printf("output sub_nr=%i\n",sub_nr);

						for (sub = 0; sub < sub_nr; sub++)
						{
							snd_pcm_info_set_subdevice(info, sub);

							if (snd_ctl_pcm_info(ctl, info) == 0)
							{
                                                                int status;
                                                                
								printf("%s: PCM device=%i:%i:%i\n", shortname, card, device,sub);

								in_devices[in_device_count].card = card;
								in_devices[in_device_count].device = device;
								in_devices[in_device_count].sub = sub;
								snprintf(in_devices[in_device_count].name, 50, "%s (%i:%i:%i)", shortname, card, device, sub);
                                                                
								in_device_count++;
								if (in_device_count >= MAX_IN_DEVICES)
									return;
							}
						}
					}
				} while (device >= 0);
			}
		}

		if (snd_card_next(&card) < 0)
			break;
	}
}

void *sound_in_init(void *p, void (*sound_in_buffer)(void *p, void *buffer, int samples), void (*log)(const char *format, ...), const podule_callbacks_t *podule_callbacks, podule_t *podule)
{
        int c;
        const char *device;
        int device_nr = 0;
        unsigned int rate = 48000;
	char portname[32];        
        snd_pcm_hw_params_t *hw_params;
        wave_in_t *wave_in = malloc(sizeof(wave_in_t));
        memset(wave_in, 0, sizeof(wave_in_t));

        device = podule_callbacks->config_get_string(podule, "sound_in_device", "0");
        sscanf(device, "%i", &device_nr);
	if (log)
	        log("sound_in_init: device_nr=%i\n", device_nr);

        wave_in->p = p;
	wave_in->sound_in_buffer = sound_in_buffer;

        sprintf(portname, "hw:%i,%i,%i", in_devices[device_nr].card,
					 in_devices[device_nr].device,
					 in_devices[device_nr].sub);
        if (log)
                log("Opening input port %s\n", portname);

	if (snd_pcm_open(&wave_in->device, portname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) < 0)
	{
                if (log)
                        log("Failed to open input device\n");
        }
        else
        {        
                snd_pcm_hw_params_malloc(&hw_params);
                snd_pcm_hw_params_any(wave_in->device, hw_params);
                snd_pcm_hw_params_set_access(wave_in->device, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
                snd_pcm_hw_params_set_format(wave_in->device, hw_params, SND_PCM_FORMAT_S16_LE);
                snd_pcm_hw_params_set_rate_near(wave_in->device, hw_params, &rate, 0);
                snd_pcm_hw_params_set_channels(wave_in->device, hw_params, 2);
                snd_pcm_hw_params(wave_in->device, hw_params);
                snd_pcm_hw_params_free(hw_params);
                
                snd_pcm_prepare(wave_in->device);
                pthread_create(&wave_in->in_thread, NULL, wave_in_callback, wave_in);
        }

        return wave_in;
}

void sound_in_close(void *p)
{
        wave_in_t *wave_in = p;
        
        wave_in->in_thread_term = 1;
        pthread_join(wave_in->in_thread, NULL);

        if (wave_in->device != NULL)
        {
                snd_pcm_close(wave_in->device);
                wave_in->device = NULL;
        }

        free(wave_in);
}

#define IN_SLEEP_TIME 5000 /*5ms*/

void *wave_in_callback(void *p)
{
        wave_in_t *wave_in = p;
        
        while (!wave_in->in_thread_term)
        {
                if (wave_in->running)
                {
                        int status = snd_pcm_readi(wave_in->device, wave_in->buffer, 4800);
                
                        if (status > 0)
                                wave_in->sound_in_buffer(wave_in->p, wave_in->buffer, status);
                        else
                                usleep(IN_SLEEP_TIME);
                }
                else
                        usleep(IN_SLEEP_TIME);
        }
}

void sound_in_start(void *p)
{
        wave_in_t *wave_in = p;

        wave_in->running = 1;
}
void sound_in_stop(void *p)
{
        wave_in_t *wave_in = p;
        
        wave_in->running = 0;
}

podule_config_selection_t *sound_in_devices_config(void)
{
        int nr_devs;
        podule_config_selection_t *sel;
        podule_config_selection_t *sel_p;
        char *in_dev_text = malloc(65536);
        int c;
        
        if (!in_queried)
                in_query();

        nr_devs = in_device_count;
        sel = malloc(sizeof(podule_config_selection_t) * (nr_devs+2));
        sel_p = sel;

        strcpy(in_dev_text, "None");
        sel_p->description = in_dev_text;
        sel_p->value = -1;
        sel_p++;
        in_dev_text += strlen(in_dev_text)+1;

        for (c = 0; c < nr_devs; c++)
        {
                strcpy(in_dev_text, in_devices[c].name);
                sel_p->description = in_dev_text;
                sel_p->value = c;
                sel_p++;

                in_dev_text += strlen(in_dev_text)+1;
        }

        strcpy(in_dev_text, "");
        sel_p->description = in_dev_text;

        return sel;
}
