/*
 * There is a lot of scrap here.  It's not very well organized junk to
 * provide minimal "emulation" of the kobj code for feeder objects.
 */

#ifndef _FEEDER_COMPAT_H_
#define _FEEDER_COMPAT_H_

#define KASSERT(x, y)   assert(x)
#define CHN_LOCKASSERT(c)


struct feeder_class;
struct pcm_channel;
struct pcm_feeder;

/* Types for function pointers */

typedef int feeder_init_t(struct pcm_feeder *f);
typedef int feeder_free_t(struct pcm_feeder *f);
typedef int feeder_set_t(struct pcm_feeder *f, int what, int value);
typedef int feeder_get_t(struct pcm_feeder *f, int what);
typedef int feeder_feed_t(struct pcm_feeder *f, struct pcm_channel* c,
                            uint8_t *feed_buffer, uint32_t feed_bytes,
                            void *source);

typedef struct {
    const char* name;
    void *method;
} kobj_method_t;

#define KOBJMETHOD(x, y) { #x, y }
#define KOBJMETHOD_END  { NULL, NULL }
#if 0
#define KOBJMETHOD(NAME, FUNC)                                          \
        {                                                               \
                &NAME##_desc,                                           \
                (kobjop_t) ((FUNC != (NAME##_t *)NULL) ? FUNC : NULL)   \
        }
#endif

struct pcmchan_matrix {
        int id;
        uint32_t channels, ext;
        struct {
                int type;
                uint32_t members;
        } map[SND_CHN_T_MAX + 1];
        uint32_t mask;
        int8_t offset[SND_CHN_T_MAX];
};

extern int feeder_volume_apply_matrix(struct pcm_feeder *,
    struct pcmchan_matrix *);

/* feeder_matrix */
extern int feeder_matrix_default_id(uint32_t);
extern struct pcmchan_matrix *feeder_matrix_default_channel_map(uint32_t);

uint32_t feeder_matrix_default_format(uint32_t);

extern int feeder_matrix_format_id(uint32_t);
extern struct pcmchan_matrix *feeder_matrix_format_map(uint32_t);

extern struct pcmchan_matrix *feeder_matrix_id_map(int);

extern int feeder_matrix_setup(struct pcm_feeder *, struct pcmchan_matrix *,
    struct pcmchan_matrix *);
extern int feeder_matrix_compare(struct pcmchan_matrix *,
    struct pcmchan_matrix *);

extern int
feeder_matrix_oss_get_channel_order(struct pcmchan_matrix *,
    unsigned long long *);
extern int
feeder_matrix_oss_set_channel_order(struct pcmchan_matrix *,
    unsigned long long *);

struct pcm_feederdesc {
    uint32_t type;
    uint32_t in;
    uint32_t out;
    uint32_t flags;
    int idx;
};

enum {
        FEEDER_ROOT,
        FEEDER_RATE,
        FEEDER_FORMAT,
        FEEDER_MATRIX,
        FEEDER_VOLUME,
        FEEDER_EQ,
        FEEDER_STDIO,
        FEEDER_LAST
};

/* feeder_eq */
enum {
        FEEDEQ_CHANNELS,
        FEEDEQ_RATE,
        FEEDEQ_TREBLE,
        FEEDEQ_BASS,
        FEEDEQ_PREAMP,
        FEEDEQ_STATE,
        FEEDEQ_DISABLE,
        FEEDEQ_ENABLE,
        FEEDEQ_BYPASS,
        FEEDEQ_UNKNOWN
};


int feeder_eq_validrate(uint32_t);

/* feeder_format */
enum {
        FEEDFORMAT_CHANNELS
};

/* feeder_volume */
enum {
        FEEDVOLUME_CLASS,
        FEEDVOLUME_CHANNELS,
        FEEDVOLUME_STATE,
        FEEDVOLUME_ENABLE,
        FEEDVOLUME_BYPASS
};

/* feeder_matrix */
/*enum {
        FEEDMATRIX_TYPE,
        FEEDMATRIX_RESET,
        FEEDMATRIX_CHANNELS_IN,
        FEEDMATRIX_CHANNELS_OUT,
        FEEDMATRIX_SET_MAP
};

enum {
        FEEDMATRIX_TYPE_NONE,
        FEEDMATRIX_TYPE_AUTO,
        FEEDMATRIX_TYPE_2X1,
        FEEDMATRIX_TYPE_1X2,
        FEEDMATRIX_TYPE_2X2
};

#define FEEDMATRIX_TYPE_STEREO_TO_MONO  FEEDMATRIX_TYPE_2X1
#define FEEDMATRIX_TYPE_MONO_TO_STEREO  FEEDMATRIX_TYPE_1X2
#define FEEDMATRIX_TYPE_SWAP_STEREO     FEEDMATRIX_TYPE_2X2
#define FEEDMATRIX_MAP(x, y)            ((((x) & 0x3f) << 6) | ((y) & 0x3f))
#define FEEDMATRIX_MAP_SRC(x)           ((x) & 0x3f)
#define FEEDMATRIX_MAP_DST(x)           (((x) >> 6) & 0x3f)*/

/* feeder_rate */
enum {
        FEEDRATE_SRC,
        FEEDRATE_DST,
        FEEDRATE_QUALITY,
        FEEDRATE_CHANNELS
};

#define FEEDRATE_RATEMIN        1
#define FEEDRATE_RATEMAX        2016000                /* 48000 * 42 */
#define FEEDRATE_MIN            1
#define FEEDRATE_MAX            0x7fffff  /* sign 24bit ~ 8ghz ! */
#define FEEDRATE_ROUNDHZ        0
#define FEEDRATE_ROUNDHZ_MIN    0
#define FEEDRATE_ROUNDHZ_MAX    500

struct pcm_feeder {
        void *data;
        struct pcm_feeder *source;
        struct pcm_feeder *parent;
        struct pcm_feederdesc *desc;
        struct feeder_class *class;
        feeder_init_t *init;
        feeder_free_t *free;
        feeder_set_t *set;
        feeder_get_t *get;
        feeder_feed_t *feed;
};

struct feeder_class {
        const char*            name;
        kobj_method_t*          methods;
        size_t                 size;
        int                    align;
        struct pcm_feederdesc  *desc;
        void                   *data;
};

#define FEEDER_DECLARE(feeder, pdata)                                   \
        static struct feeder_class feeder##_class = {                  \
                .name           = #feeder,                              \
                .methods        = feeder##_methods,                     \
                .size           = sizeof(struct pcm_feeder),           \
                .desc           = feeder##_desc,                        \
                .data           = pdata                                 \
        }

static void*
class_get_method(struct feeder_class *cls, const char* name)
{
    kobj_method_t * m = cls->methods;
    while (m->name) {
        if (strcmp(m->name, name) == 0)
            return m->method;
        m++;
    }
    return NULL;
}

static struct pcm_feeder*
feeder_create(struct feeder_class *cls, struct pcm_feederdesc *desc)
{
    struct pcm_feeder *f = (struct pcm_feeder *)malloc(sizeof(*f));

    f->source = NULL;
    f->parent = NULL;
    f->data = cls->data;
    f->class = cls;
    
    f->init = (feeder_init_t *)class_get_method(cls, "feeder_init");
    f->free = (feeder_free_t *)class_get_method(cls, "feeder_free");
    f->set = (feeder_set_t *)class_get_method(cls, "feeder_set");
    f->get = (feeder_get_t *)class_get_method(cls, "feeder_get");
    f->feed = (feeder_feed_t *)class_get_method(cls, "feeder_feed");
    f->desc = (struct pcm_feederdesc *)malloc(sizeof(*(f->desc)));
    if (desc != NULL)
        *(f->desc) = *desc;
    else
        f->desc->in = f->desc->out = 0xffffffffU;

    if (f->init && f->init(f)) {
        free(f);
        return NULL;
    }  
    return f;
}

static void
feeder_destroy(struct pcm_feeder *f)
{
    if (f->free)
        f->free(f);
    free(f->desc);
    free(f);
}

static void
feeder_flush(struct pcm_feeder *f)
{
        struct pcm_feeder *source;

        while (f != NULL && f->parent != NULL)
                f = f->parent;

        while (f != NULL) {
                source = f->source;
                feeder_destroy(f);
                f = source;
        }
}

static void
feeder_dump(struct pcm_feeder *f)
{
        int i;

        while (f != NULL && f->source != NULL)
                f = f->source;

        i = 0;

        while (f != NULL) {
                i++;
                fprintf(stderr, "%3d. %-2s%13s (0x%08x -> 0x%08x) %s\n", i,
                    (i == 1) ? "*" : " ", f->class->name,
                    f->desc->in, f->desc->out,
                    (f->parent != NULL) ? "+->" : "*");
                f = f->parent;
        }
}

#define FEEDER_CREATE(feeder, desc)     feeder_create(& feeder ## _class, desc)
#define FEEDER_DESTROY(feeder)          feeder_destroy(feeder)
#define FEEDER_FLUSH(feeder)            feeder_flush(feeder)
#define FEEDER_DUMP(feeder)             feeder_dump(feeder)
#define FEEDER_FEED(f, c, v, cnt, s)    (f)->feed(f, c, v, cnt, s)

int FEEDER_SET(struct pcm_feeder *, int, int);
int FEEDER_GET(struct pcm_feeder *, int);

int
FEEDER_SET(struct pcm_feeder    *f,
           int                  what,
           int                  value)
{
    if (f->set)
        return f->set(f, what, value);
    return -1;
}

int
FEEDER_GET(struct pcm_feeder    *f,
           int                  what)
{
    if (f->get)
        return f->get(f, what);
    return -1;
}

static struct pcm_feeder*
FEEDER_JOIN(struct pcm_feeder *src, struct pcm_feeder *dst, const char *fc)
{
    if (dst->desc->in != src->desc->out)
        errx(EX_DATAERR, "%s(): dst->desc->in=0x%08x != src->desc->out=0x%08x",
            fc, dst->desc->in, src->desc->out);
    if (dst->desc->out == 0xffffffff)
        dst->desc->out = dst->desc->in;
    dst->source = src;
    src->parent = dst;
    return dst;
}

struct pcm_channel {
        struct pcm_feeder *feeder;
        uint32_t feederflags;
        uint32_t bufsize;
        uint8_t *buffer;
        int direction;
        int volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX];
        FILE *in_fp, *out_fp;
};

#define PCMDIR_PLAY     1
#define PCMDIR_REC      -1

static int
chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val)
{
        int i;

        /*KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX &&
            (vc == SND_VOL_C_MASTER || (vc & 1)) &&
            (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN &&
            vt <= SND_CHN_T_END)) && (vt != SND_CHN_T_VOL_0DB ||
            (val >= SND_VOL_0DB_MIN && val <= SND_VOL_0DB_MAX)),
            ("%s(): invalid volume matrix c=%p vc=%d vt=%d val=%d",
            __func__, c, vc, vt, val));*/
        CHN_LOCKASSERT(c);

        if (val < 0)
                val = 0;
        if (val > 100)
                val = 100;

        c->volume[vc][vt] = val;

        /*
         * Do relative calculation here and store it into class + 1
         * to ease the job of feeder_volume.
         */
        if (vc == SND_VOL_C_MASTER) {
                for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END;
                    vc += SND_VOL_C_STEP)
                        c->volume[SND_VOL_C_VAL(vc)][vt] =
                            SND_VOL_CALC_VAL(c->volume, vc, vt);
        } else if (vc & 1) {
                if (vt == SND_CHN_T_VOL_0DB) {
                        for (i = 0; i != SND_CHN_T_MAX; i++)
                                c->volume[SND_VOL_C_VAL(vc)][i] =
                                    SND_VOL_CALC_VAL(c->volume, vc, i);
                } else
                        c->volume[SND_VOL_C_VAL(vc)][vt] =
                            SND_VOL_CALC_VAL(c->volume, vc, vt);
        }

        return (val);
}

