/*
 * GStreamer
 * Copyright (C) 2005 Thomas Vander Stichele <thomas@apestaart.org>
 * Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
 * Copyright (C) YEAR AUTHOR_NAME AUTHOR_EMAIL
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Alternatively, the contents of this file may be used under the
 * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
 * which case the following provisions apply instead of the ones
 * mentioned above:
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

 /**
 * SECTION:element-openalsrc
 * @short_description: record sound from your sound card using OpenAL
 *
 * <refsect2>
 * <para>
 * This element lets you record sound using the OpenAL
 * </para>
 * <title>Example pipelines</title>
 * <para>
 * <programlisting>
 * gst-launch -v openalsrc ! audioconvert ! vorbisenc ! oggmux ! filesink location=mymusic.ogg
 * </programlisting>
 * will record sound from your sound card using OpenAL and encode it to an Ogg/Vorbis file
 * </para>
 * </refsect2>
 */

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

#include <gst/gst.h>
#include <gst/gsterror.h>

#include "gstopenalsrc.h"

GST_DEBUG_CATEGORY_STATIC(gst_openal_src_debug);
#define GST_CAT_DEFAULT gst_openal_src_debug

/// Default device
#define DEFAULT_DEVICE              NULL
/// Default name of device
#define DEFAULT_DEVICE_NAME  ""

/**
    Filter signals and args
**/
enum {
    /* FILL ME */
    LAST_SIGNAL
};

/**
    Properties
**/
enum {
    PROP_0,
    PROP_DEVICE,
    PROP_DEVICE_NAME
};

/* the capabilities of the inputs and outputs.
 *
 * describe the real formats here.
 */

/**
    Source pad factory
**/
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE (
    "src", // name
    GST_PAD_SRC, // direction
    GST_PAD_ALWAYS, // existence
    GST_STATIC_CAPS (
        "audio/x-raw-int, "
        "endianness = (int) { " G_STRINGIFY (G_BYTE_ORDER) " }, "
        "signed = (boolean) { TRUE, FALSE }, "
        "width = (int) 16, "
        "depth = (int) 16, "
        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]; "

        "audio/x-raw-int, "
        "signed = (boolean) { TRUE, FALSE }, "
        "width = (int) 8, "
        "depth = (int) 8, "
        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]"
    )
    //GST_STATIC_CAPS ("ANY") // supported types
);

// Setup GObject basic
GST_BOILERPLATE(
    GstOpenalSrc,
    gst_openal_src,
    GstAudioSrc,
    GST_TYPE_AUDIO_SRC
);

/**
    @brief Set property
    @param Pointer of object to set property
    @param Enum id of property
    @param Value to set
    @param ???
**/
static void gst_openal_src_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);

/**
    @brief Get property
    @param Pointer of object to get property
    @param Enum id of property
    @param Value to get
    @param ???
**/
static void gst_openal_src_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

/**
    @brief Set caps
    @param Pointer of pad to set caps
    @param Caps to set
**/
/*
static gboolean gst_openal_src_set_caps (GstPad * pad, GstCaps * caps);*/

/**
    @brief Called to handle start event
    @param Pointer of base src
**/
static gboolean
gst_openal_src_start (GstBaseSrc * basesrc) ;

/**
    @brief Called to handle stop event
    @param Pointer of base src
**/
static gboolean
gst_openal_src_stop (GstBaseSrc * basesrc) ;

/**
    @brief open the device with given specs
    @param Pointer of GsAudioSrc
**/
static gboolean
gst_openal_src_open (GstAudioSrc *src);

/**
    @brief prepare resources and state to operate with the given specs
    @param Pointer of GsAudioSrc
    @param Sepcification of ring buffer
**/
static gboolean
gst_openal_src_prepare (GstAudioSrc *src, GstRingBufferSpec *spec);

/**
    @brief undo anything that was done in prepare()
    @param Pointer of GsAudioSrc
**/
static gboolean
gst_openal_src_unprepare (GstAudioSrc *src);

/**
    @brief close the device
    @param Pointer of GsAudioSrc
**/
static gboolean
gst_openal_src_close (GstAudioSrc *src);

/**
    @brief read samples from the device
    @param Pointer of GsAudioSrc
    @param Pointer of data to output
    @param Length of data to output
**/
static guint
gst_openal_src_read (GstAudioSrc *src, gpointer data, guint length);

/**
    @brief get number of samples queued in the device
    @param Pointer of GsAudioSrc
    @return Number of samples
**/
static guint
gst_openal_src_delay (GstAudioSrc *src);
/**
    @brief reset the audio device, unblock from a write
    @param Pointer of GsAudioSrc
**/
static void
gst_openal_src_reset (GstAudioSrc *src);

/* GObject vmethod implementations */

