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

PUBLIC AlsaLoopSubdevT *ApiLoopFindSubdev(SoftMixerT *mixer, const char *streamUid, const char *targetUid, AlsaSndLoopT **loop) {

    // Either allocate a free loop subdev or search for a specific targetUid when specified
	AlsaSndLoopT * _loop;
	if (targetUid) {
		cds_list_for_each_entry(_loop, &mixer->loops.list, list) {
			AlsaLoopSubdevT * subdev;
			cds_list_for_each_entry(subdev, &_loop->subdevs.list, list) {
				if (subdev->uid && !strcmp(subdev->uid, targetUid)) {
					*loop = _loop;
					return subdev;
				}
			}
		}
	} else {
		cds_list_for_each_entry(_loop, &mixer->loops.list, list) {
			AlsaLoopSubdevT * subdev;
			cds_list_for_each_entry(subdev, &_loop->subdevs.list, list) {
				if (!subdev->uid) {
					subdev->uid = strdup(streamUid);
					if (!subdev->uid) {
						SOFTMIXER_NOMEM(mixer->api);
						goto fail;
					}
					*loop = _loop;
					return subdev;
				}
			}
		}
	}
fail:
	return NULL;
}


STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, json_object *subdevJ) {

	AFB_API_DEBUG(mixer->api, "%s : %s", __func__, json_object_get_string(subdevJ));

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

    int error = wrap_json_unpack(subdevJ, "{s?s, si,si !}"
            , "uid", &subdev->uid
            , "subdev", &subdev->index
            , "numid", &subdev->numid
            );
    if (error) {
		AFB_API_ERROR(mixer->api,
					 "%s: loop=%s missing (uid|subdev|numid) error=%s json=%s",
					 __func__, loop->uid, wrap_json_get_error_string(error), json_object_get_string(subdevJ));
		goto fail_subdev;
    }

    // subdev with no UID are dynamically attached
	if (subdev->uid) {
		subdev->uid = strdup(subdev->uid);
		if (subdev->uid == NULL) {
			SOFTMIXER_NOMEM(mixer->api);
			goto fail_subdev;
		}
	}

    // create loop subdev entry point with cardidx+device+subdev in order to open subdev and not sndcard
    AlsaDevInfoT loopSubdev;
    loopSubdev.devpath=NULL;
    loopSubdev.cardid=NULL;
    loopSubdev.pcmplug_params = NULL;
    loopSubdev.cardidx = loop->sndcard->cid.cardidx;
    loopSubdev.device = loop->capture;
    loopSubdev.subdev = subdev->index;

    // assert we may open this loopback subdev in capture mode
	AlsaPcmCtlT *pcmCtl = AlsaByPathOpenPcmCtl(mixer, &loopSubdev, SND_PCM_STREAM_CAPTURE);
	if (!pcmCtl) {
		goto fail_subdev_uid;
	}

	pcmCtl->closeAtDeletion = true;

    // free PCM as we only open loop to assert it's a valid capture device
	AlsaMixerTransactionObjectForget(mixer->transaction, pcmCtl);
	AlsaPcmCtlDelete(mixer, pcmCtl);

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

    return subdev;

fail_subdev_uid:
	free((char*)subdev->uid);
fail_subdev:
	free(subdev);
fail:
    return NULL;
}

static void freeSubdev(SoftMixerT* mixer, AlsaLoopSubdevT * subdev) {
	free(subdev->uid);
	free(subdev);
}


STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_object *argsJ) {

    json_object *subdevsJ = NULL, *devicesJ = NULL;
    int error;

	AFB_API_NOTICE(mixer->api, "%s, uid %s", __func__, uid);

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

	CDS_INIT_LIST_HEAD(&loop->list);
	CDS_INIT_LIST_HEAD(&loop->subdevs.list);

	AlsaSndCtlT * sndctl = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT));
	if (sndctl == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail_loop;
	}

	CDS_INIT_LIST_HEAD(&sndctl->registryList);

	loop->sndcard = sndctl;

    error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,so,so !}"
            , "uid", &loop->uid
            , "path", &loop->sndcard->cid.devpath
            , "cardid", &loop->sndcard->cid.cardid
            , "devices", &devicesJ
            , "subdevs", &subdevsJ
            );
    if (error || !loop->uid || !subdevsJ || (!loop->sndcard->cid.devpath && !loop->sndcard->cid.cardid)) {
        AFB_API_NOTICE(mixer->api, "%s mixer=%s hal=%s missing 'uid|path|cardid|devices|subdevs' error=%s args=%s",
        		__func__, mixer->uid, uid, wrap_json_get_error_string(error),json_object_get_string(argsJ));
		goto fail_snd_card;
    }

    // try to open sound card control interface
    loop->sndcard->ctl = AlsaByPathOpenCtl(mixer, loop->uid, loop->sndcard);
    if (!loop->sndcard->ctl) {
        AFB_API_ERROR(mixer->api, "%s mixer=%s hal=%s Fail open sndcard loop=%s devpath=%s cardid=%s (please check 'modprobe snd_aloop')",
        		__func__, mixer->uid, uid, loop->uid, loop->sndcard->cid.devpath, loop->sndcard->cid.cardid);
		goto fail_snd_card;
    }

	// Default devices is playback=0 capture=1
    if (!devicesJ) {
        loop->playback = 0;
        loop->capture = 1;
    } else {
        error = wrap_json_unpack(devicesJ, "{si,si !}", "capture", &loop->capture, "playback", &loop->playback);
        if (error) {
            AFB_API_NOTICE(mixer->api, "%s mixer=%s hal=%s Loop=%s missing 'capture|playback' error=%s devices=%s",
            		__func__, mixer->uid, uid, loop->uid, wrap_json_get_error_string(error),json_object_get_string(devicesJ));
			goto fail_snd_card_ctl;
        }
    }

	AlsaLoopSubdevT * subdev;

    switch (json_object_get_type(subdevsJ)) {
        case json_type_object:
			subdev = ProcessOneSubdev(mixer, loop, subdevsJ);
			if (!subdev)
				goto fail_loop_subdev;
			loop->nbSubdevs++;
			cds_list_add_tail(&subdev->list, &loop->subdevs.list);
            break;
		case json_type_array: {
			int nbSubDevs = (int) json_object_array_length(subdevsJ);
			for (int idx = 0; idx < nbSubDevs; idx++) {
                json_object *subdevJ = json_object_array_get_idx(subdevsJ, idx);
				subdev = ProcessOneSubdev(mixer, loop, subdevJ);
				if (!subdev) {
					goto fail_loop_subdev;
				}
				loop->nbSubdevs++;
				cds_list_add_tail(&subdev->list, &loop->subdevs.list);
            }
            break;
		}
        default:
            AFB_API_ERROR(mixer->api, "%s mixer=%s hal=%s Loop=%s invalid subdevs= %s",
            		__func__, mixer->uid, uid, loop->uid, json_object_get_string(subdevsJ));
			goto fail_snd_card_ctl;
    }

	AFB_API_NOTICE(mixer->api, "%s, uid %s DONE", __func__, uid);

    return loop;

