#include "mainwindow.h"
#include <QScrollArea>
#include <QDir>
#include <QKeyEvent>
#include <QMenu>
#include <QMenuBar>
#include <QFileDialog>
#include <QStandardPaths>
#include <QMessageBox>
#include <QProgressDialog>
#include <QDebug>
#include <QDockWidget>
#include <signal.h>
#include <unistd.h>
#include <QSettings>
#include <QCoreApplication>

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

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

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

    m_info = new ImageInfo(this);
    QDockWidget *infoDock = new QDockWidget(tr("Image info"), this);
    infoDock->setWidget(m_info);
    infoDock->setObjectName("infoDock");
    addDockWidget(Qt::LeftDockWidgetArea, infoDock);
    //m_image = new ImageScrollArea(this);
    //m_image->resize(0,0);
    //setCentralWidget(m_image);
    resize(800, 600);

    m_imageGL = new ImageScrollAreaGL(this);
    setCentralWidget(m_imageGL);

    m_stretchPanel = new StretchPanel(this);
    connect(m_stretchPanel, SIGNAL(paramChanged(float,float,float)), m_imageGL->imageWidget(), SLOT(setMTFParams(float,float,float)));
    connect(m_stretchPanel, &StretchPanel::autoStretch, [&](){ m_stretchPanel->stretchImage(m_ringList->currentImage().get()); });
    connect(m_stretchPanel, &StretchPanel::invert, m_imageGL->imageWidget(), &ImageWidget::invert);
    connect(m_stretchPanel, &StretchPanel::superPixel, m_imageGL->imageWidget(), &ImageWidget::superPixel);

    m_ringList = new ImageRingList(this);
    m_filesystem = new FilesystemWidget(m_ringList, this);
    connect(m_filesystem, SIGNAL(fileSelected(int)), this, SLOT(loadFile(int)));

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

    m_databaseView = new DataBaseView(m_database, this);
    connect(m_databaseView, SIGNAL(loadFile(QString)), this, SLOT(loadFile(QString)));

    QDockWidget *stretchDock = new QDockWidget(tr("Stretch"), this);
    stretchDock->setWidget(m_stretchPanel);
    stretchDock->setObjectName("strechDock");
    addDockWidget(Qt::TopDockWidgetArea, stretchDock);

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

    QDockWidget *databaseViewDock = new QDockWidget(tr("FITS files database"), this);
    databaseViewDock->setWidget(m_databaseView);
    databaseViewDock->setObjectName("databaseViewDock");
    databaseViewDock->hide();
    addDockWidget(Qt::BottomDockWidgetArea, databaseViewDock);

    setWindowTitle(tr("Tenmon"));

    connect(m_ringList, SIGNAL(pixmapLoaded(Image*)), this, SLOT(pixmapLoaded(Image*)));
    connect(m_ringList, SIGNAL(currentImageChanged(int)), this, SLOT(updateWindowTitle()));
    connect(m_ringList, SIGNAL(infoLoaded(ImageInfoData)), m_info, SLOT(setInfo(const ImageInfoData&)));
    connect(m_ringList, SIGNAL(currentImageChanged(int)), m_filesystem, SLOT(selectFile(int)));

    QMenu *fileMenu = new QMenu(tr("File"), this);
    fileMenu->addAction(tr("Open"), this, SLOT(loadFile()), QKeySequence("Ctrl+O"));
    fileMenu->addAction(tr("Copy marked files"), this, SLOT(copyMarked()));
    fileMenu->addAction(tr("Save as"), this, SLOT(saveAs()), QKeySequence("Ctrl+S"));
    fileMenu->addAction(tr("Index directory"), this, SLOT(indexDir()));
    QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, SLOT(liveMode(bool)));
    liveModeAction->setCheckable(true);
    fileMenu->addAction(tr("Exit"), this, SLOT(close()));
    menuBar()->addMenu(fileMenu);

    QMenu *viewMenu = new QMenu(tr("View"), this);
    viewMenu->addAction(tr("Zoom In"), m_imageGL, SLOT(zoomIn()), QKeySequence::ZoomIn);
    viewMenu->addAction(tr("Zoom Out"), m_imageGL, SLOT(zoomOut()), QKeySequence::ZoomOut);
    viewMenu->addAction(tr("Best Fit"), m_imageGL, SLOT(bestFit()), QKeySequence("Ctrl+1"));
    viewMenu->addAction(tr("100%"), m_imageGL, SLOT(oneToOne()));
    viewMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence::FullScreen);
    menuBar()->addMenu(viewMenu);

    QMenu *selectMenu = new QMenu(tr("Select"), this);
    selectMenu->addAction(tr("Mark"), this, SLOT(markImage()), Qt::Key_F5);
    selectMenu->addAction(tr("Unmark"), this, SLOT(unmarkImage()), Qt::Key_F8);
    selectMenu->addSeparator();
    selectMenu->addAction(tr("Mark and next"), this, SLOT(markAndNext()), Qt::Key_M);
    selectMenu->addAction(tr("Unmark and next"), this, SLOT(unmarkAndNext()), Qt::Key_X);
    menuBar()->addMenu(selectMenu);

    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("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(stretchDock->toggleViewAction());
    dockMenu->addAction(filesystemDock->toggleViewAction());
    dockMenu->addAction(databaseViewDock->toggleViewAction());
    menuBar()->addMenu(dockMenu);

    setupSigterm();
    QSettings settings;
    restoreGeometry(settings.value("mainwindow/geometry").toByteArray());
    restoreState(settings.value("mainwindow/state").toByteArray());

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

    _lastDir = settings.value("mainwindow/lastdir", _lastDir).toString();
    m_filesystem->setDir(_lastDir);

    QStringList args = QCoreApplication::arguments();
    args.removeFirst();
    for(auto &arg : args)
    {
        QFileInfo info(arg);
        if(info.exists())
        {
            m_ringList->setFile(info.canonicalFilePath());
            updateWindowTitle();
            _lastDir = info.absoluteDir().absolutePath();
            settings.setValue("mainwindow/lastdir", _lastDir);
            break;
        }
    }

    m_imageGL->setFocus();
}

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

