#include "about.h"
#include "batchprocessing.h"
#include "filemanager.h"
#include "histogram.h"
#include "loadrunable.h"
#include "mainwindow.h"
#include "markedfiles.h"
#include "settingsdialog.h"
#include "statusbar.h"
#include <QActionGroup>
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QDockWidget>
#include <QFileDialog>
#include <QGuiApplication>
#include <QImageReader>
#include <QImageWriter>
#include <QJsonDocument>
#include <QKeyEvent>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QMimeDatabase>
#include <QNetworkReply>
#include <QProgressDialog>
#include <QScrollArea>
#include <QSettings>
#include <QStandardPaths>
#include <QStatusBar>
#include <QThreadPool>
#include <QSessionManager>
#include <QTimer>
#include <signal.h>
#include <unistd.h>

#ifdef __linux__
#include <sys/ioctl.h>
#include <linux/btrfs.h>
#include <sys/socket.h>
#endif

bool moveToTrash(const QString &path);

int MainWindow::socketPair[2] = {0, 0};

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    qRegisterMetaType<ImageInfoData>("ImageInfoData");
    qRegisterMetaType<std::shared_ptr<RawImage>>("std::shared_ptr<RawImage>");

    SettingsDialog::loadSettings();

    QStringList nameFilter;
    _saveFilter = tr("FITS (*.fits *.fit);;XISF (*.xisf);;");
    _openFilter = tr("Images (");
    QMimeDatabase db;
    auto supportedFormats = QImageReader::supportedMimeTypes();
    QStringList filters;
    for(auto format : supportedFormats)
    {
        QMimeType mimeType = db.mimeTypeForName(format);
        _openFilter.append("*.");
        _openFilter.append(mimeType.suffixes().join(" *."));
        _openFilter.append(" ");
        nameFilter.append(mimeType.suffixes());
    }
    auto supportedWrite = QImageWriter::supportedMimeTypes();
    for(auto format : supportedWrite)
    {
        QMimeType mimeType = db.mimeTypeForName(format);
        _saveFilter.append(mimeType.filterString() + ";;");
    }

    _openFilter.append("*.fit *.fits *.fts *.fz *.xisf *.cr2 *.cr3 *.nef *.dng)");
    _openFilter.append(tr(";;All files (*)"));
    nameFilter.append({"fit", "fits", "fts", "fz", "xisf", "cr2", "cr3", "nef", "dng"});
    QImageReader::setAllocationLimit(0);
    _openSuffix = {nameFilter.constBegin(), nameFilter.constEnd()};

    m_info = new ImageInfo(this);
    QDockWidget *infoDock = new QDockWidget(tr("Image info"), this);
    infoDock->setWidget(m_info);
    infoDock->setObjectName("infoDock");
    addDockWidget(Qt::LeftDockWidgetArea, infoDock);
    resize(1024, 600);
    setStatusBar(new QStatusBar(this));

    m_database = new Database(this);
    if(!m_database->init())
        QMessageBox::critical(this, tr("Can't open DB"), tr("Can't open SQLITE database"));

    m_image = new ImageScrollArea(m_database, this);
    setCentralWidget(m_image);

    StatusBar *statusBar = new StatusBar(this);
    setStatusBar(statusBar);
    connect(m_image, &ImageScrollArea::status, statusBar, &StatusBar::newStatus);

    m_stretchPanel = new StretchToolbar(this);
    connect(m_stretchPanel, &StretchToolbar::paramChanged, m_image, &ImageScrollArea::setMTFParams);
    connect(m_stretchPanel, &StretchToolbar::autoStretch, [&](){ m_stretchPanel->stretchImage(m_ringList->currentImage().get()); });
    connect(m_stretchPanel, &StretchToolbar::invert, m_image, &ImageScrollArea::invert);
    connect(m_stretchPanel, &StretchToolbar::superPixel, m_image, &ImageScrollArea::superPixel);
    connect(m_stretchPanel, &StretchToolbar::falseColor, m_image, &ImageScrollArea::falseColor);
    connect(m_stretchPanel, &StretchToolbar::drawGrid, m_image, &ImageScrollArea::drawGrid);

    m_ringList = new ImageRingList(m_database, nameFilter, this);
    m_filesystem = new FilesystemWidget(m_ringList, this);
    connect(m_filesystem, &FilesystemWidget::fileSelected, this, static_cast<void (MainWindow::*)(int)>(&MainWindow::loadFile));
    connect(m_filesystem, &FilesystemWidget::sortChanged, m_ringList, &ImageRingList::setSort);
    connect(m_filesystem, &FilesystemWidget::reverseSort, m_ringList, &ImageRingList::reverseSort);

    m_filetree = nullptr;
