/*
    SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/


//
// Some notes on mp3:
// A mp3 Frame is always samples/samplerate seconds in length
//
//
//
// What we need are raw 16 bit stereo samples at 44100 Hz which results in 588 samples
// per block (2352 bytes: 32*588 bit). 1 second are 75 blocks.
//

#include "k3bmaddecoder.h"
#include "k3bmad.h"
#include "k3bplugin_i18n.h"

#include <config-k3b.h>

#include <QDebug>
#include <QString>
#include <QFile>
#include <QVector>

#include <stdlib.h>
#include <cmath>
#include <cstdlib>

#ifdef ENABLE_TAGLIB
#include <tag.h>
#include <mpegfile.h>
#endif


K_PLUGIN_CLASS_WITH_JSON(K3bMadDecoderFactory, "k3bmaddecoder.json")

int K3bMadDecoder::MaxAllowedRecoverableErrors = 10;



class K3bMadDecoder::MadDecoderPrivate
{
public:
    MadDecoderPrivate()
        : outputBuffer(nullptr),
          outputPointer(nullptr),
          outputBufferEnd(nullptr) {
        mad_header_init( &firstHeader );
    }

    K3bMad* handle;

    QVector<unsigned long long> seekPositions;

    bool bOutputFinished;

    char* outputBuffer;
    char* outputPointer;
    char* outputBufferEnd;

    // the first frame header for technical info
    mad_header firstHeader;
    bool vbr;
};




K3bMadDecoder::K3bMadDecoder( QObject* parent  )
    : K3b::AudioDecoder( parent )
{
    d = new MadDecoderPrivate();
    d->handle = new K3bMad();
}


K3bMadDecoder::~K3bMadDecoder()
{
    cleanup();
    delete d->handle;
    delete d;
}


QString K3bMadDecoder::metaInfo( MetaDataField f )
{
#ifdef ENABLE_TAGLIB
    TagLib::MPEG::File file( QFile::encodeName( filename() ).data() );

    if ( file.tag() ) {
        switch( f ) {
        case META_TITLE:
            return TStringToQString( file.tag()->title() );
        case META_ARTIST:
            return TStringToQString( file.tag()->artist() );
        case META_COMMENT:
            return TStringToQString( file.tag()->comment() );
        default:
            return QString();
        }
    }
    else {
        return QString();
    }

#else
    return K3b::AudioDecoder::metaInfo( f );
#endif
}


bool K3bMadDecoder::analyseFileInternal( K3b::Msf& frames, int& samplerate, int& ch )
{
    initDecoderInternal();
    frames = countFrames();
    if( frames > 0 ) {
        // we convert mono to stereo all by ourselves. :)
        ch = 2;
        samplerate = d->firstHeader.samplerate;
        return true;
    }
    else
        return false;
}


bool K3bMadDecoder::initDecoderInternal()
{
    cleanup();

    d->bOutputFinished = false;

    if( !d->handle->open( filename() ) )
        return false;

    if( !d->handle->skipTag() )
        return false;

    if( !d->handle->seekFirstHeader() )
        return false;

    return true;
}


unsigned long K3bMadDecoder::countFrames()
{
    qDebug() << "(K3bMadDecoder::countFrames)";

    unsigned long frames = 0;
    bool error = false;
    d->vbr = false;
    bool bFirstHeaderSaved = false;

    d->seekPositions.clear();

    while( !error && d->handle->findNextHeader() ) {

        if( !bFirstHeaderSaved ) {
            bFirstHeaderSaved = true;
            d->firstHeader = d->handle->madFrame->header;
        }
        else if( d->handle->madFrame->header.bitrate != d->firstHeader.bitrate )
            d->vbr = true;

        //
        // position in stream: position in file minus the not yet used buffer
        //
        unsigned long long seekPos = d->handle->inputPos() -
                                     (d->handle->madStream->bufend - d->handle->madStream->this_frame + 1);

        // save the number of bytes to be read to decode i-1 frames at position i
        // in other words: when seeking to seekPos the next decoded frame will be i
        d->seekPositions.append( seekPos );
    }

    if( !d->handle->inputError() && !error ) {
        // we need the length of the track to be multiple of frames (1/75 second)
        float seconds = float(d->handle->madTimer->seconds) +
                        float(d->handle->madTimer->fraction)/float(MAD_TIMER_RESOLUTION);
        frames = static_cast<unsigned long>(ceil(seconds * 75.0));
        qDebug() << "(K3bMadDecoder) length of track " << seconds;
    }

    cleanup();

    qDebug() << "(K3bMadDecoder::countFrames) end";

    return frames;
}


int K3bMadDecoder::decodeInternal( char* _data, int maxLen )
{
    d->outputBuffer = _data;
    d->outputBufferEnd = d->outputBuffer + maxLen;
    d->outputPointer = d->outputBuffer;

    bool bOutputBufferFull = false;

    while( !bOutputBufferFull && d->handle->fillStreamBuffer() ) {

        // a mad_synth contains of the data of one mad_frame
        // one mad_frame represents a mp3-frame which is always 1152 samples
        // for us that means we need 4*1152 bytes of output buffer for every frame
        // since one sample has 16 bit
        if( d->outputBufferEnd - d->outputPointer < 4*1152 ) {
            bOutputBufferFull = true;
        }
        else if( d->handle->decodeNextFrame() ) {
            //
            // Once decoded the frame is synthesized to PCM samples. No errors
            // are reported by mad_synth_frame();
            //
            mad_synth_frame( d->handle->madSynth, d->handle->madFrame );

            // this fills the output buffer
            if( !createPcmSamples( d->handle->madSynth ) ) {
                return -1;
            }
        }
        else if( d->handle->inputError() ) {
            return -1;
        }
    }

    // flush the output buffer
    size_t buffersize = d->outputPointer - d->outputBuffer;

    return buffersize;
}


unsigned short K3bMadDecoder::linearRound( mad_fixed_t fixed )
{
    // round
    fixed += (1L << ( MAD_F_FRACBITS - 16 ));

    // clip
    if( fixed >= MAD_F_ONE - 1 )
        fixed = MAD_F_ONE - 1;
    else if( fixed < -MAD_F_ONE )
        fixed = -MAD_F_ONE;

    // quatisize
    return fixed >> (MAD_F_FRACBITS + 1 - 16 );
}


bool K3bMadDecoder::createPcmSamples( mad_synth* synth )
{
    unsigned short nsamples = synth->pcm.length;

    // this should not happen since we only decode if the
    // output buffer has enough free space
    if( d->outputBufferEnd - d->outputPointer < nsamples*4 ) {
        qDebug() <<  "(K3bMadDecoder) buffer overflow!";
        return false;
    }

    // now create the output
    for( int i = 0; i < nsamples; i++ ) {

        /* Left channel */
        unsigned short sample = linearRound( synth->pcm.samples[0][i] );
        *(d->outputPointer++) = (sample >> 8) & 0xff;
        *(d->outputPointer++) = sample & 0xff;

        /* Right channel. If the decoded stream is monophonic then
         * the right output channel is the same as the left one.
         */
        if( synth->pcm.channels == 2 )
            sample = linearRound( synth->pcm.samples[1][i] );

        *(d->outputPointer++) = (sample >> 8) & 0xff;
        *(d->outputPointer++) = sample & 0xff;
    } // pcm conversion

    return true;
}


