/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2015  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"

#ifdef HAVE_OSS

static const char *clabels[] = SOUND_DEVICE_LABELS;

static int oss_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 oss_open(struct dsp *dsp, int rec){
    int tmp;
	char errbuf[1024];
	int maxfrag = 2;

    
    dbg("open_dsp('%s',%s)\n", dsp->oss_filename, rec?"record":"playback");
    
    zg_free0(dsp->name);
    dsp->name = g_strdup(dsp->oss_filename);
    
    dsp->frames = oss_get_bufsize_frames(dsp);
	dsp->samples = dsp->frames * dsp->channels;
	dsp->bytes = dsp->samples * sizeof(short);
    
    dsp->fd = open (dsp->oss_filename, rec?O_RDONLY:O_WRONLY, 0);
    if (dsp->fd < 0){
        log_addf("Can't open file '%s': %s", dsp->name, strerror_r(errno, errbuf, sizeof(errbuf)) );
        goto err;
    }

    int a = -1, b;
    for (b = dsp->bytes; b > 0; b >>= 1) a++; 
    if (a < 6) a = 6;
    dbg("bufsize=%df %ds %db a=%d\n", dsp->frames, dsp->samples, dsp->bytes, a);
    dsp->fragment = (maxfrag<<16)|a;//0x000a;
    dbg("setting fragment to 0x%08x\n", dsp->fragment);
    if (ioctl (dsp->fd, SNDCTL_DSP_SETFRAGMENT, &dsp->fragment)){
        log_addf("Can't set fragment size to 0x%x: %s", dsp->fragment, strerror_r(errno, errbuf, sizeof(errbuf)) );
        goto err;
    }
        
    if (ioctl(dsp->fd, SNDCTL_DSP_GETFMTS, &dsp->fmt_mask)){
        log_addf(VTEXT(T_CANT_GET_FMTS), strerror_r(errno, errbuf, sizeof(errbuf)) );
        goto err;
    }
    
    tmp=dsp->oss_format;
    if (ioctl (dsp->fd, SNDCTL_DSP_SETFMT, &dsp->oss_format)){
        log_addf(VTEXT(T_CANT_SET_FMT), tmp, dsp->oss_format, strerror_r(errno, errbuf, sizeof(errbuf)) );
        
        tmp=dsp->oss_format;
        if (ioctl (dsp->fd, SNDCTL_DSP_SETFMT, &dsp->oss_format)){
            log_addf(VTEXT(T_CANT_SET_FMT_RET), tmp, dsp->oss_format, strerror_r(errno, errbuf, sizeof(errbuf)) );
            
            tmp = dsp->oss_format = AFMT_S16_NE;
            if (ioctl (dsp->fd, SNDCTL_DSP_SETFMT, &dsp->oss_format)){
                log_addf(VTEXT(T_CANT_SET_FALLBACK), tmp, dsp->oss_format, strerror_r(errno, errbuf, sizeof(errbuf)) );
                goto err;
            }
        }
        /* fallback ok */
    }
    

    tmp=dsp->channels;
    if (ioctl (dsp->fd, SNDCTL_DSP_CHANNELS, &dsp->channels)){
        log_addf(VTEXT(T_CANT_SET_CHANNELS), tmp, dsp->channels, strerror_r(errno, errbuf, sizeof(errbuf)) );
        
        tmp=dsp->channels;
        if (ioctl (dsp->fd, SNDCTL_DSP_CHANNELS, &dsp->channels)){
            log_addf(VTEXT(T_CANT_SET_CHANNELS_RET), tmp, dsp->channels, strerror_r(errno, errbuf, sizeof(errbuf)) );
            goto err;
        }
        /* fallback ok */
    }

    tmp=dsp->speed;
    if (ioctl (dsp->fd, SNDCTL_DSP_SPEED, &dsp->speed)){
        log_addf(VTEXT(T_CANT_SET_RATE), tmp, dsp->speed, strerror_r(errno, errbuf, sizeof(errbuf)) );
        
        tmp=dsp->speed;
        if (ioctl (dsp->fd, SNDCTL_DSP_SPEED, &dsp->speed)){
            log_addf(VTEXT(T_CANT_SET_RATE_RET), tmp, dsp->speed, strerror_r(errno, errbuf, sizeof(errbuf)) );
            goto err;
        }
        /* fallback ok */
    }

    if (ioctl (dsp->fd, SNDCTL_DSP_GETBLKSIZE, &dsp->blksize)){
        log_addf(VTEXT(T_CANT_SET_BLOCK_SIZE), tmp, dsp->blksize, strerror_r(errno, errbuf, sizeof(errbuf)) );
        goto err;
    }   
    //log_addf("OSS opened, rate=%d, bufsize=%df %ds %db", dsp->speed, dsp->frames, dsp->samples, dsp->bytes); 
    dbg("OSS opened, rate=%d, bufsize=%df %ds %db\n", dsp->speed, dsp->frames, dsp->samples, dsp->bytes); 
    goto x;
    
err:;
    if (dsp->fd>=0) close(dsp->fd);
    dsp->fd = -1;

x:;
   /* dbg(" dsp->fd=%d\n", dsp->fd);*/
    return dsp->fd;
} 

int oss_close(struct dsp *dsp){
    
    /*ST_START;*/
    dsp->reset(dsp);
    if (dsp->fd >= 0) {
        /*dbg("close_dsp(fd=%d)\n",dsp->fd);*/
        close(dsp->fd);
    }   
    dsp->fd=-1;
/*    ST_STOP;*/
    return 0;
}


