/* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* ====================================================================
 * Copyright (c) 2008 Carnegie Mellon University.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. 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.
 *
 * This work was supported in part by funding from the Defense Advanced 
 * Research Projects Agency and the National Science Foundation of the 
 * United States of America, and the CMU Sphinx Speech Consortium.
 *
 * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND 
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
 * NOR ITS EMPLOYEES 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.
 *
 * ====================================================================
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <pocketsphinx.h>
#include "util/byteorder.h"
#include "util/ckd_alloc.h"
#include "util/cmd_ln.h"
#include "util/pio.h"

#define TRY_FREAD(ptr, size, nmemb, stream)                             \
    if (fread(ptr, size, nmemb, stream) != (nmemb)) {                   \
        E_ERROR_SYSTEM("Failed to read %d bytes", size * nmemb);        \
        rv = -1;                                                        \
        goto error_out;                                                 \
    }

static int
arg_was_set(ps_config_t *config, const char *name)
{
    uint32 i;
    cmd_ln_t *cmdln = (cmd_ln_t *)config;
    for (i = 0; i < cmdln->f_argc; i++) {
        if (cmdln->f_argv[i] && cmdln->f_argv[i][0] == '-') {
            const char *arg = cmdln->f_argv[i] + 1;
            if (*arg == '-') arg++;
            if (0 == strcmp(arg, name))
                return 1;
        }
    }
    return 0;
}

int
ps_config_soundfile(ps_config_t *config, FILE *infh, const char *file)
{
    char header[4];
    int rv = 0;

    if (file == NULL)
        file = "(input filehandle)";
    fseek(infh, 0, SEEK_SET);
    TRY_FREAD(header, 1, 4, infh);
    fseek(infh, 0, SEEK_SET);

    if (0 == memcmp(header, "RIFF", 4)) {
        E_INFO("%s appears to be a WAV file\n", file);
        rv = ps_config_wavfile(config, infh, file);
    }
    else if (0 == memcmp(header, "NIST", 4)) {
        E_INFO("%s appears to be a NIST SPHERE file\n", file);
        rv = ps_config_nistfile(config, infh, file);
    }
    else if (0 == memcmp(header, "OggS", 4)) {
        E_INFO("%s appears to be an UNSUPPORTED Ogg file\n", file);
        rv = -1;
        goto error_out;
    }
    else if (0 == memcmp(header, "fLaC", 4)) {
        E_INFO("%s appears to be an UNSUPPORTED FLAC file\n", file);
        rv = -1;
        goto error_out;
    }
    else if (0 == memcmp(header, "\xff\xff\xff", 3)
             || 0 == memcmp(header, "\xff\xff\xfe", 3)) {
        E_INFO("%s might be an MP3 file, but who knows really! "
               "UNSUPPORTED!\n", file);
        rv = -1;
        goto error_out;
    }
    else {
        E_INFO("%s appears to be raw data\n", file);
    }

error_out:
    return rv;
}

int
ps_config_wavfile(ps_config_t *config, FILE *infh, const char *file)
{
    char id[4];
    int32 intval, header_len;
    int16 shortval;
    int rv = 0;

    if (file == NULL)
        file = "(input filehandle)";
        
    /* RIFF files are little-endian by definition. */
    ps_config_set_str(config, "input_endian", "little");

    /* Read in all the header chunks and etcetera. */
    TRY_FREAD(id, 1, 4, infh);
    /* Total file length (we don't care) */
    TRY_FREAD(&intval, 4, 1, infh);
    /* 'WAVE' */
    TRY_FREAD(id, 1, 4, infh);
    if (0 != memcmp(id, "WAVE", 4)) {
        E_ERROR("%s is not a WAVE file\n", file);
        rv = -1;
        goto error_out;
    }
    /* 'fmt ' */
    TRY_FREAD(id, 1, 4, infh);
    if (0 != memcmp(id, "fmt ", 4)) {
        E_ERROR("Format chunk missing\n");
        rv = -1;
        goto error_out;
    }
    /* Length of 'fmt ' chunk */
    TRY_FREAD(&intval, 4, 1, infh);
    SWAP_LE_32(&intval);
    header_len = intval;  /* Should be 16 */

    /* Data format. */
    TRY_FREAD(&shortval, 2, 1, infh);
    SWAP_LE_16(&shortval);
    if (shortval != 1) { /* PCM */
        E_ERROR("%s is not in PCM format\n", file);
        rv = -1;
        goto error_out;
    }

    /* Number of channels. */
    TRY_FREAD(&shortval, 2, 1, infh);
    SWAP_LE_16(&shortval);
    if (shortval != 1) { /* PCM */
        E_ERROR("%s is not single channel\n", file);
        rv = -1;
        goto error_out;
    }

    /* Sampling rate (finally!) */
    TRY_FREAD(&intval, 4, 1, infh);
    SWAP_LE_32(&intval);
    if (!arg_was_set(config, "samprate")) {
        ps_config_set_int(config, "samprate", intval);
    } else if (ps_config_int(config, "samprate") != intval) {
        E_WARN("WAVE file sampling rate %d != samprate %d\n",
               intval, ps_config_int(config, "samprate"));
    }

    /* Average bytes per second (we don't care) */
    TRY_FREAD(&intval, 4, 1, infh);

    /* Block alignment (we don't care) */
    TRY_FREAD(&shortval, 2, 1, infh);

    /* Bits per sample (must be 16) */
    TRY_FREAD(&shortval, 2, 1, infh);
    SWAP_LE_16(&shortval);
    if (shortval != 16) {
        E_ERROR("%s is not 16-bit\n", file);
        rv = -1;
        goto error_out;
    }

    /* Any extra parameters. */
    if (header_len > 16) {
        /* We might be reading standard input, so we can't use fseek.
           On the other hand, a malicious actor may have passed some
           totally implausible value for the header length, so we will
           read 4k chunks and throw them away.  */
        if (fread_skip(header_len - 16, infh) != (size_t)(header_len - 16)) {
            E_ERROR_SYSTEM("%s: Failed to read %d byte fmt chunk", file, header_len);
            rv = -1;
            goto error_out;
        }
    }

    /* Now skip to the 'data' chunk. */
    while (1) {
        TRY_FREAD(id, 1, 4, infh);
        if (0 == memcmp(id, "data", 4)) {
            /* Total number of bytes of data (we don't care). */
            TRY_FREAD(&intval, 4, 1, infh);
            break;
        }
        else {
            TRY_FREAD(&intval, 4, 1, infh);
            SWAP_LE_32(&intval);
            /* As above, skip over data without seeking. */
            if (fread_skip(intval, infh) != (size_t)intval) {
                E_ERROR_SYSTEM("%s: Failed to read %d byte %c%c%c%c chunk",
                               file, intval, id[0], id[1], id[2], id[3]);
                rv = -1;
                goto error_out;
            }
        }
    }