#define CHN_SETVOLUME(...)              chn_setvolume_matrix(__VA_ARGS__)
#define CHN_GETVOLUME(x, y, z)          ((x)->volume[y][z])

static int
chn_init(struct pcm_channel *c, int direction, uint32_t bufsize)
{
        int i;

        bzero(c, sizeof(*c));

        c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER;
        c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_PCM;

        for (i = 0; i != SND_CHN_T_MAX; i++) {
                c->volume[SND_VOL_C_MASTER][i] = SND_VOL_0DB_MASTER;
                CHN_SETVOLUME(c, SND_VOL_C_PCM, i,
                    c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB]);
        }

        c->bufsize = bufsize;
        c->buffer = malloc(bufsize);
        c->direction = direction;
        c->in_fp = stdin;
        c->out_fp = stdout;

        if (c->buffer == NULL)
                return (ENOMEM);

        return (0);
}

static void
chn_destroy(struct pcm_channel *c)
{

        if (c == NULL)
                return;

        if (c->feeder != NULL)
                FEEDER_FLUSH(c->feeder);
        if (c->buffer != NULL)
                free(c->buffer);
        if (c->in_fp != stdin)
                fclose(c->in_fp);
        if (c->out_fp != stdout)
                fclose(c->out_fp);

        free(c);
}