int oss_write(struct dsp *dsp, void *data, int frames){
	int bpf = dsp->frames / dsp->bytes;
    int ret = write(dsp->fd, data, frames * bpf);    
	if (ret > 0) ret /= bpf;
	return ret;
}


int oss_read(struct dsp *dsp, void *data, int frames){
	int bpf = dsp->frames / dsp->bytes;
    int ret = read(dsp->fd, data, frames * bpf);    
	if (ret > 0) ret /= bpf;
	return ret;
}


int oss_reset(struct dsp *dsp){
    if (!dsp || dsp->fd<0) return 0;
    return ioctl(dsp->fd, SNDCTL_DSP_RESET, NULL); 
}


int oss_sync(struct dsp *dsp){
    if (!dsp || dsp->fd<0) return 0;
    return ioctl(dsp->fd, SNDCTL_DSP_SYNC, NULL); 
}


#ifdef HAVE_SNDFILE
int oss_set_format(struct dsp *dsp, SF_INFO *sfinfo, int ret){

    switch (sfinfo->format & SF_FORMAT_SUBMASK){
/*        case SF_FORMAT_PCM_U8:
            format=AFMT_U8;
            break;
        case SF_FORMAT_PCM_S8:
            format=AFMT_S8;
            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->oss_format=AFMT_S16_NE;    
    }
    dsp->channels=sfinfo->channels; 
    if (dsp->channels>MAX_CHANNELS) dsp->channels=MAX_CHANNELS;
    dsp->speed=sfinfo->samplerate;   
    return 0;
}    
#endif

static char *stripr(char *s){
    int i;

    i=strlen(s);
    if (!i) return s;

    for (i--;i>=0;i--){
        if (s[i]!=' ') break;
        s[i]='\0';
    }

    return s;
}

int oss_get_sources(struct dsp *dsp, GString *labels){
	int fd, i, recmask;
    char s[16];
	
    g_string_truncate(labels, 0);
    if (!dsp || !dsp->oss_mixer || strlen(dsp->oss_mixer)==0) return 0;

    fd=open(dsp->oss_mixer, O_RDWR, 0);
	if (fd<0){
		return -1;
	}

	if (ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask) <0){
		close(fd);
		return -1;
	}
	
    for (i=0; i<SOUND_MIXER_NRDEVICES; i++){
        if ((recmask & (1<<i))==0) continue;
        if (strlen(labels->str)>0) g_string_append_c(labels, ';');
        safe_strncpy(s, clabels[i], sizeof(s)); 
        stripr(s);
        g_string_append(labels, s);
    }
	close(fd);
    return 0;
}

int oss_set_source(struct dsp *dsp){
	int fd, i, val;
    char s[16];


	   
    dbg("oss_set_dsp->source('%s')\n", dsp->source);
    if (!dsp->source || !*dsp->source || !dsp->oss_mixer || !*dsp->oss_mixer){
        return 0;
    }

	
    fd=open(dsp->oss_mixer, O_RDWR, 0);
	if (fd<0){
		return -1;
	}

    for (i=0; i<SOUND_MIXER_NRDEVICES; i++){
        safe_strncpy(s, clabels[i], sizeof(s)); 
        stripr(s);
        if (strcasecmp(s, dsp->source)!=0) continue;

        dbg("oss_mixer_set_source('%s') setting 0x%x\n", dsp->source, i);
        if (ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &i) <0){
            close(fd);
            return -1;
        }

        if (dsp->rlev < 0) continue;
        val = (dsp->rlev << 8 ) | dsp->rlev;
        if (ioctl(fd, MIXER_WRITE(i), &val) < 0){
            close(fd);
            return -3;
        }
        close(fd);
        return 0;
    }
    dbg("oss_mixer_set_source('%s') not found\n", dsp->source);
    
    close(fd);
    return -2;
}

int oss_set_plevel(struct dsp *dsp){
    int fd, i, val;
    char s[16];
    
    dbg("oss_set_plevel()\n");

	if (!dsp->oss_mixer || !*dsp->oss_mixer) return 0;
    if (dsp->plev < 0) return 0;
        
	
    fd=open(dsp->oss_mixer, O_RDWR, 0);
	if (fd<0){
		return -1;
	}
    dbg("SOUND_MIXER_NRDEVICES=%d\n", SOUND_MIXER_NRDEVICES);

    for (i=0; i<SOUND_MIXER_NRDEVICES; i++){
        safe_strncpy(s, clabels[i], sizeof(s)); 
        stripr(s);
        dbg("a=%s\n",s);
        if (strcasecmp(s, "Vol")!=0 && strcasecmp(s, "Pcm")!=0) continue;

        val = (dsp->plev << 8 ) | dsp->plev;
        if (ioctl(fd, MIXER_WRITE(i), &val) < 0){
            close(fd);
            return -3;
        }
    }
    
    close(fd);
    return 0;
}

char *oss_recsrc2source(int recsrc){
    int i;
    static char s[16];

    if (recsrc==0) return NULL;
    for (i=-1; recsrc; recsrc>>=1, i++);
    if (i<0) return NULL;
    if (i>=SOUND_MIXER_NRDEVICES) return NULL;

    safe_strncpy(s, clabels[i], sizeof(s)); 
    stripr(s);
    return s;
}

#endif
