/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * 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 requied 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.
 *
 */

#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <gtest/gtest.h>
#include <linux/ioctl.h>
#include <sound/asound.h>
#include <sys/types.h>
#include <tinyalsa/asoundlib.h>

#define LOG_TAG "pcmtest"
#include <utils/Log.h>

#define PCM_PREFIX	"pcm"
#define MIXER_PREFIX	"control"
#define TIMER_PREFIX	"timer"

#define MAXSTR 200
#define testPrintI(...)                 \
    do {                                \
        testPrint(stdout, __VA_ARGS__); \
    } while (0)

const char kSoundDir[] = "/dev/snd";

typedef struct PCM_NODE {
    unsigned int card;
    unsigned int device;
    unsigned int flags;
} pcm_node_t;

static pcm_node_t *pcmnodes;

static unsigned int pcms;
static unsigned int cards;
static unsigned int mixers;
static unsigned int timers;

void testPrint(FILE* stream, const char* fmt, ...) {
    char line[MAXSTR];
    va_list args;

    va_start(args, fmt);
    vsnprintf(line, sizeof(line), fmt, args);
    if (stream == stderr) {
        ALOG(LOG_ERROR, LOG_TAG, "%s", line);
    } else {
        ALOG(LOG_INFO, LOG_TAG, "%s", line);
    }
    vfprintf(stream, fmt, args);
    va_end(args);
    fputc('\n', stream);
}

unsigned int getPcmNodes(void)
{
    DIR *d;
    struct dirent *de;
    unsigned int pcount = 0;

    d = opendir(kSoundDir);
    if (d == 0)
        return 0;
    while ((de = readdir(d)) != NULL) {
        if (de->d_name[0] == '.')
            continue;
        if (strstr(de->d_name, PCM_PREFIX))
            pcount++;
    }
    closedir(d);
    return pcount;
}

int getSndDev(unsigned int pcmdevs)
{
    DIR *d;
    struct dirent *de;
    unsigned int prevcard = -1;

    d = opendir(kSoundDir);
    if (d == 0)
        return -ENXIO;
    pcmnodes = (pcm_node_t *)malloc(pcmdevs * sizeof(pcm_node_t));
    if (!pcmnodes)
        return -ENOMEM;
    pcms = 0;
    while ((de = readdir(d)) != NULL) {
        if (de->d_name[0] == '.')
            continue;
        /* printf("%s\n", de->d_name); */
        if (strstr(de->d_name, PCM_PREFIX)) {
            char flags;

            EXPECT_LE(pcms, pcmdevs) << "Too many PCMs";
            if (pcms >= pcmdevs)
                continue;
            sscanf(de->d_name, PCM_PREFIX "C%uD%u", &(pcmnodes[pcms].card),
                   &(pcmnodes[pcms].device));
            flags = de->d_name[strlen(de->d_name)-1];
            if (flags == 'c') {
                pcmnodes[pcms].flags = PCM_IN;
            } else if(flags == 'p') {
                pcmnodes[pcms].flags = PCM_OUT;
            } else {
                pcmnodes[pcms].flags = -1;
                testPrintI("Unknown PCM type = %c", flags);
            }
            if (prevcard != pcmnodes[pcms].card)
                cards++;
            prevcard = pcmnodes[pcms].card;
            pcms++;
            continue;
        }
        if (strstr(de->d_name, MIXER_PREFIX)) {
            unsigned int mixer = -1;
            sscanf(de->d_name, MIXER_PREFIX "C%u", &mixer);
            mixers++;
            continue;
        }
        if (strstr(de->d_name, TIMER_PREFIX)) {
            timers++;
            continue;
        }
    }
    closedir(d);
    return 0;
}

int getPcmParams(unsigned int i)
{
    struct pcm_params *params;
    unsigned int min;
    unsigned int max;

    params = pcm_params_get(pcmnodes[i].card, pcmnodes[i].device,
                            pcmnodes[i].flags);
    if (params == NULL)
        return -ENODEV;

    min = pcm_params_get_min(params, PCM_PARAM_RATE);
    max = pcm_params_get_max(params, PCM_PARAM_RATE);
    EXPECT_LE(min, max);
    /* printf("        Rate:\tmin=%uHz\tmax=%uHz\n", min, max); */
    min = pcm_params_get_min(params, PCM_PARAM_CHANNELS);
    max = pcm_params_get_max(params, PCM_PARAM_CHANNELS);
    EXPECT_LE(min, max);
    /* printf("    Channels:\tmin=%u\t\tmax=%u\n", min, max); */
    min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS);
    max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS);
    EXPECT_LE(min, max);
    /* printf(" Sample bits:\tmin=%u\t\tmax=%u\n", min, max); */
    min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE);
    max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE);
    EXPECT_LE(min, max);
    /* printf(" Period size:\tmin=%u\t\tmax=%u\n", min, max); */
    min = pcm_params_get_min(params, PCM_PARAM_PERIODS);
    max = pcm_params_get_max(params, PCM_PARAM_PERIODS);
    EXPECT_LE(min, max);
    /* printf("Period count:\tmin=%u\t\tmax=%u\n", min, max); */

    pcm_params_free(params);
    return 0;
}

TEST(pcmtest, CheckAudioDir) {
    pcms = getPcmNodes();
    ASSERT_GT(pcms, 0U);
}

TEST(pcmtest, GetSoundDevs) {
    int err = getSndDev(pcms);
    testPrintI(" DEVICES = PCMS:%u CARDS:%u MIXERS:%u TIMERS:%u",
               pcms, cards, mixers, timers);
    ASSERT_EQ(0, err);
}

TEST(pcmtest, CheckPcmSanity0) {
    ASSERT_NE(0U, pcms);
}

TEST(pcmtest, CheckPcmSanity1) {
    EXPECT_NE(1U, pcms % 2);
}

TEST(pcmtests, CheckMixerSanity) {
    ASSERT_NE(0U, mixers);
    ASSERT_EQ(mixers, cards);
}

TEST(pcmtest, CheckTimesSanity0) {
    ASSERT_NE(0U, timers);
}

TEST(pcmtest, CheckTimesSanity1) {
    EXPECT_EQ(1U, timers);
}

TEST(pcmtest, CheckPcmDevices) {
    for (unsigned int i = 0; i < pcms; i++) {
        EXPECT_EQ(0, getPcmParams(i));
    }
    free(pcmnodes);
}

TEST(pcmtest, CheckMixerDevices) {
    struct mixer *mixer;
    for (unsigned int i = 0; i < mixers; i++) {
         mixer = mixer_open(i);
         EXPECT_TRUE(mixer != NULL);
         if (mixer)
             mixer_close(mixer);
    }
}

TEST(pcmtest, CheckTimer) {
    int ver = 0;
    int fd = open("/dev/snd/timer", O_RDWR | O_NONBLOCK);
    ASSERT_GE(fd, 0);
    int ret = ioctl(fd, SNDRV_TIMER_IOCTL_PVERSION, &ver);
    EXPECT_EQ(0, ret);
    testPrintI(" Timer Version = 0x%x", ver);
    close(fd);
}
