/***************************************************************************
                          encoder_ogg.cpp
                             -------------------
    begin                : Sat Aug 20 2005
    copyright            : (C) 2005 by Martin Witte
    email                : witte@kawo1.rwth-aachen.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "encoder_ogg.h"

#include <tdelocale.h>
#include <stdlib.h>

RecordingEncodingOgg::RecordingEncodingOgg(TQObject *parent,            SoundStreamID ssid,
                                           const RecordingConfig &cfg, const RadioStation *rs,
                                           const TQString &filename)
    : RecordingEncoding(parent, ssid, cfg, rs, filename)
#ifdef HAVE_OGG
      ,
      m_OggOutput(NULL),
      m_OggExportBuffer(NULL),
      m_OggExportBufferSize(0)
#endif
{
    m_config.m_OutputFormat = RecordingConfig::outputOGG;
    m_config.m_SoundFormat.m_Encoding = "ogg";
    openOutput(filename);
}


RecordingEncodingOgg::~RecordingEncodingOgg()
{
    closeOutput();
}

void RecordingEncodingOgg::encode(const char *_buffer, size_t buffer_size, char *&export_buffer, size_t &export_buffer_size)
{
    if (m_error)
        return;

#ifdef HAVE_OGG
    SoundFormat &sf = m_config.m_SoundFormat;
    ogg_page     ogg_pg;
    ogg_packet   ogg_pkt;

    size_t samples = buffer_size / sf.frameSize();

    // buffer[channel][sample], normalized to -1..0..+1
    float **buffer = vorbis_analysis_buffer(&m_VorbisDSP, (samples < 512 ? 512 : samples));

    sf.convertSamplesToFloat(_buffer, buffer, samples);

    /* Tell the library how many samples (per channel) we wrote
       into the supplied buffer */
    vorbis_analysis_wrote(&m_VorbisDSP, samples);

    /* While we can get enough data from the library to analyse, one
       block at a time... */

    bool eos = false;
    while(!m_error && !eos && vorbis_analysis_blockout(&m_VorbisDSP, &m_VorbisBlock) == 1) {

        /* Do the main analysis, creating a packet */
        vorbis_analysis(&m_VorbisBlock, NULL);
        vorbis_bitrate_addblock(&m_VorbisBlock);

        while(!m_error && vorbis_bitrate_flushpacket(&m_VorbisDSP, &ogg_pkt)) {
            /* Add packet to bitstream */
            ogg_stream_packetin(&m_OggStream,&ogg_pkt);

            /* If we've gone over a page boundary, we can do actual output,
               so do so (for however many pages are available) */

            while(!m_error && !eos) {
                int result = ogg_stream_pageout(&m_OggStream, &ogg_pg);
                if (!result) break;

                int n  = fwrite(ogg_pg.header, 1, ogg_pg.header_len, m_OggOutput);
                    n += fwrite(ogg_pg.body,   1, ogg_pg.body_len,   m_OggOutput);

                m_encodedSize += n;

                if (n != (ogg_pg.header_len + ogg_pg.body_len)) {
                    m_error = true;
                    m_errorString += i18n("Failed writing data to ogg/vorbis output stream. ");
                    break;
                } else {

                    if (m_OggExportBufferSize < export_buffer_size + n) {
                        m_OggExportBuffer = (char*)realloc(m_OggExportBuffer, m_OggExportBufferSize + 2 * n);
                        m_OggExportBufferSize += 2 * n;
                    }

                    memcpy (m_OggExportBuffer + export_buffer_size, ogg_pg.header, ogg_pg.header_len);
                    export_buffer_size += ogg_pg.header_len;
                    memcpy (m_OggExportBuffer + export_buffer_size, ogg_pg.body,   ogg_pg.body_len);
                    export_buffer_size += ogg_pg.body_len;

                }
                if (ogg_page_eos(&ogg_pg))
                    eos = 1;
            }
        }
    }

    export_buffer = m_OggExportBuffer;
#endif
}


#ifdef HAVE_OGG
static void vorbis_comment_add_tag_new(vorbis_comment *vc, const TQString &tag, const TQString &value)
{
    char *stag   = strdup(tag.ascii());
    char *svalue = strdup(value.utf8());
    vorbis_comment_add_tag(vc, stag, svalue);
    delete stag;
    delete svalue;
}
#endif