fail_loop_subdev: {
	AFB_API_DEBUG(mixer->api, "%s cleanup", __func__);
	AlsaLoopSubdevT * subdev, *tmp;
	cds_list_for_each_entry_safe(subdev, tmp, &loop->subdevs.list, list) {
		cds_list_del(&subdev->list);
		freeSubdev(mixer, subdev);
	}
}

fail_snd_card_ctl:
	snd_ctl_close(loop->sndcard->ctl);
fail_snd_card:
	free(loop->sndcard);
fail_loop:
	free(loop);
fail:
    return NULL;
}

static void loopDestroy(SoftMixerT * mixer, void* arg) {
	AlsaSndLoopT * loop = (AlsaSndLoopT*) arg;
	AFB_API_DEBUG(mixer->api, "%s... %s", __func__, loop->uid);

	AlsaLoopSubdevT * subdev, *tmp;
	cds_list_for_each_entry_safe(subdev, tmp, &loop->subdevs.list, list) {
		cds_list_del(&subdev->list);
		freeSubdev(mixer, subdev);
	}

	if (loop->sndcard->ctl)
		snd_ctl_close(loop->sndcard->ctl);

	mixer->nbLoops--;
	cds_list_del(&loop->list);
	free(loop);
	AFB_API_DEBUG(mixer->api, "DONE !");

}

static AlsaSndLoopT * loopCreate(SoftMixerT *mixer, const char *uid, json_object *argsJ) {

	AlsaSndLoopT * newLoop = AttachOneLoop(mixer, uid, argsJ);
	if (!newLoop) {
		goto fail;
	}

	mixer->nbLoops++;
	cds_list_add(&newLoop->list, &mixer->loops.list);
	AlsaMixerTransactionObjectAdd(mixer->transaction, newLoop, loopDestroy);

	loopsDisplay(mixer);


fail:
	return newLoop;
}

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

	AlsaSndLoopT * newLoop = NULL;

	if (mixer->nbLoops >= mixer->max.loops) {
        AFB_IfReqFailF(mixer, request, "too-small", "mixer=%s hal=%s max loop=%d", mixer->uid, uid, mixer->max.loops);
		goto fail;
    }

    switch (json_object_get_type(argsJ)) {
            size_t count;

        case json_type_object:
			newLoop = loopCreate(mixer, uid, argsJ);
			if (!newLoop) {
				goto fail;
            }
            break;

        case json_type_array:
            count = json_object_array_length(argsJ);
			if (count > (mixer->max.loops - mixer->nbLoops)) {
                AFB_IfReqFailF(mixer, request, "too-small", "mixer=%s hal=%s max loop=%d", mixer->uid, uid, mixer->max.loops);
				goto fail_loop;
            }

            for (int idx = 0; idx < count; idx++) {
                json_object *loopJ = json_object_array_get_idx(argsJ, idx);
				newLoop = loopCreate(mixer, uid, loopJ);
				if (newLoop == NULL) {
					goto fail_loop;
                }
            }
            break;
        default:
            AFB_IfReqFailF(mixer, request, "bad-loop", "mixer=%s hal=%s loops invalid argsJ= %s", mixer->uid, uid, json_object_get_string(argsJ));
			goto fail;
    }

    return 0;

fail_loop:
fail:
    return -1;
}


static void subdevDisplay(SoftMixerT  *mixer, AlsaLoopSubdevT * subdev) {
	AFB_API_INFO(mixer->api, "\t\tsubdev id %d, uid %s", subdev->numid, subdev->uid);
}

PUBLIC void loopsDisplay(SoftMixerT *mixer) {
	AlsaSndLoopT * loop;
	AFB_API_INFO(mixer->api, "Mixer %s LOOPS:", mixer->uid);
	cds_list_for_each_entry(loop, &mixer->loops.list, list) {
		AFB_API_INFO(mixer->api, "\tLOOP %s", loop->uid);
		AlsaLoopSubdevT * subdev;
		cds_list_for_each_entry(subdev, &loop->subdevs.list, list) {
			subdevDisplay(mixer, subdev);
		}
	}
	AFB_API_INFO(mixer->api, "--------------");
}