/**
    @brief Initialize base of plugin
    @param Pointer of gclass to initiailze
**/
static void
gst_openal_src_base_init (gpointer gclass)
{
    GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);

    gst_element_class_set_details_simple(
        element_class,
        "OpenAL src", // name of plugin
        "Generic", // type
        "OpenAL source capture audio from device", //brief
        "Victor Lin <bornstub@gmail.com>" // author
    );

    // register pads
    gst_element_class_add_pad_template(
        element_class,
        gst_static_pad_template_get (&src_factory)
    );
}

/**
    @brief Initialize class of plugin
    @param Pointer of GstOpenalSrcClass to initialize
**/
static void
gst_openal_src_class_init (GstOpenalSrcClass * klass)
{
    GObjectClass *gobject_class;
    GstElementClass *gstelement_class;
    GstBaseSrcClass *gstbase_src_class;
    GstAudioSrcClass *gstaudio_src_class;

    gobject_class = G_OBJECT_CLASS(klass);
    gstelement_class = GST_ELEMENT_CLASS(klass);
    gstbase_src_class = GST_BASE_SRC_CLASS(klass);
    gstaudio_src_class = GST_AUDIO_SRC_CLASS(klass);

    // set property getter and setter
    gobject_class->set_property = gst_openal_src_set_property;
    gobject_class->get_property = gst_openal_src_get_property;

    gstaudio_src_class->open = GST_DEBUG_FUNCPTR(gst_openal_src_open);
    gstaudio_src_class->prepare = GST_DEBUG_FUNCPTR(gst_openal_src_prepare);
    gstaudio_src_class->unprepare = GST_DEBUG_FUNCPTR(gst_openal_src_unprepare);
    gstaudio_src_class->close = GST_DEBUG_FUNCPTR(gst_openal_src_close);
    gstaudio_src_class->read = GST_DEBUG_FUNCPTR(gst_openal_src_read);
    gstaudio_src_class->delay = GST_DEBUG_FUNCPTR(gst_openal_src_delay);
    gstaudio_src_class->reset = GST_DEBUG_FUNCPTR(gst_openal_src_reset);

    // install all properties
    g_object_class_install_property (
        gobject_class,
        PROP_DEVICE,
        g_param_spec_string(
            "device",
            "Device",
            "Specific capture device to open, NULL indicate default device",
            DEFAULT_DEVICE, G_PARAM_READWRITE
        )
    );

    g_object_class_install_property (
        gobject_class,
        PROP_DEVICE_NAME,
        g_param_spec_string(
            "device-name",
            "Device name",
            "Readable name of device",
            DEFAULT_DEVICE_NAME,
            G_PARAM_READABLE
        )
    );
}

/**
    @brief Initialize element instance
    @param Pointer of plugin instance to initialize
    @param Pointer of plugin class

    initialize the new element
    instantiate pads and add them to element
    set pad calback functions
    initialize instance structure
 **/
static void
gst_openal_src_init (GstOpenalSrc* osrc, GstOpenalSrcClass* gclass) {
    osrc->deviceName = DEFAULT_DEVICE_NAME;
    osrc->device = DEFAULT_DEVICE;
    osrc->deviceHandle = NULL;
}

