/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2020  Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include "header.h"

#include "fifo.h"
#include "language2.h"
#include "oss.h"
#include "pa.h"

#ifdef HAVE_PORTAUDIO

#include <portaudio.h>

int pa_initialised = 0;

//static const char *clabels[] = SOUND_DEVICE_LABELS;
void pa_info(GString *gs){
    int i, j, max, maxhainame, maxdiname;
	const PaHostApiInfo *hai;
	PaError err;
	PaSampleFormat formats[] = {paFloat32, paInt32, paInt24, paInt16, paInt8, paUInt8, paCustomFormat, 0};
	char *formatsStr[] = {"F32", "I32", "I24", "I16", "I8", "U8", "CUST", NULL};
	int terminate = 0;

	if (!pa_initialised){
		Pa_Initialize();
		pa_initialised = 1;
		terminate = 1;
	}
    g_string_append_printf(gs, "\n  portaudio_info:\n");
	g_string_append_printf(gs, "version: %s\n", Pa_GetVersionText());
	hai = Pa_GetHostApiInfo(Pa_GetDefaultHostApi());
	if (hai != NULL) g_string_append_printf(gs, "default hostapi: %s\n", hai->name);
	g_string_append_printf(gs, "default output: %d\n", Pa_GetDefaultOutputDevice());
	g_string_append_printf(gs, "default input:  %d\n", Pa_GetDefaultInputDevice());
 
    max = Pa_GetDeviceCount();
    maxhainame = 0;
    maxdiname = 0;
	for (i = 0; i < max; i++){
        int l;
		const PaDeviceInfo *di = Pa_GetDeviceInfo(i);
		hai = Pa_GetHostApiInfo(di->hostApi);
        l = strlen(hai->name);
        if (l > maxhainame) maxhainame = l;
        l = strlen(di->name);
        if (l > maxdiname) maxdiname = l;

    }
	for (i = 0; i < max; i++){
		const PaDeviceInfo *di = Pa_GetDeviceInfo(i);
		hai = Pa_GetHostApiInfo(di->hostApi);
//		if (di->hostApi != Pa_GetDefaultHostApi()) continue;
		g_string_append_printf(gs, "%2d: %-*s %-*s srate=%d  ", i, maxhainame, hai->name, maxdiname, di->name, (int)di->defaultSampleRate);
		//g_string_append_printf(gs, "hostapi=%s\n", hai->name);

		if (di->maxOutputChannels > 0){
			g_string_append_printf(gs, "output: channels=%-3d latency=%d-%dms   ", di->maxOutputChannels, (int)(di->defaultLowOutputLatency * 1000), (int)(di->defaultHighOutputLatency * 1000));
			g_string_append_printf(gs, " formats=");
			for (j = 0; ; j++){
				PaStreamParameters par;
				if (formats[j] == 0) break;
				par.channelCount = di->maxOutputChannels;
				par.device = i;
				par.hostApiSpecificStreamInfo = NULL;
				par.suggestedLatency = 0.0;
				par.sampleFormat = formats[j];
				err = Pa_IsFormatSupported(NULL, &par, di->defaultSampleRate);
				if (err == paFormatIsSupported) {
					g_string_append_printf(gs, "%s ", formatsStr[j]);
				}else{
					log_addf("output=%d  format=%s: %s", i, formatsStr[j], Pa_GetErrorText(err));
				}
			}
			
		}
		if (di->maxInputChannels > 0){
			g_string_append_printf(gs, "input: channels=%-3d latency=%d-%dms   ", di->maxInputChannels , (int)(di->defaultLowInputLatency * 1000), (int)(di->defaultHighInputLatency * 1000));
			g_string_append_printf(gs, " formats=");
			for (j = 0; ; j++){
				PaStreamParameters par;
				if (formats[j] == 0) break;
				par.channelCount = di->maxInputChannels;
				par.device = i;
				par.hostApiSpecificStreamInfo = NULL;
				par.suggestedLatency = 0.0;
				par.sampleFormat = formats[j];
				err = Pa_IsFormatSupported(&par, NULL, di->defaultSampleRate);
				if (err == paFormatIsSupported) {
					g_string_append_printf(gs, "%s ", formatsStr[j]);
				}else{
					log_addf("input=%d  format=%s: %s", i, formatsStr[j], Pa_GetErrorText(err));

				}
			}
		}
		g_string_append_printf(gs, "\n");
	}
	if (terminate){
		Pa_Terminate();
	}
}

