/*
 * Copyright(C) 2018 "IoT.bzh"
 * Author Fulup Ar Foll <fulup@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http : //www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License

for the specific language governing permissions and
 * limitations under the License.
 *
 * reference :
 * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c
 * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31
 *
 */

#define _GNU_SOURCE  // needed for vasprintf

#include "alsa-softmixer.h"
#include <pthread.h>
#include <sys/syscall.h>
#include <sched.h>

#include <signal.h>
#include <poll.h>

#include "time_utils.h"

static int xrun(snd_pcm_t * pcm, int error);
static int suspend(snd_pcm_t * pcm, int error);

typedef enum {
	PCM_COPY_MUTE,
	PCM_COPY_UNMUTE,
	PCM_COPY_END,
	PCM_COPY_LAST	// Do not put anything after
} PcmCopyEventType;

typedef struct {
	PcmCopyEventType eventType;
} PcmCopyEvent;

STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) {
    int pcmSampleSize;

    switch (pcmFormat) {

        case SND_PCM_FORMAT_S8:
        case SND_PCM_FORMAT_U8:
            pcmSampleSize = 1;
            break;

        case SND_PCM_FORMAT_U16_LE:
        case SND_PCM_FORMAT_U16_BE:
        case SND_PCM_FORMAT_S16_LE:
        case SND_PCM_FORMAT_S16_BE:
            pcmSampleSize = 2;
            break;

        case SND_PCM_FORMAT_U24_LE:
        case SND_PCM_FORMAT_U24_BE:
        case SND_PCM_FORMAT_S24_LE:
        case SND_PCM_FORMAT_S24_BE:
            pcmSampleSize = 3;
            break;

        case SND_PCM_FORMAT_U32_LE:
        case SND_PCM_FORMAT_U32_BE:
        case SND_PCM_FORMAT_S32_LE:
        case SND_PCM_FORMAT_S32_BE:
            pcmSampleSize = 4;
            break;

        default:
            pcmSampleSize = 0;
    }

    return pcmSampleSize;
}

PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) {
    int error;
    snd_pcm_hw_params_t *pxmHwParams;
    snd_pcm_sw_params_t *pxmSwParams;
    snd_pcm_format_t format;
    snd_pcm_access_t access;

    AlsaPcmHwInfoT * opts = pcm->params;
	const char * card = pcm->cid.cardid;

    const char * modeS = mode==SND_PCM_STREAM_PLAYBACK?"PLAYBACK":"CAPTURE";

	AFB_ApiDebug(mixer->api,
				"%s: mixer info %s uid %s, pcm %s, mode %s",
				__func__, mixer->info, mixer->uid, card, modeS);

    // retrieve hardware config from PCM
    snd_pcm_hw_params_alloca(&pxmHwParams);
    error = snd_pcm_hw_params_any(pcm->handle, pxmHwParams);
    if (error < 0) {
        AFB_ApiError(mixer->api, "%s: Failed to get parameters: %s", __func__, snd_strerror(error));
        goto OnErrorExit;
    }

	AFB_ApiDebug(mixer->api, "(%s): PARAMS before:", card);
    AlsaDumpPcmParams(mixer, pxmHwParams);

    if (!opts->access)
         opts->access = SND_PCM_ACCESS_RW_INTERLEAVED;

    snd_pcm_hw_params_get_access(pxmHwParams, &access);
    error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access);
    if (error) {
        AFB_ApiError(mixer->api,
					 "%s (%s) set_access failed (ignore this error): mixer=%s access=%d Fail current=%d mode error=%s",
					 __func__, card, mixer->uid, opts->access, access, snd_strerror(error));
//Fulup        goto OnErrorExit;
    };

    if (opts->format != SND_PCM_FORMAT_UNKNOWN) {
        snd_pcm_hw_params_get_format(pxmHwParams, &format);
        if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) {
            AFB_ApiError(mixer->api,
						 "%s (%s) mixer=%s Set_Format=%s (%d) FAILED current=%d error=%s",
						 __func__, card, mixer->uid, opts->formatString, opts->format, format, snd_strerror(error));
            AlsaDumpFormats(mixer, pcm->handle);
            goto OnErrorExit;
        }
    }

    if (opts->rate > 0 ) {

		AFB_ApiDebug(mixer->api,"%s (%s): set rate to %d", __func__, card, opts->rate);
        unsigned int pcmRate = opts->rate;
        /* Attempt to set the rate. Failing on a capture dev is acceptable */
        error = snd_pcm_hw_params_set_rate_near(pcm->handle, pxmHwParams, &opts->rate, 0);
        if ( mode == SND_PCM_STREAM_PLAYBACK && error < 0) {
            AFB_ApiError(mixer->api,
						 "%s (%s): mixer=%s FailSet_Rate=%d error=%s",
						 __func__, card, mixer->uid, opts->rate, snd_strerror(error));
            goto OnErrorExit;
        }

        // check we got requested rate
        if (mode == SND_PCM_STREAM_PLAYBACK && opts->rate != pcmRate) {
            AFB_ApiError(mixer->api,
						 "%s (%s): mixer=%s Set_Rate Fail ask=%dHz get=%dHz",
						 __func__, card, mixer->uid, pcmRate, opts->rate);
            goto OnErrorExit;
        }
    }

    if (opts->channels) {
        if ((error = snd_pcm_hw_params_set_channels(pcm->handle, pxmHwParams, opts->channels)) < 0) {
            AFB_ApiError(mixer->api,
						 "%s (%s): mixer=%s Set_Channels=%d Fail error=%s",
						 __func__, card, mixer->uid, opts->channels, snd_strerror(error));
            goto OnErrorExit;
        };
    }

    /* The following code, that
     * 1) sets period time/size; buffer time/size hardware params
     * 2) sets start and stop threashold in software params
     * ... is taken as such from 'aplay' in alsa-utils */

    unsigned buffer_time = 0;
    unsigned period_time = 0;
    snd_pcm_uframes_t buffer_frames = 0;
    snd_pcm_uframes_t period_frames = 0;

	error = snd_pcm_hw_params_get_buffer_time_max(pxmHwParams, &buffer_time, 0);

	AFB_ApiDebug(mixer->api, "(%s) HW_BUFFER_TIME MAX is %d", card, buffer_time);

	if (buffer_time > 500000)
		buffer_time = 500000;

	if (period_time == 0 && period_frames == 0) {
		if (buffer_time > 0)
			period_time = buffer_time / 4;
		else
			period_frames = buffer_frames / 4;
	}

	if (period_time > 0) {
		AFB_ApiDebug(mixer->api, "(%s) SET PERIOD TIME to %d", card, period_time);
		error = snd_pcm_hw_params_set_period_time_near(pcm->handle, pxmHwParams, &period_time, 0);
	}
	else {
		AFB_ApiDebug(mixer->api, "(%s) SET PERIOD SIZE...", card);
		error = snd_pcm_hw_params_set_period_size_near(pcm->handle, pxmHwParams, &period_frames, 0);
	}

	if (error < 0) {
        AFB_ApiError(mixer->api,
					 "%s (%s): mixer=%s Fail to set period in hwparams error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
	}

	if (buffer_time > 0) {
		AFB_ApiDebug(mixer->api, "(%s) SET BUFFER TIME to %d", card, buffer_time);
		error = snd_pcm_hw_params_set_buffer_time_near(pcm->handle, pxmHwParams, &buffer_time, 0);
	} else {
		AFB_ApiDebug(mixer->api, "(%s) SET BUFFER SIZE to %ld...", card, buffer_frames);
		error = snd_pcm_hw_params_set_buffer_size_near(pcm->handle, pxmHwParams, &buffer_frames);
	}

	if (error < 0) {
        AFB_ApiError(mixer->api,
					 "%s (%s): mixer=%s Fail to set buffer in hwparams error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
	}

    // store selected values
    if ((error = snd_pcm_hw_params(pcm->handle, pxmHwParams)) < 0) {
        AFB_ApiError(mixer->api,
					 "%s (%s): mixer=%s Fail to apply hwparams error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
    }

	AFB_ApiDebug(mixer->api, "(%s) PARAMS after:", card);
    AlsaDumpPcmParams(mixer, pxmHwParams);

    // check we effective hw params after optional format change
    snd_pcm_hw_params_get_channels(pxmHwParams, &opts->channels);
    snd_pcm_hw_params_get_format(pxmHwParams, &opts->format);
    snd_pcm_hw_params_get_rate(pxmHwParams, &opts->rate, 0);

	AFB_ApiDebug(mixer->api, "(%s) rate is %d", card, opts->rate);

    opts->sampleSize = AlsaPeriodSize(opts->format);
    if (opts->sampleSize == 0) {
        AFB_ApiError(mixer->api,
					 "%s (%s): mixer=%s Fail unsupported format format=%d",
					 __func__, card, mixer->uid, opts->format);
        goto OnErrorExit;
    }

    snd_pcm_uframes_t chunk_size, buffer_size;

    snd_pcm_hw_params_get_period_size(pxmHwParams, &chunk_size, 0);
    snd_pcm_hw_params_get_buffer_size(pxmHwParams, &buffer_size);
    if (chunk_size == buffer_size) {
    	AFB_ApiError(mixer->api,
					 "(%s) Can't use period equal to buffer size (%lu == %lu)",
					  card, chunk_size, buffer_size);
    	goto OnErrorExit;
    }

    int avail_min = -1;
    size_t n;
    int rate = opts->rate;

	if (avail_min < 0)
		n = chunk_size;
	else
		n = (size_t) ((double)rate * avail_min / 1000000);

    // retrieve software config from PCM
    snd_pcm_sw_params_alloca(&pxmSwParams);
    snd_pcm_sw_params_current(pcm->handle, pxmSwParams);

    // available_min is the minimum number of frames available to read,
    // when the call to poll returns. Assume this is the same for playback (not checked that)

    if ((error = snd_pcm_sw_params_set_avail_min(pcm->handle, pxmSwParams, n)) < 0) {
        AFB_ApiError(mixer->api,
					 "%s (%s): mixer=%s Fail set_buffersize error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
    };

    snd_pcm_sw_params_get_avail_min(pxmSwParams, &pcm->avail_min);

    int start_delay = 0;
	snd_pcm_uframes_t start_threshold;

    /* round up to closest transfer boundary */
    n = buffer_size;
    if (start_delay <= 0) {
    	start_threshold = n + (size_t)((double)rate * start_delay / 1000000);
    } else
    	start_threshold = (size_t)((double)rate * start_delay / 1000000);
    if (start_threshold < 1)
    	start_threshold = 1;

    if (start_threshold > n/2)
    	start_threshold = n/2;

	AFB_ApiDebug(mixer->api, "(%s) CALCULATED START THRESHOLD: %ld", card, start_threshold);

	if (mode == SND_PCM_STREAM_PLAYBACK) {
		start_threshold = 1;
	}

	AFB_ApiDebug(mixer->api, "(%s) %s: Set start threshold to %ld", card, modeS, start_threshold);
   	error = snd_pcm_sw_params_set_start_threshold(pcm->handle, pxmSwParams, start_threshold);
   	if (error < 0) {
   		AFB_ApiError(mixer->api,
					 "%s (%s): mixer=%s failed set start_threshold, error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
   		goto OnErrorExit;
   	}

    // push software params into PCM
    if ((error = snd_pcm_sw_params(pcm->handle, pxmSwParams)) < 0) {
        AFB_ApiError(mixer->api,
					 "%s (%s): mixer=%s Fail to push SW params error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
    };

    AFB_ApiNotice(mixer->api,
				  "%s (%s): mixer=%s Done channels=%d rate=%d format=%d access=%d ... DONE !",
				  __func__, card, mixer->uid, opts->channels, opts->rate, opts->format, opts->access);
    return 0;

OnErrorExit:
    return -1;
}

STATIC int AlsaPcmReadCB( AlsaPcmCopyHandleT * pcmCopyHandle) {
	char string[32];

	snd_pcm_sframes_t availIn;
	snd_pcm_t * pcmIn = pcmCopyHandle->pcmIn->handle;
	alsa_ringbuf_t * rbuf = pcmCopyHandle->rbuf;
	snd_pcm_uframes_t bufSize = alsa_ringbuf_buffer_size(rbuf);

	int err;

	// do we have waiting frames ?
	availIn = snd_pcm_avail_update(pcmIn);
	if (availIn <= 0) {
		if (availIn == -EPIPE) {
			int ret = xrun(pcmIn, (int)availIn);
			AFB_ApiDebug(pcmCopyHandle->api, "XXX read EPIPE (recov=%d) {%s}!", ret, ALSA_PCM_UID(pcmIn, string));

			// For some (undocumented...) reason, a start is mandatory.
			snd_pcm_start(pcmIn);
		}
		goto ExitOnSuccess;
	}

	while (true) {

		snd_pcm_sframes_t nbRead;

		pthread_mutex_lock(&pcmCopyHandle->mutex);
		snd_pcm_sframes_t remain = alsa_ringbuf_frames_remain_capacity(rbuf);

		if (remain <= 0) {
			pthread_mutex_unlock(&pcmCopyHandle->mutex);
			// Wake up the reader, in case it is sleeping,
			// that lets it an opportunity to pop something.
			sem_post(&pcmCopyHandle->sem);
			break;
		}

		if (remain < availIn)
			remain = availIn;

		char buf[remain*pcmCopyHandle->frame_size];
		pthread_mutex_unlock(&pcmCopyHandle->mutex);

		nbRead = snd_pcm_readi(pcmIn, buf, remain);

		if (nbRead == 0) {
			break;
		}
		if (nbRead < 0) {
			if (nbRead== -EPIPE) {
				err = xrun(pcmIn, (int)nbRead);
				AFB_ApiDebug(pcmCopyHandle->api, "read EPIPE (%d), recov %d", ++pcmCopyHandle->read_err_count, err);
				goto ExitOnSuccess;
			} else if (nbRead== -ESTRPIPE) {
				AFB_ApiDebug(pcmCopyHandle->api, "read ESTRPIPE");
				if ((err = suspend(pcmIn, (int)nbRead)) < 0)
					goto ExitOnSuccess;
				nbRead = 0;
			} else {
				goto ExitOnSuccess;
			}
		}
		pthread_mutex_lock(&pcmCopyHandle->mutex);
		alsa_ringbuf_frames_push(rbuf, buf, nbRead);
		snd_pcm_uframes_t used = alsa_ringbuf_frames_used(rbuf);
		pthread_mutex_unlock(&pcmCopyHandle->mutex);

		// Wait for having the buffer full enough before waking up the playback
		// else it will starve immediately.
		if (used > 0.8 * (double)bufSize) {
			sem_post(&pcmCopyHandle->sem);
		}

		availIn -= nbRead;

		// completed, we have read everything
		if (availIn <= 0) {
			break;
		}

	}

ExitOnSuccess:
	return 0;
}


static int xrun( snd_pcm_t * pcm, int error)
{
	int err;

	if ((err = snd_pcm_recover(pcm, error, 1)) < 0) {
		return err;
	}
	return 0;
}

static int suspend( snd_pcm_t * pcm, int error)
{
	int err;

	while ((err = snd_pcm_resume(pcm)) == -EAGAIN) {
		usleep(1);
	}
	if (err < 0)
		return xrun(pcm, error);
	return 0;
}


static void readSuspend(AlsaPcmCopyHandleT * pcmCopyHandle) {

	// will be deaf
	pcmCopyHandle->saveFd = pcmCopyHandle->pollFds[1].fd;
	pcmCopyHandle->pollFds[1].fd = -1;

	AFB_ApiNotice(pcmCopyHandle->api, "capture muted");
}

static void readResume(AlsaPcmCopyHandleT * pcmCopyHandle) {

	// undeaf it
	pcmCopyHandle->pollFds[1].fd = pcmCopyHandle->saveFd;
	snd_pcm_prepare(pcmCopyHandle->pcmIn->handle);
	snd_pcm_start(pcmCopyHandle->pcmIn->handle);
	AFB_ApiNotice(pcmCopyHandle->api, "capture unmuted");
}


static void *readThreadEntry(void *handle) {
#define LOOP_TIMEOUT_MSEC	10*1000 /* 10 seconds */

    AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle;
    pcmCopyHandle->tid = (int) syscall(SYS_gettid);
	int ix;

    AFB_ApiNotice(pcmCopyHandle->api,
                  "%s :%s/%d Started, muted=%d",
                  __func__, pcmCopyHandle->info, pcmCopyHandle->tid, pcmCopyHandle->pcmIn->mute);

	struct pollfd * eventFd =  &pcmCopyHandle->pollFds[0];
	struct pollfd * framePfds = &pcmCopyHandle->pollFds[1];

	eventFd->events  = POLLIN | POLLHUP;

	for (ix = 0; ix <pcmCopyHandle->nbPcmFds-1; ix++) {
		framePfds[ix].events = POLLIN | POLLHUP;
	}

   	bool muted = pcmCopyHandle->pcmIn->mute;

  	if (muted)
   		readSuspend(pcmCopyHandle);


    /* loop until end */
    for (;;) {

  	   	int err = poll(pcmCopyHandle->pollFds, pcmCopyHandle->nbPcmFds, LOOP_TIMEOUT_MSEC);
    	if (err < 0) {
    		AFB_ApiError(pcmCopyHandle->api, "%s: poll err %s", __func__, strerror(errno));
    		continue;
    	}

    	if (err == 0) {
    		/* timeout */
//			AFB_ApiDebug(pcmCopyHandle->api, "%s(%s) alive, mute %d", __func__, pcmCopyHandle->pcmIn->cid.cardid, muted );
    		continue;
    	}

		// handle the incoming events/mute order
		if ((eventFd->revents & EPOLLIN) != 0) {
			PcmCopyEvent event;

			size_t ret = read(eventFd->fd, &event, sizeof(event));
			if (ret <= 0)
				continue;

			switch (event.eventType) {
			case PCM_COPY_MUTE:
				if (!muted) {
					readSuspend(pcmCopyHandle);
					muted = true;
				}
				break;
			case PCM_COPY_UNMUTE:
				if (muted) {
					readResume(pcmCopyHandle);
					muted = false;
				};
				break;
			case PCM_COPY_END:
				AFB_ApiDebug(pcmCopyHandle->api, "%s ending -> EXIT", __func__);
				goto done;
				break;
			case PCM_COPY_LAST:
			default:
				AFB_ApiError(pcmCopyHandle->api, "%s: Unexpected event 0x%x", __func__, event.eventType);
				break;
    		}
    		continue;
    	}

		unsigned short revents = 0;

		int res = snd_pcm_poll_descriptors_revents(pcmCopyHandle->pcmIn->handle, framePfds, pcmCopyHandle->nbPcmFds, &revents);

		if (res == -ENODEV) {
    		sleep(1);
    		continue;
    	}

		if (revents & POLLHUP) {
    		AFB_ApiNotice(pcmCopyHandle->api, "Frame POLLHUP");
    		continue;
    	}

		AlsaPcmReadCB(pcmCopyHandle);
    }
done:
	pthread_exit(0);

	return NULL;
}


static void *writeThreadEntry(void *handle) {
    AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle;
	snd_pcm_t * pcmOut = pcmCopyHandle->pcmOut->handle;

	alsa_ringbuf_t * rbuf = pcmCopyHandle->rbuf;

	snd_pcm_status_t *pcmOutStatus;
	snd_pcm_uframes_t pcmOutSize;

	snd_pcm_sframes_t threshold;

	snd_pcm_status_alloca(&pcmOutStatus);
	snd_pcm_status(pcmOut, pcmOutStatus);
	pcmOutSize = snd_pcm_status_get_avail_max(pcmOutStatus);

	const char * cardid = pcmCopyHandle->pcmOut->cid.cardid;

	/* This threshold is the expected space available in the hw output buffer
	 * The aim is to wait to have a significant amount of space, in order to
	 * avoid to write to the device too often, or take a very small amount of
	 * frames from the ring buffer. So basically, this saves some CPU load */

	threshold = pcmOutSize / 3;

	for (;;) {

		sem_wait(&pcmCopyHandle->sem);

		while (true) {

			if (pcmCopyHandle->ending) {
				AFB_ApiDebug(pcmCopyHandle->api, "%s: ending -> EXIT", __func__);
				goto done;
			}

			snd_pcm_sframes_t used, nbWritten;
			snd_pcm_sframes_t availOut = snd_pcm_avail(pcmOut);

			if (availOut < 0) {
				if (availOut == -EPIPE) {
					AFB_ApiDebug(pcmCopyHandle->api, "%s: write update EPIPE", cardid);
					xrun(pcmOut, (int)availOut);
					continue;
				}
				if (availOut == -ESTRPIPE) {
					AFB_ApiDebug(pcmCopyHandle->api, "%s: write update ESTRPIPE", cardid);
					suspend(pcmOut, (int)availOut);
					continue;
				}
			}

			// no space for output
			if (availOut <= threshold) {
				usleep(500);
				continue;
			}

			pthread_mutex_lock(&pcmCopyHandle->mutex);
			used = alsa_ringbuf_frames_used(rbuf);
			if (used <= 0) {
				pthread_mutex_unlock(&pcmCopyHandle->mutex);
				break; // will wait again
			}

			if (used > availOut)
				used = availOut;

			char buf[used*pcmCopyHandle->frame_size];
			alsa_ringbuf_frames_pop(rbuf, buf, used);
			pthread_mutex_unlock(&pcmCopyHandle->mutex);

			nbWritten = snd_pcm_writei( pcmOut, buf, used);
			if (nbWritten <= 0) {
				if (nbWritten == -EPIPE) {
					int err = xrun(pcmOut, (int)nbWritten);
					AFB_ApiDebug(pcmCopyHandle->api, "XXX %s write EPIPE (%d), recov %d",
							pcmCopyHandle->pcmOut->cid.cardid, 	++pcmCopyHandle->write_err_count , err);

					continue;
				} else if (nbWritten == -ESTRPIPE) {
					AFB_ApiDebug(pcmCopyHandle->api, "XXX write ESTRPIPE");
					break;
				}
				AFB_ApiDebug(pcmCopyHandle->api, "Unhandled error %s", strerror(errno));
				break;
			}

		}

	}
done:
   	pthread_exit(0);
   	return NULL;
}


PUBLIC int AlsaPcmCopyMuteSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn, bool mute) {

	PcmCopyEvent event;
	event.eventType = mute?PCM_COPY_MUTE:PCM_COPY_UNMUTE;
	ssize_t ret = write(pcmIn->eventFd, &event, sizeof(event));
	(void) ret;
	return 0;
}

static int AlsaPcmCopyEndSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn) {
	PcmCopyEvent event;
	event.eventType = PCM_COPY_END;
	ssize_t ret = write(pcmIn->eventFd, &event, sizeof(event));
	(void) ret;
	return 0;
}

static int AlsaPcmCopyEnd(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle) {

	handle->ending = true;
	// wake up the reader through the eventfd
	AlsaPcmCopyEndSignal(mixer, handle->pcmIn);
	// wake up the writer through the wait semaphore
	sem_post(&handle->sem);

	return 0;
}

PUBLIC int AlsaPcmCopyStop(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle) {

	AFB_ApiDebug(mixer->api, "%s: Stopping copy threads of %s", __func__, handle->stream->uid);

	AlsaPcmCopyEnd(mixer, handle);

	if (pthread_join(handle->wthread, NULL) != 0)
		AFB_ApiDebug(mixer->api, "%s: Failed to join write thread", __func__);

	if (pthread_join(handle->rthread, NULL) != 0)
		AFB_ApiDebug(mixer->api, "%s: Failed to join read thread", __func__);

	AFB_ApiDebug(mixer->api, "%s: Copy threads of %s are STOPPED", __func__, handle->stream->uid);

	sem_destroy(&handle->sem);

	alsa_ringbuf_free(handle->rbuf);

	free(handle->pollFds);
	free(handle);

	return 0;

}

PUBLIC int AlsaPcmCopyStart(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts) {
    char string[32];
    int error;
	AlsaPcmCopyHandleT *cHandle=NULL;

    // Fulup need to check https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html

    AlsaDumpPcmInfo(mixer,"PcmIn",pcmIn->handle);
    AlsaDumpPcmInfo(mixer,"PcmOut",pcmOut->handle);

    /* remember configuration of capture */
    pcmIn->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT));
    if (!pcmIn->params) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
    }
    memcpy(pcmIn->params, opts, sizeof(AlsaPcmHwInfoT));

    pcmOut->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT));
    if (!pcmOut->params) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
    }
    memcpy(pcmOut->params, opts, sizeof(AlsaPcmHwInfoT));

    pcmIn->mixer = mixer;
    pcmOut->mixer = mixer;

	AFB_ApiDebug(mixer->api, "%s: Configure CAPTURE PCM", __func__);

    // prepare PCM for capture and replay
    error = AlsaPcmConf(mixer, pcmIn, SND_PCM_STREAM_CAPTURE);
    if (error) {
    	AFB_ApiError(mixer->api, "%s: PCM configuration for capture failed", __func__);
    	goto OnErrorExit;
    }

	AFB_ApiDebug(mixer->api, "%s: Configure PLAYBACK PCM", __func__);

    // input and output should match
    error = AlsaPcmConf(mixer, pcmOut, SND_PCM_STREAM_PLAYBACK);
    if (error) {
    	AFB_ApiError(mixer->api, "%s: PCM configuration for playback failed", __func__);
    	goto OnErrorExit;
    }

    // Prepare PCM for usage
    if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) {
        AFB_ApiError(mixer->api, "%s: Fail to prepare PLAYBACK PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

    // Prepare PCM for usage
    if ((error = snd_pcm_prepare(pcmIn->handle)) < 0) {
        AFB_ApiError(mixer->api, "%s: Fail to prepare CAPTURE PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

    // Start PCM
    if ((error = snd_pcm_start(pcmOut->handle)) < 0) {
        AFB_ApiError(mixer->api, "%s: Fail to start PLAYBACK PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

    // Start PCM
    if ((error = snd_pcm_start(pcmIn->handle)) < 0) {
        AFB_ApiError(mixer->api, "%s: Fail to start CAPTURE PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

	cHandle = calloc(1, sizeof(AlsaPcmCopyHandleT));
	if (cHandle == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
	}

    cHandle->info = "pcmCpy";
    cHandle->pcmIn = pcmIn;
    cHandle->pcmOut = pcmOut;
    cHandle->api = mixer->api;
    cHandle->channels = opts->channels;
	cHandle->stream = stream;

	cHandle->frame_size = (snd_pcm_format_physical_width(opts->format) / 8) * opts->channels;
	AFB_ApiDebug(mixer->api, "%s: Frame size is %zu", __func__, cHandle->frame_size);
	AFB_ApiDebug(mixer->api, "%s: Buffer delay is %d ms", __func__, stream->delayms);

	snd_pcm_uframes_t nbFrames = (stream->delayms * opts->rate)/1000;

    cHandle->rbuf = alsa_ringbuf_new(nbFrames, cHandle->frame_size);
	if (!cHandle->rbuf) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
	}

    cHandle->read_err_count  = 0;
    cHandle->write_err_count = 0;

	AFB_ApiDebug(mixer->api, "%s Copy buffer: nbframes is %zu", __func__, nbFrames);

    // get FD poll descriptor for capture PCM
    int pcmInCount = snd_pcm_poll_descriptors_count(pcmIn->handle);
    if (pcmInCount <= 0) {
        AFB_ApiError(mixer->api,
                     "%s: Fail pcmIn=%s get fds count error=%s",
                     __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error));
        goto OnErrorExit;
	}

	cHandle->nbPcmFds = pcmInCount+1;
	cHandle->pollFds = (struct pollfd *) malloc((cHandle->nbPcmFds)*sizeof(struct pollfd));
	if (cHandle->pollFds == NULL){
		SOFTMIXER_NOMEM(mixer->api);
    	goto OnErrorExit;
    }

	if ((error = snd_pcm_poll_descriptors(pcmIn->handle, cHandle->pollFds+1, pcmInCount)) < 0) {
        AFB_ApiError(mixer->api,
                     "%s: Fail pcmIn=%s get pollfds error=%s",
                     __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

    // create the mute pipe
	int eventFdPipe[2];
	error = pipe(eventFdPipe);
    if (error < 0) {
        AFB_ApiError(mixer->api,
                     "Unable to create the mute signaling pipe");
        goto OnErrorExit;
    }

	struct pollfd * eventPollFd = &cHandle->pollFds[0];
    // read end
	eventPollFd->fd = eventFdPipe[0];
	eventPollFd->events = POLLIN;
	eventPollFd->revents = 0;

    // write end
	pcmIn->eventFd = eventFdPipe[1];

    error = sem_init(&cHandle->sem, 0 , 0);
    if (error < 0) {
    	AFB_ApiError(mixer->api,
    	                     "%s Fail initialize loop semaphore pcmIn=%s err=%d",
    	                     __func__, ALSA_PCM_UID(pcmIn->handle, string), error);
    	goto OnErrorExit;
    }

    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);

    error = pthread_mutex_init(&cHandle->mutex, &attr);
    if (error < 0) {
    	AFB_ApiError(mixer->api,
    	                     "%s Fail initialize loop mutex pcmIn=%s err=%d",
    	                     __func__, ALSA_PCM_UID(pcmIn->handle, string), error);
    }


    /// start a thread for writing
    if ((error = pthread_create(&cHandle->wthread, NULL, &readThreadEntry, cHandle)) < 0) {
        AFB_ApiError(mixer->api,
                     "%s Fail create write thread pcmOut=%s err=%d",
                     __func__, ALSA_PCM_UID(pcmOut->handle, string), error);
        goto OnErrorExit;
    }

    // start a thread for reading
    if ((error = pthread_create(&cHandle->rthread, NULL, &writeThreadEntry, cHandle)) < 0) {
        AFB_ApiError(mixer->api,
                     "%s Fail create read thread pcmIn=%s err=%d",
                     __func__, ALSA_PCM_UID(pcmIn->handle, string), error);
        goto OnErrorExit;
    }

    // request a higher priority for each audio stream thread
    struct sched_param params;
    params.sched_priority = sched_get_priority_max(SCHED_FIFO);

    error= pthread_setschedparam(cHandle->rthread, SCHED_FIFO, &params);
    if (error) {
        AFB_ApiWarning(mixer->api,
                       "%s: Failed to increase stream read thread priority pcmIn=%s err=%s",
                       __func__, ALSA_PCM_UID(pcmIn->handle, string), strerror(error));
    }

    error= pthread_setschedparam(cHandle->wthread, SCHED_FIFO, &params);
    if (error) {
        AFB_ApiWarning(mixer->api,
                       "%s: Failed to increase stream write thread priority pcmIn=%s err=%s",
                       __func__, ALSA_PCM_UID(pcmOut->handle, string), strerror(error));
    }

	stream->copy = cHandle;

    return 0;

OnErrorExit:
    AFB_ApiError(mixer->api, "%s: - pcmIn=%s" , __func__, ALSA_PCM_UID(pcmIn->handle, string));
    AFB_ApiError(mixer->api, "%s: - pcmOut=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string));

	if (cHandle &&cHandle->pollFds) {
		free (cHandle->pollFds);
		cHandle->pollFds = NULL;
	}

	if (cHandle)
		free(cHandle);

    return -1;
}