/*
 * Cantata
 *
 * Copyright (c) 2011-2022 Craig Drummond <craig.p.drummond@gmail.com>
 *
 * ----
 *
 * 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "cachesettings.h"
#include "context/artistview.h"
#include "context/albumview.h"
#include "context/songview.h"
#include "context/contextwidget.h"
#include "context/wikipediasettings.h"
#include "covers.h"
#include "support/utils.h"
#include "support/messagebox.h"
#include "config.h"
#include "support/thread.h"
#include "settings.h"
#include "widgets/basicitemdelegate.h"
#include "models/streamsmodel.h"
#include "online/podcastsearchdialog.h"
#include "support/squeezedtextlabel.h"
#ifdef ENABLE_SCROBBLING
#include "scrobbling/scrobbler.h"
#endif
#include <QLabel>
#include <QPushButton>
#include <QStyle>
#include <QGridLayout>
#include <QFileInfo>
#include <QDir>
#include <QList>
#include <QHeaderView>

static const int constMaxRecurseLevel=4;

static void calculate(const QString &d, const QStringList &types, int &items, quint64 &space, int level=0)
{
    if (!d.isEmpty() && level<constMaxRecurseLevel) {
        QDir dir(d);
        if (dir.exists()) {
            QFileInfoList files=dir.entryInfoList(types, QDir::Files|QDir::NoDotAndDotDot);
            for (const QFileInfo &file: files) {
                space+=file.size();
            }
            items+=files.count();

            QFileInfoList dirs=dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
            for (const QFileInfo &subDir: dirs) {
                calculate(subDir.absoluteFilePath(), types, items, space, level+1);
            }
        }
    }
}

static void deleteAll(const QString &d, const QStringList &types, int level=0)
{
    if (!d.isEmpty() && level<constMaxRecurseLevel) {
        QDir dir(d);
        if (dir.exists()) {
            QFileInfoList dirs=dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
            for (const QFileInfo &subDir: dirs) {
                deleteAll(subDir.absoluteFilePath(), types, level+1);
            }

            QFileInfoList files=dir.entryInfoList(types, QDir::Files|QDir::NoDotAndDotDot);
            for (const QFileInfo &file: files) {
                QFile::remove(file.absoluteFilePath());
            }
            if (0!=level) {
                QString dirName=dir.dirName();
                if (!dirName.isEmpty()) {
                    dir.cdUp();
                    dir.rmdir(dirName);
                }
            }
        }
    }
}

CacheItemCounter::CacheItemCounter(const QString &name, const QString &d, const QStringList &t)
    : dir(d)
    , types(t)
{
    thread=new Thread(name+" CacheCleaner");
    moveToThread(thread);
    thread->start();
}

CacheItemCounter::~CacheItemCounter()
{
    if (thread) {
        thread->stop();
    }
}

void CacheItemCounter::getCount()
{
    int items=0;
    quint64 space=0;
    calculate(dir, types, items, space);
    emit count(items, space);
}

void CacheItemCounter::deleteAll()
{
    ::deleteAll(dir, types);
    getCount();
}

CacheItem::CacheItem(const QString &title, const QString &d, const QStringList &t, QTreeWidget *p, Type ty)
    : QTreeWidgetItem(p, QStringList() << title)
    , counter(new CacheItemCounter(title, d, t))
    , empty(true)
    , usedSpace(0)
    , type(ty)
{
    connect(this, SIGNAL(getCount()), counter, SLOT(getCount()), Qt::QueuedConnection);
    connect(this, SIGNAL(deleteAll()), counter, SLOT(deleteAll()), Qt::QueuedConnection);
    connect(counter, SIGNAL(count(int, quint64)), this, SLOT(update(int, quint64)), Qt::QueuedConnection);
    connect(this, SIGNAL(updated()), p, SIGNAL(itemSelectionChanged()));
}

CacheItem::~CacheItem()
{
    delete counter;
}

void CacheItem::update(int itemCount, quint64 space)
{
    setText(1, QString::number(itemCount));
    setText(2, Utils::formatByteSize(space));
    empty=0==itemCount;
    usedSpace=space;
    setStatus();
    emit updated();
}

void CacheItem::setStatus(const QString &str)
{
    QFont f(font(0));

    if (!str.isEmpty()) {
        f.setItalic(true);
        setText(1, str);
        setText(2, str);
    }
    setFont(1, f);
    setFont(2, f);
}

void CacheItem::clean()
{
    setStatus(tr("Deleting..."));
    emit deleteAll();
    switch (type) {
    case Type_Covers:       Covers::self()->clearNameCache(); break;
    case Type_ScaledCovers: Covers::self()->clearScaleCache(); break;
    default: break;
    }
}

void CacheItem::calculate()
{
    setStatus(tr("Calculating..."));
    emit getCount();
}

static inline void setResizeMode(QHeaderView *hdr, int idx, QHeaderView::ResizeMode mode)
{
    hdr->setSectionResizeMode(idx, mode);
}

CacheTree::CacheTree(QWidget *parent)
    : QTreeWidget(parent)
    , calculated(false)
{
    setHeaderLabels(QStringList() << tr("Name") << tr("Item Count") << tr("Space Used"));
    setAllColumnsShowFocus(true);
    setSelectionMode(QAbstractItemView::ExtendedSelection);
    setRootIsDecorated(false);
    setSortingEnabled(false);
    //setSortingEnabled(true);
    //sortByColumn(0, Qt::AscendingOrder);
    setResizeMode(header(), 0, QHeaderView::Stretch);
    setResizeMode(header(), 1, QHeaderView::Stretch);
    setResizeMode(header(), 2, QHeaderView::Stretch);
    header()->setStretchLastSection(true);
    setAlternatingRowColors(false);
    setItemDelegate(new BasicItemDelegate(this));
}

CacheTree::~CacheTree()
{
}

void CacheTree::showEvent(QShowEvent *e)
{
    if (!calculated) {
        for (int i=0; i<topLevelItemCount(); ++i) {
            static_cast<CacheItem *>(topLevelItem(i))->calculate();
        }
        calculated=true;
    }
    QTreeWidget::showEvent(e);
}


SpaceLabel::SpaceLabel(QWidget *p)
    : SqueezedTextLabel(p)
{
    QFont f=font();
    f.setItalic(true);
    setFont(f);
    update(tr("Calculating..."));
}

void SpaceLabel::update(int space)
{
    update(Utils::formatByteSize(space));
}

void SpaceLabel::update(const QString &text)
{
    setText(tr("Total space used: %1").arg(text));
}

CacheSettings::CacheSettings(QWidget *parent)
    : QWidget(parent)
{
    int spacing=Utils::layoutSpacing(this);
    QGridLayout *layout=new QGridLayout(this);
    layout->setMargin(0);
    int row=0;
    int col=0;
    QLabel *label=new QLabel(tr("Cantata caches various pieces of information (covers, lyrics, etc). Below is a summary of Cantata's "
                                  "current cache usage."), this);
    QFont f(Utils::smallFont(label->font()));
    f.setItalic(true);
    label->setFont(f);
    label->setWordWrap(true);
    layout->addWidget(label, row++, col, 1, 2);
    layout->addItem(new QSpacerItem(spacing, spacing, QSizePolicy::Fixed, QSizePolicy::Fixed), row++, 0);

    tree=new CacheTree(this);
    layout->addWidget(tree, row++, col, 1, 2);

    new CacheItem(tr("Covers"), Utils::cacheDir(Covers::constCoverDir, false), QStringList() << "*.jpg" << "*.png", tree,
                  CacheItem::Type_Covers);
    new CacheItem(tr("Scaled Covers"), Utils::cacheDir(Covers::constScaledCoverDir, false), QStringList() << "*.jpg" << "*.png", tree,
                  CacheItem::Type_ScaledCovers);
    new CacheItem(tr("Backdrops"), Utils::cacheDir(ContextWidget::constCacheDir, false), QStringList() << "*.jpg" << "*.png", tree);
    new CacheItem(tr("Lyrics"), Utils::cacheDir(SongView::constLyricsDir, false), QStringList() << "*"+SongView::constExtension, tree);
    new CacheItem(tr("Artist Information"), Utils::cacheDir(ArtistView::constCacheDir, false), QStringList() << "*"+ArtistView::constInfoExt
                  << "*"+ArtistView::constSimilarInfoExt << "*.json.gz" << "*.jpg" << "*.png", tree);
    new CacheItem(tr("Album Information"), Utils::cacheDir(AlbumView::constCacheDir, false), QStringList() << "*"+AlbumView::constInfoExt << "*.jpg" << "*.png", tree);
    new CacheItem(tr("Track Information"), Utils::cacheDir(SongView::constCacheDir, false), QStringList() << "*"+AlbumView::constInfoExt, tree);
    new CacheItem(tr("Stream Listings"), Utils::cacheDir(StreamsModel::constSubDir, false), QStringList() << "*"+StreamsModel::constCacheExt, tree);
    new CacheItem(tr("Podcast Directories"), Utils::cacheDir(PodcastSearchDialog::constCacheDir, false), QStringList() << "*"+PodcastSearchDialog::constExt, tree);
    new CacheItem(tr("Wikipedia Languages"), Utils::cacheDir(WikipediaSettings::constSubDir, false), QStringList() << "*.xml.gz", tree);
    #ifdef ENABLE_SCROBBLING
    new CacheItem(tr("Scrobble Tracks"), Utils::cacheDir(Scrobbler::constCacheDir, false), QStringList() << "*.xml.gz", tree);
    #endif

    for (int i=0; i<tree->topLevelItemCount(); ++i) {
        connect(static_cast<CacheItem *>(tree->topLevelItem(i)), SIGNAL(updated()), this, SLOT(updateSpace()));
    }

    spaceLabel=new SpaceLabel(this);
    button=new QPushButton(tr("Delete All"), this);
    layout->addWidget(spaceLabel, row, 0, 1, 1);
    layout->addWidget(button, row++, 1, 1, 1);
    button->setEnabled(false);

    connect(tree, SIGNAL(itemSelectionChanged()), this, SLOT(controlButton()));
    connect(button, SIGNAL(clicked()), this, SLOT(deleteAll()));
}

CacheSettings::~CacheSettings()
{
}

void CacheSettings::controlButton()
{
    button->setEnabled(false);
    for (int i=0; i<tree->topLevelItemCount(); ++i) {
        CacheItem *item=static_cast<CacheItem *>(tree->topLevelItem(i));

        if (item->isSelected() && !item->isEmpty()) {
            button->setEnabled(true);
            break;
        }
    }
}

void CacheSettings::deleteAll()
{
    QList<CacheItem *> toDelete;
    for (int i=0; i<tree->topLevelItemCount(); ++i) {
        CacheItem *item=static_cast<CacheItem *>(tree->topLevelItem(i));

        if (item->isSelected() && !item->isEmpty()) {
            toDelete.append(item);
        }
    }

    if (!toDelete.isEmpty()) {
        if (1==toDelete.count()) {
            if (MessageBox::Yes==MessageBox::warningYesNo(this, tr("Delete all '%1' items?").arg(toDelete.at(0)->name()),
                                                          tr("Delete Cache Items"), StdGuiItem::del(), StdGuiItem::cancel())) {
                toDelete.first()->clean();
            }
        } else if (toDelete.count()>1) {
            static const QChar constBullet(0x2022);

            QString items;
            for (CacheItem *i: toDelete) {
                items+=constBullet+QLatin1Char(' ')+i->name()+QLatin1Char('\n');
            }

            if (MessageBox::No==MessageBox::warningYesNo(this, tr("Delete items from all selected categories?")+QLatin1String("\n\n")+items,
                                                         tr("Delete Cache Items"), StdGuiItem::del(), StdGuiItem::cancel())) {
                return;
            }

            for (CacheItem *i: toDelete) {
                i->clean();
            }
        }
    }
}

void CacheSettings::updateSpace()
{
    quint64 space=0;
    for (int i=0; i<tree->topLevelItemCount(); ++i) {
        space+=static_cast<CacheItem *>(tree->topLevelItem(i))->spaceUsed();
    }
    spaceLabel->update(space);
}

#include "moc_cachesettings.cpp"
