/*
 * Copyright (C) 2014, Sebastian Dröge <sebastian@centricular.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation
 * version 2.1 of the License.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>

#include "gstomxatmosdec.h"

GST_DEBUG_CATEGORY_STATIC (gst_omx_atmos_dec_debug_category);
#define GST_CAT_DEFAULT gst_omx_atmos_dec_debug_category

#define DEFAULT_PROP_SPKCFG "lr"


/* prototypes */
static gboolean gst_omx_atmos_dec_set_format (GstOMXAudioDec * dec, GstOMXPort * port, GstCaps * caps);
static gboolean gst_omx_atmos_dec_is_format_change (GstOMXAudioDec * dec, GstOMXPort * port, GstCaps * caps);
static gint gst_omx_atmos_dec_get_samples_per_frame (GstOMXAudioDec * dec, GstOMXPort * port);
static gboolean gst_omx_atmos_dec_get_channel_positions (GstOMXAudioDec * dec, GstOMXPort * port, GstAudioChannelPosition position[OMX_AUDIO_MAXCHANNELS]);
static void gst_omx_atmos_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
static gboolean gst_omx_atmos_dec_set_parament(GstOMXAudioDec * dec);

enum
{
    PROP_0,
    PROP_SAMPLES_PER_FRAME,
    PROP_ATMOS_DEC_AUDIO_PROFILE,
    PROP_ATMOS_DEC_OUTPUT_CHANNELS,
    PROP_ATMOS_DEC_DUAL_MODE
};

/* class initialization */

#define DEBUG_INIT \
  GST_DEBUG_CATEGORY_INIT (gst_omx_atmos_dec_debug_category, "omxatmosdec", 0, \
      "debug category for gst-omx atmos audio decoder");

G_DEFINE_TYPE_WITH_CODE (GstOMXATMOSDec, gst_omx_atmos_dec, GST_TYPE_OMX_AUDIO_DEC, DEBUG_INIT);

