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

#ifdef HAVE_AVIRT
#include <avirt/avirt.h>
#endif

#include <string.h>

#define UID_AVIRT_LOOP "AVIRT-Loopback"

#define REGNUMID_0  51
#define REGNUMID_1  57
#define REGNUMID_2  63
#define REGNUMID_3  69
#define REGNUMID_4  75
#define REGNUMID_5  81
#define REGNUMID_6  87
#define REGNUMID_7  93
#define REGNUMID_8  99
#define REGNUMID_9  105
#define REGNUMID_10 111
#define REGNUMID_11 117
#define REGNUMID_12 123
#define REGNUMID_13 129
#define REGNUMID_14 135
#define REGNUMID_15 141

struct RegistryNumidMap {
    int index;
    int numid;
} numidmap[] = {
    { 0,  REGNUMID_0  },
    { 1,  REGNUMID_1  },
    { 2,  REGNUMID_2  },
    { 3,  REGNUMID_3  },
    { 4,  REGNUMID_4  },
    { 5,  REGNUMID_5  },
    { 6,  REGNUMID_6  },
    { 7,  REGNUMID_7  },
    { 8,  REGNUMID_8  },
    { 9,  REGNUMID_9  },
    { 10, REGNUMID_10 },
    { 11, REGNUMID_11 },
    { 12, REGNUMID_12 },
    { 13, REGNUMID_13 },
    { 14, REGNUMID_14 },
    { 15, REGNUMID_15 },
};

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 int CheckOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, AlsaLoopSubdevT *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) {
        AFB_API_ERROR(mixer->api, "%s: Failed", __func__);
        return -1;
    }

    pcmCtl->closeAtDeletion = true;

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

    return 0;
}

STATIC AlsaLoopSubdevT *ProcessOneAvirtSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, json_object *streamJ, int index) {
    char *uid, *zone_uid;
    AlsaSndZoneT *zone;
    AFB_API_INFO(mixer->api, "%s: %d", __func__, index);
    int error;

    uid = alloca(32);

    error = wrap_json_unpack(streamJ, "{ss,s?s,s?s,ss,s?s,s?i,s?b,s?o,s?s !}"
            , "uid", &uid
            , "verb", NULL
            , "info", NULL
            , "zone", &zone_uid
            , "source", NULL
            , "volume", NULL
            , "mute", NULL
            , "params", NULL
            , "ramp", NULL
            );
    if (error) {
        AFB_API_NOTICE(mixer->api,
                       "%s: hal=%s missing 'uid|[info]|zone|source||[volume]|[mute]|[params]' error=%s stream=%s",
                       __func__, uid, wrap_json_get_error_string(error), json_object_get_string(streamJ));
        goto OnErrorExit;
    }

    if (mixer->nbZones != 0) {
        zone = ApiZoneGetByUid(mixer, zone_uid);
        if (!zone)
          goto OnErrorExit;
    } else {
        AFB_API_ERROR(mixer->api, "%s: No zones defined!", __func__);
        goto OnErrorExit;
    }
#ifdef HAVE_AVIRT
    error = snd_avirt_stream_new(uid, zone->ccount,
                                 SND_PCM_STREAM_PLAYBACK, "ap_loopback", false);
    if (error < 0) {
        AFB_API_ERROR(mixer->api,
        "%s: mixer=%s stream=%s could not create AVIRT stream [errno=%d]",
        __func__, mixer->uid, uid, error);
        return NULL;
    }
#endif
    AlsaLoopSubdevT *subdev = calloc(1, sizeof (AlsaPcmCtlT));
    subdev->uid = NULL;
    subdev->index = index;
    subdev->numid = numidmap[index].index;

    if (CheckOneSubdev(mixer, loop, subdev) < 0)
      return NULL;

    return subdev;

OnErrorExit:
    AFB_API_ERROR(mixer->api, "%s fail", __func__);
    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;
		}
	}

    if (CheckOneSubdev(mixer, loop, subdev) < 0)
      goto fail_subdev_uid;

	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 *streamsJ) {

    AlsaLoopSubdevT * subdev;
    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,s?o,s?o !}"
            , "uid", &loop->uid
            , "path", &loop->sndcard->cid.devpath
            , "cardid", &loop->sndcard->cid.cardid
            , "devices", &devicesJ
            , "subdevs", &subdevsJ
            );

    if (loop->uid) {
        if (!strcmp(loop->uid, UID_AVIRT_LOOP))
            loop->avirt = true;
        else
            loop->avirt = false;
    }

    if (error || !loop->uid || (!loop->avirt && !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;
    }

    if (!loop->avirt) {
        // 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;
            }
        }

        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;
        }
    } else { // loop->avirt == true
#ifdef HAVE_AVIRT
        AFB_API_NOTICE(mixer->api, "nbStreams: %d max.streams: %d", mixer->nbStreams, mixer->max.streams);

        if (mixer->nbStreams >= mixer->max.streams)
            goto fail_snd_card;

        // Create AVIRT streams
        switch (json_object_get_type(streamsJ)) {
            case json_type_object:
                subdev = ProcessOneAvirtSubdev(mixer, loop, streamsJ, 0);
                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(streamsJ);
                if (nbSubDevs >= mixer->max.streams)
                    goto fail_loop_subdev;
                for (int idx = 0; idx < nbSubDevs; idx++) {
                    json_object *streamJ = json_object_array_get_idx(streamsJ, idx);
                    subdev = ProcessOneAvirtSubdev(mixer, loop, streamJ, idx);
                    if (!subdev) {
                        goto fail_loop_subdev;
                    }
                    loop->nbSubdevs++;
                    cds_list_add_tail(&subdev->list, &loop->subdevs.list);
                }
                break;
            }
            default:
                goto fail_snd_card;
        }

        snd_avirt_card_configure();

        // 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;
        }
#endif
    }

	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 *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:
	/* in the case of avirt, loop->sndcard->ctl may be NULL */
	if (loop->sndcard->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);

#ifdef HAVE_AVIRT
	if (loop->avirt)
		snd_avirt_card_unconfigure();
#endif

	free(loop);
	AFB_API_DEBUG(mixer->api, "DONE !");

}

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

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

	mixer->nbLoops++;
	cds_list_add(&newLoop->list, &mixer->loops.list);
	/* We need the loops to be at the end of the list, so that they are destroyed last */
	AlsaMixerTransactionObjectAddTail(mixer->transaction, newLoop, loopDestroy);

	loopsDisplay(mixer);


fail:
	return newLoop;
}

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

	AlsaSndLoopT * newLoop = NULL;

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

	if (mixer->nbLoops >= mixer->max.loops) {
		AFB_API_ERROR(mixer->api, "%s: too many loops !", __func__);
		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, streamsJ);
			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;
            }

            for (int idx = 0; idx < count; idx++) {
                json_object *loopJ = json_object_array_get_idx(argsJ, idx);
				newLoop = loopCreate(mixer, uid, loopJ, streamsJ);
				if (newLoop == NULL) {
					goto fail;
                }
            }
            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:
    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, "--------------");
}