void MainWindow::keyPressEvent(QKeyEvent *event)
{
    switch (event->key())
    {
    case Qt::Key_Left:
        m_ringList->decrement();
        break;
    case Qt::Key_Right:
        m_ringList->increment();
        break;
    default:
        event->ignore();
        break;
    }

    if(event->isAccepted())
        updateWindowTitle();
}

void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
    event->ignore();
}

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, SIGNAL(activated(int)), this, SLOT(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::socketNotify()
{
    socketNotifier->setEnabled(false);
    char tmp;
    read(socketPair[1], &tmp, sizeof(tmp));
    close();
    socketNotifier->setEnabled(true);
}

void MainWindow::pixmapLoaded(Image *image)
{
    //m_image->setImage(image->pixmap());
    if(image->rawImage())
    {
        m_imageGL->setImage(image->rawImage());
    }
}

void MainWindow::loadFile()
{
    QString file = QFileDialog::getOpenFileName(this, tr("Open file"), _lastDir, tr("Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)"));
    loadFile(file);
}

void MainWindow::loadFile(const QString path)
{
    if(!path.isEmpty())
    {
        QFileInfo info(path);
        m_ringList->setFile(info.canonicalFilePath());
        updateWindowTitle();
        if(info.isDir())
            _lastDir = info.absolutePath();
        else
            _lastDir = info.canonicalPath();
        QSettings settings;
        settings.setValue("mainwindow/lastdir", _lastDir);
        m_filesystem->setDir(_lastDir);
    }
}

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

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

void MainWindow::saveAs()
{
    QString file = QFileDialog::getSaveFileName(this, tr("Save as"), _lastDir, tr("Images (*.jpg *.png *.JPG *.PNG)"));
    if(!file.isEmpty())
    {
        QImage img = m_imageGL->imageWidget()->renderToImage();
        if(!img.isNull())
            img.save(file);
    }
}

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

        updateWindowTitle();
    }
}

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

        updateWindowTitle();
    }
}

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

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

void MainWindow::copyMarked()
{
    QString dest = QFileDialog::getExistingDirectory(this, tr("Select destination"));
    QDir dir(dest);
    if(!dest.isEmpty() && dir.exists())
    {
        int i = 0;
        QStringList files = m_database->getMarkedFiles();
        QProgressDialog progress(tr("Copying"), tr("Cancel"), 0, files.size(), this);
        progress.setWindowModality(Qt::WindowModal);
        progress.show();
        foreach(const QString &file, files)
        {
            QFileInfo info(file);
            QFile srcFile(file);
            QFile dstFile(dir.absoluteFilePath(info.fileName()));

            if(dstFile.exists())
                continue;

            if(progress.wasCanceled())
                break;
#ifdef __linux__
            srcFile.open(QIODevice::ReadOnly);
            dstFile.open(QIODevice::WriteOnly);
            if(ioctl(dstFile.handle(), BTRFS_IOC_CLONE, srcFile.handle()) < 0)
            {
                dstFile.remove();
                dstFile.close();
                qDebug() << dstFile.fileName();
                srcFile.copy(dstFile.fileName());
            }
#else
            srcFile.copy(dstFile.fileName());
#endif
            progress.setValue(i++);
        }
    }
}

void MainWindow::toggleFullScreen()
{
    if(isFullScreen())
    {
        showNormal();
        if(_maximized)showMaximized();
    }
    else
    {
        _maximized = isMaximized();
        showFullScreen();
    }
}

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::updateWindowTitle()
{
    ImagePtr ptr = m_ringList->currentImage();
    if(ptr)
    {
        QFileInfo info(ptr->name());
        QString title = info.fileName();
        if(m_database->isMarked(ptr->name()))
                title += " *";
        setWindowTitle(title);
    }
}