static struct pcm_channel *
chn_create(int direction, uint32_t bufsize)
{
        struct pcm_channel *c;

        if (!(direction == PCMDIR_PLAY || direction == PCMDIR_REC))
                errx(EINVAL, "invalid direction: 0x%08x", direction);

        c = malloc(sizeof(*c));
        if (c != NULL && chn_init(c, direction, bufsize) != 0) {
                chn_destroy(c);
                c = NULL;
        }

        return (c);
}

#define chn_addfeeder(x, y)     chn_addfeeder_int(x, y, __func__)
static int
chn_addfeeder_int(struct pcm_channel *c, struct pcm_feeder *f, const char *fc)
{

        if (c == NULL)
                return (EINVAL);

        c->feeder = (c->feeder != NULL) ? FEEDER_JOIN(c->feeder, f, fc) : f;

        return (0);
}

#if 0
static struct pcm_feeder *
chn_findfeeder(struct pcm_channel *c, uint32_t type)
{
        struct pcm_feeder *f;

        f = c->feeder;
        while (f != NULL && f->desc != NULL) {
                if (f->desc->type == type)
                        return f;
                f = f->source;
        }

        return NULL;
}
#endif

#define chn_feed(c)                                                     \
        FEEDER_FEED((c)->feeder, c, (c)->buffer, (c)->bufsize, NULL)

