/*
 * AlsaLibMapping -- provide low level interface with ALSA lib (extracted from alsa-json-gateway code)
 * Copyright (C) 2015,2016,2017, 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 <sys/inotify.h>

#include <urcu/list.h>

#include <afb-timer.h>

#include <wrap-json.h>

#include "Alsa-ApiHat.h"

// Defines for inotify events
#define INOTIFY_EVENT_SIZE      (sizeof(struct inotify_event))
#define INOTIFY_EVENT_BUF_LEN   (1024 * (INOTIFY_EVENT_SIZE + 16))

#define CARD_DIR_TO_WATCH       "/dev/snd/by-path"

#define PCM_DEV_FILE_STRING     "/dev/snd/pcmC%iD%i%c"
#define PCM_EVENT_NAME          "pcm-%s-%i,%i,%i-monitoring"

#ifndef MAX_SND_HAL
#define MAX_SND_HAL 10
#endif

typedef enum {
    UNKNOWN_EVENT = 0,
    CARD_CONTROL_CHANGE_EVENTS = 1,
    CARD_MONITORING_EVENTS = 2,
    PCM_AVAILABILITY_EVENTS = 3
} EventTypeT;

typedef struct {
    unsigned int card;
    unsigned int device;
    unsigned int subdevice;
    snd_pcm_stream_t stream;
    int available;

    char *pcmFileToMonitor;
    int fd;
    int wd;

    sd_event_source *src;
    afb_event_t afbevt;

    struct cds_list_head node;
} pcmEvtHandleT;

typedef struct {
    int fd;
    int wd;
    sd_event_source *src;
    afb_event_t afbevt;
} sndCardEvtHandleT;

// generic sndctrl event handle hook to event callback when pooling
typedef struct {
    struct pollfd pfds;
    sd_event_source *src;
    snd_ctl_t *ctlDev;
    afb_event_t afbevt;
} ctrlEvtHandleT;

typedef struct {
    int available;
    ctrlEvtHandleT *evtHandle;
} sndHandleT;

typedef struct {
    sndHandleT sndHandles[MAX_SND_CARD];
    sndCardEvtHandleT *cardMonitoring;
    struct cds_list_head pcmMonitoringHead;
    afb_api_t apiHandle;
} sndCardsT;

typedef struct {
    char *createdFileName;
    sndCardsT *sndCards;
} sndCardCardFileEventHandleT;

typedef struct {
    int  cardid;
    char *devid;
    char *apiprefix;
    char *drivername;
    char *shortname;
    char *longname;
} cardRegistryT;

cardRegistryT *cardRegistry[MAX_SND_HAL + 1];

STATIC int getHalIdxFromCardid (int cardid) {
    
    for (int idx = 0; idx < MAX_SND_HAL; idx++) {
        if (!cardRegistry[idx]) return -1;
        if (cardRegistry[idx]->cardid == cardid) return idx;
    }
    
    return -1;
}

STATIC EventTypeT alsaGetDevIdFromString(char *eventTypeString)
{
    if(! eventTypeString)
        return UNKNOWN_EVENT;

    if(! strcasecmp(eventTypeString, "controls"))
        return CARD_CONTROL_CHANGE_EVENTS;

    if(! strcasecmp(eventTypeString, "cards"))
        return CARD_MONITORING_EVENTS;

    if(! strcasecmp(eventTypeString, "pcm"))
        return PCM_AVAILABILITY_EVENTS;

    return UNKNOWN_EVENT;
}

STATIC EventTypeT alsaGetEventTypeFromQuery(json_object *queryJ)
{
    char *eventTypeString = NULL;

    if(wrap_json_unpack(queryJ, "{s:s}", "event", &eventTypeString))
        return UNKNOWN_EVENT;

    if(! eventTypeString)
        return UNKNOWN_EVENT;

    return alsaGetDevIdFromString(eventTypeString);
}

PUBLIC char *alsaGetDevIdFromQuery(json_object *queryJ)
{
    char *devId = NULL;

    if(wrap_json_unpack(queryJ, "{s:s}", "devid", &devId))
        return NULL;

    return devId;
}

PUBLIC queryModeE alsaGetModeFromQuery(json_object *queryJ)
{
    queryModeE mode = QUERY_QUIET;

    if(wrap_json_unpack(queryJ, "{s:i}", "mode", &mode))
        return QUERY_QUIET;

   return mode;
}

STATIC void freePcmHandle(pcmEvtHandleT *pcmEventHandle)
{
    if(! pcmEventHandle)
        return;

    if(pcmEventHandle->node.prev && pcmEventHandle->node.next)
        cds_list_del(&pcmEventHandle->node);

    if(pcmEventHandle->src)
        sd_event_source_unref(pcmEventHandle->src);

    if(afb_event_is_valid(pcmEventHandle->afbevt))
        afb_event_unref(pcmEventHandle->afbevt);

    if(pcmEventHandle->fd != -1 && pcmEventHandle->wd != -1)
        inotify_rm_watch(pcmEventHandle->fd, pcmEventHandle->wd);

    if(pcmEventHandle->fd != -1)
        close(pcmEventHandle->fd);

    free(pcmEventHandle->pcmFileToMonitor);

    free(pcmEventHandle);
}

STATIC void freeSndCardHandle(sndCardEvtHandleT **cardMonitoringHandle)
{
    sndCardEvtHandleT *cardMonitoringHandleToFree;

    if(! cardMonitoringHandle || ! *cardMonitoringHandle)
        return;

    cardMonitoringHandleToFree = *cardMonitoringHandle;

    if(cardMonitoringHandleToFree->src)
        sd_event_source_unref(cardMonitoringHandleToFree->src);

    if(afb_event_is_valid(cardMonitoringHandleToFree->afbevt))
        afb_event_unref(cardMonitoringHandleToFree->afbevt);

    if(cardMonitoringHandleToFree->fd != -1 && cardMonitoringHandleToFree->wd != -1)
        inotify_rm_watch(cardMonitoringHandleToFree->fd, cardMonitoringHandleToFree->wd);

    if(cardMonitoringHandleToFree->fd != -1)
        close(cardMonitoringHandleToFree->fd);

    free(cardMonitoringHandleToFree);

    *cardMonitoringHandle = NULL;
}

STATIC void freeCardAlsaControlHandle(ctrlEvtHandleT **cardControlHandle)
{
    ctrlEvtHandleT *cardControlHandleToFree;

    if(! cardControlHandle || ! *cardControlHandle)
        return;

    cardControlHandleToFree = *cardControlHandle;

    if(cardControlHandleToFree->src)
        sd_event_source_unref(cardControlHandleToFree->src);

    if(afb_event_is_valid(cardControlHandleToFree->afbevt))
        afb_event_unref(cardControlHandleToFree->afbevt);

    if(cardControlHandleToFree->ctlDev)
        snd_ctl_close(cardControlHandleToFree->ctlDev);

    free(cardControlHandleToFree);

    *cardControlHandle = NULL;
}

STATIC int updatePcmAvailabilityAndFireEvent(pcmEvtHandleT *pcmEventHandle)
{
    int err, pcmStatus, subscriberCount = -1;

    char *pcmName;

    json_object *eventJ = NULL;

    pcmStatus = alsaIsPcmAvailableUsingId(pcmEventHandle->card,
                                          pcmEventHandle->device,
                                          pcmEventHandle->subdevice,
                                          pcmEventHandle->stream);
    if(pcmEventHandle->available == pcmStatus)
        return 0;

    pcmEventHandle->available = pcmStatus;

    err = asprintf(&pcmName,
                    "hw:%i,%i,%i",
                    pcmEventHandle->card,
                    pcmEventHandle->device,
                    pcmEventHandle->subdevice);
    if(err <= 0) {
        AFB_ERROR("Did not succeed to generate pcm name string (card : %i, device : %i, subdevice : %i)",
                  pcmEventHandle->card,
                  pcmEventHandle->device,
                  pcmEventHandle->subdevice);
        freePcmHandle(pcmEventHandle);
        return -1;
    }

    err = wrap_json_pack(&eventJ,
                         "{s:s, s:i, s:i}",
                         "name", pcmName,
                         "stream", pcmEventHandle->stream,
                         "available", pcmEventHandle->available);
    if(err) {
        AFB_ERROR("Did not succeed to generate pcm event json info (name : %s, stream : %s, available : %i)",
                  pcmName,
                  (pcmEventHandle->stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
                  pcmEventHandle->available);
        free(pcmName);
        freePcmHandle(pcmEventHandle);
        return -2;
    }

    subscriberCount = afb_event_push(pcmEventHandle->afbevt, eventJ);

    if(! subscriberCount) {
        AFB_WARNING("Nobody listening for pcm %s hw:%i,%i,%i availability events, stop monitoring from now on",
                    (pcmEventHandle->stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
                    pcmEventHandle->card,
                    pcmEventHandle->device,
                    pcmEventHandle->subdevice);
        freePcmHandle(pcmEventHandle);
        return -3;
    }

    return 0;
}

STATIC void updateSelectedAlsaCardsAvailabilityAndFireEvents(sndCardsT *sndCards, int first, int last)
{
    int idx, available, subscriberCount = -1;
    char *cardId;

    json_object *generatedEventJ = NULL;

    if(first < 0 || first >= MAX_SND_CARD)
        return;

    if(last < 0 || last >= MAX_SND_CARD)
        return;

    if(last < first)
        return;

    for(idx = first; idx < (last + 1); idx++) {
        available = snd_card_load(idx);
        if(available && ! sndCards->sndHandles[idx].available) {
           sndCards->sndHandles[idx].available = 1;

            if(sndCards->cardMonitoring &&
               ! wrap_json_pack(&generatedEventJ, "{s:o}", "cardAdded", getCardInfo(idx)))
                subscriberCount = afb_event_push(sndCards->cardMonitoring->afbevt, generatedEventJ);
        }
        else if(! available && sndCards->sndHandles[idx].available) {
            sndCards->sndHandles[idx].available = 0;

            if(sndCards->cardMonitoring &&
               (asprintf(&cardId, "hw:%i", idx) > 0) &&
               ! wrap_json_pack(&generatedEventJ, "{s:s}", "cardRemoved", cardId))
                subscriberCount = afb_event_push(sndCards->cardMonitoring->afbevt, generatedEventJ);

            if(sndCards->sndHandles[idx].evtHandle) {
                sd_event_source_unref(sndCards->sndHandles[idx].evtHandle->src);
                afb_event_unref(sndCards->sndHandles[idx].evtHandle->afbevt);
                snd_ctl_close(sndCards->sndHandles[idx].evtHandle->ctlDev);
                free(sndCards->sndHandles[idx].evtHandle);
                sndCards->sndHandles[idx].evtHandle = NULL;
            }
        }
    }

    if(! subscriberCount) {
        AFB_WARNING("Nobody listening for 'card-monitoring' events, stop monitoring from now on");
        freeSndCardHandle(&sndCards->cardMonitoring);
    }
}

STATIC void updateAllAlsaCardsAvailabilityAndFireEvents(sndCardsT *sndCards)
{
    updateSelectedAlsaCardsAvailabilityAndFireEvents(sndCards, 0, (MAX_SND_CARD - 1));
}

// This routine is called every few microseconds to verify when the newly created sound card file will be openable (using timer event firing)
STATIC int sndCardTimerForCreatedFileCB(TimerHandleT *context)
{
    int createdFileCardNb;

    sndCardCardFileEventHandleT *createdFileHandle = (sndCardCardFileEventHandleT *) context->context;

    createdFileCardNb = getCardNbFromCardPath(createdFileHandle->createdFileName);
    if(createdFileCardNb < 0) {
        AFB_ERROR("Error %i happened when tried to get created card name file '%s' card nb",
                  createdFileCardNb,
                  createdFileHandle->createdFileName);
        return -1;
    }

    updateSelectedAlsaCardsAvailabilityAndFireEvents(createdFileHandle->sndCards, createdFileCardNb, createdFileCardNb);

    free(createdFileHandle->createdFileName);
    free(createdFileHandle);

    context->count = 1;

    return 1;
}

STATIC int pcmEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData)
{
    int err;
    ssize_t length, i = 0;
    char buffer[INOTIFY_EVENT_BUF_LEN];

    struct inotify_event *event;

    pcmEvtHandleT *pcmEventHandle = (pcmEvtHandleT *) userData;

    if(revents & EPOLLERR) {
        AFB_ERROR("An error has been send by event loop polling, prevent new errors to be fired by deleting event src");
        freePcmHandle(pcmEventHandle);
        return -1;
    }

    if(revents & EPOLLIN) {
        length = read(fd, buffer, INOTIFY_EVENT_BUF_LEN);
        if(length < 0) {
            AFB_ERROR("Nothing read using inotify");
            freePcmHandle(pcmEventHandle);
            return -2;
        }

        while(i < length) {
            event = (struct inotify_event *) &buffer[i];

            i += INOTIFY_EVENT_SIZE + event->len;

            if((event->mask & IN_DELETE) || (event->mask & IN_DELETE_SELF)) {
                AFB_WARNING("Monitored file has been deleted, delete monitoring");
                freePcmHandle(pcmEventHandle);
                return -3;
            }

            if((event->mask & IN_OPEN) || (event->mask & IN_CLOSE)) {
                err = updatePcmAvailabilityAndFireEvent(pcmEventHandle);
                if(err) {
                    AFB_ERROR("Error %i happened when tried to refresh %s pcm hw:%i,%i,%i availability",
                              err,
                              (pcmEventHandle->stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
                              pcmEventHandle->card,
                              pcmEventHandle->device,
                              pcmEventHandle->subdevice);
                    return -4;
                }
            }
        }
    }

    return 0;
}

// This routine is called when sound cards are added/removed (when inotify '/dev/snd/by-path' events are fired)
STATIC int sndCardEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData)
{
    ssize_t length, i = 0;
    char buffer[INOTIFY_EVENT_BUF_LEN];
    char *createdFile;

    struct inotify_event *event;

    sndCardCardFileEventHandleT *createdFileHandle;
    TimerHandleT *createdFileTimerHandle;

    sndCardsT *sndCards = (sndCardsT *) userData;

    if(revents & EPOLLERR) {
        AFB_ERROR("An error has been send by event loop polling, prevent new errors to be fired by deleting event src");
        freeSndCardHandle(&sndCards->cardMonitoring);
        return -1;
    }

    if(revents & EPOLLIN) {
        length = read(fd, buffer, INOTIFY_EVENT_BUF_LEN);
        if(length < 0) {
            AFB_ERROR("Nothing read using inotify");
            freeSndCardHandle(&sndCards->cardMonitoring);
            return -2;
        }

        while(i < length) {
            event = (struct inotify_event *) &buffer[i];

            i += INOTIFY_EVENT_SIZE + event->len;

            if(! event->len)
                continue;
            
            if((event->mask & IN_CREATE) &&
               ! (event->mask & IN_ISDIR) &&
                (asprintf(&createdFile, "%s/%s", CARD_DIR_TO_WATCH, event->name) > 0)) {
                createdFileHandle = calloc(1, sizeof(sndCardCardFileEventHandleT));
                createdFileHandle->createdFileName = strdup(createdFile);
                createdFileHandle->sndCards = sndCards;

                createdFileTimerHandle = calloc(1, sizeof(TimerHandleT));

                createdFileTimerHandle->uid = "sound card file creation delay";
                createdFileTimerHandle->count = 10;
                createdFileTimerHandle->delay = 2;

                TimerEvtStart(sndCards->apiHandle, createdFileTimerHandle, sndCardTimerForCreatedFileCB, (void *) createdFileHandle);
            }

            if((event->mask & IN_DELETE) && ! (event->mask & IN_ISDIR))
                updateAllAlsaCardsAvailabilityAndFireEvents(sndCards);
        }
    }

    return 0;
}

// This routine is called when ALSA control events are fired
STATIC int sndCtlEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData)
{
    int err, ctlNumid, subscriberCount = -1;
    unsigned int mask;

    snd_ctl_elem_id_t *elemId;
    snd_ctl_event_t *eventId;

    sndHandleT *sndHandle = (sndHandleT *) userData;
    ctlRequestT ctlRequest;

    json_object *ctlEventJ, *ctlEventAdditionalInfoJ;

    if((revents & EPOLLERR) != 0) {
        AFB_ERROR("An error has been send by event loop polling, prevent new errors to be fired by deleting event src");
        freeCardAlsaControlHandle(&sndHandle->evtHandle);
        return -1;
    }

    if((revents & EPOLLHUP) != 0) {
        AFB_NOTICE("SndCtl hanghup [car disconnected]");
        return 0;
    }

    if((revents & EPOLLIN) != 0) {
        // initialise event structure on stack
        snd_ctl_event_alloca(&eventId);
        snd_ctl_elem_id_alloca(&elemId);

        err = snd_ctl_read(sndHandle->evtHandle->ctlDev, eventId);
        if(err < 0) {
            AFB_ERROR("Did not succeed to read control event");
            freeCardAlsaControlHandle(&sndHandle->evtHandle);
            return -2;
        }

        // we only process sndctrl element
        if(snd_ctl_event_get_type(eventId) != SND_CTL_EVENT_ELEM)
            return 0;

        // we only process value changed events
        mask = snd_ctl_event_elem_get_mask(eventId);
        if(! (mask & SND_CTL_EVENT_MASK_VALUE))
            return 0;

        snd_ctl_event_elem_get_id(eventId, elemId);

        err = alsaGetSingleCtl(sndHandle->evtHandle->ctlDev, elemId, &ctlRequest, QUERY_FULL);
        if(err) {
            AFB_ERROR("Did not succeed to get control info and values");
            freeCardAlsaControlHandle(&sndHandle->evtHandle);
            return -3;
        }

        // If CTL as a value use it as container for response
        if(ctlRequest.valuesJ) {
            ctlEventJ = ctlRequest.valuesJ;
        }
        else {
            ctlEventJ = json_object_new_object();
            ctlNumid = snd_ctl_event_elem_get_numid(eventId);
            json_object_object_add(ctlEventJ, "id", json_object_new_int(ctlNumid));
        }

        err = wrap_json_pack(&ctlEventAdditionalInfoJ,
                             "{s:s, s:i, s:i, s:i}",
                             "name", snd_ctl_event_elem_get_name(eventId),
                             "card", snd_ctl_event_elem_get_interface(eventId),
                             "dev", snd_ctl_event_elem_get_device(eventId),
                             "sub", snd_ctl_event_elem_get_subdevice(eventId));
        if(! err)
            wrap_json_object_add(ctlEventJ, ctlEventAdditionalInfoJ);

        AFB_DEBUG("sndCtlEventCB=%s", json_object_get_string(ctlEventJ));
        subscriberCount = afb_event_push(sndHandle->evtHandle->afbevt, ctlEventJ);
    }

    if(! subscriberCount) {
        AFB_WARNING("Nobody listening for card %i control events, stop monitoring from now on", snd_ctl_event_elem_get_interface(eventId));
        freeCardAlsaControlHandle(&sndHandle->evtHandle);
        return -4;
    }

    return 0;
}

STATIC afb_event_t alsaEvtSubscribeUnsubscribePcmAvailabilityEvent(afb_req_t request,
                                                                   json_object *queryJ,
                                                                   sndCardsT *sndCards,
                                                                   EventSubscribeUnsubscribeT subscriptionType)
{
    int err,
        card = NO_CARD_SELECTED,
        device = NO_DEVICE_SELECTED,
        subdevice = NO_SUBDEVICE_SELECTED;

    char *pcmName = NULL, *pcmEventName = NULL;

    snd_pcm_stream_t stream;

    pcmEvtHandleT *requestedPcmEventHandle = NULL, *currentPcmEventHandle;

    if(! request || ! queryJ || ! sndCards) {
        afb_req_fail(request, "invalid-args", "Arguments of call are invalid");
        return NULL;
    }

    err = wrap_json_unpack(queryJ,
                           "{s:s, s?:i, s?:i, s?:i, s?:s, s:i !}",
                           "event", NULL,
                           "card", &card,
                           "device", &device,
                           "subdevice", &subdevice,
                           "name", &pcmName,
                           "stream", &stream);
    if(err || (! pcmName && (card == NO_CARD_SELECTED || device == NO_DEVICE_SELECTED || subdevice == NO_SUBDEVICE_SELECTED))) {
        afb_req_fail_f(request, "invalid_request", "Request json is invalid (%s)", json_object_get_string(queryJ));
        return NULL;
    }

    if(pcmName && (sscanf(pcmName, "hw:%i,%i,%i", &card, &device, &subdevice) != 3)) {
        afb_req_fail_f(request, "invalid_request", "Request pcm name is invalid (%s)", pcmName);
        return NULL;
    }

    if(card < 0 || card > MAX_SND_CARD ||
       device < 0 || device > MAX_CARD_DEVICES ||
       subdevice < 0 || subdevice > MAX_DEVICE_SUBDEVICES) {
        afb_req_fail_f(request,
                       "invalid_request",
                       "Requested pcm is not valid (card : %i, device : %i, subdevice : %i)",
                       card,
                       device,
                       subdevice);
        return NULL;
    }

    cds_list_for_each_entry(currentPcmEventHandle, &sndCards->pcmMonitoringHead, node) {
        if(currentPcmEventHandle->card == card &&
           currentPcmEventHandle->device == device &&
           currentPcmEventHandle->subdevice == subdevice &&
           currentPcmEventHandle->stream == stream) {
            requestedPcmEventHandle = currentPcmEventHandle;
            break;
        }
    }

    if(requestedPcmEventHandle) {
        return requestedPcmEventHandle->afbevt;
    }
    else if(subscriptionType == EVENT_UNSUBSCRIBE) {
        afb_req_fail_f(request,
                       "unknown_event",
                       "Can not unsubscribe unknown event (card : %i, device : %i, subdevice : %i, stream : %i)",
                       card,
                       device,
                       subdevice,
                       stream);
        return NULL;
    }

    requestedPcmEventHandle = calloc(1, sizeof(pcmEvtHandleT));
    if(! requestedPcmEventHandle) {
        afb_req_fail_f(request,
                       "eventhandle_alloc",
                       "Error when tried to allocate pcm event data structure (card : %i, device : %i, stream : %i, stream : %i)",
                       card,
                       device,
                       subdevice,
                       stream);
       return NULL;;
    }

    requestedPcmEventHandle->card = card;
    requestedPcmEventHandle->device = device;
    requestedPcmEventHandle->subdevice = subdevice;
    requestedPcmEventHandle->stream = stream;
    requestedPcmEventHandle->fd = -1;
    requestedPcmEventHandle->wd = -1;

    err = asprintf(&requestedPcmEventHandle->pcmFileToMonitor,
                   PCM_DEV_FILE_STRING,
                   card,
                   device,
                   (stream == SND_PCM_STREAM_PLAYBACK) ? 'p' : 'c');
    if(err <= 0) {
        afb_req_fail_f(request,
                       "monitored_file",
                       "Did not succeed to generate pcm file to monitor string (card : %i, device : %i, stream : %i)",
                       card,
                       device,
                       stream);
        freePcmHandle(requestedPcmEventHandle);
        return NULL;
    }

    err = asprintf(&pcmEventName,
                   PCM_EVENT_NAME,
                   (stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
                   card,
                   device,
                   subdevice);
    if(err <= 0) {
        afb_req_fail_f(request,
                       "pcm_event_name",
                       "Did not succeed to generate pcm event name string (card : %i, device : %i, stream : %i, stream : %i)",
                       card,
                       device,
                       subdevice,
                       stream);
        freePcmHandle(requestedPcmEventHandle);
        return NULL;
    }

    requestedPcmEventHandle->afbevt = afb_daemon_make_event(pcmEventName);
    if(! afb_event_is_valid(requestedPcmEventHandle->afbevt)) {
        afb_req_fail_f(request, "pcm-monitoring-event", "Cannot register new %s event", pcmEventName);
        free(pcmEventName);
        freePcmHandle(requestedPcmEventHandle);
        return NULL;
    }

    free(pcmEventName);

    requestedPcmEventHandle->fd = inotify_init();
    if(requestedPcmEventHandle->fd < 0) {
        afb_req_fail_f(request, "inotify-init", "Error %s happened while getting file descriptor for inotify", strerror(errno));
        freePcmHandle(requestedPcmEventHandle);
        return NULL;
    }

    requestedPcmEventHandle->wd = inotify_add_watch(requestedPcmEventHandle->fd,
                                                    requestedPcmEventHandle->pcmFileToMonitor,
                                                    IN_ALL_EVENTS);
    if(requestedPcmEventHandle->wd < 0) {
        afb_req_fail_f(request,
                       "inotify-watch",
                       "Error %s happened while setting watcher on '%s' file using inotify",
                       strerror(errno),
                       requestedPcmEventHandle->pcmFileToMonitor);
        freePcmHandle(requestedPcmEventHandle);
        return NULL;
    }

    requestedPcmEventHandle->available = alsaIsPcmAvailableUsingId(card, device, subdevice, stream);
    if(requestedPcmEventHandle->available < 0) {
        afb_req_fail_f(request,
                       "pcm-availability",
                       "Did not succeed to get pcm current availability (card : %i, device : %i, stream : %i, stream : %i)",
                       card,
                       device,
                       subdevice,
                       stream);
        freePcmHandle(requestedPcmEventHandle);
        return NULL;
    }

    err = sd_event_add_io(afb_daemon_get_event_loop(),
                          &requestedPcmEventHandle->src,
                          requestedPcmEventHandle->fd,
                          EPOLLIN,
                          pcmEventCB,
                          requestedPcmEventHandle);
    if(err < 0) {
        afb_req_fail_f(request,
                       "register-mainloop",
                       "Cannot hook pcm %s hw:%i,%i,%i event to mainloop err=%d",
                       (stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
                       card,
                       device,
                       subdevice,
                       err);
        freePcmHandle(requestedPcmEventHandle);
        return NULL;
    }

    cds_list_add_tail(&requestedPcmEventHandle->node, &sndCards->pcmMonitoringHead);

    return requestedPcmEventHandle->afbevt;
}

// Subscribe to each time a sound card is added/removed (using inotify '/dev/snd/by-path' events)
STATIC afb_event_t alsaEvtSubscribeUnsubscribeSoundCardEvent(afb_req_t request,
                                                             sndCardsT *sndCards,
                                                             EventSubscribeUnsubscribeT subscriptionType)
{
    int err;

    if(sndCards->cardMonitoring)
        return sndCards->cardMonitoring->afbevt;
    else if(subscriptionType == EVENT_UNSUBSCRIBE)
        return NULL;

    updateAllAlsaCardsAvailabilityAndFireEvents(sndCards);

    sndCards->cardMonitoring = malloc(sizeof (sndCardEvtHandleT));
    if(! sndCards->cardMonitoring) {
        afb_req_fail(request, "card-monitoring-event", "Error while allocating card added monitoring handle");
        return NULL;
    }

    sndCards->cardMonitoring->fd = -1;
    sndCards->cardMonitoring->wd = -1;

    sndCards->cardMonitoring->fd = inotify_init();
    if(sndCards->cardMonitoring->fd < 0) {
        afb_req_fail_f(request, "inotify-init", "Error %s happened while getting file descriptor for inotify", strerror(errno));
        freeSndCardHandle(&sndCards->cardMonitoring);
        return NULL;
    }

    sndCards->cardMonitoring->wd = inotify_add_watch(sndCards->cardMonitoring->fd, CARD_DIR_TO_WATCH, IN_CREATE | IN_DELETE);
    if(sndCards->cardMonitoring->wd < 0) {
        afb_req_fail_f(request,
                       "inotify-watch",
                       "Error %s happened while setting watcher on '%s' directory using inotify",
                       strerror(errno),
                       CARD_DIR_TO_WATCH);
        freeSndCardHandle(&sndCards->cardMonitoring);
        return NULL;
    }

    sndCards->cardMonitoring->afbevt = afb_daemon_make_event("card-monitoring");
    if(! afb_event_is_valid(sndCards->cardMonitoring->afbevt)) {
        afb_req_fail_f(request, "card-monitoring-event", "Cannot register new binder card-monitoring event");
        freeSndCardHandle(&sndCards->cardMonitoring);
        return NULL;
    }

    // register sound event to binder main loop
    err = sd_event_add_io(afb_daemon_get_event_loop(),
                          &sndCards->cardMonitoring->src,
                          sndCards->cardMonitoring->fd,
                          EPOLLIN,
                          sndCardEventCB,
                          sndCards);
    if(err < 0) {
        afb_req_fail_f(request, "register-mainloop", "Cannot hook sound card events to mainloop err=%d", err);
        freeSndCardHandle(&sndCards->cardMonitoring);
        return NULL;
    }

    return sndCards->cardMonitoring->afbevt;
}

// Subscribe to every Alsa CtlEvent send by a given board
STATIC afb_event_t alsaEvtSubscribeUnsubscribeAlsaControlEvent(afb_req_t request,
                                                               sndCardsT *sndCards,
                                                               char* devId,
                                                               EventSubscribeUnsubscribeT subscriptionType)
{
    sndHandleT *currentSndHandle = NULL;
    snd_ctl_t *ctlDev = NULL;
    int err, cardId;
    snd_ctl_card_info_t *cardinfo;

    // open control interface for devid
    err = snd_ctl_open(&ctlDev, devId, SND_CTL_READONLY);
    if(err < 0) {
        afb_req_fail_f(request, "devid-unknown", "SndCard devid=%s Not Found err=%s", devId, snd_strerror(err));
        return NULL;
    }

    snd_ctl_card_info_alloca(&cardinfo);
    err = snd_ctl_card_info(ctlDev, cardinfo);
    if(err < 0) {
        afb_req_fail_f(request, "devid-invalid", "SndCard devid=%s Not Found err=%s", devId, snd_strerror(err));
        snd_ctl_close(ctlDev);
        return NULL;
    }

    cardId = snd_ctl_card_info_get_card(cardinfo);

    if(cardId < 0 || cardId >= MAX_SND_CARD) {
        afb_req_fail_f(request, "devid-invalid", "SndCard cardId=%i invalid", cardId);
        snd_ctl_close(ctlDev);
        return NULL;
    }

    currentSndHandle = &sndCards->sndHandles[cardId];

    if(currentSndHandle->evtHandle) {
        snd_ctl_close(ctlDev);
        return currentSndHandle->evtHandle->afbevt;
    }
    else if(subscriptionType == EVENT_UNSUBSCRIBE) {
        snd_ctl_close(ctlDev);
        return NULL;
    }

    currentSndHandle->evtHandle = malloc(sizeof (ctrlEvtHandleT));
    currentSndHandle->evtHandle->ctlDev = ctlDev;

    // subscribe for sndctl events attached to devid
    err = snd_ctl_subscribe_events(currentSndHandle->evtHandle->ctlDev, 1);
    if(err < 0) {
        afb_req_fail_f(request, "alsa-ctl-subscribe-fail", "Cannot subscribe events from devid=%s err=%d", devId, err);
        freeCardAlsaControlHandle(&currentSndHandle->evtHandle);
        return NULL;
    }

    // get pollfd attach to this sound board
    snd_ctl_poll_descriptors(currentSndHandle->evtHandle->ctlDev, &currentSndHandle->evtHandle->pfds, 1);

    // register sound event to binder main loop
    err = sd_event_add_io(afb_daemon_get_event_loop(),
                          &currentSndHandle->evtHandle->src,
                          currentSndHandle->evtHandle->pfds.fd,
                          EPOLLIN,
                          sndCtlEventCB,
                          currentSndHandle);
    if(err < 0) {
        afb_req_fail_f(request, "register-mainloop", "Cannot hook events to mainloop devid=%s err=%d", devId, err);
        freeCardAlsaControlHandle(&currentSndHandle->evtHandle);
        return NULL;
    }

    // create binder event attached to devid name
    currentSndHandle->evtHandle->afbevt = afb_daemon_make_event(devId);
    if(! afb_event_is_valid(currentSndHandle->evtHandle->afbevt)) {
        afb_req_fail_f(request, "register-event", "Cannot register new binder event name=%s", devId);
        freeCardAlsaControlHandle(&currentSndHandle->evtHandle);
        return NULL;
    }

    return currentSndHandle->evtHandle->afbevt;
}

// Subscribe to alsacore event
PUBLIC void alsaEvtSubscribeUnsubscribe(afb_req_t request, EventSubscribeUnsubscribeT subscriptionType)
{
    int err = -1;

    EventTypeT eventType;

    char *devId = NULL;

    static sndCardsT *sndCards = NULL;

    afb_event_t eventToSubscribeUnsubscribe;

    json_object *queryJ;

    if(! sndCards) {
        sndCards = calloc(1, sizeof(sndCardsT));
        sndCards->apiHandle = afb_req_get_api(request);
        CDS_INIT_LIST_HEAD(&sndCards->pcmMonitoringHead);
    }

    queryJ = afb_req_json(request);

    eventType = alsaGetEventTypeFromQuery(queryJ);
    switch(eventType) {
            case CARD_CONTROL_CHANGE_EVENTS:
                devId = alsaGetDevIdFromQuery(queryJ);
                if(! devId) {
                    afb_req_fail_f(request, "devid-missing", "Invalid query='%s'", json_object_get_string(queryJ));
                    return;
                }

                eventToSubscribeUnsubscribe = alsaEvtSubscribeUnsubscribeAlsaControlEvent(request, sndCards, devId, subscriptionType);
                break;

            case CARD_MONITORING_EVENTS:
                eventToSubscribeUnsubscribe = alsaEvtSubscribeUnsubscribeSoundCardEvent(request, sndCards, subscriptionType);
                break;

            case PCM_AVAILABILITY_EVENTS:
                eventToSubscribeUnsubscribe = alsaEvtSubscribeUnsubscribePcmAvailabilityEvent(request, queryJ, sndCards, subscriptionType);
                break;

            case UNKNOWN_EVENT:
            default:
                afb_req_fail_f(request, "unknown-event", "Invalid query='%s'", json_object_get_string(queryJ));
                return;
    }

    if(! eventToSubscribeUnsubscribe)
        return;

    // subscribe/unsubscribe to binder event
    if(subscriptionType == EVENT_SUBSCRIBE)
        err = afb_req_subscribe(request, eventToSubscribeUnsubscribe);
    else if(subscriptionType == EVENT_UNSUBSCRIBE)
        err = afb_req_unsubscribe(request, eventToSubscribeUnsubscribe);

    if(err) {
        afb_req_fail_f(request,
                       "register-eventname",
                       "Cannot %s binder event",
                       (subscriptionType == EVENT_SUBSCRIBE) ? "subscribe" : "unsubscribe");
        return;
    }

    afb_req_success(request, NULL, NULL);
}

PUBLIC void alsaEvtSubscribe(afb_req_t request)
{
    alsaEvtSubscribeUnsubscribe(request, EVENT_SUBSCRIBE);
}

PUBLIC void alsaEvtUnsubscribe(afb_req_t request)
{
    alsaEvtSubscribeUnsubscribe(request, EVENT_UNSUBSCRIBE);
}

// Subscribe to every Alsa CtlEvent send by a given board

STATIC json_object *alsaProbeCardId(afb_req_t request) {
    char devid [32];
    const char *ctlName, *shortname, *longname, *mixername, *drivername;
    int done, mode, card, err, index, idx;
    json_object *responseJ, *tmpJ;
    snd_ctl_t *ctlDev;
    snd_ctl_card_info_t *cardinfo;

    json_object* queryJ = afb_req_json(request);
        
    done = json_object_object_get_ex(queryJ, "sndname", &tmpJ);
    if (!done || json_object_get_type(tmpJ) != json_type_string) {
        afb_req_fail_f(request, "argument-missing", "sndname=SndCardName missing");
        goto OnErrorExit;
    }
    const char *sndname = json_object_get_string(tmpJ);
    
    done = json_object_object_get_ex(queryJ, "mode", &tmpJ);
    if (!done) {
        mode = 0;
    } else {
        mode = json_object_get_int(tmpJ);        
    }
    

    // loop on potential card number
    snd_ctl_card_info_alloca(&cardinfo);
    char *driverId = NULL; // when not name match use drivername as backup plan
    for (card = 0; card < MAX_SND_CARD; card++) {

        // build card devid and probe it
        snprintf(devid, sizeof (devid), "hw:%i", card);

        // open control interface for devid
        err = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY);
        if (err < 0) continue;

        // extract sound card information
        snd_ctl_card_info(ctlDev, cardinfo);
        index = snd_ctl_card_info_get_card(cardinfo);
        ctlName = snd_ctl_card_info_get_id(cardinfo);
        shortname = snd_ctl_card_info_get_name(cardinfo);
        longname = snd_ctl_card_info_get_longname(cardinfo);
        mixername  = snd_ctl_card_info_get_mixername(cardinfo);
        drivername = snd_ctl_card_info_get_driver(cardinfo);

        snd_ctl_close(ctlDev);
        
        // check if short|long name match
        if (!strcasecmp(sndname, ctlName)) break;
        if (!strcasecmp(sndname, shortname)) break;
        
        // if name does not match search for a free HAL with driver name matching
        if (driverId==NULL && getHalIdxFromCardid(card)<0 && !strcasecmp(sndname, drivername)) driverId=strdup(devid);
    }

    if (card == MAX_SND_CARD) {
        if (!driverId) {
            afb_req_fail_f(request, "ctlDev-notfound", "Fail to find card with name=%s", sndname);
            goto OnErrorExit;
        } 
                
        // refresh devid and clean up driverId
        strncpy (devid, driverId, sizeof(devid));       
        free(driverId);
        
        err = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY);
        if (err < 0) {
            afb_req_fail_f(request, "ctlDev-notfound", "Fail to find card with name=%s devid=%s", sndname, devid);
            goto OnErrorExit;
        }

        // Sound not found by name, backup to driver name
        snd_ctl_card_info(ctlDev, cardinfo);
        index = snd_ctl_card_info_get_card(cardinfo);
        ctlName = snd_ctl_card_info_get_id(cardinfo);
        shortname = snd_ctl_card_info_get_name(cardinfo);
        longname = snd_ctl_card_info_get_longname(cardinfo);
        mixername  = snd_ctl_card_info_get_mixername(cardinfo);
        drivername = snd_ctl_card_info_get_driver(cardinfo);
        AFB_WARNING("alsaProbeCardId Fallback to HAL=%s ==> devid=%s name=%s long=%s\n ", drivername, devid, shortname, longname);
        snd_ctl_close(ctlDev);
    }

    // proxy ctlevent as a binder event
    responseJ = json_object_new_object();
    json_object_object_add(responseJ, "index", json_object_new_int(index));
    json_object_object_add(responseJ, "cardid", json_object_new_int(card));
    json_object_object_add(responseJ, "devid", json_object_new_string(devid));
    json_object_object_add(responseJ, "shortname", json_object_new_string(shortname));
    
    if (mode > 0) {
        json_object_object_add(responseJ, "longname", json_object_new_string(longname));
        json_object_object_add(responseJ, "mixername", json_object_new_string(mixername));
        json_object_object_add(responseJ, "drivername", json_object_new_string(drivername));
        
    }

    // search for a HAL binder card mapping name to api prefix
    for (idx = 0; (idx < MAX_SND_HAL && cardRegistry[idx]); idx++) {
        if (!strcmp(cardRegistry[idx]->shortname, shortname)) {
            json_object_object_add(responseJ, "halapi", json_object_new_string(cardRegistry[idx]->apiprefix));
            break;
        }
    }

    return responseJ;

OnErrorExit:
    return NULL;
}

// Make alsaProbeCardId compatible with AFB request

PUBLIC void alsaGetCardId(afb_req_t request) {

    json_object *responseJ = alsaProbeCardId(request);
    if (responseJ) afb_req_success(request, responseJ, NULL);
}


// Return HAL information about a given sound card ID

STATIC int getHalApiFromCardid(int cardid, json_object *responseJ) {

    
    int idx = getHalIdxFromCardid (cardid);
    if (idx < 0) goto OnErrorExit;
    
    json_object_object_add(responseJ, "api", json_object_new_string(cardRegistry[idx]->apiprefix));
    if (cardRegistry[idx]->shortname)json_object_object_add(responseJ, "shortname", json_object_new_string(cardRegistry[idx]->shortname));
    if (cardRegistry[idx]->longname) json_object_object_add(responseJ, "longname", json_object_new_string(cardRegistry[idx]->longname));

    return 0;

OnErrorExit:
    return -1;
}


// Return list of active resgistrated HAL with corresponding sndcard

PUBLIC void alsaActiveHal(afb_req_t request) {
    json_object *responseJ = json_object_new_array();

    for (int idx = 0; idx < MAX_SND_HAL; idx++) {
        if (!cardRegistry[idx]) break;

        json_object *haldevJ = json_object_new_object();
        json_object_object_add(haldevJ, "api", json_object_new_string(cardRegistry[idx]->apiprefix));
        if (cardRegistry[idx]->devid) json_object_object_add(haldevJ, "devid", json_object_new_string(cardRegistry[idx]->devid));
        if (cardRegistry[idx]->shortname)json_object_object_add(haldevJ, "shortname", json_object_new_string(cardRegistry[idx]->shortname));
        if (cardRegistry[idx]->drivername)json_object_object_add(haldevJ, "drivername", json_object_new_string(cardRegistry[idx]->drivername));
        if (cardRegistry[idx]->longname) json_object_object_add(haldevJ, "longname", json_object_new_string(cardRegistry[idx]->longname));
        json_object_array_add(responseJ, haldevJ);
    }

    afb_req_success(request, responseJ, NULL);
}


// Register loaded HAL with board Name and API prefix

PUBLIC void alsaRegisterHal(afb_req_t request) {
    static int index = 0;
    json_object *responseJ;
    const char *shortname, *apiPrefix;

    apiPrefix = afb_req_value(request, "prefix");
    if (apiPrefix == NULL) {
        afb_req_fail_f(request, "argument-missing", "prefix=BindingApiPrefix missing");
        goto OnErrorExit;
    }

    shortname = afb_req_value(request, "sndname");
    if (shortname == NULL) {
        afb_req_fail_f(request, "argument-missing", "sndname=SndCardName missing");
        goto OnErrorExit;
    }

    if (index == MAX_SND_HAL) {
        afb_req_fail_f(request, "alsahal-toomany", "Fail to register sndname=[%s]", shortname);
        goto OnErrorExit;
    }

    // alsaGetCardId should be check to register only valid card
    responseJ = alsaProbeCardId(request);
    if (responseJ) {
        json_object *tmpJ;
        int done;

        cardRegistry[index] = malloc(sizeof (cardRegistry));
        cardRegistry[index]->apiprefix = strdup(apiPrefix);
        cardRegistry[index]->shortname = strdup(shortname);
        
        json_object_object_get_ex(responseJ, "cardid", &tmpJ);
        cardRegistry[index]->cardid = json_object_get_int(tmpJ);

        done = json_object_object_get_ex(responseJ, "devid", &tmpJ);
        if (done) cardRegistry[index]->devid = strdup(json_object_get_string(tmpJ));
        else cardRegistry[index]->devid = NULL;

        done = json_object_object_get_ex(responseJ, "drivername", &tmpJ);
        if (done) cardRegistry[index]->drivername = strdup(json_object_get_string(tmpJ));
        else cardRegistry[index]->drivername = NULL;

        done = json_object_object_get_ex(responseJ, "longname", &tmpJ);
        if (done) cardRegistry[index]->longname = strdup(json_object_get_string(tmpJ));
        else cardRegistry[index]->longname = NULL;

        // make sure register close with a null value
        index++;
        cardRegistry[index] = NULL;

        afb_req_success(request, responseJ, NULL);
    }

    // If OK return sound card Alsa ID+Info
    return;

OnErrorExit:
    return;
}

PUBLIC void alsaPcmInfo (afb_req_t request) {
    int done, mode, err;
    json_object *tmpJ, *responseJ = NULL;
    snd_pcm_t *pcmHandle = NULL;
    snd_pcm_info_t * pcmInfo = NULL;
    
    json_object* queryJ = afb_req_json(request);
    
    done = json_object_object_get_ex(queryJ, "name", &tmpJ);
    if (!done || json_object_get_type(tmpJ) != json_type_string) {
        afb_req_fail_f(request, "name:invalid", "PCM 'name:xxx' missing or not a string query='%s'", json_object_get_string(queryJ));
        goto OnErrorExit;
    }
    const char *pcmName = json_object_get_string(tmpJ);
    
    done = json_object_object_get_ex(queryJ, "stream", &tmpJ);
    if (done && json_object_get_type(tmpJ) != json_type_int) {
        afb_req_fail_f(request, "stream:invalid", "PCM 'stream:SND_PCM_STREAM_PLAYBACK/SND_PCM_STREAM_CAPTURE' should be integer query='%s'", json_object_get_string(queryJ));
        goto OnErrorExit;
    }
    snd_pcm_stream_t pcmStream = json_object_get_int(tmpJ);
    
    done = json_object_object_get_ex(queryJ, "mode", &tmpJ);
    if (!done) {
        mode = 0;
    } else {
        mode = json_object_get_int(tmpJ);        
    }
    
    // open PCM from its name
    err= snd_pcm_open(&pcmHandle, pcmName, pcmStream, 0);
    if (err < 0) {
        afb_req_fail_f(request, "pcm:invalid", "PCM 'name:%s' does cannot open error=%s", pcmName, snd_strerror(err));
        goto OnErrorExit;
    }
    
    // get pcm info
    snd_pcm_info_alloca(&pcmInfo);      
    err = snd_pcm_info(pcmHandle,pcmInfo);
    if (err < 0)  {
        afb_req_fail_f(request, "pcm:error", "PCM 'name:%s' fail to retrieve info error=%s", pcmName, snd_strerror(err));
        goto OnErrorExit;
    }

    // get sub-device number
    int cardId = snd_pcm_info_get_card(pcmInfo);
    if (cardId < 0 )   {
        afb_req_fail_f(request, "pcm:error", "PCM 'name:%s' fail to retrieve sndcard device error=%s", pcmName, snd_strerror(cardId));
        goto OnErrorExit;
    }
    
    // prepare an object for response
    responseJ = json_object_new_object();
    
    err = getHalApiFromCardid (cardId, responseJ);
    if (err < 0 )   {
        afb_req_fail_f(request, "pcm:error", "PCM 'name:%s' snddev=hw:%d fail to retrieve hal API", pcmName, cardId);
        goto OnErrorExit;
    }

    json_object_object_add(responseJ, "type", json_object_new_int(snd_pcm_type(pcmHandle)));

    // in mode mode we return full info about PCM
    if (mode > 0) {
        json_object_object_add(responseJ, "stream", json_object_new_int(snd_pcm_info_get_stream (pcmInfo)));
        json_object_object_add(responseJ, "cardid", json_object_new_int(snd_pcm_info_get_card(pcmInfo)));      
        json_object_object_add(responseJ, "devid" , json_object_new_int(snd_pcm_info_get_device (pcmInfo)));     
        json_object_object_add(responseJ, "subid" , json_object_new_int(snd_pcm_info_get_subdevice (pcmInfo)));     
    }
    
    // in super mode we also return information about snd card
    if (mode > 1) {
        
        json_object_object_add(responseJ, "id"     , json_object_new_string(snd_pcm_info_get_id (pcmInfo)));
        json_object_object_add(responseJ, "name"   , json_object_new_string(snd_pcm_info_get_name (pcmInfo)));
        json_object_object_add(responseJ, "subdev" , json_object_new_string(snd_pcm_info_get_subdevice_name (pcmInfo)));       
    }
    
    afb_req_success(request, responseJ, NULL);
    snd_pcm_close (pcmHandle);
    return;
    
OnErrorExit:
    if (responseJ) json_object_put (responseJ);
    if (pcmHandle) snd_pcm_close (pcmHandle);
    return;
}