/*
 * KSnapshot
 *
 * (c) Richard J. Moore 1997-2002
 * (c) Matthias Ettrich 2000
 * (c) Aaron J. Seigo 2002
 * (c) Nadeem Hasan 2003
 * (c) Bernd Brandstetter 2004
 * (c) Emanoil Kotsev 2023
 *
 * Released under the LGPL see file LICENSE for details.
 */


#include <tdelocale.h>
#include <kimageio.h>
#include <tdefiledialog.h>
#include <kimagefilepreview.h>
#include <tdemessagebox.h>
#include <kdebug.h>
#include <tdeapplication.h>
#include <kprinter.h>
#include <tdeio/netaccess.h>
#include <ksavefile.h>
#include <kstandarddirs.h>
#include <tdetempfile.h>
#include <kde_file.h>

#include <tqbitmap.h>
#include <tqimage.h>
#include <tqclipboard.h>
#include <tqvbox.h>

#include <tdeaccel.h>
#include <knotifyclient.h>
#include <khelpmenu.h>
#include <tdepopupmenu.h>
#include <kpushbutton.h>
#include <tdestartupinfo.h>
#include <kiconloader.h>
#include <kprocess.h>
#include <krun.h>

#include <tqcursor.h>
#include <tqregexp.h>
#include <tqpainter.h>
#include <tqpaintdevicemetrics.h>
#include <tqwhatsthis.h>

#include <functional>
#include <memory>
#include <utility>

#include <stdlib.h>

#include <fcntl.h>
#include <unistd.h>

#include "ksnapshot.h"
#include "regiongrabber.h"
#include "windowgrabber.h"
#include "ksnapshotwidget.h"

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <config.h>

#include <tdeglobal.h>

#define kApp TDEApplication::kApplication()

// A quick and dirty scope guard implementation
class ExitGuard {
public:
  template<class Callable>
  ExitGuard(Callable && undo_func) : f(std::forward<Callable>(undo_func)) {}
  ExitGuard(ExitGuard && other) : f(std::move(other.f)) {
      other.f = nullptr;
  }

  ~ExitGuard() {
    if(f) { f(); f = nullptr; }
  }

  ExitGuard(const ExitGuard&) = delete;
  void operator= (const ExitGuard&) = delete;

private:
    std::function<void()> f;
};