#if !defined(FLATPAK) || !defined(__aarch64__)//bug with QTreeView and QFileSystemModel on ARM64 under flatpak
    m_filetree = new Filetree(this);
    connect(m_filetree, &Filetree::fileSelected, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
    connect(m_filetree, &Filetree::copyFiles, [this](const QString &path){ copyOrMove(true, path); });
    connect(m_filetree, &Filetree::moveFiles, [this](const QString &path){ copyOrMove(false, path); });
    connect(m_filetree, &Filetree::indexDirectory, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::indexDir));
#endif

    m_databaseView = new DataBaseView(m_database, this);
    connect(m_databaseView, &DataBaseView::loadFile, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));

#ifdef PLATESOLVER
    _plateSolving = new PlateSolving(this);
    addDockWidget(Qt::RightDockWidgetArea, _plateSolving);
    _plateSolving->hide();
#endif

    _databaseTree = new DatabaseTree(m_database, this);

    QToolBar *navigationToolbar = new QToolBar(tr("Navigation toolbar"), this);
    navigationToolbar->setObjectName("navigationtoolbar");
    navigationToolbar->hide();
    QAction *prevAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowLeft), tr("Previous image"));
    prevAction->setShortcuts({Qt::Key_Left, Qt::Key_Up});
    QAction *nextAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowRight), tr("Next image"));
    nextAction->setShortcuts({Qt::Key_Right, Qt::Key_Down});
    QAction *prevSubAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowUp), tr("Prev sub image"), Qt::Key_PageUp);
    QAction *nextSubAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowDown), tr("Next sub image"), Qt::Key_PageDown);
    connect(prevAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::decrement));
    connect(nextAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::increment));
    connect(prevSubAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::prevSubImage));
    connect(nextSubAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::nextSubImage));

    addToolBar(Qt::TopToolBarArea, navigationToolbar);
    addToolBar(Qt::TopToolBarArea, m_stretchPanel);

    QDockWidget *filesystemDock = new QDockWidget(tr("Filesystem"), this);
    filesystemDock->setWidget(m_filesystem);
    filesystemDock->setObjectName("filesystemDock");
    addDockWidget(Qt::LeftDockWidgetArea, filesystemDock);

    QDockWidget *databaseViewDock = new QDockWidget(tr("FITS/XISF files database"), this);
    databaseViewDock->setWidget(m_databaseView);
    databaseViewDock->setObjectName("databaseViewDock");
    databaseViewDock->hide();
    connect(databaseViewDock, &QDockWidget::visibilityChanged, m_databaseView, &DataBaseView::visible);
    addDockWidget(Qt::BottomDockWidgetArea, databaseViewDock);

    QDockWidget *filetreeDock = nullptr;
#if !defined(FLATPAK) || !defined(__aarch64__)
    filetreeDock = new QDockWidget(tr("File tree"), this);
    filetreeDock->setWidget(m_filetree);
    filetreeDock->setObjectName("filetreeDock");
    databaseViewDock->hide();
    addDockWidget(Qt::LeftDockWidgetArea, filetreeDock);
#endif

    Histogram *histogram = new Histogram(this);
    QDockWidget *histogramDock = new QDockWidget(tr("Histogram"), this);
    histogramDock->setWidget(histogram);
    histogramDock->setObjectName("histogramDock");
    histogramDock->hide();
    addDockWidget(Qt::LeftDockWidgetArea, histogramDock);

    DatabaseTreeView *databaseTreeView = new DatabaseTreeView(m_database, this);
    QDockWidget *databaseTreeDock = new QDockWidget(tr("Database Tree"), this);
    databaseTreeDock->setObjectName("databasetreeDock");
    databaseTreeDock->setWidget(databaseTreeView);
    databaseTreeDock->hide();
    connect(databaseTreeDock, &QDockWidget::visibilityChanged, databaseTreeView, &DatabaseTreeView::visible);
    connect(databaseTreeView, &DatabaseTreeView::loadFile, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
    addDockWidget(Qt::BottomDockWidgetArea, databaseTreeDock);

    setWindowTitle(tr("Tenmon"));

    connect(m_ringList, &ImageRingList::pixmapLoaded, m_image, &ImageScrollArea::imageLoaded);
    connect(m_ringList, &ImageRingList::currentImageChanged, this, &MainWindow::updateWindowTitle);
    connect(m_ringList, &ImageRingList::infoLoaded, m_info, &ImageInfo::setInfo);
    connect(m_ringList, &ImageRingList::currentImageChanged, m_filesystem, &FilesystemWidget::selectFile);
    connect(m_ringList, &ImageRingList::thumbnailLoaded, m_image, &ImageScrollArea::thumbnailLoaded);
    connect(m_ringList, &ImageRingList::pixmapLoaded, m_stretchPanel, &StretchToolbar::imageLoaded);
    connect(m_ringList, &ImageRingList::pixmapLoaded, histogram, &Histogram::imageLoaded);
#ifdef PLATESOLVER
    connect(m_ringList, &ImageRingList::pixmapLoaded, _plateSolving, &PlateSolving::imageLoaded);
    connect(_plateSolving, &PlateSolving::headerUpdated, m_ringList, &ImageRingList::reloadImage);
#endif
    connect(m_image, &ImageScrollArea::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));

    QMenu *fileMenu = new QMenu(tr("File"), this);
    fileMenu->addAction(tr("Open"), QKeySequence::Open, this, static_cast<void (MainWindow::*)()>(&MainWindow::loadFile));
    fileMenu->addAction(tr("Open directory recursively"), this, &MainWindow::loadDir);
    QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, &MainWindow::saveAs);
    fileMenu->addSeparator();