#ifndef M_NOWAIT
#define M_NOWAIT 0
#endif

#ifndef M_ZERO
#define M_ZERO 1
#endif

#ifndef DSP_DEFAULT_SPEED
#define DSP_DEFAULT_SPEED 8000
#endif

#define MALLOC_DEFINE(x, y, z) static const int x = 0;
#define malloc(x, y, z) calloc(1, x)
#define free(x, y) free(x)

static __inline u_int min(u_int a, u_int b) { return ((a < b) ? a : b); }
static __inline u_int max(u_int a, u_int b) { return ((a < b) ? b : a); }

static __inline int32_t
sndbuf_zerodata(uint32_t fmt)
{
        return ((fmt & AFMT_SIGNED) ? 0x00 : 0x80);
}

/*
 * 14bit format scoring
 * --------------------
 *
 *  13  12  11  10   9   8        2        1   0    offset
 * +---+---+---+---+---+---+-------------+---+---+
 * | X | X | X | X | X | X | X X X X X X | X | X |
 * +---+---+---+---+---+---+-------------+---+---+
 *   |   |   |   |   |   |        |        |   |
 *   |   |   |   |   |   |        |        |   +--> signed?
 *   |   |   |   |   |   |        |        |
 *   |   |   |   |   |   |        |        +------> bigendian?
 *   |   |   |   |   |   |        |
 *   |   |   |   |   |   |        +---------------> total channels
 *   |   |   |   |   |   |
 *   |   |   |   |   |   +------------------------> AFMT_A_LAW
 *   |   |   |   |   |
 *   |   |   |   |   +----------------------------> AFMT_MU_LAW
 *   |   |   |   |
 *   |   |   |   +--------------------------------> AFMT_8BIT
 *   |   |   |
 *   |   |   +------------------------------------> AFMT_16BIT
 *   |   |
 *   |   +----------------------------------------> AFMT_24BIT
 *   |
 *   +--------------------------------------------> AFMT_32BIT
 */
#define score_signeq(s1, s2)    (((s1) & 0x1) == ((s2) & 0x1))
#define score_endianeq(s1, s2)  (((s1) & 0x2) == ((s2) & 0x2))
#define score_cheq(s1, s2)      (((s1) & 0xfc) == ((s2) & 0xfc))
#define score_chgt(s1, s2)      (((s1) & 0xfc) > ((s2) & 0xfc))
#define score_chlt(s1, s2)      (((s1) & 0xfc) < ((s2) & 0xfc))
#define score_val(s1)           ((s1) & 0x3f00)
#define score_cse(s1)           ((s1) & 0x7f)