KSnapshot::KSnapshot(TQWidget *parent, const char *name, bool grabCurrent)
  : DCOPObject("interface"),
    KDialogBase(parent, name, true, TQString(), Help|User1, User1,
    true, KStdGuiItem::quit() )
{
    grabber = new TQWidget( 0, 0, WStyle_Customize | WX11BypassWM );
    grabber->move( -1000, -1000 );
    grabber->installEventFilter( this );

    TDEStartupInfo::appStarted();

    TQVBox *vbox = makeVBoxMainWidget();
    mainWidget = new KSnapshotWidget( vbox, "mainWidget" );

    connect(mainWidget, TQ_SIGNAL(startImageDrag()), TQ_SLOT(slotDragSnapshot()));
    connect(mainWidget, TQ_SIGNAL(newClicked()), TQ_SLOT(slotGrab()));
    connect(mainWidget, TQ_SIGNAL(saveClicked()), TQ_SLOT(slotSaveAs()));
    connect(mainWidget, TQ_SIGNAL(printClicked()), TQ_SLOT(slotPrint()));
    connect(mainWidget, TQ_SIGNAL(copyClicked()), TQ_SLOT(slotCopy()));
    connect(mainWidget, TQ_SIGNAL(openWithKPClicked()), TQ_SLOT(slotOpenWithKP()));
    connect(tqApp,      TQ_SIGNAL(aboutToQuit()), TQ_SLOT(slotAboutToQuit()));

    grabber->show();
    grabber->grabMouse( waitCursor );

    if ( !grabCurrent ) {
        snapshot = TQPixmap::grabWindow( tqt_xrootwin() );
        timestamp = TQDateTime::currentDateTime();
        setLocalFilePath(TQString::null);
    } else {
        mainWidget->setMode( WindowUnderCursor );
        mainWidget->setIncludeDecorations( true );
        performGrab();
    }

    updatePreview();
    grabber->releaseMouse();
    grabber->hide();

    TDEConfig *conf=TDEGlobal::config();
    conf->setGroup("GENERAL");
    mainWidget->setDelay(conf->readNumEntry("delay",0));
    mainWidget->setMode( conf->readNumEntry( "mode", 0 ) );
    mainWidget->setIncludeDecorations(conf->readBoolEntry("includeDecorations",true));
    filename = KURL::fromPathOrURL( conf->readPathEntry( "filename", TQDir::currentDirPath()+"/"+i18n("snapshot")+"1.png" ));

    // Make sure the name is not already being used
    while(TDEIO::NetAccess::exists( filename, false, this )) {
        autoincFilename();
    }

    connect( &grabTimer, TQ_SIGNAL( timeout() ), this, TQ_SLOT(  grabTimerDone() ) );
    connect( &updateTimer, TQ_SIGNAL( timeout() ), this, TQ_SLOT(  updatePreview() ) );
    TQTimer::singleShot( 0, this, TQ_SLOT( updateCaption() ) );

    KHelpMenu *helpMenu = new KHelpMenu(this, TDEGlobal::instance()->aboutData(), false);

    TQPushButton *helpButton = actionButton( Help );
    helpButton->setPopup(helpMenu->menu());

    // Populate Open With... menu
    TDEPopupMenu *popupOpenWith = new TDEPopupMenu(this);
    openWithOffers = TDETrader::self()->query("image/png", "Type == 'Application'");
    int i = 0;
    for (TDETrader::OfferList::Iterator it = openWithOffers.begin(); it != openWithOffers.end(); ++it)
    {
        popupOpenWith->insertItem(SmallIcon((*it)->icon()), (*it)->name(), i);
        ++i; // we need menu ids to match with OfferList indexes
    }
    mainWidget->btnOpenWith->setPopup(popupOpenWith);
    connect(popupOpenWith, TQ_SIGNAL(activated(int)), this, TQ_SLOT(slotOpenWith(int)));

    // Check for KolourPaint availability
    KService::Ptr kpaint = KService::serviceByDesktopName("kolourpaint");
    if (!kpaint) {
        mainWidget->btnOpenWithKP->hide();
    }

    TDEAccel* accel = new TDEAccel(this);
    accel->insert(TDEStdAccel::Quit, kapp, TQ_SLOT(quit()));
    accel->insert( "QuickSave", i18n("Quick Save Snapshot &As..."),
                   i18n("Save the snapshot to the file specified by the user without showing the file dialog."),
                   CTRL+SHIFT+Key_S, this, TQ_SLOT(slotSave()));
    accel->insert(TDEStdAccel::Save, this, TQ_SLOT(slotSaveAs()));
//    accel->insert(TDEShortcut(CTRL+Key_A), this, TQ_SLOT(slotSaveAs()));
    accel->insert( "SaveAs", i18n("Save Snapshot &As..."),
                   i18n("Save the snapshot to the file specified by the user."),
                   CTRL+Key_A, this, TQ_SLOT(slotSaveAs()));
    accel->insert(TDEStdAccel::Print, this, TQ_SLOT(slotPrint()));
    accel->insert(TDEStdAccel::New, this, TQ_SLOT(slotGrab()));
    accel->insert(TDEStdAccel::Copy, this, TQ_SLOT(slotCopy()));

    accel->insert( "Quit2", Key_Q, this, TQ_SLOT(slotSave()));
    accel->insert( "Save2", Key_S, this, TQ_SLOT(slotSaveAs()));
    accel->insert( "Print2", Key_P, this, TQ_SLOT(slotPrint()));
    accel->insert( "New2", Key_N, this, TQ_SLOT(slotGrab()));
    accel->insert( "New3", Key_Space, this, TQ_SLOT(slotGrab()));

    setEscapeButton( User1 );
    connect( this, TQ_SIGNAL( user1Clicked() ), TQ_SLOT( reject() ) );

    mainWidget->btnNew->setFocus();

    oldWinPos = pos();
}

