/*
 * 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.
 *
 */

#define _GNU_SOURCE  // needed for vasprintf

#include "alsa-softmixer.h"
#include <math.h>

// move from vol % to absolute value
#define CONVERT_RANGE(val, min, max) ceil((val) * ((max) - (min)) * 0.01 + (min))
#define CONVERT_VOLUME(val, min, max) (int) CONVERT_RANGE ((double)val, (double)min, (double)max)

// move from volume to percentage (extract from alsa-utils)

STATIC int CONVERT_PERCENT(long val, long min, long max) {
    long range = max - min;
    int tmp;
    if (range == 0)
        return 0;
    val -= min;
    tmp = (int) rint((double) val / (double) range * 100);
    return tmp;
}

typedef enum {
    RVOL_ABS,
    RVOL_ADD,
    RVOL_DEL,

    RVOL_NONE
} volumeT;

typedef struct {
    const char *uid;
    SoftMixerT *mixer;
    AlsaSndPcmT* pcm;
} apiVerbHandleT;

STATIC AlsaPcmChannelT * ProcessOneChannel(SoftMixerT *mixer, const char *uid, json_object *argsJ) {
    AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT));
	if (channel == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail;
	}
    int error = wrap_json_unpack(argsJ, "{ss,si !}", "uid", &channel->uid, "port", &channel->port);
	if (error) {
		AFB_ApiError(mixer->api, "%s: sndcard=%s channel: missing (uid||port) error=%s json=%s",
				__func__, uid, wrap_json_get_error_string(error), json_object_get_string(argsJ));
		goto fail_channel;
	}

	CDS_INIT_LIST_HEAD(&channel->list);

    channel->uid = strdup(channel->uid);
	if (channel->uid == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail_channel;
	}
    return channel;

fail_channel:
    free(channel);
fail:
    return NULL;
}

STATIC int PcmAttachOneCtl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, json_object *argsJ, AlsaSndControlT *control) {
    snd_ctl_elem_id_t* elemId = NULL;
    snd_ctl_elem_info_t *elemInfo;
    int numid = 0;
    long value = ALSA_DEFAULT_PCM_VOLUME;
    const char *name;

    int error = wrap_json_unpack(argsJ, "{s?i,s?s,s?i !}"
            , "numid", &numid
            , "name", &name
            , "value", &value
            );
    if (error || (!numid && !name)) {
        AFB_ApiError(mixer->api,
        		     "%s: cardid=%s channel: missing (numid|name|value) error=%s json=%s",
					 __func__, sndcard->cid.name, wrap_json_get_error_string(error), json_object_get_string(argsJ));
		goto fail;
    }

    if (numid > 0) {
        elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid);
        if (!elemId) {
			AFB_ApiError(mixer->api, "%s sndard=%s fail to find control numid=%d",
					__func__, sndcard->cid.cardid, numid);
			goto fail;
        }

    } else {
        elemId = AlsaCtlGetNameElemId(mixer, sndcard, name);
        if (!elemId) {
			AFB_ApiError(mixer->api, "%s sndard=%s fail to find control name=%s",
					__func__, sndcard->cid.cardid, name);
			goto fail;
        }
    }

    snd_ctl_elem_info_alloca(&elemInfo);
    snd_ctl_elem_info_set_id(elemInfo, elemId);
    control->name = strdup(snd_ctl_elem_info_get_name(elemInfo));
	if (!control->name) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail_elemId;
	}
    control->numid = snd_ctl_elem_info_get_numid(elemInfo);

    if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) {
		AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not loadable",
				__func__, sndcard->cid.cardid, control->numid, control->name);
		goto fail_control_name;
    }

    if (!snd_ctl_elem_info_is_writable(elemInfo)) {
		AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not writable",
				__func__, sndcard->cid.cardid, control->numid, control->name);
		goto fail_control_name;
    }

    control->count = snd_ctl_elem_info_get_count(elemInfo);
    switch (snd_ctl_elem_info_get_type(elemInfo)) {
        case SND_CTL_ELEM_TYPE_BOOLEAN:
            control->min = 0;
            control->max = 1;
            control->step = 0;
            error = CtlElemIdSetLong(mixer, sndcard, elemId, value);
            break;

        case SND_CTL_ELEM_TYPE_INTEGER:
        case SND_CTL_ELEM_TYPE_INTEGER64:
            control->min = snd_ctl_elem_info_get_min(elemInfo);
            control->max = snd_ctl_elem_info_get_max(elemInfo);
            control->step = snd_ctl_elem_info_get_step(elemInfo);
            error = CtlElemIdSetLong(mixer, sndcard, elemId, (int) CONVERT_VOLUME(value, control->min, control->max));
            break;

        default:
			AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' invalid/unsupported type=%d",
						 __func__, sndcard->cid.cardid, control->numid, control->name, snd_ctl_elem_info_get_type(elemInfo));
			goto fail_control_name;
    }

    if (error) {
		AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not writable",
				__func__, sndcard->cid.cardid, control->numid, control->name);
		goto fail_control_name;
    }

    free(elemId);
    return 0;