static int
fmtvalid(uint32_t fmt, uint32_t *fmtlist)
{
        int i;

        for (i = 0; fmtlist[i]; i++)
                if (fmt == fmtlist[i])
                        return 1;
        return 0;
}

static uint32_t
chn_fmtscore(uint32_t fmt)
{
        uint32_t ret;

        ret = 0;
        if (fmt & AFMT_SIGNED)
                ret |= 1 << 0;
        if (fmt & AFMT_BIGENDIAN)
                ret |= 1 << 1;
        /*if (fmt & AFMT_STEREO)
                ret |= (2 & 0x3f) << 2;
        else
                ret |= (1 & 0x3f) << 2;*/
        ret |= (AFMT_CHANNEL(fmt) & 0x3f) << 2;
        if (fmt & AFMT_A_LAW)
                ret |= 1 << 8;
        else if (fmt & AFMT_MU_LAW)
                ret |= 1 << 9;
        else if (fmt & AFMT_8BIT)
                ret |= 1 << 10;
        else if (fmt & AFMT_16BIT)
                ret |= 1 << 11;
        else if (fmt & AFMT_24BIT)
                ret |= 1 << 12;
        else if (fmt & AFMT_32BIT)
                ret |= 1 << 13;

        return ret;
}

static uint32_t
chn_fmtbestfunc(uint32_t fmt, uint32_t *fmts, int cheq)
{
        uint32_t best, score, score2, oldscore;
        int i;

        if (fmt == 0 || fmts == NULL || fmts[0] == 0)
                return 0;

        if (fmtvalid(fmt, fmts))
                return fmt;

        best = 0;
        score = chn_fmtscore(fmt);
        oldscore = 0;
        for (i = 0; fmts[i] != 0; i++) {
                score2 = chn_fmtscore(fmts[i]);
                if (cheq && !score_cheq(score, score2) &&
                    (score_chlt(score2, score) ||
                    (oldscore != 0 && score_chgt(score2, oldscore))))
                                continue;
                if (oldscore == 0 ||
                            (score_val(score2) == score_val(score)) ||
                            (score_val(score2) == score_val(oldscore)) ||
                            (score_val(score2) > score_val(oldscore) &&
                            score_val(score2) < score_val(score)) ||
                            (score_val(score2) < score_val(oldscore) &&
                            score_val(score2) > score_val(score)) ||
                            (score_val(oldscore) < score_val(score) &&
                            score_val(score2) > score_val(oldscore))) {
                        if (score_val(oldscore) != score_val(score2) ||
                                    score_cse(score) == score_cse(score2) ||
                                    ((score_cse(oldscore) != score_cse(score) &&
                                    !score_endianeq(score, oldscore) &&
                                    (score_endianeq(score, score2) ||
                                    (!score_signeq(score, oldscore) &&
                                    score_signeq(score, score2)))))) {
                                best = fmts[i];
                                oldscore = score2;
                        }
                }
        }
        return best;
}

static uint32_t
chn_fmtbestbit(uint32_t fmt, uint32_t *fmts)
{
        return chn_fmtbestfunc(fmt, fmts, 0);
}

static uint32_t
chn_fmtbestchannel(uint32_t fmt, uint32_t *fmts)
{
        return chn_fmtbestfunc(fmt, fmts, 1);
}

static uint32_t
chn_fmtbest(uint32_t fmt, uint32_t *fmts)
{
        uint32_t best1, best2;
        uint32_t score, score1, score2;

        if (fmtvalid(fmt, fmts))
                return fmt;

        best1 = chn_fmtbestchannel(fmt, fmts);
        best2 = chn_fmtbestbit(fmt, fmts);

        if (best1 != 0 && best2 != 0 && best1 != best2) {
                /*if (fmt & AFMT_STEREO)*/
                if (AFMT_CHANNEL(fmt) > 1)
                        return best1;
                else {
                        score = score_val(chn_fmtscore(fmt));
                        score1 = score_val(chn_fmtscore(best1));
                        score2 = score_val(chn_fmtscore(best2));
                        if (score1 == score2 || score1 == score)
                                return best1;
                        else if (score2 == score)
                                return best2;
                        else if (score1 > score2)
                                return best1;
                        return best2;
                }
        } else if (best2 == 0)
                return best1;
        else
                return best2;
}

#endif  /* !_FEEDER_COMPAT_H_ */