/* plugin.h
** Copyright (c) 2019-2020, The Linux Foundation.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above
**     copyright notice, this list of conditions and the following
**     disclaimer in the documentation and/or other materials provided
**     with the distribution.
**   * Neither the name of The Linux Foundation nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
** ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

#ifndef TINYALSA_PLUGIN_H
#define TINYALSA_PLUGIN_H

#include <poll.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <time.h>

#include <sound/asound.h>

/* static initializers */

#define SND_VALUE_ENUM(etexts, eitems)    \
    {.texts = etexts, .items = eitems}

#define SND_VALUE_BYTES(csize)    \
    {.size = csize }

#define SND_VALUE_INTEGER(icount, imin, imax, istep) \
    {.count = icount, .min = imin, .max = imax, .step = istep }

#define SND_VALUE_TLV_BYTES(csize, cget, cput)       \
    {.size = csize, .get = cget, .put = cput }

/* pointer based initializers */
#define INIT_SND_CONTROL_INTEGER(c, cname, cget, cput, cint, pval, pdata)   \
    {                                                                       \
        c->iface = SNDRV_CTL_ELEM_IFACE_MIXER;                              \
        c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE;                        \
        c->type = SNDRV_CTL_ELEM_TYPE_INTEGER;                              \
        c->name = cname; c->value = &cint; c->get = cget; c->put = cput;    \
        c->private_value = pval; c->private_data = pdata;                   \
    }

#define INIT_SND_CONTROL_BYTES(c, cname, cget, cput, cint, pval, pdata)     \
    {                                                                       \
        c->iface = SNDRV_CTL_ELEM_IFACE_MIXER;                              \
        c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE;                        \
        c->type = SNDRV_CTL_ELEM_TYPE_BYTES;                                \
        c->name = cname; c->value = &cint; c->get = cget; c->put = cput;    \
        c->private_value = pval; c->private_data = pdata;                   \
    }

#define INIT_SND_CONTROL_ENUM(c, cname, cget, cput, cenum, pval, pdata)     \
    {                                                                       \
        c->iface = SNDRV_CTL_ELEM_IFACE_MIXER;                              \
        c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE;                        \
        c->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;                           \
        c->name = cname; c->value = cenum; c->get = cget; c->put = cput;    \
        c->private_value = pval; c->private_data = pdata;                   \
    }

#define INIT_SND_CONTROL_TLV_BYTES(c, cname, cbytes, priv_val, priv_data)  \
    {                                                                      \
        c->iface = SNDRV_CTL_ELEM_IFACE_MIXER;                             \
        c->access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE;                   \
        c->type = SNDRV_CTL_ELEM_TYPE_BYTES;                               \
        c->name = cname; c->value = &cbytes;                               \
        c->private_value = priv_val; c->private_data = priv_data;          \
    }

struct mixer_plugin;
struct pcm_plugin;
struct snd_node;

/** Operations that are required to be registered by the plugin.
 * @ingroup libtinyalsa-pcm
 */
struct pcm_plugin_ops {
    /** open the pcm plugin */
    int (*open) (struct pcm_plugin **plugin, unsigned int card,
                 unsigned int device, unsigned int flags);
    /** close the pcm plugin */
    int (*close) (struct pcm_plugin *plugin);
    /** Set the PCM hardware parameters to the plugin */
    int (*hw_params) (struct pcm_plugin *plugin,
                      struct snd_pcm_hw_params *params);
    /** Set the PCM software parameters to the plugin */
    int (*sw_params) (struct pcm_plugin *plugin,
                      struct snd_pcm_sw_params *params);
    /** Synchronize the pointer */
    int (*sync_ptr) (struct pcm_plugin *plugin,
                     struct snd_pcm_sync_ptr *sync_ptr);
    /** Write frames to plugin to be rendered to output */
    int (*writei_frames) (struct pcm_plugin *plugin,
                          struct snd_xferi *x);
    /** Read frames from plugin captured from input */
    int (*readi_frames) (struct pcm_plugin *plugin,
                         struct snd_xferi *x);
    /** Obtain the timestamp for the PCM */
    int (*ttstamp) (struct pcm_plugin *plugin,
                    int *tstamp);
    /** Prepare the plugin for data transfer */
    int (*prepare) (struct pcm_plugin *plugin);
    /** Start data transfer from/to the plugin */
    int (*start) (struct pcm_plugin *plugin);
    /** Signal the plugin to drain PCM */
    int (*drain) (struct pcm_plugin *plugin);
    /** Stop a PCM dropping pending frames if drain() is NOT called.
     *  Stop a PCM preserving pending frames if drain() is called. */
    int (*drop) (struct pcm_plugin *plugin);
    /** Any custom or alsa specific ioctl implementation */
    int (*ioctl) (struct pcm_plugin *plugin,
                  int cmd, void *arg);
    void *(*mmap) (struct pcm_plugin *plugin, void *addr, size_t length,
                   int prot, int flags, off_t offset);
    int (*munmap) (struct pcm_plugin *plugin, void *addr, size_t length);
    int (*poll) (struct pcm_plugin *plugin, struct pollfd *pfd, nfds_t nfds,
                 int timeout);
};

