/*****************************************************************
|
|      Ogg/Vorbis Codec for Xaudio
|      This is a wrapper for the libvorbis implementation
|
|      (c) 1996-2001 Xaudio Corporation
|      Author: Gilles Boccon-Gibod (gilles@xaudio.com)
|
 ****************************************************************/

/*----------------------------------------------------------------------
|       includes
+---------------------------------------------------------------------*/
#include <stdlib.h>
#include <string.h>

#include "decoder.h"
#include "xaudio.h"
#include "vorbis_codec.h"

#include "vorbis/codec.h"
#include "vorbis/vorbisfile.h"

/*----------------------------------------------------------------------
|       constants
+---------------------------------------------------------------------*/
#define VORBIS_CODEC_BUFFER_SIZE     1024  /* samples */
#define VORBIS_CODEC_VERSION_STRING  "1.0"

/*----------------------------------------------------------------------
|       macros
+---------------------------------------------------------------------*/
#define strings_equal(s1,s2) (!strcmp(s1,s2))

/*----------------------------------------------------------------------
|       types and classes
+---------------------------------------------------------------------*/
class VorbisCodec {
public:
    VorbisCodec(XA_DecoderInfo *decoder);
    virtual ~VorbisCodec();
    XA_DecoderInfo *m_Decoder;
    const char     *m_MimeType;
    OggVorbis_File  m_VorbisFile;
    unsigned long   m_Offset;
    unsigned long   m_Size;
    unsigned int    m_Frequency;
    unsigned int    m_Bitrate;
    unsigned int    m_NbChannels;
    unsigned long   m_Duration;
    bool            m_LocalSeek;
};

struct XA_CodecInstance : public VorbisCodec
{
    XA_CodecInstance(XA_DecoderInfo *decoder) : VorbisCodec(decoder) {};
};

/*----------------------------------------------------------------------
|       VorbisCodec contructor
+---------------------------------------------------------------------*/
VorbisCodec::VorbisCodec(XA_DecoderInfo *decoder) :
    m_Decoder(decoder),
    m_MimeType("audio/x-vorbis"),
    m_Offset(0),
    m_Size(0),
    m_Frequency(0),
    m_Bitrate(0),
    m_NbChannels(0),
    m_Duration(0),
    m_LocalSeek(false)
{
};

/*----------------------------------------------------------------------
|       VorbisCodec destructor
+---------------------------------------------------------------------*/
VorbisCodec::~VorbisCodec() {
};

/*----------------------------------------------------------------------
|       codec_module_probe
+---------------------------------------------------------------------*/
static int
codec_module_probe(const char *mime_type, const char *name)
{
    /* look for out mime type */
    if (strings_equal(mime_type, "audio/x-vorbis") ||
        strings_equal(mime_type, "audio/x-ogg")) {
        return XA_SUCCESS;
    }

    /* look for our extension */
    if (strings_equal(mime_type, "audio/unknown") ||
        strings_equal(mime_type, "audio")         ||
        strings_equal(mime_type, "unknown")) {
        /* find extension */
        const char *ptr = name;
        const char *ext = NULL;
        while (*ptr) {
            if ((*ptr++) == '.') ext = ptr;
        }
        if (ext) {
            if (strings_equal(ext, "ogg")) {
                return XA_SUCCESS;
            }
        }
    }

    /* does not belong to us */
    return XA_FAILURE;
}

static int
codec_module_get_info(XA_CodecModuleInfo *info)
{
    strcpy((char*)info->name, "Ogg/Vorbis Audio Codec");
    info->version = 0;
    info->caps = 0;
    info->min_buffer_size = 4096;

    return XA_SUCCESS;
}

/*----------------------------------------------------------------------
|       codec_module_query
+---------------------------------------------------------------------*/
//#####static int
//#####codec_module_query(XA_CodecModuleQuery *query, unsigned long query_mask)
//#####{
//#####    if (query_mask & XA_DECODER_CODEC_QUERY_MODULE_NAME) {
//#####        strcpy(query->name, "Ogg/Vorbis Audio Codec");
//#####        strcpy(query->description, "Version " VORBIS_CODEC_VERSION_STRING);
//#####    }
//#####
//#####    if (query_mask & XA_DECODER_CODEC_QUERY_NB_DEVICES) {
//#####        query->index = 1;
//#####    }
//#####
//#####    /* if we're asked for the module name, stop here */
//#####    if (query_mask & XA_DECODER_CODEC_QUERY_MODULE_NAME) {
//#####        query->flags = 0;
//#####        return XA_SUCCESS;
//#####    }
//#####
//#####    if (query_mask & XA_DECODER_CODEC_QUERY_DEVICE_NAME) {
//#####        switch (query->index) {
//#####            default: return XA_ERROR_INVALID_PARAMETERS;
//#####          case 0:
//#####            strcpy(query->name, "Ogg/Vorbis");
//#####            strcpy(query->description, "[Wrapper for libvorbis]");
//#####            query->flags = 0;
//#####            break;
//#####        }
//#####    }
//#####
//#####    if (query_mask & XA_DECODER_CODEC_QUERY_BUFFER_SIZE) {
//#####        query->min_buffer_size = 4096;
//#####    }
//#####
//#####    if (query_mask & XA_DECODER_CODEC_QUERY_CAPS) {
//#####        query->flags = 0;
//#####    }
//#####
//#####    return XA_SUCCESS;
//#####}