fail_control_name:
	free((char*)control->name);
fail_elemId:
	free(elemId);
fail:
    return -1;
}

STATIC int PcmSetControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaSndControlT *control, volumeT volType, int *newvol, int *oldval) {
    snd_ctl_elem_id_t* elemId = NULL;
    snd_ctl_elem_info_t *elemInfo;
    int error, value = 0;
    long curval;

    assert(control->numid);

    elemId = AlsaCtlGetNumidElemId(mixer, sndcard, control->numid);
    if (!elemId) {
		AFB_ApiError(mixer->api, "%s sndard=%s fail to find control numid=%d", __func__, sndcard->cid.cardid, control->numid);
        goto OnErrorExit;
    }

    snd_ctl_elem_info_alloca(&elemInfo);
    snd_ctl_elem_info_set_id(elemInfo, elemId);

    if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) {
		AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not loadable", __func__, sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    if (!snd_ctl_elem_info_is_writable(elemInfo)) {
		AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not writable", __func__, sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    error = CtlElemIdGetLong(mixer, sndcard, elemId, &curval);
    if (error) {
		AFB_ApiError(mixer->api, "%s sndcard=%s fail to read control numid=%d", __func__, sndcard->cid.cardid, control->numid);
        goto OnErrorExit;
    }

    switch (snd_ctl_elem_info_get_type(elemInfo)) {

        case SND_CTL_ELEM_TYPE_BOOLEAN:
            error = CtlElemIdSetLong(mixer, sndcard, elemId, *newvol);
            break;

        case SND_CTL_ELEM_TYPE_INTEGER:
        case SND_CTL_ELEM_TYPE_INTEGER64:

            switch (volType) {
                case RVOL_ADD:
                    value = CONVERT_PERCENT(curval, control->min, control->max) + *newvol;
                    break;
                case RVOL_DEL:
                    value = CONVERT_PERCENT(curval, control->min, control->max) - *newvol;
                    break;
                default:
                    value = *newvol;
            }

            error = CtlElemIdSetLong(mixer, sndcard, elemId, CONVERT_VOLUME(value, control->min, control->max));
            if (error) {
                AFB_ApiError(mixer->api, "PcmSetControl sndard=%s fail to write control numid=%d value=%d", sndcard->cid.cardid, control->numid, value);
                goto OnErrorExit;
            }
            break;

        default:
            AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' invalid/unsupported type=%d", sndcard->cid.cardid, control->numid, control->name, snd_ctl_elem_info_get_type(elemInfo));
            goto OnErrorExit;
    }

    if (error) {
        AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name);
        goto OnErrorExit;
    }

    *oldval = CONVERT_PERCENT(curval, control->min, control->max);
    *newvol = value;
    free(elemId);
    return 0;