#if !defined(FLATPAK) || !defined(__aarch64__)
    fileMenu->addAction(tr("File manager"), this, &MainWindow::openFileManager);
#endif
    fileMenu->addAction(tr("Copy marked files"), Qt::Key_F5, this, &MainWindow::copyMarked);
    fileMenu->addAction(tr("Move marked files"), Qt::Key_F6, this, &MainWindow::moveMarked);
    QAction *deleteAction = fileMenu->addAction(tr("Move marked files to trash"), QKeySequence::Delete, this, &MainWindow::deleteMarked);
#ifdef Q_OS_MACOS
    deleteAction->setShortcuts(QList<QKeySequence>({Qt::Key_Backspace, QKeySequence::Delete}));
#else
    deleteAction->setShortcuts(QKeySequence::Delete);
#endif
    fileMenu->addSeparator();
    fileMenu->addAction(tr("Index directory"), this, static_cast<void (MainWindow::*)()>(&MainWindow::indexDir));
    fileMenu->addAction(tr("Reindex files"), this, &MainWindow::reindex);
    fileMenu->addAction(tr("Export database to CSV"), this, &MainWindow::exportCSV);
    fileMenu->addAction(tr("Batch processing"), Qt::Key_B | Qt::CTRL, [this](){
        BatchProcessing *batchProcessing = new BatchProcessing(m_database, this);
        batchProcessing->exec();
        delete batchProcessing;
    });
    fileMenu->addSeparator();
    QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, &MainWindow::liveMode);
    liveModeAction->setCheckable(true);
    QAction *exitAction = fileMenu->addAction(tr("Exit"), QCoreApplication::instance(), &QCoreApplication::quit, Qt::QueuedConnection);
    exitAction->setShortcut(QKeySequence::Quit);
    menuBar()->addMenu(fileMenu);

    QMenu *editMenu = new QMenu(tr("Edit"), this);
    editMenu->addAction(tr("Settings"), this, &MainWindow::showSettingsDialog);
    menuBar()->addMenu(editMenu);

    QMenu *navigationMenu = new QMenu(tr("Navigation"), this);
    navigationMenu->addAction(prevAction);
    navigationMenu->addAction(nextAction);
    navigationMenu->addAction(prevSubAction);
    navigationMenu->addAction(nextSubAction);
    menuBar()->addMenu(navigationMenu);

    QMenu *viewMenu = new QMenu(tr("View"), this);
    viewMenu->addAction(tr("Zoom In"), QKeySequence::ZoomIn, m_image, &ImageScrollArea::zoomIn);
    viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_image, &ImageScrollArea::zoomOut);
    viewMenu->addAction(tr("Best Fit"), QKeySequence("Ctrl+1"), m_image, &ImageScrollArea::bestFit);
    viewMenu->addAction(tr("100%"), QKeySequence("Ctrl+0"), m_image, &ImageScrollArea::oneToOne);
    viewMenu->addSeparator();
    QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask"));
    QActionGroup *bayerActionGroup = new QActionGroup(this);
    QAction *rggbAction = bayerActionGroup->addAction(tr("RGGB"));//0 0
    QAction *grbgAction = bayerActionGroup->addAction(tr("GRBG"));//1 0
    QAction *gbrgAction = bayerActionGroup->addAction(tr("GBRG"));//0 1
    QAction *bggrAction = bayerActionGroup->addAction(tr("BGGR"));//1 1
    rggbAction->setCheckable(true); rggbAction->setData(0); rggbAction->setIcon(QIcon(":/bayer.png"));
    grbgAction->setCheckable(true); grbgAction->setData(1); grbgAction->setIcon(QIcon(":/grbg.png"));
    gbrgAction->setCheckable(true); gbrgAction->setData(2); gbrgAction->setIcon(QIcon(":/gbrg.png"));
    bggrAction->setCheckable(true); bggrAction->setData(3); bggrAction->setIcon(QIcon(":/bggr.png"));
    bayerMenu->addActions({rggbAction, grbgAction, gbrgAction, bggrAction});
    viewMenu->addMenu(bayerMenu);
    connect(bayerActionGroup, &QActionGroup::triggered, [this](QAction *action){
        int data = action->data().toInt();
        m_image->setBayerMask(data);
        QSettings settings;
        settings.setValue("mainwindow/bayermask", data);
    });

    QStringList colormaps = {"Autumn", "Bone", "Jet", "Winter", "Rainbow", "Ocean", "Summer", "Spring", "Cool", "HSV", "Pink", "Hot", "Parula", "Magma",
    "Inferno", "Plasma", "Viridis", "Cividis", "Twilight", "Twilight shifted", "Turbo", "Deepgreen"};
    QMenu *colormapMenu = viewMenu->addMenu(tr("Colormap"));
    QActionGroup *colormapActionGroup = new QActionGroup(this);

    QImage cmImg = ImageWidget::loadColormap();
    for(int i=0; i<cmImg.height(); i++)
    {
        QImage icon = cmImg.copy(0, i, cmImg.width(), 1).scaled(32, 32);
        QAction *action = colormapActionGroup->addAction(i < colormaps.size() ? colormaps[i] : tr("User %1").arg(i - colormaps.size() + 1));
        action->setIcon(QPixmap::fromImage(icon));
        action->setCheckable(true); action->setData(i);
        colormapMenu->addAction(action);
    }
    viewMenu->addMenu(colormapMenu);
    connect(colormapActionGroup, &QActionGroup::triggered, [this](QAction *action){
        int data = action->data().toInt();
        m_image->setColormap(data);
        QSettings settings;
        settings.setValue("mainwindow/colormap", data);
    });

    QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), Qt::Key_F2, [this](bool checked){
        if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails();
        m_image->allocateThumbnails(m_ringList->imageNames());
        m_image->showThumbnail(checked);
        if(checked)m_ringList->loadThumbnails();
        else m_ringList->stopLoading();
    });
    thumbnailsAction->setCheckable(true);
    QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow);
    slideshowAction->setCheckable(true);
    viewMenu->addSeparator();
    auto actionList = m_stretchPanel->actions();
    actionList.removeFirst();
    viewMenu->addActions(actionList);
    menuBar()->addMenu(viewMenu);

    QMenu *selectMenu = new QMenu(tr("Select"), this);
    selectMenu->addAction(tr("Mark"), Qt::Key_F7, this, &MainWindow::markImage);
    selectMenu->addAction(tr("Unmark"), Qt::Key_F8, this, &MainWindow::unmarkImage);
    selectMenu->addSeparator();
    selectMenu->addAction(tr("Mark and next"), Qt::Key_M, this, &MainWindow::markAndNext);
    selectMenu->addAction(tr("Unmark and next"), Qt::Key_X, this, &MainWindow::unmarkAndNext);
    selectMenu->addSeparator();
    selectMenu->addAction(tr("Show marked list"), this, &MainWindow::showMarkFilesDialog);
    QAction *openMarked = selectMenu->addAction(tr("Open marked"), m_ringList, &ImageRingList::setMarked);
    menuBar()->addMenu(selectMenu);
    fileMenu->insertAction(saveAs, openMarked);

    /*QMenu *analyzeMenu = new QMenu(tr("Analyze"), this);
    QActionGroup *analyzeGroup = new QActionGroup(this);
    connect(analyzeGroup, &QActionGroup::triggered, [](QAction* action) {
        static QAction* lastAction = nullptr;
        if(action == lastAction)
        {
            action->setChecked(false);
            lastAction = nullptr;
        }
        else
            lastAction = action;
        });

    QAction *statsAction = analyzeGroup->addAction(tr("Image statistics"));
    QAction *peakAction = analyzeGroup->addAction(tr("Peak finder"));
    QAction *starAction = analyzeGroup->addAction(tr("Star finder"));
    statsAction->setCheckable(true);
    peakAction->setCheckable(true);
    starAction->setCheckable(true);
    connect(statsAction, SIGNAL(toggled(bool)), this, SLOT(imageStats(bool)));
    connect(peakAction, SIGNAL(toggled(bool)), this, SLOT(peakFinder(bool)));
    connect(starAction, SIGNAL(toggled(bool)), this, SLOT(starFinder(bool)));
    analyzeMenu->addActions({statsAction, peakAction, starAction});
    menuBar()->addMenu(analyzeMenu);*/

    QMenu *dockMenu = new QMenu(tr("Docks"), this);
    dockMenu->addAction(infoDock->toggleViewAction());
    dockMenu->addAction(m_stretchPanel->toggleViewAction());
    dockMenu->addAction(navigationToolbar->toggleViewAction());
    dockMenu->addAction(filesystemDock->toggleViewAction());
    dockMenu->addAction(databaseViewDock->toggleViewAction());
    dockMenu->addAction(databaseTreeDock->toggleViewAction());
    if(filetreeDock)dockMenu->addAction(filetreeDock->toggleViewAction());
    dockMenu->addAction(histogramDock->toggleViewAction());