static void
gst_openal_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) {
    GstOpenalSrc *osrc = GST_OPENAL_SRC (object);
    switch (prop_id) {
        case PROP_DEVICE:
            osrc->device = g_value_dup_string (value);
            break;
        case PROP_DEVICE_NAME:
            osrc->deviceName = g_value_dup_string (value);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

static void
gst_openal_src_get_property (GObject* object, guint prop_id, GValue * value, GParamSpec * pspec) {
    GstOpenalSrc *osrc = GST_OPENAL_SRC (object);
    switch (prop_id) {
        case PROP_DEVICE:
            g_value_set_string (value, osrc->device);
            break;
        case PROP_DEVICE_NAME:
            g_value_set_string (value, osrc->deviceName);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

/**
    @brief open the device with given specs
    @param Pointer of GsAudioSrc
**/
static gboolean
gst_openal_src_open(GstAudioSrc *asrc) {
    GstOpenalSrc *osrc = GST_OPENAL_SRC(asrc);
    // We don't do anything here
    return TRUE;
}

/**
    @brief prepare resources and state to operate with the given specs
    @param Pointer of GsAudioSrc
    @param Sepcification of ring buffer
**/
static gboolean
gst_openal_src_prepare(GstAudioSrc *asrc, GstRingBufferSpec *spec) {
    GstOpenalSrc *osrc = GST_OPENAL_SRC(asrc);
    ALenum format;
    guint64 bufferSize;
/*
    GST_INFO_OBJECT(osrc, "Audio specification : ");
    GST_INFO_OBJECT(osrc, "Rate : %d", spec->rate);
    GST_INFO_OBJECT(osrc, "Width : %d", spec->width);
    GST_INFO_OBJECT(osrc, "Depth : %d", spec->depth);
    GST_INFO_OBJECT(osrc, "Channels : %d", spec->channels);
    GST_INFO_OBJECT(osrc, "Latency time : %d", spec->latency_time);
    GST_INFO_OBJECT(osrc, "Buffer time : %d", spec->buffer_time);
    GST_INFO_OBJECT(osrc, "Bytes per sample : %d", spec->bytes_per_sample);
*/
    switch(spec->width) {
        case 8:
            format = AL_FORMAT_STEREO8;
            break;
        case 16:
            format = AL_FORMAT_STEREO16;
            break;
    }

    bufferSize = spec->buffer_time * spec->rate * spec->bytes_per_sample / 1000000;

    // open device
    osrc->deviceHandle = alcCaptureOpenDevice(osrc->device, spec->rate, format, bufferSize);
    if(!osrc->deviceHandle) {
        GST_ELEMENT_ERROR(
            osrc,
            RESOURCE,
            FAILED,
            ("Can't open device \"%s\"", osrc->device),
            ("Can't open device \"%s\"", osrc->device)
        );
        return FALSE;
    }
    GST_INFO_OBJECT(osrc, "Open device : %s", osrc->deviceName);

    osrc->deviceName = alcGetString(osrc->device, ALC_DEVICE_SPECIFIER);
    osrc->bytes_per_sample = spec->bytes_per_sample;

    alcCaptureStart(osrc->deviceHandle);
    GST_INFO_OBJECT(osrc, "Start capture");
    return TRUE;
}

/**
    @brief undo anything that was done in prepare()
    @param Pointer of GsAudioSrc
**/
static gboolean
gst_openal_src_unprepare(GstAudioSrc *asrc) {
    GstOpenalSrc *osrc = GST_OPENAL_SRC(asrc);
    if(osrc->deviceHandle) {
        alcCaptureStop(osrc->deviceHandle);
        alcCaptureCloseDevice(osrc->deviceHandle);
    }
    GST_INFO_OBJECT(osrc, "Close device : %s", osrc->deviceName);
    return TRUE;
}

/**
    @brief close the device
    @param Pointer of GsAudioSrc
**/
static gboolean
gst_openal_src_close(GstAudioSrc *asrc) {
    // We don't do anything here
    return TRUE;
}

/**
    @brief read samples from the device
    @param Pointer of GsAudioSrc
    @param Pointer of data to output
    @param Length of data to output in bytes
**/
static guint
gst_openal_src_read(GstAudioSrc *asrc, gpointer data, guint length) {
    GstOpenalSrc *osrc = GST_OPENAL_SRC(asrc);
    guint samples;
    alcGetIntegerv(osrc->deviceHandle, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
    if(samples * osrc->bytes_per_sample > length) {
        samples = length / osrc->bytes_per_sample;
    }
    if(samples) {
        GST_DEBUG_OBJECT(osrc, "Read samples : %s", samples);
        alcCaptureSamples(osrc->deviceHandle, data, samples);
    }
    return samples * osrc->bytes_per_sample;
}

/**
    @brief get number of samples queued in the device
    @param Pointer of GsAudioSrc
    @return Number of samples
**/
static guint
gst_openal_src_delay (GstAudioSrc *asrc) {
    GstOpenalSrc *osrc = GST_OPENAL_SRC(asrc);
    guint samples;
    alcGetIntegerv(osrc->deviceHandle, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
    return samples;
}

/**
    @brief reset the audio device, unblock from a write
    @param Pointer of GsAudioSrc
**/
static void
gst_openal_src_reset (GstAudioSrc *asrc) {
    // We don't do anything here
}

/**
    @brief Init every thing about this plugin
    @param Pointer of plugin to initialize

    entry point to initialize the plug-in
    initialize the plug-in itself
    register the element factories and other features
**/
static gboolean
plugin_init (GstPlugin* plugin) {
    /* debug category for fltering log messages
    *
    * exchange the string 'Template plugin' with your description
    */
    GST_DEBUG_CATEGORY_INIT(gst_openal_src_debug, "openalsrc",
      0, "OpenAL source capture audio from device");

    // register this element
    return gst_element_register (plugin, "openalsrc", GST_RANK_NONE,
      GST_TYPE_OPENAL_SRC);
}

#define PACKAGE "test"

#pragma data_seg (".share")

/* gstreamer looks for this structure to register plugins
 *
 * exchange the string 'Template plugin' with your plugin description
 */
GST_PLUGIN_DEFINE (
    GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    "openalsrc", // short, but unique name of the plugin
    "OpenAL source capture audio from device", // information about the purpose of the plugin
    plugin_init, // full version string (e.g. VERSION from config.h)
    "0.01",
    "LGPL", // under which licence the package has been released, e.g. GPL, LGPL.
    "GStreamer", // the package-name (e.g. PACKAGE_NAME from config.h)
    "http://gstreamer.net/" // a description from where the package comes from (e.g. the homepage URL)
)
#pragma data_seg()