KSnapshot::~KSnapshot()
{
}

void KSnapshot::resizeEvent( TQResizeEvent * /*event*/)
{
    if( !updateTimer.isActive() )
        updateTimer.start(200, true);
    else
        updateTimer.changeInterval(200);
}

bool KSnapshot::save( const TQString &filename )
{
    return save( KURL::fromPathOrURL( filename ));
}

bool KSnapshot::save( const KURL& url )
{
    if ( TDEIO::NetAccess::exists( url, false, this ) ) {
        const TQString title = i18n( "File Exists" );
        const TQString text = i18n( "<qt>Do you really want to overwrite <b>%1</b>?</qt>" ).arg(url.prettyURL());
        if (KMessageBox::Continue != KMessageBox::warningContinueCancel( this, text, title, i18n("Overwrite") ) )
        {
            return false;
        }
    }

    TQString type( KImageIO::type(url.path()) );
    if ( type.isNull() )
        type = "PNG";

    bool ok = false;

    if ( url.isLocalFile() ) {
        KSaveFile saveFile( url.path() );
        if ( saveFile.status() == 0 ) {
            if ( snapshot.save( saveFile.file(), type.latin1() ) ) {
                ok = saveFile.close();
                if (ok) {
                    setLocalFilePath(saveFile.name());
                }
            }
        }
    }
    else {
        KTempFile tmpFile;
        tmpFile.setAutoDelete( true );
        if ( tmpFile.status() == 0 ) {
            if ( snapshot.save( tmpFile.file(), type.latin1() ) ) {
                if ( tmpFile.close() )
                    ok = TDEIO::NetAccess::upload( tmpFile.name(), url, this );
            }
        }
    }

    TQApplication::restoreOverrideCursor();
    if ( !ok ) {
        kdWarning() << "KSnapshot was unable to save the snapshot" << endl;

        TQString caption = i18n("Unable to save image");
        TQString text = i18n("KSnapshot was unable to save the image to\n%1.")
            .arg(url.prettyURL());
        KMessageBox::error(this, text, caption);
    }

    return ok;
}

void KSnapshot::slotSave()
{
    if ( save(filename) ) {
        modified = false;
        autoincFilename();
    }
}

void KSnapshot::slotSaveAs()
{
    TQStringList mimetypes = KImageIO::mimeTypes( KImageIO::Writing );
    KFileDialog dlg( filename.url(), mimetypes.join(" "), this, "filedialog", true);

    dlg.setOperationMode( KFileDialog::Saving );
    dlg.setCaption( i18n("Save As") );

    KImageFilePreview *ip = new KImageFilePreview( &dlg );
    dlg.setPreviewWidget( ip );

    if ( !dlg.exec() )
        return;

    KURL url = dlg.selectedURL();
    if ( !url.isValid() )
        return;

    if ( save(url) ) {
        filename = url;
        modified = false;
        autoincFilename();
    }
}

void KSnapshot::slotCopy()
{
    TQClipboard *cb = TQApplication::clipboard();
    cb->setPixmap( snapshot );
}

void KSnapshot::slotDragSnapshot()
{
    TQDragObject *drobj = new SnapshotDrag(snapshot.convertToImage(), this);
    drobj->setPixmap(mainWidget->preview());
    drobj->dragCopy();
}

