#include <windows.h>
#include <mmsystem.h>
#include <stdint.h>
#include <stdio.h>
#include "podule_api.h"
#include "sound_in.h"

typedef struct wave_in_t
{
        HWAVEIN device;
        
        WAVEHDR buffer_hdrs[2];
        int16_t buffers[2][4800*2];
        
        int running;
        int buffers_pending;

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

static void CALLBACK wave_in_callback(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);

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 num_devs = waveInGetNumDevs();
        int c;
        WAVEFORMATEX format;
        MMRESULT hr;
        const char *device;
        int device_nr = 0;
        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;

	if (log)
	        log("%i WaveIn devs %p\n", num_devs, wave_in);
        for (c = 0; c < num_devs; c++)
        {
                WAVEINCAPS caps;
                
                waveInGetDevCaps(c, &caps, sizeof(caps));
		if (log)
                	log("dev %i : %s, formats=%08x\n", c, caps.szPname, caps.dwFormats);
        }
        
        format.wFormatTag = WAVE_FORMAT_PCM;
        format.nChannels = 2;
        format.nSamplesPerSec = 48000;
        format.nAvgBytesPerSec = 48000*2*2;
        format.nBlockAlign = 4;
        format.wBitsPerSample = 16;
        format.cbSize = 0;
        
        hr = waveInOpen(&wave_in->device, device_nr, &format, (DWORD_PTR)wave_in_callback,
                        (DWORD_PTR)wave_in, CALLBACK_FUNCTION);
        if (hr != MMSYSERR_NOERROR)
        {
                char string[256];
		if (log)
	                log("waveInOpen failed %i\n", hr);
                waveInGetErrorText(hr, string, 255);
		if (log)
	                log("%s\n", string);
        }
        
        for (c = 0; c < 2; c++)
        {
                WAVEHDR *hdr = &wave_in->buffer_hdrs[c];
                
                memset(hdr, 0, sizeof(WAVEHDR));
                hdr->lpData = (LPSTR)wave_in->buffers[c];
                hdr->dwBufferLength = 4800*2*2;
                hdr->dwBytesRecorded = 0;
                hdr->dwUser = (DWORD_PTR)wave_in;
                hdr->dwFlags = 0;
                
                hr = waveInPrepareHeader(wave_in->device, hdr, sizeof(WAVEHDR));
                if (hr != MMSYSERR_NOERROR)
		{
			if (log)
	                        log("waveInPrepareHeader %i failed %i\n", c, hr);
		}
        }
                
        return wave_in;
}

void sound_in_close(void *p)
{
        wave_in_t *wave_in = p;
        
        waveInUnprepareHeader(wave_in->device, &wave_in->buffer_hdrs[0], sizeof(WAVEHDR));
        waveInUnprepareHeader(wave_in->device, &wave_in->buffer_hdrs[1], sizeof(WAVEHDR));
        waveInReset(wave_in->device);
        waveInClose(wave_in->device);
        free(wave_in);
}

static void CALLBACK wave_in_callback(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
        wave_in_t *wave_in = (wave_in_t *)dwInstance;
        WAVEHDR *hdr;
        MMRESULT hr;
        
        switch (uMsg)
        {
                case WIM_DATA:
                hdr = (WAVEHDR *)dwParam1;
                if (wave_in->running)
                {
                        wave_in->sound_in_buffer(wave_in->p, hdr->lpData, hdr->dwBytesRecorded / 4);
                        waveInAddBuffer(wave_in->device, hdr, sizeof(WAVEHDR));
                }
                else if (wave_in->buffers_pending)
                        wave_in->buffers_pending--;
                break;
        }
}

void sound_in_start(void *p)
{
        wave_in_t *wave_in = p;
        int c;
        int sleeps = 0;
        
        while (wave_in->buffers_pending)
        {
                Sleep(100);
                sleeps++;
                if (sleeps >= 5)
                {
//                        lark_log("Timed out on buffers pending %i\n", wave_in->buffers_pending);
                        break;
                }
        }
        
        for (c = 0; c < 2; c++)
                waveInAddBuffer(wave_in->device, &wave_in->buffer_hdrs[c], sizeof(WAVEHDR));

        wave_in->running = 1;
        waveInStart(wave_in->device);
}
void sound_in_stop(void *p)
{
        wave_in_t *wave_in = p;
        
//        lark_log("sound_in_stop\n");
        wave_in->running = 0;
        wave_in->buffers_pending = 2;
        waveInReset(wave_in->device);
}

podule_config_selection_t *sound_in_devices_config(void)
{
        int nr_devs = waveInGetNumDevs();
        podule_config_selection_t *sel = malloc(sizeof(podule_config_selection_t) * (nr_devs+2));
        podule_config_selection_t *sel_p = sel;
        char *wave_dev_text = malloc(65536);
        int c;

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

        for (c = 0; c < nr_devs; c++)
        {
                WAVEINCAPS caps;

                waveInGetDevCaps(c, &caps, sizeof(caps));
                strcpy(wave_dev_text, caps.szPname);

                sel_p->description = wave_dev_text;
                sel_p->value = c;
                sel_p++;

                wave_dev_text += strlen(wave_dev_text)+1;
        }

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

        return sel;
}