/** Minimum and maximum values for hardware parameter constraints.
 * @ingroup libtinyalsa-pcm
 */
struct pcm_plugin_min_max {
    /** Minimum value for the hardware parameter */
    unsigned int min;
    /** Maximum value for the hardware parameter */
    unsigned int max;
};

/** Encapsulate the hardware parameter constraints
 * @ingroup libtinyalsa-pcm
 */
struct pcm_plugin_hw_constraints {
    /** Value for SNDRV_PCM_HW_PARAM_ACCESS param */
    uint64_t access;
    /** Value for SNDRV_PCM_HW_PARAM_FORMAT param.
     * As of this implementation ALSA supports 52 formats */
    uint64_t format;
    /** Value for SNDRV_PCM_HW_PARAM_SAMPLE_BITS param */
    struct pcm_plugin_min_max bit_width;
    /** Value for SNDRV_PCM_HW_PARAM_CHANNELS param */
    struct pcm_plugin_min_max channels;
    /** Value for SNDRV_PCM_HW_PARAM_RATE param */
    struct pcm_plugin_min_max rate;
    /** Value for SNDRV_PCM_HW_PARAM_PERIODS param */
    struct pcm_plugin_min_max periods;
    /** Value for SNDRV_PCM_HW_PARAM_PERIOD_BYTES param */
    struct pcm_plugin_min_max period_bytes;
};

struct pcm_plugin {
    /** Card number for the pcm device */
    unsigned int card;
    /** device number for the pcm device */
    unsigned int device;
    /** pointer to the contraints registered by the plugin */
    struct pcm_plugin_hw_constraints *constraints;
    /** Indicates read/write mode, etc.. */
    int mode;
    /* Pointer to hold the plugin's private data */
    void *priv;
    /* Tracks the plugin state */
    unsigned int state;
};

typedef void (*mixer_event_callback)(struct mixer_plugin *);

struct mixer_plugin_ops {
    int (*open) (struct mixer_plugin **plugin, unsigned int card);
    void (*close) (struct mixer_plugin **plugin);
    int (*subscribe_events) (struct mixer_plugin *plugin,
                             mixer_event_callback event_cb);
    ssize_t (*read_event) (struct mixer_plugin *plugin,
                           struct snd_ctl_event *ev, size_t size);
};

struct snd_control {
    snd_ctl_elem_iface_t iface;
    unsigned int access;
    const char *name;
    snd_ctl_elem_type_t type;
    void *value;
    int (*get) (struct mixer_plugin *plugin,
                struct snd_control *control,
                struct snd_ctl_elem_value *ev);
    int (*put) (struct mixer_plugin *plugin,
                struct snd_control *control,
                struct snd_ctl_elem_value *ev);
    uint32_t private_value;
    void *private_data;
};

struct mixer_plugin {
    unsigned int card;
    void *priv;

    int eventfd;
    int subscribed;
    int event_cnt;

    struct snd_control *controls;
    unsigned int num_controls;
};

struct snd_value_enum {
    unsigned int items;
    char **texts;
};

struct snd_value_bytes {
    unsigned int size;
};

struct snd_value_tlv_bytes {
    unsigned int size;
    int (*get) (struct mixer_plugin *plugin,
                struct snd_control *control,
                struct snd_ctl_tlv *tlv);
    int (*put) (struct mixer_plugin *plugin,
                struct snd_control *control,
                struct snd_ctl_tlv *tlv);
};

struct snd_value_int {
    unsigned int count;
    int min;
    int max;
    int step;
};

/** Operations defined by the plugin.
 * */
struct snd_node_ops {
    /** Function pointer to get card definition */
    void* (*open_card)(unsigned int card);
    /** Function pointer to release card definition */
    void (*close_card)(void *card);
    /** Get interger type properties from device definition */
    int (*get_int)(void *node, const char *prop, int *val);
    /** Get string type properties from device definition */
    int (*get_str)(void *node, const char *prop, char **val);
    /** Function pointer to get mixer definition */
    void* (*get_mixer)(void *card);
    /** Function pointer to get PCM definition */
    void* (*get_pcm)(void *card, unsigned int id);
    /** Reserved for other nodes such as compress */
    void* reserved[4];
};

#endif /* end of TINYALSA_PLUGIN_H */