#ifdef PLATESOLVER
    dockMenu->addAction(_plateSolving->toggleViewAction());
#endif
    menuBar()->addMenu(dockMenu);

    QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
    helpMenu->addAction(tr("Help"), QKeySequence::HelpContents, [this]{ HelpDialog *help = new HelpDialog(this); help->show(); });
    helpMenu->addAction(tr("About Tenmon"), [this]{ About about(this); about.exec(); });
    helpMenu->addAction(tr("About Qt"), [this](){ QMessageBox::aboutQt(this); });
    helpMenu->addAction(tr("Check for update"), this, &MainWindow::checkNewVersion);

    setupSigterm();
    QSettings settings;
    restoreGeometry(settings.value("mainwindow/geometry").toByteArray());
    restoreState(settings.value("mainwindow/state").toByteArray());
    int bayermask = settings.value("mainwindow/bayermask", 0).toInt();
    switch(bayermask)
    {
    default:
    case 0:
        rggbAction->setChecked(true); break;
    case 1:
        grbgAction->setChecked(true); break;
    case 2:
        gbrgAction->setChecked(true); break;
    case 3:
        bggrAction->setChecked(true); break;
    }
    int colormap = settings.value("mainwindow/colormap", 4).toInt();
    if(colormap >= 0 && colormap < colormapActionGroup->actions().size())
        colormapActionGroup->actions().at(colormap)->setChecked(true);

    m_image->setBayerMask(bayermask);
    m_image->setColormap(colormap);

    QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
    if(standardLocations.size())
        _lastDir = standardLocations.first();

    _lastDir = settings.value("mainwindow/lastdir", _lastDir).toString();

    m_image->setFocus();

    // workaround for nasty wayland backend bug https://bugreports.qt.io/browse/QTBUG-87332
    if(static_cast<QGuiApplication*>(QCoreApplication::instance())->platformName() == "wayland")
    {
        infoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
        filesystemDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
        databaseViewDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
        if(filetreeDock)filetreeDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
        if(_plateSolving)_plateSolving->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
        m_stretchPanel->setFloatable(false);
    }

    if(settings.value("settings/checkupdate", true).toBool())
    {
        QDateTime lastcheck = settings.value("settings/lastcheck").toDateTime();
        QDateTime weekago = QDateTime::currentDateTimeUtc().addDays(-7);
        if(!lastcheck.isValid() || lastcheck < weekago)
            checkNewVersion(true);
    }
}

