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

PUBLIC AlsaPcmHwInfoT *ApiSinkGetParamsByZone(SoftMixerT *mixer, const char *target) {

    // try to attach a zone as stream playback sink
    AlsaSndZoneT *zone = ApiZoneGetByUid(mixer, target);

	if (!zone) {
		AFB_API_ERROR(mixer->api, "%s: Unable to find zone %s", __func__, target);
		goto fail;
	}
	if (zone->nbSinks == 0) {
		AFB_API_ERROR(mixer->api, "%s: Zone %s has no sinks", __func__, target);
		goto fail;
	}

	// use 1st channel to find attached sound card.
	AlsaPcmChannelT * pcmChannel = cds_list_first_entry(&zone->sinks.list, AlsaPcmChannelT , list);
	const char *channel = pcmChannel->uid;

	AFB_API_DEBUG(mixer->api, "%s: first channel uid is %s, zone has %d sinks", __func__, channel, zone->nbSinks);

	AlsaSndPcmT * sink = NULL;

	cds_list_for_each_entry(sink, &mixer->sinks.list, list) {
		AlsaPcmChannelT * pcmChannel;
		cds_list_for_each_entry(pcmChannel, &sink->channels.list, list) {
			if (pcmChannel->uid && !strcasecmp(channel, pcmChannel->uid)) {
				AFB_API_DEBUG(mixer->api, "%s: found %s, return", __func__, pcmChannel->uid);
				return sink->sndcard->params;
			}
		}
    }

fail:
    return NULL;
}

PUBLIC AlsaSndPcmT  *ApiSinkGetByUid(SoftMixerT *mixer, const char *target) {
    // if no attached zone found, then try direct sink attachment
	AlsaSndPcmT * sink;
	cds_list_for_each_entry(sink, &mixer->sinks.list, list) {
		if (sink->uid && !strcasecmp(sink->uid, target))
			return sink;
	}
    
    return NULL;
}

static void sinkDestroy(SoftMixerT* mixer, void * arg) {
	AlsaSndPcmT * sink = (AlsaSndPcmT*) arg;
	AFB_API_DEBUG(mixer->api, "%s... %s", __func__, sink->uid);

	cds_list_del(&sink->list);
	mixer->nbSinks--;
	ApiPcmDelete(mixer, sink);
}

AlsaSndPcmT * sinkCreate(SoftMixerT * mixer, afb_req_t request, const char * uid, json_object * argsJ) {
	AlsaPcmCtlT* dmixConfig = NULL;
	char *dmixUid = NULL;
	AlsaSndPcmT * newSink = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_PLAYBACK, argsJ);
	if (!newSink) {
		goto fail;
	}
	/* DMIX only supports REAL hardware */
	if (!newSink->isPcmPlug)  {
		// move from hardware to DMIX attach to sndcard
		if (asprintf(&dmixUid, "dmix-%s", newSink->uid) == -1) {
			SOFTMIXER_NOMEM(mixer->api);
			goto fail_pcm;
		}
		dmixConfig = AlsaCreateDmix(mixer, dmixUid, newSink, 0);
		if (!dmixConfig) {
			afb_req_fail_f(request, "internal-error", "mixer=%s sink=%s fail to create DMIX config", mixer->uid, newSink->uid);
			goto fail_dmix;
		}
    }

	mixer->nbSinks++;
	cds_list_add_tail(&newSink->list, &mixer->sinks.list);
	AlsaMixerTransactionObjectAdd(mixer->transaction, newSink, sinkDestroy);

	return newSink;
fail:
fail_pcm:
	// TODO delete PCM
fail_dmix:
	free(dmixUid);
	return NULL;
}


PUBLIC int ApiSinkAttach(SoftMixerT *mixer, afb_req_t request, const char * uid, json_object * argsJ) {

	if (mixer->nbSinks >= mixer->max.sinks) {
        afb_req_fail_f(request, "too-small", "mixer=%s max sink=%d", mixer->uid, mixer->max.sinks);
		goto fail;
    }

    switch (json_object_get_type(argsJ)) {
		long count;

		case json_type_object: {
			AlsaSndPcmT * newSink = sinkCreate(mixer, request, uid, argsJ);
			if (!newSink) {
                afb_req_fail_f(request, "bad-pcm", "mixer=%s invalid sink= %s", mixer->uid, json_object_get_string(argsJ));
				goto fail;
            }
            break;
		}
		case json_type_array: {
            count = json_object_array_length(argsJ);
			if (count > (mixer->max.sinks - mixer->nbSinks)) {
                afb_req_fail_f(request, "too-small", "mixer=%s max sink=%d", mixer->uid, mixer->max.sinks);
				goto fail;
            }

            for (int idx = 0; idx < count; idx++) {
                json_object *sinkJ = json_object_array_get_idx(argsJ, idx);
				AlsaSndPcmT * newSink = sinkCreate(mixer, request, uid, sinkJ);
				if (!newSink) {
                    afb_req_fail_f(request, "bad-pcm", "mixer=%s invalid sink= %s", mixer->uid, json_object_get_string(sinkJ));
					goto fail;
                }
            }
            break;
		}
        default:
            afb_req_fail_f(request, "invalid-syntax", "mixer=%s sinks invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ));
			goto fail;
    }

    return 0;

fail:
    return -1;
}