static int pa_get_bufsize_frames(struct dsp *dsp){
	double period_time = 0.0;
	double wanted = 0.050;
	int frames = 64;

	if (dsp->period_time > 0) wanted = dsp->period_time / 1000.0;

	while (period_time < wanted){
		frames <<= 1;
		period_time = (double)frames / (double)dsp->speed;
	} 
	return frames;
}


int pa_open2(struct dsp *dsp, int rec){
    PaError err;
    const PaDeviceInfo *di;
	const PaStreamInfo *si;
    
    zg_free0(dsp->name);
	di = Pa_GetDeviceInfo(dsp->pa_params.device);
    if (di)
        dsp->name = g_strdup(di->name);
    else
        dsp->name = g_strdup_printf(VTEXT(T_UNKNOWN_PORTAUDIO_DEV), dsp->pa_params.device);

    dsp->frames = pa_get_bufsize_frames(dsp);
	dsp->samples = dsp->frames * dsp->channels;
	dsp->bytes = dsp->samples * sizeof(short);

    err = Pa_OpenStream(
        &dsp->pa, 
        rec ? &dsp->pa_params : NULL, 
        !rec ? &dsp->pa_params : NULL,														
        (double)dsp->speed,
		dsp->frames,
        paNoFlag,
        NULL,
        NULL);

    if (err != paNoError){
		log_addf(VTEXT(T_CANT_OPEN_PA_STREAM), dsp->pa_params.device, rec ? VTEXT(T_RECORD2) : VTEXT(T_PLAYBACK), Pa_GetErrorText(err));
        dsp->pa = NULL;
        return -1;
    }



	err = Pa_StartStream(dsp->pa);
	if (err != paNoError){
        log_addf(VTEXT(T_CANT_PA_STREAM), rec ? VTEXT(T_RECORD2) : VTEXT(T_PLAYBACK), dsp->pa_params.device, Pa_GetErrorText(err));
		Pa_CloseStream(dsp->pa);
        dsp->pa = NULL;
        return -1;
	}
	si = Pa_GetStreamInfo(dsp->pa);
	if (si) dbg("PortAudio %s stream opened rate=%1.1f Hz, latency=%1.1f ms, bufsize=%df %ds %db\n", rec ? VTEXT(T_RECORD2): VTEXT(T_PLAYBACK), si->sampleRate, (rec ? si->inputLatency : si->outputLatency) * 1000, dsp->frames, dsp->samples, dsp->bytes);

    return 0;
} 

int pa_close2(struct dsp *dsp){
    PaError err;

    //dsp->reset(dsp);

    if (dsp->pa != NULL){
		err = Pa_StopStream(dsp->pa);
		if (err != paNoError){
			log_addf(VTEXT(T_CANT_STOP_PA), Pa_GetErrorText(err));
		}
        err = Pa_CloseStream(dsp->pa);
		if (err != paNoError){
			log_addf(VTEXT(T_CANT_CLOSE_PA), Pa_GetErrorText(err));
		}
    }
    dsp->pa = NULL;
    return 0;
}

int pa_write2(struct dsp *dsp, void *data, int frames){
    if (Pa_WriteStream(dsp->pa, data, frames) != paNoError){
        return -1;
    }
    return frames;
}


int pa_read2(struct dsp *dsp, void *data, int frames){
    if (Pa_ReadStream(dsp->pa, data, frames) != paNoError){
		return -1;
	}
    return frames;
}


int pa_reset2(struct dsp *dsp){
    // probably impossible for synchro portaudio
    return -1;
}


int pa_sync2(struct dsp *dsp){
    // probably not needed for synchro portaudio
    return -1;
}