error_out:
    return rv;
}

int
ps_config_nistfile(ps_config_t *config, FILE *infh, const char *file)
{
    char hdr[1024];
    char *line, *c;
    int rv = 0;

    if (file == NULL)
        file = "(input filehandle)";

    TRY_FREAD(hdr, 1, 1024, infh);
    hdr[1023] = '\0';

    /* Roughly parse it to find the sampling rate and byte order
     * (don't bother with other stuff) */
    if ((line = strstr(hdr, "sample_rate")) == NULL) {
        E_ERROR("No sampling rate in NIST header!\n");
        rv = -1;
        goto error_out;
    }
    c = strchr(line, '\n');
    if (c) *c = '\0';
    c = strrchr(line, ' ');
    if (c == NULL) {
        E_ERROR("Could not find sampling rate!\n");
        rv = -1;
        goto error_out;
    }
    ++c;
    if (ps_config_int(config, "samprate") == 0)
        ps_config_set_int(config, "samprate", atoi(c));
    else if (ps_config_int(config, "samprate") != atoi(c)) {
        E_WARN("NIST file sampling rate %d != samprate %d\n",
               atoi(c), ps_config_int(config, "samprate"));
    }

    if (line + strlen(line) < hdr + 1023)
        line[strlen(line)] = ' ';
    if ((line = strstr(hdr, "sample_byte_format")) == NULL) {
        E_ERROR("No sample byte format in NIST header!\n");
        rv = -1;
        goto error_out;
    }
    c = strchr(line, '\n');
    if (c) *c = '\0';
    c = strrchr(line, ' ');
    if (c == NULL) {
        E_ERROR("Could not find sample byte order!\n");
        rv = -1;
        goto error_out;
    }
    ++c;
    if (0 == memcmp(c, "01", 2)) {
        ps_config_set_str(config, "input_endian", "little");
    }
    else if (0 == memcmp(c, "10", 2)) {
        ps_config_set_str(config, "input_endian", "big");
    }
    else {
        E_ERROR("Unknown byte order %s\n", c);
        rv = -1;
        goto error_out;
    }

error_out:
    return rv;
}
