/*
    Tucnak - VHF contest log
    Copyright (C) 2018 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 "rtlsdr.h"

#ifdef HAVE_LIBRTLSDR

#define DEFAULT_BUF_LENGTH (16 * 32 * 512)


static int rtlsdr_get_bufsize_frames(struct dsp *dsp);


int rtlsdr_device_search(char *s)
{
	int i, device_count, device, offset;
	char *s2;
	char vendor[256], product[256], serial[256];

	device_count = rtlsdr_get_device_count();
	if (!device_count) return -1;
	
	dbg("Found %d device(s):\n", device_count);
	for (i = 0; i < device_count; i++) {
		rtlsdr_get_device_usb_strings(i, vendor, product, serial);
		dbg("  %d:  %s, %s, SN: %s\n", i, vendor, product, serial);
	}
	dbg("\n");

	/* does string look like raw id number */
	device = (int)strtol(s, &s2, 0);
	if (s2[0] == '\0' && device >= 0 && device < device_count) {
		dbg("Using device %d: %s\n", device, rtlsdr_get_device_name((uint32_t)device));
		return device;
	}

	/* does string exact match a serial */
	for (i = 0; i < device_count; i++) {
		rtlsdr_get_device_usb_strings(i, vendor, product, serial);
		if (strcmp(s, serial) != 0) continue;

		device = i;
		dbg("Using device %d: %s\n", device, rtlsdr_get_device_name((uint32_t)device));
		return device;
	}

	/* does string prefix match a serial */
	for (i = 0; i < device_count; i++) {
		rtlsdr_get_device_usb_strings(i, vendor, product, serial);
		if (strncmp(s, serial, strlen(s)) != 0) continue;

		device = i;
		dbg("Using device %d: %s\n", device, rtlsdr_get_device_name((uint32_t)device));
		return device;
	}

	/* does string suffix match a serial */
	for (i = 0; i < device_count; i++) {
		rtlsdr_get_device_usb_strings(i, vendor, product, serial);
		offset = strlen(serial) - strlen(s);
		if (offset < 0) continue;

		if (strncmp(s, serial+offset, strlen(s)) != 0) continue;
		device = i;
		dbg("Using device %d: %s\n", device, rtlsdr_get_device_name((uint32_t)device));
		return device;
	}
	dbg("No matching devices found.\n");
	return -1;
}


int rtlsdr_open2(struct dsp *dsp, int rec){
    //char errbuf[1024];
	int dev_index, ret;

	dev_index = rtlsdr_device_search(dsp->rtlsdr_device);
	if (dev_index < 0) {
		log_addf("rtlsdr device %s not found", dsp->rtlsdr_device);
		goto err;
	}

	zg_free0(dsp->name);
	dsp->name = g_strdup_printf("rtlsdr#%d(%s)", dev_index, dsp->rtlsdr_device);
    
    dbg("open_dspy('%s',%s)\n", dsp->name, rec?"record":"playback");
    
    dsp->frames = rtlsdr_get_bufsize_frames(dsp);	 // 2048 @288kS/s and block 4096
    dsp->samples = dsp->frames * dsp->channels;		 // 4096 @288kS/s and block 4096
    dsp->bytes = dsp->samples * sizeof(short);		 // 8192 @288kS/s and block 4096

	dsp->rtlsdr_buf = g_new0(unsigned char, dsp->samples); // not bytes, only 1B per sample; dsp->samples is for internal format (short int)
    
	ret = rtlsdr_open(&dsp->rtlsdr_dev, (uint32_t)dev_index);
    if (ret < 0){
        log_addf("Can't open rtlsdr#%s", dsp->name);
        goto err;
    }
    
    dbg("rtlsdr opened, rate=%d, bufsize=%df %ds %db \n", dsp->speed, dsp->frames, dsp->samples, dsp->bytes); 


	ret = rtlsdr_set_sample_rate(dsp->rtlsdr_dev, dsp->speed);
	if (ret < 0){
		log_addf("Can't set sample rate %u", (uint32_t)dsp->speed);
		goto err;
	}
	
	ret = rtlsdr_set_center_freq(dsp->rtlsdr_dev, dsp->rtlsdr_frequency);
	if (ret < 0){
		log_addf("Can't set center frequency %u", dsp->rtlsdr_frequency);
		goto err;
	}
	
	ret = rtlsdr_set_tuner_gain_mode(dsp->rtlsdr_dev, 0);
	if (ret < 0){
		log_addf("Can't set auto gain");
		goto err;
	}
	
	//r = rtlsdr_set_freq_correction(dev, ppm_error);

	ret = rtlsdr_reset_buffer(dsp->rtlsdr_dev);
	if (ret < 0){
		log_addf("Can't reset buffer");
		goto err;
	}

    goto x;
    
err:;
	rtlsdr_close2(dsp);

x:;
    return 1;
}