void K3bMadDecoder::cleanup()
{
    d->handle->cleanup();
}


bool K3bMadDecoder::seekInternal( const K3b::Msf& pos )
{
    //
    // we need to reset the complete mad stuff
    //
    if( !initDecoderInternal() )
        return false;

    //
    // search a position
    // This is all hacking, I don't really know what I am doing here... ;)
    //
    double mp3FrameSecs = static_cast<double>(d->firstHeader.duration.seconds)
                          + static_cast<double>(d->firstHeader.duration.fraction) / static_cast<double>(MAD_TIMER_RESOLUTION);

    double posSecs = static_cast<double>(pos.totalFrames()) / 75.0;

    // seekPosition to seek after frame i
    unsigned int frame = static_cast<unsigned int>( posSecs / mp3FrameSecs );

    // Rob said: 29 frames is the theoretically max frame reservoir limit (whatever that means...)
    // it seems that mad needs at most 29 frames to get ready
    unsigned int frameReservoirProtect = ( frame > 29 ? 29 : frame );

    frame -= frameReservoirProtect;

    // seek in the input file behind the already decoded data
    d->handle->inputSeek( d->seekPositions[frame] );

    qDebug() << "(K3bMadDecoder) Seeking to frame " << frame << " with "
             << frameReservoirProtect << " reservoir frames." << Qt::endl;

    // decode some frames ignoring MAD_ERROR_BADDATAPTR errors
    unsigned int i = 1;
    while( i <= frameReservoirProtect ) {
        d->handle->fillStreamBuffer();
        if( mad_frame_decode( d->handle->madFrame, d->handle->madStream ) ) {
            if( MAD_RECOVERABLE( d->handle->madStream->error ) ) {
                if( d->handle->madStream->error == MAD_ERROR_BUFLEN )
                    continue;
                else if( d->handle->madStream->error != MAD_ERROR_BADDATAPTR ) {
                    qDebug() << "(K3bMadDecoder) Seeking: recoverable mad error ("
                             << mad_stream_errorstr(d->handle->madStream) << ")" << Qt::endl;
                    continue;
                }
                else {
                    qDebug() << "(K3bMadDecoder) Seeking: ignoring ("
                             << mad_stream_errorstr(d->handle->madStream) << ")" << Qt::endl;
                }
            }
            else
                return false;
        }

        if( i == frameReservoirProtect )  // synth only the last frame (Rob said so ;)
            mad_synth_frame( d->handle->madSynth, d->handle->madFrame );

        ++i;
    }

    return true;
}


QString K3bMadDecoder::fileType() const
{
    switch( d->firstHeader.layer ) {
    case MAD_LAYER_I:
        return "MPEG1 Layer I";
    case MAD_LAYER_II:
        return "MPEG1 Layer II";
    case MAD_LAYER_III:
        return "MPEG1 Layer III";
    default:
        return "Mp3";
    }
}

