/*
 * 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 <string.h>
#include <pthread.h>

static struct cds_list_head mixerList;

static void MixerDelete(SoftMixerT * mixer);

// AVIRT: temporary loops JSON
static json_object *LoopsJ = NULL;

static void MixerExit() {
	SoftMixerT *mixer, *tmp;
	printf("-------------------------- %s ------------------------!\n", __func__);

	cds_list_for_each_entry_safe(mixer, tmp, &mixerList, list) {
		// remove this mixer from the global mixer list
		cds_list_del(&mixer->list);
		AFB_API_INFO(mixer->api, "Terminating mixer %s", mixer->uid);

		AlsaMixerTransaction * transaction, * tmp_trans;
		cds_list_for_each_entry_safe(transaction, tmp_trans, &mixer->transactionList, transaction_node) {
			AlsaMixerTransactionDelete(transaction);
		}
		AFB_API_INFO(mixer->api, "Mixer %s terminated", mixer->uid);
		MixerDelete(mixer);
	}
	printf("------------------------- %s DONE ! Bye ! --------------\n", __func__);
}

CTLP_ONLOAD(plugin, callbacks){

	AFB_API_INFO(plugin->api, "MIXER INIT");
	CDS_INIT_LIST_HEAD(&mixerList);

    atexit(MixerExit);
	return 0;
}


static void MixerDetachVerb(afb_req_t request) {
    AFB_REQ_DEBUG(request,
                  "Detach verb not implemented yet, received request json %s",
                  json_object_get_string(afb_req_json(request)));
    afb_req_success(request, NULL, "Function not implemented");
}

STATIC json_object *MixerInfoOneStream(AlsaStreamAudioT *stream, int verbose) {
    json_object *alsaJ, *responseJ;


    if (!verbose) {
        wrap_json_pack(&responseJ, "{ss, ss, ss}", "uid", stream->uid, "verb", stream->verb, "alsa", stream->source);
    } else {

        wrap_json_pack(&alsaJ, "{ss,si,si}"
                , "cardid", stream->source
                , "volume", stream->volume
                , "mute", stream->mute
                );
        wrap_json_pack(&responseJ, "{ss,ss,so}"
                , "uid", stream->uid
                , "verb", stream->verb
                , "alsa", alsaJ
                );
    }
    return (responseJ);
}

STATIC json_object * MixerInfoStreams(SoftMixerT *mixer, json_object *streamsJ, int verbose) {
    int error;
    const char * key;
    json_object *valueJ;
    json_object *responseJ = NULL;
	AlsaStreamAudioT * stream;

    switch (json_object_get_type(streamsJ)) {

        case json_type_null:
        case json_type_boolean:
            // list every existing stream
            responseJ = json_object_new_array();
			cds_list_for_each_entry(stream, &mixer->streams.list, list) {
				valueJ = MixerInfoOneStream(stream, verbose);
                json_object_array_add(responseJ, valueJ);
            }
            break;

        case json_type_string:
            key = json_object_get_string(streamsJ);
			cds_list_for_each_entry(stream, &mixer->streams.list, list) {
				if (strcasecmp(stream->uid, key))
					continue;
				responseJ = MixerInfoOneStream(stream, verbose);
                break;
            }
            break;

        case json_type_object:
            error = wrap_json_unpack(streamsJ, "{ss}", "uid", &key);
            if (error) {
                AFB_API_ERROR(mixer->api,
                             "%s: missing 'uid' request streamJ=%s error=%s position=%d",
                             __func__, json_object_get_string(streamsJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error));
                goto OnErrorExit;
            }
			cds_list_for_each_entry(stream, &mixer->streams.list, list) {
				if (strcasecmp(stream->uid, key))
					continue;
				responseJ = MixerInfoOneStream(stream, verbose);
                break;
            }
            break;

        case json_type_array:
            responseJ = json_object_new_array();
            for (int idx = 0; idx < json_object_array_length(streamsJ); idx++) {
                json_object *streamJ = json_object_array_get_idx(streamsJ, idx);

                valueJ = MixerInfoStreams(mixer, streamJ, verbose);
                if (!valueJ) {
                    AFB_API_ERROR(mixer->api,
								 "%s: failed to find stream=%s",
								 __func__, json_object_get_string(streamJ));
                    goto OnErrorExit;
                }
                json_object_array_add(responseJ, valueJ);
            }
            break;

        default:
			AFB_API_ERROR(mixer->api, "%s: unsupported json type streamsJ=%s", __func__, json_object_get_string(streamsJ));
            goto OnErrorExit;
    }

    return (responseJ);

OnErrorExit:
    return NULL;
}

STATIC json_object *MixerInfoOneRamp(AlsaVolRampT *ramp, int verbose) {
    json_object *responseJ;

    if (!verbose) {
        wrap_json_pack(&responseJ, "{ss}", "uid", ramp->uid);
    } else {
        wrap_json_pack(&responseJ, "{ss,si,si,si}"
                , "uid", ramp->uid
                , "delay", ramp->delay
                , "step_down", ramp->stepDown
                , "step_up", ramp->stepUp
                );
    }
    return (responseJ);
}

STATIC json_object *MixerInfoRamps(SoftMixerT *mixer, json_object *rampsJ, int verbose) {
    int error;
    const char * key;
    json_object *valueJ;
    json_object *responseJ = NULL;
	AlsaVolRampT * ramp;

    switch (json_object_get_type(rampsJ)) {

        case json_type_null:
        case json_type_boolean:
            // list every existing ramp
            responseJ = json_object_new_array();
			cds_list_for_each_entry(ramp, &mixer->ramps.list, list) {

				valueJ = MixerInfoOneRamp(ramp, verbose);
                json_object_array_add(responseJ, valueJ);
            }
            break;

        case json_type_string:
            key = json_object_get_string(rampsJ);
			cds_list_for_each_entry(ramp, &mixer->ramps.list, list) {
				if (strcasecmp(ramp->uid, key)) continue;
				responseJ = MixerInfoOneRamp(ramp, verbose);
                break;
            }
            break;

        case json_type_object:
            error = wrap_json_unpack(rampsJ, "{ss}", "uid", &key);
            if (error) {
				AFB_API_ERROR(mixer->api, "%s: missing 'uid' request rampJ=%s error=%s position=%d",
							 __func__, json_object_get_string(rampsJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error));
                goto OnErrorExit;
            }
			cds_list_for_each_entry(ramp, &mixer->ramps.list, list) {
				if (strcasecmp(ramp->uid, key)) continue;
				responseJ = MixerInfoOneRamp(ramp, verbose);
                break;
            }
            break;

        case json_type_array:
            responseJ = json_object_new_array();
            for (int idx = 0; idx < json_object_array_length(rampsJ); idx++) {
                json_object *rampJ = json_object_array_get_idx(rampsJ, idx);

                valueJ = MixerInfoRamps(mixer, rampJ, verbose);
                if (!valueJ) {
					AFB_API_ERROR(mixer->api, "%s: fail to find ramp=%s", __func__, json_object_get_string(rampsJ));
                    goto OnErrorExit;
                }
                json_object_array_add(responseJ, valueJ);
            }
            break;

        default:
			AFB_API_ERROR(mixer->api, "%s: unsupported json type rampsJ=%s", __func__, json_object_get_string(rampsJ));
            goto OnErrorExit;
    }

    return (responseJ);

OnErrorExit:
    return NULL;
}

STATIC json_object *MixerInfoOneZone(AlsaSndZoneT *zone, int verbose) {
    json_object *responseJ;

	AlsaPcmChannelT * channel;

    if (!verbose) {
        wrap_json_pack(&responseJ, "{ss}", "uid", zone->uid);
    } else {
        json_object *responseJ = json_object_new_object();
		if (zone->nbSinks > 0) {
            json_object *sinksJ = json_object_new_array();

			cds_list_for_each_entry(channel, &zone->sinks.list, list) {
                json_object *channelJ;
                wrap_json_pack(&channelJ, "{ss,si}"
						, "uid", channel->uid
						, "port", channel->port
                        );
                json_object_array_add(sinksJ, channelJ);
            }
            json_object_object_add(responseJ, "sinks", sinksJ);
        }

		if (zone->nbSources > 0) {
            json_object *sourcesJ = json_object_new_array();

			cds_list_for_each_entry(channel, &zone->sources.list, list) {
                json_object *channelJ;
                wrap_json_pack(&channelJ, "{ss,si}"
						, "uid", channel->uid
						, "port", channel->port
                        );
                json_object_array_add(sourcesJ, channelJ);
            }
            json_object_object_add(responseJ, "source", sourcesJ);
        }

        if (zone->params) {
            json_object *paramsJ;
            wrap_json_pack(&paramsJ, "{si,ss,si}"
                    , "rate", zone->params->rate
                    , "format", zone->params->formatString
                    , "channels", zone->params->channels
                    );
            json_object_object_add(responseJ, "params", paramsJ);
        }
    }
    return (responseJ);
}

STATIC json_object *MixerInfoZones(SoftMixerT *mixer, json_object *zonesJ, int verbose) {
    int error;
    const char * key;
    json_object *valueJ;
    json_object *responseJ = NULL;

	AlsaSndZoneT  *zone;

    switch (json_object_get_type(zonesJ)) {

        case json_type_null:
        case json_type_boolean:
            // list every existing zone
            responseJ = json_object_new_array();
			cds_list_for_each_entry(zone, &mixer->zones.list, list) {
				valueJ = MixerInfoOneZone(zone, verbose);
                json_object_array_add(responseJ, valueJ);
            }
            break;

        case json_type_string:
            key = json_object_get_string(zonesJ);
			cds_list_for_each_entry(zone, &mixer->zones.list, list) {
				if (strcasecmp(zone->uid, key))
					continue;
				responseJ = MixerInfoOneZone(zone, verbose);
                break;
            }
            break;

        case json_type_object:
            error = wrap_json_unpack(zonesJ, "{ss}", "uid", &key);
            if (error) {
                AFB_API_ERROR(mixer->api,
                             "%s: missing 'uid' request zoneJ=%s error=%s position=%d",
                             __func__ ,json_object_get_string(zonesJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error));
                goto OnErrorExit;
            }
			cds_list_for_each_entry(zone, &mixer->zones.list, list) {
				if (strcasecmp(zone->uid, key)) continue;
				responseJ = MixerInfoOneZone(zone, verbose);
                break;
            }
            break;

        case json_type_array:
            responseJ = json_object_new_array();
            for (int idx = 0; idx < json_object_array_length(zonesJ); idx++) {
                json_object *zoneJ = json_object_array_get_idx(zonesJ, idx);

                valueJ = MixerInfoZones(mixer, zoneJ, verbose);
                if (!valueJ) {
					AFB_API_ERROR(mixer->api, "%s: fail to find zone=%s", __func__, json_object_get_string(zonesJ));
                    goto OnErrorExit;
                }
                json_object_array_add(responseJ, valueJ);
            }
            break;

        default:
			AFB_API_ERROR(mixer->api, "%s: unsupported json type zonesJ=%s", __func__, json_object_get_string(zonesJ));
            goto OnErrorExit;
    }

	AFB_API_NOTICE(mixer->api, "%s: response=%s", __func__, json_object_get_string(responseJ));
    return (responseJ);

OnErrorExit:
    return NULL;
}

STATIC json_object *MixerInfoOnePcm(AlsaSndPcmT *pcm, int verbose) {
    json_object *responseJ;

    if (!verbose) {
        wrap_json_pack(&responseJ, "{ss,ss}", "uid", pcm->uid, "verb", pcm->verb);
    } else {
        json_object *sndcardJ, *alsaJ;
        wrap_json_pack(&sndcardJ, "{ss,si,si,si}"
                ,"cardid", pcm->sndcard->cid.cardid
                ,"name", pcm->sndcard->cid.name
                ,"longname", pcm->sndcard->cid.longname
                ,"index", pcm->sndcard->cid.cardidx
                ,"device", pcm->sndcard->cid.device
                ,"subdev", pcm->sndcard->cid.subdev
                );

        wrap_json_pack(&alsaJ, "{ss,ss,so}"
                , "volume", pcm->volume
                , "mute", pcm->mute
				, "ccount", pcm->nbChannels
                );
        wrap_json_pack(&responseJ, "{ss,ss,so,so}"
                , "uid", pcm->uid
                , "verb", pcm->verb
                , "sndcard", sndcardJ
                , "alsa", alsaJ
                );
    }
    return (responseJ);
}

STATIC json_object *MixerInfoPcms(SoftMixerT *mixer, json_object *pcmsJ, snd_pcm_stream_t direction, int verbose) {
    int error;
    const char * key;
    json_object *valueJ;
    json_object *responseJ = NULL;
	AlsaSndPcmT * pcms = NULL, *pcm;

    switch (direction) {
        case SND_PCM_STREAM_PLAYBACK:
			pcms = &mixer->sinks;
            break;

        case SND_PCM_STREAM_CAPTURE:
			pcms = &mixer->sources;
            break;
        default:
			AFB_API_ERROR(mixer->api, "%s: invalid Direction should be SND_PCM_STREAM_PLAYBACK|SND_PCM_STREAM_capture", __func__);
            goto OnErrorExit;
    }

    switch (json_object_get_type(pcmsJ)) {

        case json_type_null:
        case json_type_boolean:
            // list every existing pcm
            responseJ = json_object_new_array();
			cds_list_for_each_entry(pcm, &pcms->list, list) {
				valueJ = MixerInfoOnePcm(pcm, verbose);
                json_object_array_add(responseJ, valueJ);
            }
            break;

        case json_type_string:
            key = json_object_get_string(pcmsJ);
			cds_list_for_each_entry(pcm, &pcms->list, list) {
				if (strcasecmp(pcm->uid, key))
					continue;
				responseJ = MixerInfoOnePcm(pcm, verbose);
                break;
            }
            break;

        case json_type_object:
            error = wrap_json_unpack(pcmsJ, "{ss}", "uid", &key);
            if (error) {
                AFB_API_ERROR(mixer->api,
                             "%s: missing 'uid' request pcmJ=%s error=%s position=%d",
                             __func__, json_object_get_string(pcmsJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error));
                goto OnErrorExit;
            }
			cds_list_for_each_entry(pcm, &pcms->list, list) {
				if (strcasecmp(pcm->uid, key))
					continue;
				responseJ = MixerInfoOnePcm(pcm, verbose);
                break;
            }
            break;

        case json_type_array:
            responseJ = json_object_new_array();
            for (int idx = 0; idx < json_object_array_length(pcmsJ); idx++) {
                json_object *pcmJ = json_object_array_get_idx(pcmsJ, idx);

                valueJ = MixerInfoPcms(mixer, pcmJ, direction, verbose);
                if (!valueJ) {
                    AFB_API_ERROR(mixer->api, "%s: fail to find %s=%s",
                                 __func__, direction==SND_PCM_STREAM_PLAYBACK?"playback":"capture", json_object_get_string(pcmJ));
                    goto OnErrorExit;
                }
                json_object_array_add(responseJ, valueJ);
            }
            break;

        default:
            AFB_API_ERROR(mixer->api,
                         "%s: unsupported json type pcmsJ=%s",
                         __func__, json_object_get_string(pcmsJ));
            goto OnErrorExit;
    }

    return (responseJ);

OnErrorExit:
    return NULL;
}

STATIC void MixerInfoAction(afb_req_t request, json_object * argsJ) {

    SoftMixerT *mixer = (SoftMixerT*) afb_req_get_vcbdata(request);
    int error, verbose = 0;
    json_object *streamsJ = NULL, *rampsJ = NULL, *zonesJ = NULL, *capturesJ = NULL, *playbacksJ = NULL;

    error = wrap_json_unpack(argsJ, "{s?b,s?o,s?o,s?o,s?o,s?o !}"
            , "verbose", &verbose
            , "streams", &streamsJ
            , "ramps", &rampsJ
            , "captures", &capturesJ
            , "playbacks", &playbacksJ
            , "zones", &zonesJ
            );
    if (error) {
        afb_req_fail_f(request, "invalid-syntax", "list missing 'verbose|streams|ramps|captures|playbacks|zones' argsJ=%s", json_object_get_string(argsJ));
        return;
    }

    json_object *responseJ = json_object_new_object();

    if (streamsJ) {
        json_object *resultJ = MixerInfoStreams(mixer, streamsJ, verbose);
        if (!resultJ) {
            afb_req_fail_f(request, "not-found", "fail to find streams (should be a boolean, a string, or an array of them) argsJ=%s", json_object_get_string(streamsJ));
            return;
        }
        json_object_object_add(responseJ, "streams", resultJ);
    }

    if (rampsJ) {
        json_object *resultJ = MixerInfoRamps(mixer, rampsJ, verbose);
        json_object_object_add(responseJ, "ramps", resultJ);
    }

    if (zonesJ) {
        json_object *resultJ = MixerInfoZones(mixer, zonesJ, verbose);
        json_object_object_add(responseJ, "zones", resultJ);
    }

    if (playbacksJ) {
        json_object *resultJ = MixerInfoPcms(mixer, playbacksJ, SND_PCM_STREAM_PLAYBACK, verbose);
        json_object_object_add(responseJ, "playbacks", resultJ);
    }

    if (capturesJ) {
        json_object *resultJ = MixerInfoPcms(mixer, capturesJ, SND_PCM_STREAM_CAPTURE, verbose);
        json_object_object_add(responseJ, "captures", resultJ);
    }

    afb_req_success(request, responseJ, NULL);
    return;
}

STATIC void MixerInfoVerb(afb_req_t request) {
    json_object *argsJ = afb_req_json(request);
    MixerInfoAction(request, argsJ);
}

STATIC void MixerAttachVerb(afb_req_t request) {
    SoftMixerT *mixer = (SoftMixerT*) afb_req_get_vcbdata(request);
    const char *uid = NULL, *prefix = NULL;
    json_object *playbacksJ = NULL, *capturesJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *rampsJ = NULL, *loopsJ = NULL;
    json_object *argsJ = afb_req_json(request);
    json_object *responseJ = json_object_new_object();
    int error;
	AlsaMixerTransaction *transaction = NULL, *existingTransaction;

	AFB_API_INFO(mixer->api, "%s: %s", __func__, json_object_get_string(argsJ));

    error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?o,s?o,s?o,s?o,s?o,s?o,s?o !}"
            , "uid", &uid
            , "prefix", &prefix
            , "mixerapi", NULL
            , "dependencies", NULL
            , "ramps", &rampsJ
            , "playbacks", &playbacksJ
            , "captures", &capturesJ
            , "loops", &loopsJ
            , "zones", &zonesJ
            , "streams", &streamsJ
            );
    if (error) {
        afb_req_fail_f(request,
                     "invalid-syntax",
					 "%s mixer=%s missing 'uid|ramps|dependencies|playbacks|captures|zones|streams' error=%s args=%s",
					 __func__, mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ));
		goto fail;
	}

    cds_list_for_each_entry(existingTransaction, &mixer->transactionList, transaction_node) {
        if(! strcmp(uid, existingTransaction->uid)) {
            AFB_API_INFO(mixer->api,
                         "'attach' call with json '%s' will be ignored because json 'uid' "
                         "has already been used in a previous transaction",
                         json_object_get_string(argsJ));
            afb_req_success(request, NULL, NULL);
            return;
        }
    }

	transaction = AlsaMixerTransactionNew(mixer, uid);
	if (transaction == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail;
    }

	mixer->transaction = transaction;

	AFB_API_INFO(mixer->api, "%s set PLAYBACKS", __func__);

    if (playbacksJ) {
        error = ApiSinkAttach(mixer, request, uid, playbacksJ);
		if (error) goto fail;

        json_object *resultJ = MixerInfoPcms(mixer, playbacksJ, SND_PCM_STREAM_PLAYBACK, 0);
        json_object_object_add(responseJ, "playbacks", resultJ);
    }

	AFB_API_INFO(mixer->api, "%s set CAPTURES", __func__);

    if (capturesJ) {
        error = ApiSourceAttach(mixer, request, uid, capturesJ);
        if (error) {
            AFB_API_ERROR(mixer->api,"%s: source attach failed", __func__);
			goto fail;
        }

        json_object *resultJ = MixerInfoPcms(mixer, capturesJ, SND_PCM_STREAM_CAPTURE, 0);
        json_object_object_add(responseJ, "captures", resultJ);
    }

    AFB_API_INFO(mixer->api, "%s set ZONES", __func__);

    if (zonesJ) {
        error = ApiZoneAttach(mixer, request, uid, zonesJ);
		if (error)
			goto fail;
        
        json_object *resultJ = MixerInfoZones(mixer, zonesJ, 0);
        json_object_object_add(responseJ, "zone", resultJ);
    }

    // In AVIRT mode, we require both the loops and streams JSON objects to
    // construct the loopbacks, so when the loops are set, but the streams
    // are not, we need to save the loops until the streams are given to us
	if (streamsJ && (loopsJ || LoopsJ)) {
		AFB_API_INFO(mixer->api, "%s set LOOPS/AVIRT", __func__);
		error = ApiLoopAttach(mixer, request, uid, ((loopsJ) ? loopsJ : LoopsJ), streamsJ);
		if (error) {
			goto fail;
		}
		// Now, forget the saved LoopsJ
		if (LoopsJ)
			LoopsJ = NULL;
	}

    AFB_API_INFO(mixer->api, "%s set RAMPS", __func__);

    if (rampsJ) {
        error = ApiRampAttach(mixer, request, uid, rampsJ);
		if (error)
			goto fail;

        json_object *resultJ = MixerInfoRamps(mixer, rampsJ, 0);
        json_object_object_add(responseJ, "ramps", resultJ);
    }

    AFB_API_INFO(mixer->api,"%s set STREAMS", __func__);

    if (streamsJ) {
        error = ApiStreamAttach(mixer, request, uid, prefix, streamsJ);
		if (error)
			goto fail;

        json_object *resultJ = MixerInfoStreams(mixer, streamsJ, 0);
        json_object_object_add(responseJ, "streams", resultJ);
    }

	error = afb_api_add_verb(mixer->api, uid, "Post Attach API", AlsaMixerTransactionVerbCB, transaction, NULL, 0, 0);
	if (error) {
		AFB_API_ERROR(mixer->api, "%s mixer=%s verb=%s fail to register post attach Verb ",
					__func__, mixer->uid, uid);
		goto fail;
	}

    AFB_API_NOTICE(mixer->api, "%s responseJ=%s", __func__, json_object_get_string(responseJ));
    afb_req_success(request, responseJ, NULL);

    AFB_API_INFO(mixer->api,"%s DONE", __func__);
    return;

fail:

	if (mixer->transaction)
		AlsaMixerTransactionDelete(mixer->transaction);

	mixer->transaction = NULL;

	AFB_API_ERROR(mixer->api,"%s FAILED", __func__);
    return;
}


// Every HAL export the same API & Interface Mapping from SndCard to AudioLogic is done through alsaHalSndCardT
STATIC afb_verb_t CtrlApiVerbs[] = {
    /* VERB'S NAME         FUNCTION TO CALL         SHORT DESCRIPTION */
    { .verb = "attach", .callback = MixerAttachVerb, .info = "attach resources to mixer"},
    { .verb = "detach", .callback = MixerDetachVerb, .info = "detach existing mixer streams, zones, ..."},
    { .verb = "info", .callback = MixerInfoVerb, .info = "list existing mixer streams, zones, ..."},
    { .verb = NULL} /* marker for end of the array */
};