int rtlsdr_close2(struct dsp *dsp){
	if (dsp->rtlsdr_dev != NULL)
	{
		rtlsdr_close(dsp->rtlsdr_dev);
        dsp->rtlsdr_dev = NULL;
	}
    return 0;
}


                            
int rtlsdr_read(struct dsp *dsp, void *data, int frames){
	/*int n_read, ret;
	unsigned char *tail;

	do{
		dbg("rtlsdr_read begin  head=%d  len=%d  size=%d\n", dsp->rtlsdr_bufhead, dsp->rtlsdr_buflen, dsp->rtlsdr_bufsize);
		if (dsp->rtlsdr_buflen >= dsp->bytes){
			unsigned char *pc;
			short int *ps = (short*)data;
			int i;

			if (dsp->rtlsdr_bufhead + dsp->rtlsdr_buflen > dsp->rtlsdr_bufsize && dsp->rtlsdr_bufsize > 0){ // real size of buf is bufsize*2
				memmove(dsp->rtlsdr_buf, dsp->rtlsdr_buf + dsp->rtlsdr_bufhead, dsp->rtlsdr_bufsize);
				dsp->rtlsdr_bufhead = 0;
			}

			pc = (unsigned char*)dsp->rtlsdr_buf + dsp->rtlsdr_bufhead;
			for (i = 0; i < dsp->samples; i++){
				*ps++ = (((short)*pc++) - 0x80) * 0x100;
			}
			dsp->rtlsdr_bufhead += dsp->bytes;
			dsp->rtlsdr_buflen -= dsp->bytes;

			dbg("rtlsdr_read ret    head=%d  len=%d  size=%d\n", dsp->rtlsdr_bufhead, dsp->rtlsdr_buflen, dsp->rtlsdr_bufsize);
			return dsp->frames;
		}

		tail = dsp->rtlsdr_buf + dsp->rtlsdr_buflen;
    	dbg("rtlsdr_read read   head=%d  len=%d  size=%d\n", dsp->rtlsdr_bufhead, dsp->rtlsdr_buflen, dsp->rtlsdr_bufsize);
		ret = rtlsdr_read_sync(dsp->rtlsdr_dev, dsp->rtlsdr_buf + dsp->rtlsdr_bufhead, dsp->rtlsdr_bufsize, &n_read);  
		if (ret < 0){
			return -1;
		}

		dsp->rtlsdr_buflen += n_read;
	}while(n_read > 0);

	return 0;*/

	int n_read, ret, i;
	unsigned char *pc;
	short int *ps;

	if (frames != dsp->frames){
		return -1;
	}
	ret = rtlsdr_read_sync(dsp->rtlsdr_dev, dsp->rtlsdr_buf, dsp->samples, &n_read);  
	if (ret < 0){
		return ret;
	}


	ps = (short*)data;
	pc = (unsigned char*)dsp->rtlsdr_buf;
	for (i = 0; i < dsp->samples; i++){
		*ps = (((short)*pc) - 0x80) * 0x100;
		ps++;
		pc++;
	}
			


	return dsp->frames;
}


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

	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 rtlsdr_set_sdr_format(struct dsp *dsp, int frames, int speed, int rec){
	dsp->channels = 2;
	dsp->speed = speed;
	dsp->period_time = frames * 1000 / dsp->speed;
	return 0;
}

#endif