OnErrorExit:
    if (elemId)free(elemId);
    return -1;
}

STATIC void ApiPcmVerbCB(AFB_ReqT request) {
    apiVerbHandleT *handle = (apiVerbHandleT*) afb_req_get_vcbdata(request);
    int error, verbose = 0, doInfo = 0, doToggle = 0, doMute = -1;
    json_object *volumeJ = NULL;
    json_object *responseJ = NULL;
    json_object *argsJ = afb_req_json(request);

    SoftMixerT *mixer = handle->mixer;
    AlsaSndCtlT *sndcard = handle->pcm->sndcard;
    assert(mixer && sndcard);

    error = wrap_json_unpack(argsJ, "{s?b,s?b,s?b,s?b,s?o !}"
            , "verbose", &verbose
            , "info", &doInfo
            , "mute", &doMute
            , "toggle", &doToggle
            , "volume", &volumeJ
            );
    if (error) {
        AFB_ReqFailF(request, "syntax-error", "Missing 'mute|volume|toggle|quiet' args=%s error=%s", json_object_get_string(argsJ), wrap_json_get_error_string(error));
        goto OnErrorExit;
    }

    if (verbose) responseJ=json_object_new_object();
    
    if (doMute != -1) {
        int mute = (int) doMute;

        error += AlsaCtlNumidSetLong(mixer, sndcard, handle->pcm->mute.numid, mute);
        if (error) {
            AFB_ReqFailF(request, "invalid-numid", "Fail to set pause numid=%d", handle->pcm->mute.numid);
            goto OnErrorExit;
        }

        if (verbose) {
            json_object_object_add(responseJ, "mute", json_object_new_boolean((json_bool) mute));
        }
    }

    if (doToggle) {
        long mute;

        error += AlsaCtlNumidGetLong(mixer, handle->pcm->sndcard, handle->pcm->mute.numid, &mute);
        error += AlsaCtlNumidSetLong(mixer, handle->pcm->sndcard, handle->pcm->mute.numid, !mute);
        if (error) {
            AFB_ReqFailF(request, "invalid-numid", "Fail to toogle pause numid=%d", handle->pcm->mute.numid);
            goto OnErrorExit;
        }

        if (verbose) {
            json_object_object_add(responseJ, "mute", json_object_new_boolean((json_bool)!mute));
        }
    }

    if (volumeJ) {
        volumeT volType;

        int newvol, oldvol;
        const char*volString;

        switch (json_object_get_type(volumeJ)) {
            case json_type_string:
                volString = json_object_get_string(volumeJ);
                switch (volString[0]) {
                    case '+':
                        sscanf(&volString[1], "%d", &newvol);
                        volType = RVOL_ADD;
                        break;

                    case '-':
                        sscanf(&volString[1], "%d", &newvol);
                        volType = RVOL_DEL;
                        break;
                    default:
                        error = sscanf(&volString[0], "%d", &newvol);
                        volType = RVOL_ABS;
                        if (error != 1) {
                            AFB_ReqFailF(request, "not-integer", "relative volume should start by '+|-' value=%s", json_object_get_string(volumeJ));
                            goto OnErrorExit;
                        }
                }
                break;
            case json_type_int:
                volType = RVOL_ABS;
                newvol = json_object_get_int(volumeJ);
                break;
            case json_type_null:
                volType=RVOL_ADD;
                newvol=0;
                break;
            default:
                AFB_ReqFailF(request, "not-integer", "volume should be string or integer value=%s", json_object_get_string(volumeJ));
                goto OnErrorExit;

        }

        error = PcmSetControl(mixer, handle->pcm->sndcard, &handle->pcm->volume, volType, &newvol, &oldvol);
        if (error) {
            AFB_ReqFailF(request, "invalid-ctl", "Fail to set volume hal=%s card=%s numid=%d name=%s value=%d"
                    , handle->uid, handle->pcm->sndcard->cid.cardid, handle->pcm->volume.numid, handle->pcm->volume.name, newvol);
            goto OnErrorExit;
        }
        
        if (verbose) {
            json_object_object_add(responseJ, "volnew", json_object_new_int(newvol));
            json_object_object_add(responseJ, "volold", json_object_new_int(oldvol));
        }
    }

    AFB_ReqSuccess(request, responseJ, handle->uid);
    return;

OnErrorExit:
    return;
}

