/***************************************************************************
 *   Copyright (C) 2008-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 <QCloseEvent>
#include <QSettings>
#include <QDir>
#include <QObject>
#include <QList>
#include <QMetaObject>
#include <QApplication>
#include <QDialog>
#include <QPluginLoader>
#include <QTranslator>
#include <QTimer>
#include "statehandler.h"
#include "visualfactory.h"
#include "visualbuffer_p.h"
#include "visual.h"

Visual::Visual(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f)
{
    setAttribute(Qt::WA_DeleteOnClose, true);
    setAttribute(Qt::WA_QuitOnClose, false);
}

Visual::~Visual()
{
    qCDebug(core) << Q_FUNC_INFO;
}

void Visual::closeEvent (QCloseEvent *event)
{
    m_visuals.removeAll(this);
    if (event->spontaneous () && m_vis_map.key(this))
    {
        VisualFactory *factory = m_vis_map.key(this);
        m_vis_map.remove(factory);
        Visual::setEnabled(factory, false);
        emit closedByUser();
    }
    else
    {
        if (m_vis_map.key(this))
        {
            VisualFactory *factory = m_vis_map.key(this);
            m_vis_map.remove(factory);
        }
    }
    QWidget::closeEvent(event);
}

bool Visual::takeData(float *left, float *right)
{
    m_buffer.mutex()->lock();
    VisualNode *node = m_buffer.take();
    if(node)
    {
        if(left && right)
        {
            memcpy(left, node->data[0], QMMP_VISUAL_NODE_SIZE * sizeof(float));
            memcpy(right, node->data[1], QMMP_VISUAL_NODE_SIZE * sizeof(float));
        }
        else if(left && !right)
        {
            for(int i = 0; i < QMMP_VISUAL_NODE_SIZE; ++i)
                left[i] = qBound(-1.0f, (node->data[0][i] + node->data[1][i]) / 2, 1.0f);
        }
    }
    m_buffer.mutex()->unlock();
    return node != nullptr;
}

//static members
QList<VisualFactory*> *Visual::m_factories = nullptr;
QHash <const VisualFactory*, QString> *Visual::m_files = nullptr;
QList<Visual*> Visual::m_visuals;
QHash<VisualFactory*, Visual*> Visual::m_vis_map;
QWidget *Visual::m_parentWidget = nullptr;
QObject *Visual::m_receiver = nullptr;
const char *Visual::m_member = nullptr;
VisualBuffer Visual::m_buffer;

QList<VisualFactory *> Visual::factories()
{
    checkFactories();
    return *m_factories;
}

QString Visual::file(const VisualFactory *factory)
{
    checkFactories();
    return m_files->value(factory);
}

void Visual::setEnabled(VisualFactory *factory, bool enable)
{
    checkFactories();
    if (!m_factories->contains(factory))
        return;

    QString name = factory->properties().shortName;
    QSettings settings;
    QStringList visList = settings.value(u"Visualization/enabled_plugins"_s).toStringList();

    if (enable)
    {
        if (!visList.contains(name))
            visList << name;
        if (!m_vis_map.value(factory) && m_parentWidget)
        {
            createVisualization(factory, m_parentWidget);
        }
    }
    else
    {
        visList.removeAll(name);
        if (m_vis_map.value(factory))
        {
            m_visuals.removeAll(m_vis_map.value(factory));
            m_vis_map.value(factory)->close();
            m_vis_map.remove (factory);
        }
    }
    settings.setValue(u"Visualization/enabled_plugins"_s, visList);
}

bool Visual::isEnabled(const VisualFactory *factory)
{
    checkFactories();
    QString name = factory->properties().shortName;
    QSettings settings;
    QStringList visList = settings.value(u"Visualization/enabled_plugins"_s).toStringList();
    return visList.contains(name);
}

void Visual::add(Visual *visual)
{
    if (!m_visuals.contains(visual))
    {
        Qmmp::State st = StateHandler::instance() ? StateHandler::instance()->state() : Qmmp::Stopped;
        if(st == Qmmp::Playing || st == Qmmp::Buffering || st == Qmmp::Paused)
            visual->start();
        m_visuals.append(visual);
    }
}

void Visual::remove(Visual *visual)
{
    m_visuals.removeAll(visual);
}

void Visual::initialize(QWidget *parent , QObject *receiver, const char *member)
{
    m_receiver = receiver;
    m_member = member;
    m_parentWidget = parent;
    for(VisualFactory *factory : factories())
    {
        if(isEnabled(factory))
        {
            QTimer::singleShot(0, parent, [factory, parent] { createVisualization(factory, parent); });
        }
    }
}

QList<Visual*>* Visual::visuals()
{
    return &m_visuals;
}

void Visual::showSettings(VisualFactory *factory, QWidget *parent)
{
    QDialog *dialog = factory->createSettings(parent);
    if (!dialog)
        return;

    if (dialog->exec() == QDialog::Accepted && m_vis_map.contains(factory))
    {
        Visual *visual = m_vis_map.value(factory);
        remove(visual);
        visual->close();
        createVisualization(factory, m_parentWidget);
    }
    dialog->deleteLater();
}

void Visual::addAudio(float *pcm, int samples, int channels, qint64 ts, qint64 delay)
{
    m_buffer.mutex()->lock();
    m_buffer.add(pcm, samples, channels, ts, delay);
    m_buffer.mutex()->unlock();
}

void Visual::clearBuffer()
{
    m_buffer.mutex()->lock();
    m_buffer.clear();
    m_buffer.mutex()->unlock();
}

void Visual::start()
{}

void Visual::stop()
{}

void Visual::checkFactories()
{
    if (!m_factories)
    {
        m_factories = new QList<VisualFactory *>;
        m_files = new QHash <const VisualFactory*, QString>;

        for(const QString &filePath : Qmmp::findPlugins(u"Visual"_s))
        {
            QPluginLoader loader(filePath);
            QObject *plugin = loader.instance();
            if (loader.isLoaded())
                qCDebug(core) << "loaded plugin" << QFileInfo(filePath).fileName();
            else
                qCWarning(core) << loader.errorString();

            VisualFactory *factory = nullptr;
            if (plugin)
                factory = qobject_cast<VisualFactory *>(plugin);

            if (factory)
            {
                m_factories->append(factory);
                m_files->insert(factory, filePath);
                if(!factory->translation().isEmpty())
                {
                    QTranslator *translator = new QTranslator(qApp);
                    if(translator->load(factory->translation() + Qmmp::systemLanguageID()))
                        qApp->installTranslator(translator);
                    else
                        delete translator;
                }
            }
        }
    }
}

void Visual::createVisualization(VisualFactory *factory, QWidget *parent)
{
    Visual *visual = factory->create(parent);
    if (m_receiver && m_member)
        connect(visual, SIGNAL(closedByUser()), m_receiver, m_member);
    visual->setWindowFlags(visual->windowFlags() | Qt::Window);
    qCDebug(core) << "added visualization:" << factory->properties().shortName;
    m_vis_map.insert(factory, visual);
    add(visual);
    visual->show();
}