bool RecordingEncodingOgg::openOutput(const TQString &output)
{
#ifdef HAVE_OGG
    m_OggOutput = fopen(output.ascii(), "wb+");
    if (!m_OggOutput) {
        m_errorString += i18n("Cannot open Ogg/Vorbis output file %1. ").arg(output);
        m_error = true;
    }

    m_OggExportBuffer = (char*)malloc(m_OggExportBufferSize = 65536); // start with a 64k buffer


    /* Have vorbisenc choose a mode for us */
    vorbis_info_init(&m_VorbisInfo);

    SoundFormat &sf = m_config.m_SoundFormat;
    if (vorbis_encode_setup_vbr(&m_VorbisInfo, sf.m_Channels, sf.m_SampleRate, m_config.m_oggQuality)) {
        m_error = true;
        m_errorString = i18n("Ogg/Vorbis Mode initialisation failed: invalid parameters for quality\n");
        vorbis_info_clear(&m_VorbisInfo);
        return false;
    }

    /* Turn off management entirely (if it was turned on). */
    vorbis_encode_ctl(&m_VorbisInfo, OV_ECTL_RATEMANAGE_SET, NULL);
    vorbis_encode_setup_init(&m_VorbisInfo);

    /* Now, set up the analysis engine, stream encoder, and other
       preparation before the encoding begins.
     */

    vorbis_analysis_init(&m_VorbisDSP, &m_VorbisInfo);
    vorbis_block_init(&m_VorbisDSP, &m_VorbisBlock);

    ogg_stream_init (&m_OggStream, m_SoundStreamID.getID());

    /* Now, build the three header packets and send through to the stream
       output stage (but defer actual file output until the main encode loop) */

    ogg_packet header_main;
    ogg_packet header_comments;
    ogg_packet header_codebooks;

    /* Build the packets */
    vorbis_comment  vc;
    vorbis_comment_init (&vc);
    vorbis_comment_add_tag_new(&vc, "creator", "TDERadio" VERSION);
    vorbis_comment_add_tag_new(&vc, "title",   m_RadioStation->longName().utf8());
    vorbis_comment_add_tag_new(&vc, "date",    TQDateTime::currentDateTime().toString(Qt::ISODate));

    vorbis_analysis_headerout(&m_VorbisDSP, &vc,
                              &header_main, &header_comments, &header_codebooks);

    /* And stream them out */
    ogg_stream_packetin(&m_OggStream, &header_main);
    ogg_stream_packetin(&m_OggStream, &header_comments);
    ogg_stream_packetin(&m_OggStream, &header_codebooks);

    int      result;
    ogg_page ogg_page;
    while((result = ogg_stream_flush(&m_OggStream, &ogg_page))) {

        if (!result) break;

        int n  = fwrite(ogg_page.header, 1, ogg_page.header_len, m_OggOutput);
            n += fwrite(ogg_page.body,   1, ogg_page.body_len,   m_OggOutput);

        if(n != ogg_page.header_len + ogg_page.body_len) {
            m_error = true;
            m_errorString += i18n("Failed writing Ogg/Vorbis header to output stream\n");
            break;
        }
    }

    vorbis_comment_clear (&vc);

    if (m_error) {
        if (m_OggOutput)  fclose (m_OggOutput);
        m_OggOutput = NULL;
        free(m_OggExportBuffer);
        m_OggExportBuffer     = NULL;
        m_OggExportBufferSize = 0;

        ogg_stream_clear(&m_OggStream);
        vorbis_block_clear(&m_VorbisBlock);
        vorbis_dsp_clear(&m_VorbisDSP);
        vorbis_info_clear(&m_VorbisInfo);
    }

    return !m_error;
#endif
}


void RecordingEncodingOgg::closeOutput()
{
#ifdef HAVE_OGG
    if (m_OggOutput) {

        char     *tmp_buf  = NULL;
        size_t    tmp_size = 0;
        // flush buffer
        encode(tmp_buf, tmp_size, tmp_buf, tmp_size);

        fclose(m_OggOutput);
        m_OggOutput = NULL;

        free(m_OggExportBuffer);
        m_OggExportBuffer     = NULL;
        m_OggExportBufferSize = 0;

        ogg_stream_clear(&m_OggStream);
        vorbis_block_clear(&m_VorbisBlock);
        vorbis_dsp_clear(&m_VorbisDSP);
        vorbis_info_clear(&m_VorbisInfo);
    }
#endif
}