/***************************************************************************
 *   Copyright (C) 2011-2025 by Ilya Kotov                                 *
 *   forkotov02@ya.ru                                                      *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include <taglib/id3v2header.h>
#include <taglib/tbytevector.h>
#include <qmmp/buffer.h>
#include <qmmp/output.h>
#include <math.h>
#include <stdio.h>
#include "tagextractor.h"
#include "decoder_mpg123.h"

ssize_t mpg123_read_cb (void *src, void *buf, size_t size)
{
    DecoderMPG123 *d = static_cast<DecoderMPG123 *>(src);
    return d->input()->read((char *)buf, size);
}

off_t mpg123_seek_cb(void *src, off_t offset, int whence)
{
    DecoderMPG123 *d = static_cast<DecoderMPG123 *>(src);
    if (d->input()->isSequential())
            return -1;

        long start = 0;
        switch (whence)
        {
        case SEEK_END:
            start = d->input()->size();
            break;

        case SEEK_CUR:
            start = d->input()->pos();
            break;

        case SEEK_SET:
        default:
            start = 0;
        }

        if (!d->input()->seek(start + offset))
            return -1;
        return d->input()->pos();
}

DecoderMPG123::DecoderMPG123(QIODevice *i) : Decoder(i)
{}

DecoderMPG123::~DecoderMPG123()
{
    cleanup(m_handle);
    m_handle = nullptr;
}

bool DecoderMPG123::initialize()
{
    m_errors = 0;
    if (input()->isSequential ()) //for streams only
    {
        TagExtractor extractor(input());
        if(!extractor.id3v2tag().isEmpty())
            addMetaData(extractor.id3v2tag());
    }

    int err = mpg123_init();
    if(err != MPG123_OK)
    {
        qCWarning(plugin, "basic setup goes wrong: %s", mpg123_plain_strerror(err));
        return false;
    }
    int channels = 0;

    if(!(m_handle = mpg123_new(nullptr, &err)))
    {
        qCWarning(plugin, "basic setup goes wrong: %s", mpg123_plain_strerror(err));
        return false;
    }

    mpg123_param (m_handle, MPG123_ADD_FLAGS, MPG123_SEEKBUFFER | MPG123_FUZZY, 0);

    if((err = mpg123_replace_reader_handle(m_handle, mpg123_read_cb, mpg123_seek_cb, nullptr)) != MPG123_OK)
    {
        qCWarning(plugin, "mpg123 error: %s", mpg123_plain_strerror(err));
        cleanup(m_handle);
        m_handle = nullptr;
        return false;
    }
    setMPG123Format(MPG123_ENC_FLOAT_32);

    if((err = mpg123_open_handle(m_handle, this)) != MPG123_OK)
    {
        qCWarning(plugin, "mpg123 error: %s", mpg123_plain_strerror(err));
        cleanup(m_handle);
        m_handle = nullptr;
        return false;
    }

    if((err = mpg123_getformat(m_handle, &m_rate, &channels, &m_mpg123_encoding)) != MPG123_OK)
    {
        qCWarning(plugin, "mpg123 error: %s", mpg123_plain_strerror(err));
        cleanup(m_handle);
        m_handle = nullptr;
        return false;
    }
    //check format
    if(m_mpg123_encoding != MPG123_ENC_FLOAT_32)
    {
        cleanup(m_handle);
        qCWarning(plugin, "bad encoding: 0x%x!\n", m_mpg123_encoding);
        m_handle = nullptr;
        return false;
    }

    if(!input()->isSequential())
    {
        if((err = mpg123_scan(m_handle)) != MPG123_OK)
            qCWarning(plugin, "mpg123 error: %s", mpg123_plain_strerror(err));
        //duration
        m_totalTime = (qint64) mpg123_length(m_handle) * 1000 / m_rate;
    }
    else
    {
        if((err = mpg123_info(m_handle, &m_frame_info)) != MPG123_OK)
            qCWarning(plugin, "mpg123 error: %s", mpg123_plain_strerror(err));

        if(m_frame_info.version == MPG123_1_0)
            setProperty(Qmmp::FORMAT_NAME, QStringLiteral("MPEG-1 layer %1").arg(m_frame_info.layer));
        else if(m_frame_info.version == MPG123_2_0)
            setProperty(Qmmp::FORMAT_NAME, QStringLiteral("MPEG-2 layer %1").arg(m_frame_info.layer));
        else if(m_frame_info.version == MPG123_2_5)
            setProperty(Qmmp::FORMAT_NAME, QStringLiteral("MPEG-2.5 layer %1").arg(m_frame_info.layer));

        m_totalTime = 0;
    }

    configure(m_rate, channels, Qmmp::PCM_FLOAT);
    return true;
}

qint64 DecoderMPG123::totalTime() const
{
    return m_totalTime;
}

int DecoderMPG123::bitrate() const
{
    return m_frame_info.bitrate;
}

qint64 DecoderMPG123::read(unsigned char *data, qint64 size)
{
    size_t done = 0;
    int err = mpg123_read(m_handle, data, size, &done);
    if(err < 0)
    {
        err = mpg123_errcode(m_handle);
        if(!m_errors)
            qCWarning(plugin, "decoder error: %s", mpg123_plain_strerror(err));

        if(m_errors < 10)
        {
            m_errors++;
            if(err == MPG123_RESYNC_FAIL && done > 0)
                memset(data, 0, done);
            return done;
        }
        return -1;
    }

    if(err != MPG123_DONE && err != MPG123_OK)
    {
        qCWarning(plugin, "decoder error: %s", mpg123_plain_strerror(err));
        return -1;
    }
    m_errors = 0;
    mpg123_info(m_handle, &m_frame_info);
    return done;
}

void DecoderMPG123::seek(qint64 pos)
{
    if(m_totalTime > 0)
    {
        mpg123_seek(m_handle, pos * m_rate / 1000, SEEK_SET);
    }
}

void DecoderMPG123::cleanup(mpg123_handle *handle)
{
    if(handle)
    {
        mpg123_close(handle);
        mpg123_delete(handle);
    }
}

void DecoderMPG123::setMPG123Format(int encoding)
{
    int sample_rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 };

    /* Ensure that this output format will not change (it could, when we allow it). */
    mpg123_format_none(m_handle);
    for(unsigned int i = 0; i < sizeof(sample_rates)/sizeof(int); ++i)
        mpg123_format(m_handle, sample_rates[i], MPG123_MONO | MPG123_STEREO, encoding);
    m_mpg123_encoding = encoding;
}