#ifdef HAVE_SNDFILE
int pa_set_format(struct dsp *dsp, SF_INFO *sfinfo, int rec){
	const PaDeviceInfo *pi;
    
    if (rec){
		if (dsp->pa_rec_s != NULL && strlen(dsp->pa_rec_s) > 0) {
			dsp->pa_rec = pa_find_device(dsp->pa_rec_s, dsp->pa_rec_hi, 0, 1);
		}
		if (dsp->pa_rec < 0) dsp->pa_rec = Pa_GetDefaultInputDevice();

        dsp->pa_params.device = dsp->pa_rec;

		pi = Pa_GetDeviceInfo(dsp->pa_params.device);
        if (pi) dsp->pa_params.suggestedLatency = pi->defaultHighInputLatency;

    }else{
		if (dsp->pa_play_s != NULL && strlen(dsp->pa_play_s) > 0) {
			dsp->pa_play = pa_find_device(dsp->pa_play_s, dsp->pa_play_hi, 1, 0);
		}
		if (dsp->pa_play < 0) dsp->pa_play = Pa_GetDefaultOutputDevice();
        dsp->pa_params.device =  dsp->pa_play;

		pi = Pa_GetDeviceInfo(dsp->pa_params.device);
        if (pi) dsp->pa_params.suggestedLatency = pi->defaultHighOutputLatency;

    }
	dsp->pa_params.suggestedLatency = 0.00;
    dsp->pa_params.channelCount = sfinfo->channels;
    dsp->pa_params.hostApiSpecificStreamInfo = NULL;

    dsp->channels = sfinfo->channels; 
    if (dsp->channels > MAX_CHANNELS) dsp->channels = MAX_CHANNELS;

    switch (sfinfo->format & SF_FORMAT_SUBMASK){
        case SF_FORMAT_PCM_U8:
            dsp->pa_params.sampleFormat = paUInt8;
            break;
        case SF_FORMAT_PCM_S8:
            dsp->pa_params.sampleFormat = paInt8;
            break;
        /*case SF_FORMAT_ULAW:
            dsp->oss_format=AFMT_MU_LAW;
            break;            
        case SF_FORMAT_ALAW:
            dsp->oss_format=AFMT_A_LAW;
            break;           
        case SF_FORMAT_IMA_ADPCM:
            dsp->oss_format=AFMT_IMA_ADPCM;
            break;  */          
        default:
            dsp->pa_params.sampleFormat = paInt16;    
    }

    dsp->speed = sfinfo->samplerate;   
//    dbg("pa_set_format() speed=%d channels=%d\n", dsp->speed, dsp->channels);
    return 0;

}    

int pa_set_sdr_format(struct dsp *dsp, int frames, int speed, int rec){
	const PaDeviceInfo *pi;
    
	if (rec){
		if (dsp->pa_rec < 0) return -1;
		dsp->pa_params.device = dsp->pa_rec;
	}else{
		if (dsp->pa_play < 0) return -1;
		dsp->pa_params.device = dsp->pa_play;
	}

	pi = Pa_GetDeviceInfo(dsp->pa_params.device);
    if (!pi) return -2;

	dsp->speed = speed;
	if (dsp->speed == 0) dsp->speed = (int)pi->defaultSampleRate;
	dsp->channels = 2;
	dsp->period_time = frames * 1000 / dsp->speed;

	dsp->pa_params.sampleFormat = paInt16;    
	dsp->pa_params.suggestedLatency = 0.00;
	dsp->pa_params.channelCount = dsp->channels;
    dsp->pa_params.hostApiSpecificStreamInfo = NULL;
    return 0;

}    

int pa_find_device(const char* name, PaHostApiIndex hostapi, int play, int rec) {
	int n = Pa_GetDeviceCount();
	int i;
	for (i = 0; i < n; i++)
	{
		const PaDeviceInfo* di = Pa_GetDeviceInfo(i);
		if (di->hostApi != hostapi) continue;
		if (play && di->maxOutputChannels == 0) continue;
		if (rec && di->maxInputChannels == 0) continue;
		if (strcmp(di->name, name) == 0) return i;
	}
	return -1;
}

#endif


#endif