QStringList K3bMadDecoder::supportedTechnicalInfos() const
{
    return QString( i18n("Channels") + ';' +
                    i18n("Sampling Rate") + ';' +
                    i18n("Bitrate") + ';' +
                    i18n("Layer") + ';' +
                    i18n("Emphasis") + ';' +
                    i18n("Copyright") + ';' +
                    i18n("Original") + ';' +
                    i18n("CRC") ).split( ';' );
}


QString K3bMadDecoder::technicalInfo( const QString& name ) const
{
    if( name == i18n("Channels") ) {
        switch( d->firstHeader.mode ) {
        case MAD_MODE_SINGLE_CHANNEL:
            return i18n("Mono");
        case MAD_MODE_DUAL_CHANNEL:
            return i18n("Dual");
        case MAD_MODE_JOINT_STEREO:
            return i18n("Joint Stereo");
        case MAD_MODE_STEREO:
            return i18n("Stereo");
        default:
            return "?";
        }
    }
    else if( name == i18n("Sampling Rate") )
        return i18n("%1 Hz",d->firstHeader.samplerate);
    else if( name == i18n("Bitrate") ) {
        if( d->vbr )
            return i18n("VBR");
        else
            return i18n("%1 bps",d->firstHeader.bitrate);
    }
    else if(  name == i18n("Layer") ){
        switch( d->firstHeader.layer ) {
        case MAD_LAYER_I:
            return "I";
        case MAD_LAYER_II:
            return "II";
        case MAD_LAYER_III:
            return "III";
        default:
            return "?";
        }
    }
    else if( name == i18n("Emphasis") ) {
        switch( d->firstHeader.emphasis ) {
        case MAD_EMPHASIS_NONE:
            return i18n("None");
        case MAD_EMPHASIS_50_15_US:
            return i18n("50/15 ms");
        case MAD_EMPHASIS_CCITT_J_17:
            return i18n("CCITT J.17");
        default:
            return i18n("Unknown");
        }
    }
    else if( name == i18n("Copyright") )
        return ( d->firstHeader.flags & MAD_FLAG_COPYRIGHT ? i18n("Yes") : i18n("No") );
    else if( name == i18n("Original") )
        return ( d->firstHeader.flags & MAD_FLAG_ORIGINAL ? i18n("Yes") : i18n("No") );
    else if( name == i18n("CRC") )
        return ( d->firstHeader.flags & MAD_FLAG_PROTECTION ? i18n("Yes") : i18n("No") );
    else
        return QString();
}


K3bMadDecoderFactory::K3bMadDecoderFactory( QObject* parent, const QVariantList& )
    : K3b::AudioDecoderFactory( parent )
{
}


K3bMadDecoderFactory::~K3bMadDecoderFactory()
{
}


K3b::AudioDecoder* K3bMadDecoderFactory::createDecoder( QObject* parent) const
{
    return new K3bMadDecoder( parent );
}


bool K3bMadDecoderFactory::canDecode( const QUrl& url )
{
    //
    // HACK:
    //
    // I am simply no good at this and this detection code is no good as well
    // It always takes waves for mp3 files so we introduce this hack to
    // filter out wave files. :(
    //
    QFile f( url.toLocalFile() );
    if( !f.open( QIODevice::ReadOnly ) )
        return false;
    char buffer[12];
    if( f.read( buffer, 12 ) != 12 )
        return false;
    if( !qstrncmp( buffer, "RIFF", 4 ) &&
        !qstrncmp( buffer + 8, "WAVE", 4 ) )
        return false;
    f.close();


    K3bMad handle;
    if( !handle.open( url.toLocalFile() ) )
        return false;

    handle.skipTag();
    if( !handle.seekFirstHeader() )
        return false;

    if( handle.findNextHeader() ) {
        int c = MAD_NCHANNELS( &handle.madFrame->header );
        int layer = handle.madFrame->header.layer;
        unsigned int s = handle.madFrame->header.samplerate;

        //
        // find 4 more mp3 headers (random value since 2 was not enough)
        // This way we get most of the mp3 files while sorting out
        // for example wave files.
        //
        int cnt = 1;
        while( handle.findNextHeader() ) {
            // compare the found headers
            if( MAD_NCHANNELS( &handle.madFrame->header ) == c &&
                handle.madFrame->header.layer == layer &&
                handle.madFrame->header.samplerate == s ) {
                // only support layer III for now since otherwise some wave files
                // are taken for layer I
                if( ++cnt >= 5 ) {
                    qDebug() << "(K3bMadDecoder) valid mpeg 1 layer " << layer
                             << " file with " << c << " channels and a samplerate of "
                             << s << Qt::endl;
                    return ( layer == MAD_LAYER_III );
                }
            }
            else
                break;
        }
    }

    qDebug() << "(K3bMadDecoder) unsupported format: " << url.toLocalFile();

    return false;
}

#include "k3bmaddecoder.moc"

#include "moc_k3bmaddecoder.cpp"
