/*
 * Copyright 2017 The Emscripten Authors.  All rights reserved.
 * Emscripten is available under two separate licenses, the MIT license and the
 * University of Illinois/NCSA Open Source License.  Both these licenses can be
 * found in the LICENSE file.
 */

// This test attempts to open all possible capture devices, each one
// several times with a combination of "reasonable" parameters, and
// checks some basic conformance to expectations w.r.t the spec.
//
// Wishlist:
// - Any operation a closed device should fail; 
// - Trying to open multiple devices with the same name at the same time
//   and different settings should be fine;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <unistd.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#define ASSUME_AL_FLOAT32
#endif
#include <AL/al.h>
#include <AL/alc.h>

#ifdef ASSUME_AL_FLOAT32
// We, in particular, are expected to support this, since
// this is the data type used by Web Audio.
#define AL_FORMAT_MONO_FLOAT32                   0x10010
#define AL_FORMAT_STEREO_FLOAT32                 0x10011
#endif

#define countof(x) (sizeof(x)/sizeof((x)[0]))

static int result = EXIT_SUCCESS;

static void end_test() {
#ifdef __EMSCRIPTEN__
    REPORT_RESULT(result);
#endif
    exit(result);
}

// OfflineAudioContexts are required to support sample rates ranging
// from 22050 to 96000.
// Should we test for purposefully-ugly sample rates within that range, too ?
static const ALCuint SAMPLE_RATES[] = {
    22050, 32000, 37800, 44100, 48000, 88200, 96000
};

static const ALenum FORMATS[] = {
    AL_FORMAT_MONO8, 
    AL_FORMAT_MONO16,
    AL_FORMAT_STEREO8,
    AL_FORMAT_STEREO16,
#ifdef ASSUME_AL_FLOAT32
    AL_FORMAT_MONO_FLOAT32,
    AL_FORMAT_STEREO_FLOAT32,
#endif
};

static const char* alformat_string(ALenum format) {
    switch(format) {
    #define CASE(X) case X: return #X;
    CASE(AL_FORMAT_MONO8)
    CASE(AL_FORMAT_MONO16)
    CASE(AL_FORMAT_STEREO8)
    CASE(AL_FORMAT_STEREO16)
#ifdef ASSUME_AL_FLOAT32
    CASE(AL_FORMAT_MONO_FLOAT32)
    CASE(AL_FORMAT_STEREO_FLOAT32)
#endif
    #undef CASE
    }
    return "<no_string_available>";
}

static void check_device_sanity_with_params(const char *name, ALCuint sample_rate, ALenum format, ALCsizei buffer_size) {

    printf(
        "Testing \"%s\" @%uHz with %u sample-frames (format: %s)...\n", 
        name, (unsigned)sample_rate, (unsigned) buffer_size,
        alformat_string(format)
    );

    ALCdevice* dev = alcCaptureOpenDevice(name, sample_rate, format, buffer_size);

    if(dev) {
        const char *claimed = alcGetString(dev, ALC_CAPTURE_DEVICE_SPECIFIER);
        if(strcmp(name, claimed)) {
            fprintf(stderr, "The device \"%s\" claims to be actually named \"%s\", which is not correct behavior.\n", name, claimed);
            result = EXIT_FAILURE;
        }
        ALCboolean could_close = alcCaptureCloseDevice(dev);
        if(!could_close) {
            fprintf(stderr, "alcCaptureCloseDevice() with \"%s\" failed!\n", name);
            result = EXIT_FAILURE;
        }
        return;
    }

    ALCenum err = alcGetError(dev);
    fprintf(stderr,
        "alcCaptureOpenDevice(\"%s\", sample_rate=%u, format=%s, "
        "buffer_size=%u) failed with ALC error %x (%s)\n", 
        name, (unsigned)sample_rate, alformat_string(format), 
        (unsigned) buffer_size,
        (unsigned) err, alcGetString(NULL, err)
    );
    result = EXIT_FAILURE;
}

static void check_device_sanity(const char *name) {
    for(int si=0 ; si<countof(SAMPLE_RATES) ; ++si) {
        for(int fi=0 ; fi<countof(FORMATS) ; ++fi) {
            // 8 seconds of data
            check_device_sanity_with_params(
                name, SAMPLE_RATES[si], FORMATS[fi], 8*SAMPLE_RATES[si]
            );
        }
    }
}

static bool is_defaultname_in_names(const char *dft, const char *names) {
    for(const char *name = names; *name ; name += 1+strlen(name)) {
        if(!strcmp(dft, name))
            return true;
    }
    return false;
}

int main() {

    const char *dft = alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
    const char *names = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);

    if(!is_defaultname_in_names(dft, names)) {
        printf(
            "The default device specifier (\"%s\") was not found amongst the device specifier list : ...\n"
            , dft
        );
        for(const char *name = names; *name ; name += 1+strlen(name)) {
            printf("- \"%s\";\n", name);
        }
        printf("... this is not an error, though.\n\n");
    }

    for(const char *name = names; *name ; name += 1+strlen(name)) {
        check_device_sanity(name);
    }

    end_test();
}