STATIC int LoadStaticVerbs(SoftMixerT *mixer, afb_verb_t * verbs) {
    int errcount = 0;

    for (int idx = 0; verbs[idx].verb; idx++) {
        errcount += afb_api_add_verb(mixer->api, CtrlApiVerbs[idx].verb, CtrlApiVerbs[idx].info, CtrlApiVerbs[idx].callback, (void*) mixer, CtrlApiVerbs[idx].auth, 0, 0);
    }

    return errcount;
};

CTLP_CAPI(MixerAttach, source, argsJ, responseJ) {
    SoftMixerT *mixer = source->context;
    json_object *playbackJ = NULL, *captureJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *rampsJ = NULL, *loopsJ = NULL;
    const char* uid = source->uid, *prefix = NULL;

	AlsaMixerTransaction * transaction = NULL;

    int error;

    error = wrap_json_unpack(argsJ, "{s?s, s?o,s?o,s?o,s?o,s?o,s?o !}"
            , "prefix", &rampsJ
            , "ramps", &rampsJ
            , "playbacks", &playbackJ
            , "captures", &captureJ
            , "loops", &loopsJ
            , "zones", &zonesJ
            , "streams", &streamsJ
            );
    if (error) {
		AFB_API_ERROR(mixer->api, "%s: invalid-syntax mixer=%s error=%s args=%s",
				__func__, mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ));
        goto OnErrorExit;
    }

	transaction = AlsaMixerTransactionNew(mixer, uid);
	if (transaction == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
	}

	mixer->transaction = transaction;

    if (playbackJ) {
        error = ApiSinkAttach(mixer, NULL, uid, playbackJ);
        if (error) goto OnErrorExit;
    }

    if (captureJ) {
        error = ApiSourceAttach(mixer, NULL, uid, captureJ);
        if (error) goto OnErrorExit;
    }

    // In AVIRT mode, we require both the loops and streams JSON objects to
    // construct the loopbacks, so when the loops are set, but the streams
    // are not, we need to save the loops until the streams are given to us
    if (loopsJ && streamsJ) {
        error = ApiLoopAttach(mixer, NULL, uid, loopsJ, streamsJ);
        if (error) goto OnErrorExit;
    } else {
      LoopsJ = loopsJ;
    }

    if (zonesJ) {
        error = ApiZoneAttach(mixer, NULL, uid, zonesJ);
        if (error) goto OnErrorExit;
    }

    if (rampsJ) {
        error = ApiRampAttach(mixer, NULL, uid, rampsJ);
        if (error) goto OnErrorExit;
    }

    if (streamsJ) {
        error = ApiStreamAttach(mixer, NULL, uid, prefix, streamsJ);
        if (error) goto OnErrorExit;
    }

    mixer->transaction = NULL;

    // return mixer info data after attach
    return 0;