MainWindow::~MainWindow()
{
    delete m_database;
}

void MainWindow::setupSigterm()
{
#ifdef __linux__
    struct sigaction signal;

    signal.sa_handler = MainWindow::signalHandler;
    sigemptyset(&signal.sa_mask);
    signal.sa_flags = 0;
    signal.sa_flags |= SA_RESTART;

    sigaction(SIGHUP, &signal, 0);
    sigaction(SIGTERM, &signal, 0);
    sigaction(SIGINT, &signal, 0);

    ::socketpair(AF_UNIX, SOCK_STREAM, 0, socketPair);
    socketNotifier = new QSocketNotifier(socketPair[1], QSocketNotifier::Read, this);
    connect(socketNotifier, &QSocketNotifier::activated, this, &MainWindow::socketNotify);
#endif
}

void MainWindow::signalHandler(int)
{
    char a = 1;
    ::write(socketPair[0], &a, sizeof(a));
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    QSettings settings;
    settings.setValue("mainwindow/geometry", saveGeometry());
    settings.setValue("mainwindow/state", saveState());
    QMainWindow::closeEvent(event);
}

void MainWindow::copyOrMove(bool copy)
{
    QString dest = QFileDialog::getExistingDirectory(this,
                                                     tr("Select destination"),
                                                     _lastDir,
                                                     QFileDialog::ShowDirsOnly);
    copyOrMove(copy, dest);
}

