/*
  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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "archivebrowser.h"
#include "archivebrowsermodel.h"
#include "copyworker.h"
#include "smtreeview.h"
#include "smglobals.h"
#include "pictureviewer2.h"
#include "delegates.h"
#include "helper.h"
ArchiveBrowser::ArchiveBrowser(QWidget *parent) : QWidget(parent), mSelectedSize(0), mSelectedItems(0){
    //prep
    mModel = static_cast(SmGlobals::instance()->model("BrowserModel"));
    mProxy = new ArchiveBrowserModelProxy;
    mProxy->setSourceModel(mModel);
    mTree = new SmTreeView("ui/archivebrowserheaders");
    mTree->setModel(mProxy);
    mTree->setColumnHidden(ArchiveBrowserModel::GenericId, true);
    mTree->setColumnHidden(ArchiveBrowserModel::NodeType, true);
    mTree->setItemDelegateForColumn(ArchiveBrowserModel::TotalSize, new SizeDelegate(this));
    mTree->setItemDelegateForColumn(ArchiveBrowserModel::FileType, new FileTypeDelegate(this));
    mTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
    QToolBar *toolBar = new QToolBar;
    QWidget *spacer1 = new QWidget;
    toolBar->addWidget(spacer1);
    spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
    QLabel *qualityL = new QLabel(tr("Quality"));
    toolBar->addWidget(qualityL);
    mQualityFilter = new QComboBox;
    toolBar->addWidget(mQualityFilter);
    setupQualityFilter();
    connect(mQualityFilter, QOverload::of(&QComboBox::currentIndexChanged), mProxy, &ArchiveBrowserModelProxy::setQualityFilter);
    toolBar->addSeparator();
    mSizeFilter = new QCheckBox(tr("Filter by size"));
    connect(mSizeFilter, &QCheckBox::stateChanged, mProxy, &ArchiveBrowserModelProxy::setSizeFilter);
    toolBar->addWidget(mSizeFilter);
    toolBar->addSeparator();
    QAction *refreshA = new QAction(QIcon(":/refresh.png"), tr("Refresh"), this);
    connect(refreshA, &QAction::triggered, this, &ArchiveBrowser::refresh);
    toolBar->addAction(refreshA);
    mTree->addAction(refreshA);
    toolBar->addSeparator();
    mTree->addAction(Helper::createSeparator(this));
    QAction *playSelectedA = new QAction(QIcon(":/dildo.png"), tr("Play selected..."), this);
    connect(playSelectedA, &QAction::triggered, this, &ArchiveBrowser::playSelected);
    toolBar->addAction(playSelectedA);
    mTree->addAction(playSelectedA);
    QAction *moveToUSBA = new QAction(QIcon(":/clean_tampon.png"), tr("Move to USB..."), this);
    connect(moveToUSBA, &QAction::triggered, this, &ArchiveBrowser::moveToUSB);
    toolBar->addAction(moveToUSBA);
    mTree->addAction(moveToUSBA);
    QAction *moveToBurnA = new QAction(QIcon(":/shackles.png"), tr("Move to burn..."), this);
    connect(moveToBurnA, &QAction::triggered, this, &ArchiveBrowser::moveToBurn);
    toolBar->addAction(moveToBurnA);
    mTree->addAction(moveToBurnA);
    toolBar->addSeparator();
    mTree->addAction(Helper::createSeparator(this));
    QIcon downArrowIcon = Helper::icon(QColor(255,85,255), Qt::white, QChar(0x2b07), true, true);
    QAction *expandAllA = new QAction(downArrowIcon, tr("Expand all"), this);
    connect(expandAllA, &QAction::triggered, archiveTree(), &SmTreeView::expandAll);
    toolBar->addAction(expandAllA);
    mTree->addAction(expandAllA);
    QIcon upArrowIcon = Helper::icon(QColor(255,85,255), Qt::white, QChar(0x2b06), true, true);
    QAction *collapseAllA = new QAction(upArrowIcon, tr("Collapse all"), this);
    connect(collapseAllA, &QAction::triggered, archiveTree(), &SmTreeView::collapseAll);
    toolBar->addAction(collapseAllA);
    mTree->addAction(collapseAllA);
    toolBar->addSeparator();
    mTree->readHeaderConfig();
    QHash hData = mModel->headerData();
    QStringList hDataSorted = hData.keys();
    std::sort(hDataSorted.begin(), hDataSorted.end());
    QActionGroup *hDataAG = new QActionGroup(this);
    hDataAG->setExclusive(false);
    for(const QString &h : hDataSorted){
        QAction *a = new QAction(h, this);
        a->setCheckable(true);
        a->setData(hData.value(h));
        hDataAG->addAction(a);
        if(!mTree->header()->isSectionHidden(hData.value(h))){
            a->setChecked(true);
        }
        connect(a, &QAction::triggered, [=] { mTree->toggleHeader(a); });
    }
    QIcon headerIcon = Helper::icon(QColor(255,85,255), Qt::white, 'H', true, true);
    QAction *headerA = new QAction(headerIcon, tr("Show headers"), this);
    QMenu *headerMenu = new QMenu;
    headerMenu->addActions(hDataAG->actions());
    headerA->setMenu(headerMenu);
    toolBar->addAction(headerA);
    toolBar->addSeparator();
    toolBar->addAction(SmGlobals::instance()->globalAction());
    QWidget *spacer2 = new QWidget;
    spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
    toolBar->addWidget(spacer2);
    //connect
    connect(mTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ArchiveBrowser::browserSelectionChanged);
    connect(mTree, &SmTreeView::doubleClicked, this, &ArchiveBrowser::itemDoubleClicked);
    connect(mModel, &ArchiveBrowserModel::populated, this, &ArchiveBrowser::resetAll);
    //make widget
    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addWidget(toolBar);
    mainLayout->addWidget(mTree);
    setLayout(mainLayout);
    mTree->setSortingEnabled(true);
    //copyworker
    mUSBProgress = 0;
    mCopyWorker = new CopyWorker(this);
    connect(mCopyWorker, &CopyWorker::error, this, &ArchiveBrowser::copyError);
    connect(mCopyWorker, &CopyWorker::success, this, &ArchiveBrowser::copySuccess);
    connect(mCopyWorker, &CopyWorker::file, this, &ArchiveBrowser::setCopyFile);
}
void ArchiveBrowser::browserSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
    QModelIndexList selectedIdx = selectedRows(selected);
    QModelIndexList deselectedIdx = selectedRows(deselected);
    for(const QModelIndex &sel : selectedIdx){
        mSelectedSize += sel.data(ArchiveBrowserModel::TotalSizeRole).toDouble();
        ++mSelectedItems;
        mModel->setData(sel, true, ArchiveBrowserModel::SelectedRole);
    }
    for(const QModelIndex &desel : deselectedIdx){
        mSelectedSize -= desel.data(ArchiveBrowserModel::TotalSizeRole).toDouble();
        --mSelectedItems;
        mModel->setData(desel, false, ArchiveBrowserModel::SelectedRole);
    }
    emit sizeChanged(mSelectedSize);
    emit itemCountChanged(mSelectedItems);
    qint64 remaining = DVDSIZE - mSelectedSize;
    mProxy->setBytesRemaining(remaining);
}
void ArchiveBrowser::readConfig(){
    QSettings s;
    QString qualFilter = s.value("ui/browserquality", tr("(none)")).toString();
    mQualityFilter->setCurrentText(qualFilter);
}
void ArchiveBrowser::writeSettings(){
    mTree->writeHeaderConfig();
    QSettings s;
    s.setValue("ui/browserquality", mQualityFilter->currentText());
}
void ArchiveBrowser::moveToBurn() {
    QModelIndexList sel = mTree->selectionModel()->selectedRows();
    if(sel.isEmpty()){
        return;
    }
    QSettings s;
    QString destDirS = s.value("paths/burn").toString();
    QDir burnDir(destDirS);
    if(!burnDir.exists()){
        QString msg = QString(tr("Destination directory %1 does not exist!\nBailing out!")).arg(destDirS);
        QMessageBox::critical(this, tr("Error"), msg);
        return;
    }
    QString msg = QString(tr("This will do the following:
- Move %1 file(s) to %2
- Update the DVD no. for %1 files
Continue?
")).arg(sel.size()).arg(destDirS);
    int retval = QMessageBox::question(this, tr("Question"), msg, QMessageBox::Yes | QMessageBox::No);
    if(retval == QMessageBox::Yes){
        QList filesToUpdate;
        for(const QModelIndex &idx : sel){
            QString dirName = idx.data(ArchiveBrowserModel::NameRole).toString();
            dirName.replace(' ', '.');
            burnDir.mkdir(dirName);
            QString burnDirS = QString("%1/%2").arg(destDirS).arg(dirName);
            QModelIndex real = mProxy->mapToSource(idx);
            QModelIndexList children = mModel->children(real);
            for(const QModelIndex &child : children){
                QFileInfo current(child.data(ArchiveBrowserModel::FullPathRole).toString());
                int type = child.data(ArchiveBrowserModel::FileTypeRole).toInt();
                QString destination = QString("%1/%2").arg(burnDirS).arg(current.fileName());
                if(type == FT_MOVIE){
                    QFile::rename(current.absoluteFilePath(), destination);
                    filesToUpdate << child.data(ArchiveBrowserModel::GenericIdRole).toInt();
                }else{
                    QFile::copy(current.absoluteFilePath(), destination);
                }
            }
        }
        mModel->updateDVDNo(filesToUpdate);
        mModel->refresh();
        mProxy->setBytesRemaining(0);
    }
}
void ArchiveBrowser::moveToUSB(){
    QModelIndexList sel = mTree->selectionModel()->selectedRows();
    if(sel.isEmpty()){
        return;
    }
    QSettings s;
    QString destDirS = s.value("paths/usb").toString();
    QFileInfo destFi(destDirS);
    if(!destFi.exists() || !destFi.isDir()){
        QString msg = QString(tr("%1 does not exist or isn't a directory!")).arg(destFi.absoluteFilePath());
        QMessageBox::critical(this, tr("Error"), msg);
        return;
    }
    int nextDVDNo = mModel->nextDVDNo();
    QString dvdDirS = QString("DVD_%1").arg(QString::number(nextDVDNo));
    QDir dest(destDirS);
    if(dest.exists(dvdDirS)){
        QString msg = QString(tr("Something fishy is going on: %1 already exists in %2!")).arg(dvdDirS).arg(destDirS);
        QMessageBox::critical(this, tr("Error"), msg);
        return;
    }
    bool mkdir = dest.mkdir(dvdDirS);
    if(!mkdir){
        QString msg = QString(tr("Failed to create %1 in %2!")).arg(dvdDirS).arg(destDirS);
        QMessageBox::critical(this, tr("Error"), msg);
        return;
    }
    // this one is .../DVD_123
    QString finalDir = QString("%1/%2").arg(destDirS).arg(dvdDirS);
    QString msg = QString(tr("This will do the following:
- Move %1 file(s) to %2
- Update the DVD no. for %1 files
Continue?
")).arg(sel.size()).arg(finalDir);
    int retval = QMessageBox::question(this, tr("Question"), msg, QMessageBox::Yes | QMessageBox::No);
    if(retval == QMessageBox::Yes){
        mCopyWorker->clear();
        QDir finalD(finalDir);
        for(const QModelIndex &idx : sel){
            QString dirName = idx.data(ArchiveBrowserModel::NameRole).toString();
            dirName.replace(' ', '.');
            // this one .../DVD_123/series_name
            // don't check for success, it may already exist!
            finalD.mkdir(dirName);
            QModelIndex real = mProxy->mapToSource(idx);
            QModelIndexList children = mModel->children(real);
            for(const QModelIndex &child : children){
                QString source = child.data(ArchiveBrowserModel::FullPathRole).toString();
                QFileInfo sFi(source);
                QString destination = QString("%1/%2/%3").arg(finalDir).arg(dirName).arg(sFi.fileName());
                mCopyWorker->enqueue(source, destination);
                mCopyWorker->appendData(source, child.data(ArchiveBrowserModel::FileTypeRole));
                mCopyWorker->appendData(source, child.data(ArchiveBrowserModel::GenericIdRole));
            }
        }
        mUSBProgress = new QProgressDialog(this);
        connect(mCopyWorker, &CopyWorker::bytesRead, mUSBProgress, &QProgressDialog::setValue);
        mUSBProgress->setLabelText(tr("Copying files..."));
        mUSBProgress->setMinimum(0);
        mUSBProgress->setMaximum(mCopyWorker->max());
        mUSBProgress->show();
        mCopyWorker->start();
    }
}
void ArchiveBrowser::refresh() {
    mModel->refresh();
}
void ArchiveBrowser::playSelected(){
    QPair pgdata = Helper::programData("movieviewer");
    QModelIndexList sel = mTree->selectionModel()->selectedRows();
    QStringList movieFiles;
    for(const QModelIndex &idx : sel){
        QModelIndex real = mProxy->mapToSource(idx);
        QModelIndexList children = mModel->children(real);
        for(const QModelIndex &ci : children){
            if(ci.data(ArchiveBrowserModel::FileTypeRole).toInt() == 1){
                movieFiles << ci.data(ArchiveBrowserModel::FullPathRole).toString();
            }
        }
    }
    if(movieFiles.isEmpty()){
        return;
    }
    QString prg = pgdata.first;
    QStringList args = pgdata.second;
    args.append(movieFiles);
    QProcess::startDetached(prg, args);
}
void ArchiveBrowser::itemDoubleClicked(QModelIndex cur){
    if(cur.data(ArchiveBrowserModel::NodeTypeRole).toInt() == ArchiveBrowserModel::SeriesPartNode){
        return;
    }
    if(cur.data(ArchiveBrowserModel::NodeTypeRole).toInt() == ArchiveBrowserModel::FileNode){
        int fileType = cur.data(ArchiveBrowserModel::FileTypeRole).toInt();
        PictureViewer2 *picView = SmGlobals::instance()->pictureViewer();
        if(fileType == FT_MOVIE){
            QPixmap pm = Helper::preview(cur.data(ArchiveBrowserModel::FullPathRole).toString());
            picView->setPixmap(pm);
        }else{
            picView->setFile(cur.data(ArchiveBrowserModel::FullPathRole).toString());
        }
        picView->show();
    }
}
void ArchiveBrowser::setupQualityFilter(){
    mQualityFilter->clear();
    QList qualities = mModel->availableQualities();
    std::sort(qualities.begin(), qualities.end());
    QStringList qualityList = QStringList() << tr("(none)");
    for(int q : qualities){
        qualityList << QString::number(q);
    }
    mQualityFilter->addItems(qualityList);
}
void ArchiveBrowser::resetAll() {
    mTree->selectionModel()->clear();
    mSelectedItems = 0;
    mSelectedSize = 0;
    emit sizeChanged(0);
    emit itemCountChanged(0);
}
void ArchiveBrowser::copyError(QString error){
    mUSBProgress->hide();
    QMessageBox::critical(this, tr("Copy Error"), error);
    mCopyWorker->disconnect(mUSBProgress);
    QProgressDialog *old = mUSBProgress;
    old->deleteLater();
    mUSBProgress = 0;
}
void ArchiveBrowser::copySuccess(QString success){
    mUSBProgress->hide();
    QMessageBox::information(this, tr("Copy Success"), success);
    mCopyWorker->disconnect(mUSBProgress);
    QProgressDialog *old = mUSBProgress;
    old->deleteLater();
    mUSBProgress = 0;
    QString msg = QString(tr("Delete source files and update database?"));
    int q = QMessageBox::question(this, tr("Question"), msg);
    if(q == QMessageBox::Yes){
        QHash > data = mCopyWorker->data();
        QList filesToUpdate;
        for(const QString &source : data.keys()){
            int ft = data.value(source).at(0).toInt();
            if(ft == FT_MOVIE){
                QFile::remove(source);
                filesToUpdate << data.value(source).at(1).toInt();
            }
        }
        mModel->updateDVDNo(filesToUpdate);
        mModel->refresh();
        mProxy->setBytesRemaining(0);
        emit needFSFreeUpdate();
        mSizeFilter->setChecked(false);
    }
}
void ArchiveBrowser::setCopyFile(QString file){
    QString msg = QString(tr("Copying %1")).arg(file);
    mUSBProgress->setLabelText(msg);
}
QModelIndexList ArchiveBrowser::selectedRows(const QItemSelection &sel){
    QModelIndexList retval;
    QModelIndexList selIdx = sel.indexes();
    for(const QModelIndex &idx : selIdx){
        QModelIndex real = mProxy->mapToSource(idx);
        if(real.column() == 0){
            retval << real;
        }
    }
    return retval;
}