/*----------------------------------------------------------------------
|       codec_new
+---------------------------------------------------------------------*/
static int
codec_new(XA_CodecInstance       **codec,
          const char              *mime_type,
          const char              *name,
          XA_CodecModuleClassInfo *class_info,
          XA_DecoderInfo          *decoder)
{
    /* allocate a codec object */
    try {
        *codec = (XA_CodecInstance *)new VorbisCodec(decoder);
    } catch (...) {
        *codec = NULL;
        return XA_FAILURE;
    }

    return XA_SUCCESS;
}

/*----------------------------------------------------------------------
|       codec_delete
+---------------------------------------------------------------------*/
static int
codec_delete(XA_CodecInstance *codec)
{
    delete codec;

    return XA_SUCCESS;
}

/*----------------------------------------------------------------------
|       codec_decode
+---------------------------------------------------------------------*/
static int
codec_decode(XA_CodecInstance    *codec,
             XA_OutputBufferInfo *buffer,
             XA_StreamInfo       *info)
{
    int current_section;
    int result;

    /* mime type is always the same */
    if (info->mime_type != codec->m_MimeType) {
        info->mime_type = codec->m_MimeType;
        info->changed |= XA_DECODER_STREAM_MIME_TYPE_CHANGED;
    }

    result = ov_read(&codec->m_VorbisFile,
                     (char *)buffer->pcm_samples,
                     VORBIS_CODEC_BUFFER_SIZE * codec->m_NbChannels * 2,
                     0,
                     2,
                     1,
                     &current_section);
    if (result == 0) {
        return XA_ERROR_INPUT_EOS;
    } else if (result == OV_HOLE) {
        return XA_ERROR_INVALID_FRAME;
    } else if (result < 0) {
        return XA_FAILURE;
    }

    /* set the buffer parameters */
    buffer->nb_samples       = result/(codec->m_NbChannels * 2);
    buffer->bytes_per_sample = 2;
    buffer->nb_channels      = codec->m_NbChannels;
    buffer->sample_rate      = codec->m_Frequency;

    return XA_SUCCESS;
}

/*----------------------------------------------------------------------
|       codec_get_stream_info
+---------------------------------------------------------------------*/
static int
codec_get_stream_info(XA_CodecInstance *codec,
                      XA_StreamInfo    *info)
{
    /* mime type */
    info->mime_type  = codec->m_MimeType;
    info->changed   |= XA_DECODER_STREAM_MIME_TYPE_CHANGED;

    info->bitrate     = codec->m_Bitrate;
    info->frequency   = codec->m_Frequency;
    info->nb_channels = codec->m_NbChannels;
    info->duration    = codec->m_Duration;
    info->changed |= XA_DECODER_STREAM_PARAMETERS_CHANGED;

    return XA_FAILURE;
}

/*----------------------------------------------------------------------
|       codec_read_callback
+---------------------------------------------------------------------*/
size_t
codec_read_callback(void *ptr, size_t size, size_t nbelem, void *datasource)
{
    VorbisCodec  *codec = (VorbisCodec *)datasource;
    unsigned long requested;
    int           result;

    requested = size*nbelem;
    if (requested > codec->m_Size - codec->m_Offset) {
        requested = codec->m_Size - codec->m_Offset;
    }
    result = codec->m_Decoder->callbacks.input_read(codec->m_Decoder,
                                                    ptr,
                                                    requested);
    if (result == XA_ERROR_INPUT_EOS) {
        return 0;
    } else if (result < 0) {
      return /*-1*/0; /* return EOS on error ? */
    } else {
        codec->m_Offset += size*nbelem;
        return nbelem;
    }
}