OnErrorExit:
	if (mixer->transaction)
		AlsaMixerTransactionDelete(mixer->transaction);

	mixer->transaction = NULL;
    return -1;
}

static void MixerDelete(SoftMixerT * mixer) {
	free((char*)mixer->info);
	free((char*)mixer->uid);
	free(mixer);
}

CTLP_CAPI(MixerCreate, source, argsJ, responseJ) {

    SoftMixerT *mixer = calloc(1, sizeof (SoftMixerT));
	if (mixer == NULL) {
		SOFTMIXER_NOMEM(source->api);
		goto fail;
	}
    source->context = mixer;

    int error;
    mixer->max.loops = SMIXER_DEFLT_RAMPS;
    mixer->max.sinks = SMIXER_DEFLT_SINKS;
    mixer->max.sources = SMIXER_DEFLT_SOURCES;
    mixer->max.zones = SMIXER_DEFLT_ZONES;
    mixer->max.streams = SMIXER_DEFLT_STREAMS;
    mixer->max.ramps = SMIXER_DEFLT_RAMPS;

    if (json_object_get_type(argsJ) != json_type_object) {
		AFB_API_ERROR(source->api, "%s: invalid object type= %s", __func__, json_object_get_string(argsJ));
		goto fail;
    }

    error = wrap_json_unpack(argsJ, "{ss,s?s,s?i,s?i,s?i,s?i,s?i,s?i !}"
            , "uid", &mixer->uid
            , "info", &mixer->info
            , "max_loop", &mixer->max.loops
            , "max_sink", &mixer->max.sinks
            , "max_source", &mixer->max.sources
            , "max_zone", &mixer->max.zones
            , "max_stream", &mixer->max.streams
            , "max_ramp", &mixer->max.ramps
            );
    if (error) {
		AFB_API_NOTICE(source->api,
					  "%s missing 'uid|max_loop|max_sink|max_source|max_zone|max_stream|max_ramp' error=%s mixer=%s",
					  __func__, wrap_json_get_error_string(error), json_object_get_string(argsJ));
		goto fail_mixer;
    }

    // make sure string do not get deleted
    mixer->uid = strdup(mixer->uid);
	if (mixer->uid == NULL) {
		SOFTMIXER_NOMEM(source->api);
		goto fail_mixer;
	}
	if (mixer->info) {
		mixer->info = strdup(mixer->info);
		if (mixer->info == NULL) {
			SOFTMIXER_NOMEM(source->api);
			goto fail_uid;
		}
	}

	mixer->nbLoops = 0;
	mixer->nbSinks = 0;
	mixer->nbSources = 0;
	mixer->nbZones = 0;
	mixer->nbStreams = 0;
	mixer->nbZones = 0;

	CDS_INIT_LIST_HEAD(&mixer->loops.list);
	CDS_INIT_LIST_HEAD(&mixer->sinks.list);
	CDS_INIT_LIST_HEAD(&mixer->sources.list);
	CDS_INIT_LIST_HEAD(&mixer->zones.list);
	CDS_INIT_LIST_HEAD(&mixer->streams.list);
	CDS_INIT_LIST_HEAD(&mixer->ramps.list);
	CDS_INIT_LIST_HEAD(&mixer->transactionList);

    mixer->sdLoop = afb_api_get_event_loop(source->api);
    mixer->api = source->api;
    afb_api_set_userdata(source->api, mixer);

    error = LoadStaticVerbs(mixer, CtrlApiVerbs);
	if (error)
		goto fail_info;

	cds_list_add_tail(&mixer->list, &mixerList);

    return 0;

fail_info:
	free((char*)mixer->info);
fail_uid:
	free((char*)mixer->uid);
fail_mixer:
	free(mixer);
fail:
    return -1;
}