void KSnapshot::slotGrab()
{
    oldWinPos = pos();
    hide();

    if ( mainWidget->delay() )
        grabTimer.start( mainWidget->delay() * 1000, true );
    else {
        if ( mainWidget->mode() == Region ) {
            rgnGrab = new RegionGrabber();
            connect( rgnGrab, TQ_SIGNAL( regionGrabbed( const TQPixmap & ) ),
                    TQ_SLOT( slotRegionGrabbed( const TQPixmap & ) ) );
        }
        else {
            grabber->show();
            grabber->grabMouse( crossCursor );
        }
    }
}

void KSnapshot::slotPrint()
{
    KPrinter printer;
    if (snapshot.width() > snapshot.height())
        printer.setOrientation(KPrinter::Landscape);
    else
        printer.setOrientation(KPrinter::Portrait);

    tqApp->processEvents();

    if (printer.setup(this, i18n("Print Screenshot")))
    {
        tqApp->processEvents();

        TQPainter painter(&printer);
        TQPaintDeviceMetrics metrics(painter.device());

        float w = snapshot.width();
        float dw = w - metrics.width();
        float h = snapshot.height();
        float dh = h - metrics.height();
        bool scale = false;

        if ( (dw > 0.0) || (dh > 0.0) )
            scale = true;

        if ( scale ) {

            TQImage img = snapshot.convertToImage();
            tqApp->processEvents();

            float newh, neww;
            if ( dw > dh ) {
                neww = w-dw;
                newh = neww/w*h;
            }
            else {
                newh = h-dh;
                neww = newh/h*w;
            }

            img = img.smoothScale( int(neww), int(newh), TQImage::ScaleMin );
            tqApp->processEvents();

            int x = (metrics.width()-img.width())/2;
            int y = (metrics.height()-img.height())/2;

            painter.drawImage( x, y, img);
        }
        else {
            int x = (metrics.width()-snapshot.width())/2;
            int y = (metrics.height()-snapshot.height())/2;
            painter.drawPixmap( x, y, snapshot );
        }
    }

    tqApp->processEvents();
}

void KSnapshot::slotRegionGrabbed( const TQPixmap &pix )
{
    rgnGrab->deleteLater();
    newSnapshot(pix);
}

void KSnapshot::newSnapshot( const TQPixmap &pix )
{
    if ( !pix.isNull() )
    {
        snapshot = pix;
        updatePreview();
        timestamp = TQDateTime::currentDateTime();
        setLocalFilePath(TQString::null);
        modified = true;
        updateCaption();
    }

    TQApplication::restoreOverrideCursor();
    move(oldWinPos);
    show();
}

void KSnapshot::slotOpenWith(int id)
{
    openWithExternalApp(*openWithOffers[id]);
}

void KSnapshot::slotOpenWithKP() {
    KService::Ptr kpaint = KService::serviceByDesktopName("kolourpaint");
    if (kpaint) {
        openWithExternalApp(*kpaint);
    }
}

/// Writes the snapshot to a temporary file
TQString KSnapshot::saveTempFile() {

    // construct a pretty name for the temporary file
    TQString base_fname =
        i18n("A temporary filename; prefer dashes (-) as word separators if required", "snapshot")
            .append(timestamp.toString("-yyyyMMdd-hhmmss"));
    TQString base_fpath = locateLocal("tmp", base_fname);
    TQString fname = base_fpath + ".png";

    // We want the pretty names; unfortunately KTempFile forces us to use mkstemp-style name; which
    // has a random string at the end, which is quite ugly. On the other hand TQFile doesn't
    // provide any means to fail if file already exist (i.e. O_EXCL). So in order to have best of
    // both worlds we will have to try to open the file manually and if it fails, we will fallback
    // to KTempFile.
    int fd = -1;
    ExitGuard closeGuard { [&fd](){ if (fd>=0) { ::close(fd); } } };

    std::unique_ptr<KTempFile> tmpFile; // used only if manual open() fails
    std::unique_ptr<TQFile> tqfile; // used only if manual open() succeeds
    TQFile* file; // either tqfile or tmpFile->file()

    fd = KDE_open(TQFile::encodeName(fname), O_WRONLY | O_CREAT | O_EXCL, 0600);

    if (fd>=0) {
        tqfile = std::unique_ptr<TQFile>(new TQFile(fname));
        tqfile->open( IO_WriteOnly, fd ); // according to docs TQFile won't close the fd so there is
                                          // an exit guard above
        file = tqfile.get();

    } else {
        tmpFile = std::unique_ptr<KTempFile>(new KTempFile(base_fpath + "-", ".png"));
        if (!tmpFile || tmpFile->status() != 0) {
            return TQString::null;
        }
        file = tmpFile->file();
        fname = tmpFile->name();
    }

    // Actually save the image
    bool ok = snapshot.save(file, "PNG");

    if (!ok) {
        file->remove();
        return TQString::null;
    }

    tempFiles.append(fname);

    return fname;
}