/*----------------------------------------------------------------------
|       codec_seek_callback
+---------------------------------------------------------------------*/
int
codec_seek_callback(void *datasource, ogg_int64_t offset, int whence)
{
    VorbisCodec *codec = (VorbisCodec *)datasource;
    int          result;

    if (whence == SEEK_CUR) {
        offset += codec->m_Offset;
    } else if (whence == SEEK_END) {
        offset = codec->m_Size - offset;
        if (offset < 0) offset = 0;
    }
    result = codec->m_Decoder->callbacks.input_seek(codec->m_Decoder,
                                                    (long)offset);
    if (result < 0) {
        return -1;
    } else {
        codec->m_Offset = (unsigned long)offset;
        return 0;
    }
}

/*----------------------------------------------------------------------
|       codec_close_callback
+---------------------------------------------------------------------*/
int
codec_close_callback(void *datasource)
{
    /* ignore */
    return 0;
}

/*----------------------------------------------------------------------
|       codec_tell_callback
+---------------------------------------------------------------------*/
long
codec_tell_callback(void *datasource)
{
    VorbisCodec *codec = (VorbisCodec *)datasource;

    return codec->m_Offset;
}

/*----------------------------------------------------------------------
|       codec_notify_input_open
+---------------------------------------------------------------------*/
static int
codec_notify_input_open(XA_CodecInstance *codec,
                        const char       *mime_type,
                        const char       *name)
{
    ov_callbacks callbacks;
    int          result;

    /* reset offset and size */
    codec->m_Offset = 0;
    codec->m_Size   = codec->m_Decoder->input->size;

    /* setup callbacks */
    callbacks.read_func  = codec_read_callback;
    callbacks.seek_func  = codec_seek_callback;
    callbacks.close_func = codec_close_callback;
    callbacks.tell_func  = codec_tell_callback;

    /* initialize the vorbis file structure */
    codec->m_LocalSeek = true;
    result = ov_open_callbacks(codec,
                               &codec->m_VorbisFile,
                               NULL,
                               0,
                               callbacks);
    codec->m_LocalSeek = false;
    if (result < 0) {
        return XA_ERROR_UNSUPPORTED_FORMAT;
    }

    /* get info about the stream */
    codec->m_LocalSeek = true;
    vorbis_info *info   = ov_info(&codec->m_VorbisFile, -1);
    codec->m_Frequency  = info->rate;
    codec->m_NbChannels = info->channels;
    codec->m_Bitrate    = ov_bitrate(&codec->m_VorbisFile, -1)/1000;
    if (codec->m_Frequency) {
        codec->m_Duration = (long)(1000.0f*
             (float)ov_pcm_total(&codec->m_VorbisFile,-1)/(float)info->rate);
    }
    codec->m_LocalSeek = false;

    return XA_SUCCESS;
}

/*----------------------------------------------------------------------
|       codec_notify_input_close
+---------------------------------------------------------------------*/
static int
codec_notify_input_close(XA_CodecInstance *codec)
{
    ov_clear(&codec->m_VorbisFile);
    return XA_SUCCESS;
}

/*----------------------------------------------------------------------
|       codec_notify_input_seek
+---------------------------------------------------------------------*/
static int
codec_notify_input_seek(XA_CodecInstance *codec,
                        unsigned long offset)
{
    if (!codec->m_LocalSeek) {
        codec->m_LocalSeek = true;
        ov_raw_seek(&codec->m_VorbisFile, codec->m_Decoder->input->offset);
        codec->m_LocalSeek = false;
    }

    return XA_SUCCESS;
}

/*----------------------------------------------------------------------
|       vorbis_codec_module_register
+---------------------------------------------------------------------*/
extern "C" int XA_EXPORT
vorbis_codec_module_register(XA_CodecModule *module)
{

    module->api_version_id            = XA_SYNC_API_VERSION;
    module->codec_module_probe        = codec_module_probe;
    /* module->codec_module_query        = codec_module_query; */
    module->codec_module_get_info     = codec_module_get_info;
    module->codec_new                 = codec_new;
    module->codec_delete              = codec_delete;
    module->codec_decode              = codec_decode;
    module->codec_get_stream_info     = codec_get_stream_info;
    module->codec_get_seek_position   = NULL;
    module->codec_get_stream_position = NULL;
    module->codec_notify_input_open   = codec_notify_input_open;
    module->codec_notify_input_close  = codec_notify_input_close;
    module->codec_notify_input_seek   = codec_notify_input_seek;
    module->codec_set_config          = NULL;
    module->codec_get_config          = NULL;
    module->codec_send_message        = NULL;

    return XA_SUCCESS;
}