PUBLIC AlsaPcmHwInfoT* ApiPcmParamsDup(SoftMixerT* mixer, AlsaPcmHwInfoT* params) {
	AlsaPcmHwInfoT* _params = (AlsaPcmHwInfoT*) malloc(sizeof(AlsaPcmHwInfoT));
	if (params)
		memcpy(_params, params, sizeof(AlsaPcmHwInfoT));
	return _params;
}

PUBLIC void ApiPcmDelParams(SoftMixerT* mixer, AlsaPcmHwInfoT* params) {
	free(params);
}

PUBLIC void ApiPcmParamsShow(SoftMixerT * mixer, const char *msg, const AlsaPcmHwInfoT * params) {
	AFB_ApiInfo(mixer->api, "%s PARAMS: rate=%d, format=%d, formatString=%s",
			msg, params->rate, params->format, params->formatString);

}

PUBLIC AlsaPcmHwInfoT * ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object * paramsJ) {

    const char *format = NULL, *access = NULL;
	AlsaPcmHwInfoT *params = calloc(1, sizeof (AlsaPcmHwInfoT));
	if (params == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail;
	}

    // some default values
    params->rate = ALSA_DEFAULT_PCM_RATE;
    params->channels = 2;
    params->sampleSize = 0;

    if (paramsJ) {
        int error =
				wrap_json_unpack(paramsJ, "{s?i,s?s,s?s !}",
							 "rate",	&params->rate,
							 "format",  &format,
				             "access",  &access);
        if (error) {
			AFB_ApiError(mixer->api, "%s: sndcard=%s invalid params=%s",
					__func__, uid, json_object_get_string(paramsJ));
			goto fail_params;
        }
    }

    // default format
	if (!format) {
        params->format = SND_PCM_FORMAT_S16_LE;
		strcpy(params->formatString, SND_FORMAT_DEFAULT);
        goto check_access;
    }
    strncpy(params->formatString, format, SND_FORMAT_STRING_LEN);

#define FORMAT_CHECK(arg) if (!strcmp(format,#arg)) { params->format = SND_PCM_FORMAT_##arg; goto check_access; }

    FORMAT_CHECK(S16_LE);
    FORMAT_CHECK(S16_BE);
    FORMAT_CHECK(U16_LE);
    FORMAT_CHECK(U16_BE);
    FORMAT_CHECK(S32_BE);
    FORMAT_CHECK(S32_LE);
    FORMAT_CHECK(U32_BE);
    FORMAT_CHECK(U32_LE);
    FORMAT_CHECK(S24_BE);
    FORMAT_CHECK(S24_LE);
    FORMAT_CHECK(U24_BE);
    FORMAT_CHECK(U24_LE);
    FORMAT_CHECK(S8);
    FORMAT_CHECK(U8);
    FORMAT_CHECK(FLOAT_LE);
    FORMAT_CHECK(FLOAT_BE);

	AFB_ApiError(mixer->api, "%s: %s(params) unsupported format 'S16_LE|S32_L|...' format=%s", __func__, uid, format);
	goto fail_params;

check_access:
	AFB_ApiNotice(mixer->api, "%s: %s format set to SND_PCM_FORMAT_%s", __func__, uid, params->formatString);

#define ACCESS_CHECK(arg) if (!strcmp(access,#arg)) { params->access = SND_PCM_ACCESS_##arg; goto success;}

    if (!access) {
        params->access = SND_PCM_ACCESS_RW_INTERLEAVED;
        goto success;
    }

    ACCESS_CHECK(MMAP_INTERLEAVED);
    ACCESS_CHECK(MMAP_NONINTERLEAVED);
    ACCESS_CHECK(MMAP_COMPLEX);
    ACCESS_CHECK(RW_INTERLEAVED);
    ACCESS_CHECK(RW_NONINTERLEAVED);

	AFB_ApiNotice(mixer->api,
				  "%s:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s",
				  __func__,uid, access);
	goto fail_params;

success:
	AFB_ApiNotice(mixer->api, "%s:%s access set to %s", __func__, uid, access);
    return params;

fail_params:
	ApiPcmDelParams(mixer, params);
fail:
    return NULL;
}