void KSnapshot::setLocalFilePath(TQString fp) {
    localFilePath = fp;
    if(!fp.isEmpty()) {
        TQFileInfo fi(fp);
        currentFilePathTimestamp = fi.lastModified ();
    } else {
        currentFilePathTimestamp = TQDateTime();
    }
}

/// Returns a local file for the current snapshot. It may be either a file explicitly saved by the
/// user or a temporary one saved internally
TQString KSnapshot::localFile() {
    bool needSaveTemp = true;
    if(!localFilePath.isEmpty()) {
        TQFileInfo fi(localFilePath);
        // Checks that nobody have overwritten the file since we saved it
        if( fi.exists() && currentFilePathTimestamp == fi.lastModified()) {
            needSaveTemp = false;
        }
    }

    if(needSaveTemp) {
        setLocalFilePath( saveTempFile() );
    }

    return localFilePath;
}

void KSnapshot::openWithExternalApp(const KService &service) {
    // Write snapshot to temporary file
    TQString file = localFile();

    if (file.isEmpty()) {
        KMessageBox::error(this, i18n("KSnapshot was unable to create temporary file."),
                                 i18n("Unable to save image"));
        return;
    }

    // Launch application
    KURL::List list;
    list.append(file);
    TQStringList args = KRun::processDesktopExec(service, list, false, false);

    TDEProcess *externalApp = new TDEProcess;
    *externalApp << args;

    if (!externalApp->start(TDEProcess::OwnGroup)) {
        KMessageBox::error(this, i18n("Cannot start %1!").arg(service.name()));
    }
}

void KSnapshot::slotAboutToQuit()
{
    for(const TQString &file: tempFiles)
    {
        TQFile::remove(file);
    }
    tempFiles.clear();

    TDEConfig *conf=TDEGlobal::config();
    conf->setGroup("GENERAL");
    conf->writeEntry("delay",mainWidget->delay());
    conf->writeEntry("mode",mainWidget->mode());
    conf->writeEntry("includeDecorations",mainWidget->includeDecorations());
    KURL url = filename;
    url.setPass( TQString() );
    conf->writePathEntry("filename",url.url());
}

void KSnapshot::closeEvent( TQCloseEvent * e )
{
    e->accept();
}

bool KSnapshot::eventFilter( TQObject* o, TQEvent* e)
{
    if ( o == grabber && e->type() == TQEvent::MouseButtonPress ) {
        TQMouseEvent* me = (TQMouseEvent*) e;
        if ( TQWidget::mouseGrabber() != grabber )
            return false;
        if ( me->button() == TQt::LeftButton )
            performGrab();
    }
    return false;
}

void KSnapshot::autoincFilename()
{
    // Extract the filename from the path
    TQString name= filename.fileName();

    // If the name contains a number then increment it
    TQRegExp numSearch("[0-9]+");

    // Does it have a number?
    int start = numSearch.search(name);
    if (start != -1) {
        // It has a number, increment it
        int len = numSearch.matchedLength();
        TQString numAsStr= name.mid(start, len);
        TQString number = TQString::number(numAsStr.toInt() + 1);
        number = number.rightJustify( len, '0');
        name.replace(start, len, number );
    }
    else {
        // no number
        start = name.findRev('.');
        if (start != -1) {
            // has a . somewhere, e.g. it has an extension
            name.insert(start, '1');
        }
        else {
            // no extension, just tack it on to the end
            name += '1';
        }
    }

    // Rebuild the path
    KURL newURL = filename;
    newURL.setFileName( name );
    setURL( newURL.url() );
}