static void
gst_omx_atmos_dec_class_init (GstOMXATMOSDecClass * klass)
{
    GObjectClass *gobject_class = (GObjectClass *) klass;

    GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
    GstOMXAudioDecClass *audiodec_class = GST_OMX_AUDIO_DEC_CLASS (klass);
    gobject_class->set_property = gst_omx_atmos_dec_set_property;

    audiodec_class->set_format = GST_DEBUG_FUNCPTR (gst_omx_atmos_dec_set_format);
    audiodec_class->is_format_change = GST_DEBUG_FUNCPTR (gst_omx_atmos_dec_is_format_change);
    audiodec_class->get_samples_per_frame = GST_DEBUG_FUNCPTR (gst_omx_atmos_dec_get_samples_per_frame);
    audiodec_class->get_channel_positions = GST_DEBUG_FUNCPTR (gst_omx_atmos_dec_get_channel_positions);
    audiodec_class->set_parament = GST_DEBUG_FUNCPTR (gst_omx_atmos_dec_set_parament);

    audiodec_class->cdata.default_sink_template_caps = "audio/atmos, " "rate=(int)[8000,48000], " "channels=(int)[1,8], " "parsed=(boolean) true";

    gst_element_class_set_static_metadata (element_class,
                                           "OpenMAX ATMOS Audio Decoder",
                                           "Codec/Decoder/Audio",
                                           "Decode ATMOS audio streams",
                                           "Yuan.Fang<yuan.fang@mediatek.com>");

    gst_omx_set_default_role (&audiodec_class->cdata, "audio_decoder.atmos");

    g_object_class_install_property (gobject_class, PROP_SAMPLES_PER_FRAME,
                                     g_param_spec_int("spf", "SAMPLES PER FRAME", "samples per frame",
                                                      0, 1536,
                                                      1536, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
    g_object_class_install_property (gobject_class, PROP_ATMOS_DEC_AUDIO_PROFILE,
                                     g_param_spec_uint("atmos-profile", "atmos dec audio profile", "atmos dec audio profile",
                                                      0, 4096,
                                                      0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
    g_object_class_install_property (gobject_class, PROP_ATMOS_DEC_OUTPUT_CHANNELS,
                                     g_param_spec_uint("output-channels", "atmos dec output channels", "atmos dec output channels",
                                                      0, 24,
                                                      0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
    g_object_class_install_property (gobject_class, PROP_ATMOS_DEC_DUAL_MODE,
                                     g_param_spec_uint("dual-mode", "dual mono mode", "dual-mono-mode",
                                                      0, 3,
                                                      0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
}

static GstMessage *
gst_cs_message_new_audio_info (GstElement * ele, gint64 stream_type, gint64 proc_type, gint64 stream_ch_num, gint64 stream_ch_mask, guint32 atmos_sample_rate, guint32 first_au_latency)
{
    GstStructure *s;

    s = gst_structure_new ("atmos_audio_info",
                           "stream-type", G_TYPE_INT64, stream_type,
                           "proc-type", G_TYPE_INT64, proc_type,
                           "atmos-stream-num", G_TYPE_INT64, stream_ch_num,
                           "atmos-stream-mask", G_TYPE_INT64, stream_ch_mask,
                           "atmos-sample-rate", G_TYPE_UINT, atmos_sample_rate,
                           "first-au-latency", G_TYPE_UINT, first_au_latency,
                           NULL);

    return gst_message_new_element(GST_OBJECT(ele), s);
}

static void
gst_cs_message_post_audio_info (GstElement *ele, gint64 stream_type, gint64 proc_type, gint64 stream_ch_num, gint64 stream_ch_mask, guint32 atmos_sample_rate, guint32 first_au_latency)
{
    GstMessage *m = gst_cs_message_new_audio_info (ele, stream_type, proc_type, stream_ch_num, stream_ch_mask, atmos_sample_rate, first_au_latency);
    gst_element_post_message(GST_ELEMENT (ele), m);
}

static OMX_ERRORTYPE
gst_omx_atmos_dec_event_handler (OMX_HANDLETYPE hComponent, OMX_PTR pAppData, OMX_EVENTTYPE eEvent,
                                 OMX_U32 nData1, OMX_U32 nData2, OMX_PTR pEventData, void* plugin_data)
{
    GstOMXATMOSDec *self = GST_OMX_ATMOS_DEC (plugin_data);
    GST_INFO_ATMOS* info = (GST_INFO_ATMOS*) pEventData;

#if 1 // for debug
    g_print ("[gstomxatmosdec]  self %p info %p\n", self, info);
#endif

    if (info)
    {
        g_print ("[gstomxatmosdec] stream type %d, proc_type %d\n", info->stream_type, info->proc_type);
        g_print ("[gstomxatmosdec] stream_channle_num: 0x%x\n", info->atmos_stream_ch_num);
        g_print ("[gstomxatmosdec] stream_channle_mask: 0x%x\n", info->atmos_stream_ch_mask);
        g_print ("[gstomxatmosdec] atmos_sample_rate: %d\n", info->u4stream_sample_rate);
        g_print ("[gstomxatmosdec] first_au_latency: %d\n", info->u4first_au_latency);
        gst_cs_message_post_audio_info (self, info->stream_type, info->proc_type, info->atmos_stream_ch_num, info->atmos_stream_ch_mask, info->u4stream_sample_rate, info->u4first_au_latency);
    }
}

static void
gst_omx_atmos_dec_init (GstOMXATMOSDec * self)
{
    self->spf = -1;
    gst_omx_component_set_codec_info_callback (self->parent.dec, gst_omx_atmos_dec_event_handler, self);
}

static gboolean
gst_omx_atmos_dec_set_format (GstOMXAudioDec * dec, GstOMXPort * port, GstCaps * caps)
{
    GstOMXATMOSDec *self = GST_OMX_ATMOS_DEC (dec);
    OMX_PARAM_PORTDEFINITIONTYPE port_def;
    OMX_AUDIO_PARAM_ATMOSTYPE atmos_param;
    OMX_ERRORTYPE err;
    GstStructure *s;
    gint rate, channels, layer, mpegaudioversion;
    GST_ERROR_OBJECT (self, "[YF][ATMOSDEC]gst_omx_atmos_dec_set_format");

    gst_omx_port_get_port_definition (port, &port_def);
    port_def.format.audio.eEncoding = OMX_AUDIO_CodingATMOS;
    err = gst_omx_port_update_port_definition (port, &port_def);
    if (err != OMX_ErrorNone)
    {
        GST_ERROR_OBJECT (self,
                          "Failed to set ATMOS format on component: %s (0x%08x)",
                          gst_omx_error_to_string (err), err);
        return FALSE;
    }

    GST_OMX_INIT_STRUCT (&atmos_param);
    atmos_param.nPortIndex = port->index;

    err =gst_omx_component_get_parameter (dec->dec, OMX_IndexParamAudioAtmos, &atmos_param);
    if (err != OMX_ErrorNone)
    {
        GST_ERROR_OBJECT (self,
                          "Failed to get ATMOS parameters from component: %s (0x%08x)",
                          gst_omx_error_to_string (err), err);
        return FALSE;
    }

    s = gst_caps_get_structure (caps, 0);

    if (!gst_structure_get_int (s, "rate", &rate) || !gst_structure_get_int (s, "channels", &channels))
    {
        GST_ERROR_OBJECT (self, "Incomplete caps");
        return FALSE;
    }

    atmos_param.nChannels = channels;
    atmos_param.nBitRate = 0;
    atmos_param.nSampleRate = rate;
    atmos_param.nAudioBandWidth = 0;
    atmos_param.eChannelMode = 0;

    err = gst_omx_component_set_parameter (dec->dec, OMX_IndexParamAudioAtmos, &atmos_param);
    if (err != OMX_ErrorNone)
    {
        GST_ERROR_OBJECT (self, "Error setting ATMOS parameters: %s (0x%08x)", gst_omx_error_to_string (err), err);
        return FALSE;
    }

    return TRUE;
}

static gboolean
gst_omx_atmos_dec_is_format_change (GstOMXAudioDec * dec, GstOMXPort * port, GstCaps * caps)
{
    GstOMXATMOSDec *self = GST_OMX_ATMOS_DEC (dec);
    OMX_AUDIO_PARAM_ATMOSTYPE atmos_param;
    OMX_ERRORTYPE err;
    GstStructure *s;
    gint rate, channels, layer, mpegaudioversion;

    GST_OMX_INIT_STRUCT (&atmos_param);
    atmos_param.nPortIndex = port->index;

    err = gst_omx_component_get_parameter (dec->dec, OMX_IndexParamAudioAtmos, &atmos_param);
    if (err != OMX_ErrorNone)
    {
        GST_ERROR_OBJECT (self,
                          "Failed to get ATMOS parameters from component: %s (0x%08x)",
                          gst_omx_error_to_string (err), err);
        return FALSE;
    }

    s = gst_caps_get_structure (caps, 0);

    if (!gst_structure_get_int (s, "rate", &rate) || !gst_structure_get_int (s, "channels", &channels))
    {
        GST_ERROR_OBJECT (self, "Incomplete caps");
        return FALSE;
    }

    if (atmos_param.nChannels != channels)
        return TRUE;

    if (atmos_param.nSampleRate != rate)
        return TRUE;

    return FALSE;
}

static gint
gst_omx_atmos_dec_get_samples_per_frame (GstOMXAudioDec * dec, GstOMXPort * port)
{
    //return GST_OMX_ATMOS_DEC (dec)->spf;
    return -1;
}

static gboolean
gst_omx_atmos_dec_get_channel_positions (GstOMXAudioDec * dec, GstOMXPort * port, GstAudioChannelPosition position[OMX_AUDIO_MAXCHANNELS])
{
    OMX_AUDIO_PARAM_PCMMODETYPE pcm_param;
    OMX_ERRORTYPE err;

    GST_OMX_INIT_STRUCT (&pcm_param);
    pcm_param.nPortIndex = port->index;
    err = gst_omx_component_get_parameter (dec->dec, OMX_IndexParamAudioPcm, &pcm_param);
    if (err != OMX_ErrorNone)
    {
        GST_ERROR_OBJECT (dec, "Failed to get PCM parameters: %s (0x%08x)", gst_omx_error_to_string (err), err);
        return FALSE;
    }

    switch (pcm_param.nChannels)
    {
    case 1:
        position[0] = GST_AUDIO_CHANNEL_POSITION_MONO;
        break;
    case 2:
        position[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
        position[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
        break;
    case 3:
        position[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER;
        position[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
        position[2] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
        break;
    case 4:
        position[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER;
        position[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
        position[2] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
        position[3] = GST_AUDIO_CHANNEL_POSITION_REAR_CENTER;
        break;
    case 5:
        position[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER;
        position[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
        position[2] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
        position[3] = GST_AUDIO_CHANNEL_POSITION_REAR_LEFT;
        position[4] = GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT;
        break;
    case 6:
        position[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER;
        position[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
        position[2] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
        position[3] = GST_AUDIO_CHANNEL_POSITION_REAR_LEFT;
        position[4] = GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT;
        position[5] = GST_AUDIO_CHANNEL_POSITION_LFE1;
        break;
    default:
        return FALSE;
    }

    return TRUE;
}

static gboolean
gst_omx_atmos_dec_set_parament(GstOMXAudioDec * dec)
{
    GstOMXATMOSDec *self = GST_OMX_ATMOS_DEC (dec);
    OMX_AUDIO_ATMOS_APP_PARAM atmos_app_param;
    OMX_ERRORTYPE err;
    memset(&atmos_app_param, 0, sizeof(OMX_AUDIO_ATMOS_APP_PARAM)); 
    if((self->atmos_dec_output_channels > 0) && (self->atmos_dec_output_channels <= 24) )
    {
        atmos_app_param.u4Channels = self->atmos_dec_output_channels;
        gst_omx_component_set_parameter (self->parent.dec, OMX_IndexConfigATMOSDecMode, &atmos_app_param);
        if (err != OMX_ErrorNone)
        {
            GST_ERROR_OBJECT (dec, "Failed to get PCM parameters: %s (0x%08x)", gst_omx_error_to_string (err), err);
            return FALSE;
        }
        g_print ("[gstomxatmosdec] set atmos dec output channels to component!\n");
    }

    return TRUE;
}

static void
gst_omx_atmos_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
{
    GstOMXATMOSDec *filter = (GstOMXATMOSDec*) (object);

    switch (prop_id)
    {
    case PROP_SAMPLES_PER_FRAME:
        filter->spf = g_value_get_int (value);
        g_print("[gstomxatmosdec] atmos samples per frame: %d\n", filter->spf);
        break;

    case PROP_ATMOS_DEC_AUDIO_PROFILE:
        filter->atmos_dec_audio_profile = g_value_get_uint (value);

        if ((filter->parent.dec == NULL) || (gst_omx_component_get_state (filter->parent.dec, GST_CLOCK_TIME_NONE) < OMX_StateIdle))
        {
            g_print ("[gstomxatmosdec] profile set too early\n");
        }
        else
        {
            OMX_AUDIO_ATMOS_APP_PARAM_PROFILE_T atmos_profile;
            memset(&atmos_profile, 0, sizeof(OMX_AUDIO_ATMOS_APP_PARAM_PROFILE_T));

            g_print ("[gstomxatmosdec] atmos profile set (%u)\n", filter->atmos_dec_audio_profile);
            atmos_profile.u4DecAudioProfile = filter->atmos_dec_audio_profile;
            gst_omx_component_set_parameter (filter->parent.dec, OMX_IndexConfigATMOSDecProfile, &atmos_profile);
        }
        break;
    case PROP_ATMOS_DEC_OUTPUT_CHANNELS:
        filter->atmos_dec_output_channels = g_value_get_uint (value);
        g_print ("[gstomxatmosdec] atmos dec output channels (%u)\n", filter->atmos_dec_output_channels);
        break;
    case PROP_ATMOS_DEC_DUAL_MODE:
        filter->atmos_dec_dual_mode = g_value_get_uint (value);
        if ((filter->parent.dec == NULL) || (gst_omx_component_get_state (filter->parent.dec, GST_CLOCK_TIME_NONE) < OMX_StateIdle))
        {
            g_print ("[gstomxatmosdec] profile set too early\n");
        }
        else
        {
            OMX_AUDIO_ATMOS_APP_PARAM_DUAL_MODE_T atmos_dualmode;
            memset(&atmos_dualmode, 0, sizeof(OMX_AUDIO_ATMOS_APP_PARAM_DUAL_MODE_T));
            g_print ("[gstomxatmosdec] atmos dual mono mode set (%u)\n", filter->atmos_dec_dual_mode);
            atmos_dualmode.u4DecDualMode = filter->atmos_dec_dual_mode;
            gst_omx_component_set_parameter (filter->parent.dec, OMX_IndexConfigATMOSDecDualMode, &atmos_dualmode);
        }
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