void MainWindow::copyOrMove(bool copy, const QString &dest)
{
    QDir dir(dest);
    if(!dest.isEmpty() && dir.exists())
    {
        int i = 0;
        int missing = 0;
        bool overwriteAll = false;
        bool skipAll = false;
        QStringList files = m_database->getMarkedFiles();
        QProgressDialog progress(copy ? tr("Copying") : tr("Moving"), tr("Cancel"), 0, files.size(), this);
        progress.setWindowModality(Qt::WindowModal);
        progress.show();
        for(const QString &file : files)
        {
            bool result = false;
            QFileInfo info(file);
            QFile srcFile(file);
            QFile dstFile(dir.absoluteFilePath(info.fileName()));

            if(!srcFile.exists())
            {
                missing++;
                continue;
            }

            if(dstFile.exists())
            {
                if(skipAll)
                {
                    continue;
                }
                else if(overwriteAll)
                {
                    dstFile.remove();
                }
                else
                {
                    QMessageBox::StandardButton button = QMessageBox::question(this, tr("Overwrite file?"), tr("Destination file %1 already exists. Overwrite?").arg(dstFile.fileName()),
                                            QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll);
                    switch (button)
                    {
                    case QMessageBox::YesToAll:
                        overwriteAll = true;
                    case QMessageBox::Yes:
                        dstFile.remove();
                        break;
                    case QMessageBox::NoToAll:
                        skipAll = true;
                    case QMessageBox::No:
                        continue;
                    default:
                        break;
                    }
                }
            }

            if(progress.wasCanceled())
                return;
#ifdef __linux__
            if(copy)
            {
                srcFile.open(QIODevice::ReadOnly);
                dstFile.open(QIODevice::WriteOnly);
                if(ioctl(dstFile.handle(), BTRFS_IOC_CLONE, srcFile.handle()) < 0)
                {
                    dstFile.remove();
                    dstFile.close();
                    result = srcFile.copy(dstFile.fileName());
                }
                else result = true;
            }
            else
            {
                result = srcFile.rename(dstFile.fileName());
            }
#else
            if(copy)
                result = srcFile.copy(dstFile.fileName());
            else
                result = srcFile.rename(dstFile.fileName());
#endif
            if(!result)
            {
                QString t = copy ? tr("Failed to copy") : tr("Failed to move");
                QString m = copy ? tr("Failed to copy from %1 to %2") : tr("Failed to move from %1 to %2");
                QMessageBox::StandardButton button = QMessageBox::warning(this, t,
                                                                          m.arg(srcFile.fileName()).arg(dir.absolutePath()),
                                                                          QMessageBox::Ignore | QMessageBox::Abort);
                if(button == QMessageBox::Abort)return;
            }
            progress.setValue(i++);
        }
        m_database->clearMarkedFiles();
        if(missing)
            QMessageBox::information(this, tr("Missing marked files"), tr("%1 marked files were missing. They were skipped.").arg(missing));
    }
}

void MainWindow::socketNotify()
{
    socketNotifier->setEnabled(false);
    char tmp;
    read(socketPair[1], &tmp, sizeof(tmp));
    close();
    socketNotifier->setEnabled(true);
}

void MainWindow::loadFile()
{
    QString file = QFileDialog::getOpenFileName(this,
                                                tr("Open file"),
                                                _lastDir,
                                                _openFilter);
    loadFile(file);
}

void MainWindow::loadFile(const QString &path)
{
    if(!path.isEmpty())
    {
        QFileInfo info(path);
        if(info.exists() && info.isReadable())
        {
            m_ringList->setFile(info.canonicalFilePath());
            updateWindowTitle();
            if(info.isDir())
                _lastDir = info.absolutePath();
            else
                _lastDir = info.canonicalPath();
            QSettings settings;
            settings.setValue("mainwindow/lastdir", _lastDir);
            if(settings.value("settings/bestfit", false).toBool())
                m_image->bestFit();
        }
        else
        {
            qWarning() << "File doesn't exist or is not readable";
        }
    }
}

void MainWindow::loadFiles(const QStringList &paths)
{
    m_ringList->setFiles(paths);
}