void KSnapshot::updatePreview()
{
    mainWidget->setPreview( snapshot );
}

void KSnapshot::grabTimerDone()
{
    if ( mainWidget->mode() == Region ) {
        rgnGrab = new RegionGrabber();
        connect( rgnGrab, TQ_SIGNAL( regionGrabbed( const TQPixmap & ) ),
            TQ_SLOT( slotRegionGrabbed( const TQPixmap & ) ) );
    }
    else {
        performGrab();
    }
    KNotifyClient::beep(i18n("The screen has been successfully grabbed."));
}

void KSnapshot::performGrab()
{
    grabber->releaseMouse();
    grabber->hide();
    grabTimer.stop();
    if ( mainWidget->mode() == ChildWindow ) {
        WindowGrabber wndGrab;
        connect( &wndGrab, TQ_SIGNAL( windowGrabbed( const TQPixmap & ) ),
                 TQ_SLOT( newSnapshot( const TQPixmap & ) ) );
        wndGrab.exec();
    }
    else {
        TQPixmap pix;
        if ( mainWidget->mode() == WindowUnderCursor ) {
            pix = WindowGrabber::grabCurrent( mainWidget->includeDecorations() );
        }
        else {
            pix = TQPixmap::grabWindow( tqt_xrootwin() );
        }

        newSnapshot(pix);
    }
}

void KSnapshot::setTime(int newTime)
{
    mainWidget->setDelay(newTime);
}

int KSnapshot::timeout()
{
    return mainWidget->delay();
}

void KSnapshot::setURL( const TQString &url )
{
    KURL newURL = KURL::fromPathOrURL( url );
    if ( newURL == filename )
        return;

    filename = newURL;
    updateCaption();
}

void KSnapshot::setPixmap(const TQPixmap &newImage) {
    snapshot = newImage;
    timestamp = TQDateTime::currentDateTime();
    setLocalFilePath(TQString::null);
    updatePreview();
}

void KSnapshot::setGrabMode( int m )
{
    mainWidget->setMode( m );
}

int KSnapshot::grabMode()
{
    return mainWidget->mode();
}

void KSnapshot::updateCaption()
{
    setCaption( kApp->makeStdCaption( filename.fileName(), true, modified ) );
}

void KSnapshot::slotMovePointer(int x, int y)
{
    TQCursor::setPos( x, y );
}

void KSnapshot::exit()
{
    reject();
}

SnapshotDrag::SnapshotDrag( TQImage image, KSnapshot * dragSource, const char * name )
    :TQImageDrag::TQImageDrag( image, dragSource, name ), ksnap(dragSource)
{
    // TQ*Drop API is a bit quirky, so to append our value to the list of formats we will have
    // to iterate over the full list provided by TQImageDrag manually just to determine how many
    // formats it supports

    for (int i=0 ; ; i++) {
        const char* format = TQImageDrag::format(i);
        if (format) {
            formats.append(format);
        } else {
            break;
        }
    }
    formats.append("text/uri-list");
}

const char * SnapshotDrag::format(int i) const
{
    if( i < (int) formats.count() ) {
        return formats[i];
    } else {
        return 0;
    }
}

TQByteArray SnapshotDrag::encodedData(const char* format) const
{
    if( strcmp(format, "text/uri-list") == 0 ) {
        return TQUriDrag::localFileToUri( ksnap->localFile() );
    } else {
        return TQImageDrag::encodedData(format);
    }
}

#include "ksnapshot.moc"