static void pcmChannelsDestroy(SoftMixerT * mixer, AlsaSndPcmT * pcm) {
	AFB_ApiDebug(mixer->api, "\t%s: pcm %s (%d channels)", __func__, pcm->uid, pcm->nbChannels);

	AlsaPcmChannelT * channel, *tmp;
	cds_list_for_each_entry_safe(channel, tmp, &pcm->channels.list, list) {
		cds_list_del(&channel->list);
		free((char *)channel->uid);
		free(channel);
	}
	AFB_ApiDebug(mixer->api, "\t%s: pcm %s DONE", __func__, pcm->uid);
}

PUBLIC void ApiPcmDelete(SoftMixerT * mixer, AlsaSndPcmT * pcm) {

	AFB_ApiDebug(mixer->api, "%s: pcm %s\n", __func__, pcm->uid);

	free(pcm->apiVerbHandle);
	AlsaSndCtlT * card = pcm->sndcard;
	if (card->cid.pcmplug_params) {
		free((char*)card->cid.pcmplug_params);
	}

	pcmChannelsDestroy(mixer, pcm);

	snd_ctl_close(card->ctl);
	free(card);
	free ((char*)pcm->mute.name);
	free ((char*)pcm->volume.name);

	if (pcm->verb &&
		(strcmp(pcm->verb, SOFTMIXER_VERB_NONE)!=0)) {
		int error = afb_api_del_verb(mixer->api, pcm->verb, (void**)NULL);
		if (error) {
			AFB_ApiError(mixer->api, "Failed to remove verb %s", pcm->verb);
		}
		free((char*)pcm->verb);
	}

	free((char*)pcm->uid);
	free(pcm);
	AFB_ApiDebug(mixer->api, "%s: done", __func__);
}

PUBLIC AlsaSndPcmT * ApiPcmNew(SoftMixerT* mixer) {
    AlsaSndPcmT *pcm = calloc(1, sizeof (AlsaSndPcmT));
	if (pcm == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail;
	}

	CDS_INIT_LIST_HEAD(&pcm->list);
	CDS_INIT_LIST_HEAD(&pcm->channels.list);
fail:
	return pcm;
}

PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object * argsJ) {

    json_object *sourceJ = NULL, *paramsJ = NULL, *sinkJ = NULL, *targetJ = NULL;
    char *apiVerb = NULL, *apiInfo = NULL;
	apiVerbHandleT *handle = NULL;
    int error;

	AlsaSndPcmT *pcm = ApiPcmNew(mixer);
	if (pcm == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail;
	}

    pcm->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT));
	if (pcm->sndcard == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail_pcm;
	}
	CDS_INIT_LIST_HEAD(&pcm->sndcard->registryList);

    error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?s,s?i,s?i,s?o,s?o,s?o !}"
            , "uid", &pcm->uid
			, "pcmplug_params", &pcm->sndcard->cid.pcmplug_params
            , "path", &pcm->sndcard->cid.devpath
            , "cardid", &pcm->sndcard->cid.cardid
            , "device", &pcm->sndcard->cid.device
            , "subdev", &pcm->sndcard->cid.subdev
            , "sink", &sinkJ
            , "source", &sourceJ
            , "params", &paramsJ
            );
    if (error) {
		AFB_ApiError(mixer->api, "%s: hal=%s missing 'uid|path|cardid|device|sink|source|params' error=%s args=%s",
				__func__, uid, wrap_json_get_error_string(error), json_object_get_string(argsJ));
		goto fail_pcm_sndcard;
    }

	if (pcm->sndcard->cid.pcmplug_params)
		pcm->isPcmPlug = true;
	else
		pcm->isPcmPlug = false;

    // try to open sound card control interface
    pcm->sndcard->ctl = AlsaByPathOpenCtl(mixer, pcm->uid, pcm->sndcard);
    if (!pcm->sndcard->ctl) {
		AFB_ApiError(mixer->api, "%s: hal=%s Fail to open sndcard uid=%s devpath=%s cardid=%s",
				__func__, uid, pcm->uid, pcm->sndcard->cid.devpath, pcm->sndcard->cid.cardid);
		goto fail_pcm_sndcard;
    }

    if (direction == SND_PCM_STREAM_PLAYBACK) {
        if (!sinkJ) {
			AFB_ApiError(mixer->api, "%s: hal=%s SND_PCM_STREAM_PLAYBACK require sinks args=%s",
					__func__, uid, json_object_get_string(argsJ));
			goto fail_pcm_sndcard_ctl;
        }
        targetJ = sinkJ;
    }

    if (direction == SND_PCM_STREAM_CAPTURE) {
        if (!sourceJ) {
			AFB_ApiError(mixer->api, "%s: hal=%s SND_PCM_STREAM_CAPTURE require sources args=%s",
					__func__, uid, json_object_get_string(argsJ));
			goto fail_pcm_sndcard_ctl;
        }
        targetJ = sourceJ;
    }

    json_object *channelsJ = NULL, *controlsJ = NULL;
    error = wrap_json_unpack(targetJ, "{so,s?o !}"
            , "channels", &channelsJ
            , "controls", &controlsJ
            );
    if (error) {
		AFB_ApiNotice(mixer->api, "%s: hal=%s pcms missing channels|[controls] error=%s (%s)",
				__func__, uid, wrap_json_get_error_string(error), json_object_get_string(argsJ));
		goto fail_pcm_sndcard_ctl;
    }

	AlsaPcmChannelT  * channel = NULL;

    if (channelsJ) {
        switch (json_object_get_type(channelsJ)) {

            case json_type_object:
				channel = ProcessOneChannel(mixer, pcm->uid, channelsJ);
				if (channel == NULL) {
					goto fail_pcm_channels;
				}

				pcm->nbChannels++;
				cds_list_add_tail(&channel->list, &pcm->channels.list);

                break;
			case json_type_array: {
				int nbChannels = (int) json_object_array_length(channelsJ);

				for (int idx = 0; idx < nbChannels; idx++) {
                    json_object *channelJ = json_object_array_get_idx(channelsJ, idx);
					channel = ProcessOneChannel(mixer, pcm->uid, channelJ);
					if (!channel)
						goto fail_pcm_channels;

					pcm->nbChannels++;
					cds_list_add_tail(&channel->list, &pcm->channels.list);
                }
                break;
			}
            default:
				AFB_ApiError(mixer->api, "%s:%s invalid pcm=%s", pcm->uid, __func__, json_object_get_string(channelsJ));
				goto fail_pcm_sndcard_ctl;
        }
    }

	// check sndcard accepts params
	pcm->sndcard->params = ApiPcmSetParams(mixer, pcm->uid, paramsJ);
	if (!pcm->sndcard->params) {
		AFB_ApiError(mixer->api, "%s: hal=%s  Fail to set params sndcard uid=%s params=%s",
				__func__, uid, pcm->uid, json_object_get_string(paramsJ));
		goto fail_pcm_sndcard_ctl;
	}

	pcm->sndcard->params->channels = pcm->nbChannels;

    if (controlsJ) {
        json_object *volJ = NULL, *muteJ = NULL;
        error = wrap_json_unpack(controlsJ, "{s?o,s?o !}"
                , "volume", &volJ
                , "mute", &muteJ
                );
        if (error) {
            AFB_ApiNotice(mixer->api,
            		      "%s: source missing [volume]|[mute] error=%s control=%s",
            		      __func__, wrap_json_get_error_string(error), json_object_get_string(controlsJ));
			goto fail_pcm_channels;
        }

		if (volJ) {
			error = PcmAttachOneCtl(mixer, pcm->sndcard, volJ, &pcm->volume);
			if (error)
				goto fail_pcm_channels;
		}
		if (muteJ) {
			error = PcmAttachOneCtl(mixer, pcm->sndcard, muteJ, &pcm->mute);
			if (error)
				goto fail_pcm_channels;
		}

        // create master control for this sink
        if (direction == SND_PCM_STREAM_PLAYBACK) {
			if (asprintf(&apiVerb, "%s:playback", pcm->uid) == -1) {
				SOFTMIXER_NOMEM(mixer->api);
				goto fail_pcm_channels;
			}
			if (asprintf(&apiInfo, "HAL:%s SND_PCM_STREAM_PLAYBACK", uid) == -1) {
				SOFTMIXER_NOMEM(mixer->api);
				goto fail_verb_name;
			}

        } else {
			if (asprintf(&apiVerb, "%s:capture", pcm->uid) == -1) {
				SOFTMIXER_NOMEM(mixer->api);
				goto fail_pcm_channels;
			}
			if (asprintf(&apiInfo, "HAL:%s SND_PCM_STREAM_PLAYBACK", uid) == -1) {
				SOFTMIXER_NOMEM(mixer->api);
				goto fail_verb_name;
			}
        }     
        
        apiVerbHandleT *handle = calloc(1, sizeof (apiVerbHandleT));
		if (handle == NULL) {
			SOFTMIXER_NOMEM(mixer->api);
			goto fail_api_info;
		}

        handle->uid = uid;
        handle->pcm = pcm;
        handle->mixer = mixer;
        pcm->verb=apiVerb;
		pcm->apiVerbHandle = handle;

        error = afb_api_add_verb(mixer->api, apiVerb, apiInfo, ApiPcmVerbCB, handle, NULL, 0, 0);
        if (error) {
			AFB_ApiError(mixer->api, "%s mixer=%s verb=%s fail to Register Master control ",
					__func__, mixer->uid, apiVerb);
			goto fail_handle;
        }
    } else {
    	/* no controls -> put dummy verb */
		pcm->verb = SOFTMIXER_VERB_NONE;
    }

    // free useless resource and secure others
    pcm->uid = strdup(pcm->uid);
	if (pcm->uid == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail_handle;
	}
	const char ** ppcmplug = &(pcm->sndcard->cid.pcmplug_params);
	if (*ppcmplug) {
		*ppcmplug = strdup(*ppcmplug);
		if (*ppcmplug == NULL) {
			SOFTMIXER_NOMEM(mixer->api);
			goto fail_pcm_uid;
		}
	}

    return pcm;

fail_pcm_uid:
	free((char*)pcm->uid);
fail_handle:
	free(handle);
fail_api_info:
	free(apiInfo);
fail_verb_name:
	free(apiVerb);
fail_pcm_channels:
	pcmChannelsDestroy(mixer, pcm);
fail_pcm_sndcard_ctl:
	free(pcm->sndcard->ctl);
fail_pcm_sndcard:
	free(pcm->sndcard);
fail_pcm:
	free ((char*)pcm->mute.name);
	free ((char*)pcm->volume.name);
	free(pcm);
fail:
    return NULL;
}