void MainWindow::loadFile(int row)
{
    m_ringList->loadFile(row);
}

void MainWindow::loadDir()
{
    QString dir = QFileDialog::getExistingDirectory(this,
                                                tr("Open directory recursively"),
                                                _lastDir);
    if(!dir.isEmpty())
    {
        _lastDir = dir;
        m_ringList->setDir(dir, QString(), true);
        QSettings settings;
        settings.setValue("mainwindow/lastdir", _lastDir);
    }
}

void MainWindow::indexDir()
{
    QString dir = QFileDialog::getExistingDirectory(this, tr("Index directory"), _lastDir, QFileDialog::ShowDirsOnly);
    indexDir(dir);
}

void MainWindow::indexDir(const QString &dir)
{
    if(!dir.isEmpty())
    {
        QProgressDialog progressDialog(tr("Indexing FITS files"), tr("Cancel"), 0, 1, this);
        progressDialog.setModal(true);
        m_database->indexDir(dir, &progressDialog);
    }
}

void MainWindow::reindex()
{
    QProgressDialog progressDialog(tr("Indexing FITS files"), tr("Cancel"), 0, 1, this);
    progressDialog.setModal(true);
    m_database->reindex(&progressDialog);
}

void MainWindow::saveAs()
{
    QString selectedFilter;
    ImagePtr ptr = m_ringList->currentImage();
    if(!ptr)return;

    QFileInfo srcFile(ptr->name());
    QString file = QFileDialog::getSaveFileName(this,
                                 tr("Save as"),
                                 _lastDir + "/" + srcFile.baseName(),
                                 _saveFilter,
                                 &selectedFilter);
    auto filterToFormat = [](const QString &file, const QString &filter) -> const QString
    {
        QString suffix = QFileInfo(file).suffix();
        if(!suffix.compare("jpg", Qt::CaseInsensitive) || !suffix.compare("jpeg", Qt::CaseInsensitive))return "jpeg";
        if(!suffix.compare("png", Qt::CaseInsensitive))return "png";
        if(!suffix.compare("fits", Qt::CaseInsensitive) || !suffix.compare("fit", Qt::CaseInsensitive))return "fits";
        if(!suffix.compare("xisf", Qt::CaseInsensitive))return "xisf";
        if(filter.contains("png"))return "png";
        if(filter.contains("fits"))return "fits";
        if(filter.contains("xisf"))return "xisf";
        QRegularExpression suf("\\(\\*\\.([a-zA-Z]+).*\\)");
        auto match = suf.match(filter);
        if(match.hasMatch())
            return match.captured(1);
        return "jpeg";
    };

    if(!file.isEmpty())
    {
        auto button = QMessageBox::question(this, tr("Apply stretch?"), tr("Apply current stretch function to image?"));

        QString format = filterToFormat(file, selectedFilter);

        convert(file, format, button == QMessageBox::Yes);
    }
}

void MainWindow::convert(const QString &outfile, const QString &format, bool stretch)
{
    QString file = m_ringList->currentImage()->name();
    ConvertRunable::ConvertParams param;
    param.stretch = stretch;
    param.mtf = m_stretchPanel->params();

    QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile, format, param));
}

void MainWindow::markImage()
{
    ImagePtr ptr = m_ringList->currentImage();
    if(ptr)
    {
        QString file = ptr->name();
        if(!file.isEmpty())
        {
            m_database->mark(file);
            m_ringList->updateMark();
        }

        updateWindowTitle();
    }
}

void MainWindow::unmarkImage()
{
    ImagePtr ptr = m_ringList->currentImage();
    if(ptr)
    {
        QString file = ptr->name();
        if(!file.isEmpty())
        {
            m_database->unmark(file);
            m_ringList->updateMark();
        }

        updateWindowTitle();
    }
}

void MainWindow::markAndNext()
{
    markImage();
    m_ringList->increment();
    updateWindowTitle();
}

void MainWindow::unmarkAndNext()
{
    unmarkImage();
    m_ringList->increment();
    updateWindowTitle();
}

void MainWindow::copyMarked()
{
    copyOrMove(true);
}

void MainWindow::moveMarked()
{
    copyOrMove(false);
}

void MainWindow::deleteMarked()
{
    QStringList files = m_database->getMarkedFiles();
    if(QMessageBox::question(this, tr("Move files to trash?"), tr("Do you want to move %1 files to trash?").arg(files.size())) != QMessageBox::Yes)
        return;

    QProgressDialog progress(tr("Moving marked files to trash"), tr("Cancel"), 0, files.size(), this);
    progress.setWindowModality(Qt::WindowModal);
    progress.show();
    int i = 0;
    for(const QString &file : files)
    {
        if(!QFile::exists(file))
            continue;

        if(progress.wasCanceled())
            return;

        if(!moveToTrash(file))
        {
            QMessageBox::warning(this, tr("Failed to move file to trash"), tr("Failed to move file to trash %1").arg(file));
            return;
        }
        progress.setValue(i++);
    }
    m_database->clearMarkedFiles();
}

void MainWindow::liveMode(bool active)
{
    m_ringList->setLiveMode(active);
}

void MainWindow::imageStats(bool imageStats)
{
    m_ringList->setCalculateStats(imageStats);
}

void MainWindow::peakFinder(bool findPeaks)
{
    m_ringList->setFindPeaks(findPeaks);
}

void MainWindow::starFinder(bool findStars)
{
    m_ringList->setFindStars(findStars);
}

void MainWindow::showMarkFilesDialog()
{
    MarkedFiles markedFiles(this);
    markedFiles.exec();
}

void MainWindow::showSettingsDialog()
{
    SettingsDialog settingsDialog(this);
    connect(&settingsDialog, &SettingsDialog::preloadChanged, m_ringList, &ImageRingList::setPreload);
    settingsDialog.exec();
}

void MainWindow::exportCSV()
{
    QStringList documentDir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
    if(documentDir.empty())documentDir.append("");
    QString file = QFileDialog::getSaveFileName(this,
                                 tr("Save as"),
                                 documentDir.first(),
                                 tr("CSV file (*.csv)"));

    if(!file.isEmpty())
        m_databaseView->exportCSV(file);
}

void MainWindow::checkNewVersion(bool autocheck)
{
    QNetworkAccessManager *manager = new QNetworkAccessManager(this);
    QNetworkRequest request(QUrl("https://gitea.nouspiro.space/api/v1/repos/nou/tenmon/releases/latest"));
    request.setRawHeader("accept", "application/json");
    QNetworkReply *reply = manager->get(request);
    connect(reply, &QNetworkReply::finished, [this, manager, reply, autocheck](){
        QJsonParseError error;
        QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error);
        if(json.isObject() && json.object().contains("tag_name"))
        {
            QSettings settings;
            settings.setValue("settings/lastcheck", QDateTime::currentDateTimeUtc());

            QString tag = json.object().value("tag_name").toString();
            QString version = getVersion();
            if(version >= tag)
            {
                if(!autocheck)
                    QMessageBox::information(this, tr("Update check"), tr("You have newest version"));
            }
            else
            {
                if(QMessageBox::question(this, tr("Update check"), tr("New version %1 is available. Do you want to download it now?").arg(tag)) == QMessageBox::Yes)
                {
                    QUrl url(json.object().value("html_url").toString());
                    qDebug() << "Opening url" << url;
                    if(url.host() == "gitea.nouspiro.space")
                        QDesktopServices::openUrl(url);
                }
            }
        }
        else
        {
            if(!autocheck)
                QMessageBox::warning(this, tr("Update check"), tr("Failed to check version"));
        }

        reply->deleteLater();
        manager->deleteLater();
    });
}

void MainWindow::openFileManager()
{
#if !defined(FLATPAK) || !defined(__aarch64__)
    FileManager *filemanager = new FileManager(_openSuffix);
    connect(filemanager, &FileManager::openFile, this, static_cast<void (MainWindow::*)(const QString&)>(&MainWindow::loadFile));
    filemanager->show();
#endif
}

void MainWindow::runScript(const QString &script, const QString &outdir, const QStringList &paths, const QString &arg, bool exit)
{
    BatchProcessing *batchProcessing = new BatchProcessing(m_database, this);
    batchProcessing->setOutputDir(outdir);
    batchProcessing->setPaths(paths);
    if(exit)batchProcessing->hide();
    QTimer::singleShot(500, [batchProcessing, script, exit, arg](){
        batchProcessing->runScript(script, arg, exit);
        batchProcessing->exec();
        delete batchProcessing;
        if(exit)QCoreApplication::exit();
    });
}

void MainWindow::updateWindowTitle()
{
    ImagePtr ptr = m_ringList->currentImage();
    if(ptr)
    {
        QDir dir(m_ringList->currentDir());
        QString title = dir.relativeFilePath(ptr->name());
        if(ptr->info().num > 1)
            title += QString(" [%1/%2]").arg(ptr->info().index + 1).arg(ptr->info().num);
        if(m_database->isMarked(ptr->name()))
                title += " *";
        setWindowTitle(title);
    }
}
