//#define USE_KWIN

#include "kdialogd.h"
#include <iostream>
#include <tdediroperator.h>
#include <kuniqueapplication.h>
#include <tqsocketnotifier.h>
#include <tdeio/netaccess.h>
#include <tdemessagebox.h>
#include <tdelocale.h>
#include <tdeconfig.h>
#include <kurlcombobox.h>
#ifdef USE_KWIN
#include <twin.h>
#else
#include <X11/Xlib.h>
#endif
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <ksockaddr.h>
#include <kdebug.h>
#include <tdeversion.h>
#include <tqtimer.h>
#ifdef KDIALOGD_APP
#include <tdecmdlineargs.h>
#include <tdeaboutdata.h>
#endif
#include <fstream>

TDEConfig *KDialogD::theirConfig=NULL;

#define CFG_KEY_DIALOG_SIZE "KDialogDSize"
#define CFG_TIMEOUT_GROUP   "General"
#ifdef KDIALOGD_APP
#define CFG_TIMEOUT_KEY     "Timeout"
#define DEFAULT_TIMEOUT     30
#endif

static TQString groupName(const TQString &app, bool fileDialog=true)
{
   return TQString(fileDialog ? "KFileDialog " : "KDirSelectDialog ")+app;
}

// from tdebase/tdesu
static int createSocket()
{
    int         sockitsFd;
    ksocklen_t  addrlen;
    struct stat s;
    const char  *sock=getSockName();
    int         stat_err=lstat(sock, &s);

    if(!stat_err && S_ISLNK(s.st_mode))
    {
        kdWarning() << "Someone is running a symlink attack on you" << endl;
        if(unlink(sock))
        {
            kdWarning() << "Could not delete symlink" << endl;
            return -1;
        }
    }

    if (!access(sock, R_OK|W_OK))
    {
        kdWarning() << "stale socket exists" << endl;
        if (unlink(sock))
        {
            kdWarning() << "Could not delete stale socket" << endl;
            return -1;
        }
    }

    sockitsFd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (sockitsFd < 0)
    {
        kdError() << "socket(): " << errno << endl;
        return -1;
    }

    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1);
    addr.sun_path[sizeof(addr.sun_path)-1] = '\000';
    addrlen = SUN_LEN(&addr);
    if (bind(sockitsFd, (struct sockaddr *)&addr, addrlen) < 0)
    {
        kdError() << "bind(): " << errno << endl;
        return -1;
    }

    struct linger lin;
    lin.l_onoff = lin.l_linger = 0;
    if (setsockopt(sockitsFd, SOL_SOCKET, SO_LINGER, (char *) &lin, sizeof(linger)) < 0)
    {
        kdError() << "setsockopt(SO_LINGER): " << errno << endl;
        return -1;
    }

    int opt = 1;
    if (setsockopt(sockitsFd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)) < 0)
    {
        kdError() << "setsockopt(SO_REUSEADDR): " << errno << endl;
        return -1;
    }
    opt = 1;
    if (setsockopt(sockitsFd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt)) < 0)
    {
        kdError() << "setsockopt(SO_KEEPALIVE): " << errno << endl;
        return -1;
    }

    if(::listen(sockitsFd, 1)<0)
    {
        kdError() << "listen(1): " << errno << endl;
        return -1;
    }

    //chmod(sock, 0600);
    return sockitsFd;
}

static void urls2Local(KURL::List &urls, TQStringList &items, TQWidget *parent)
{
    KURL::List::Iterator it(urls.begin()),
                         end(urls.end());

    for(; it!=end; ++it)
        if((*it).isLocalFile())
            items.append((*it).path());
        else
        {
#if KDE_IS_VERSION(3, 5, 0)
            KURL url(TDEIO::NetAccess::mostLocalURL(*it, parent));

            if(url.isLocalFile())
                items.append(url.path());
            else
                break;
#else
            break;
#endif
        }
}

KDialogD::KDialogD(TQObject *parent)
        : TQObject(parent),
#ifdef KDIALOGD_APP
          itsTimer(NULL),
          itsTimeoutVal(DEFAULT_TIMEOUT),
#endif
          itsFd(::createSocket()),
          itsNumConnections(0)
{
    if(itsFd<0)
    {
        kdError() << "KDialogD could not create socket" << endl;
#ifdef KDIALOGD_APP
        kapp->exit();
#endif
    }
    else
    {
        std::ofstream f(getPidFileName());

        if(f)
        {
            f << getpid();
            f.close();
        }
        if(!theirConfig)
            theirConfig=new TDEConfig("kdialogdrc", false, false);

        connect(new TQSocketNotifier(itsFd, TQSocketNotifier::Read, this),
                TQT_SIGNAL(activated(int)), this, TQT_SLOT(newConnection()));

#ifdef KDIALOGD_APP
        if(theirConfig->hasGroup(CFG_TIMEOUT_GROUP))
        {
            theirConfig->setGroup(CFG_TIMEOUT_GROUP);
            itsTimeoutVal=theirConfig->readNumEntry(CFG_TIMEOUT_KEY, DEFAULT_TIMEOUT);
            if(itsTimeoutVal<0)
                itsTimeoutVal=DEFAULT_TIMEOUT;
            theirConfig->setGroup(TQString());
        }

        kdDebug() << "Timeout:" << itsTimeoutVal << endl;
        if(itsTimeoutVal)
            connect(itsTimer=new TQTimer(this), TQT_SIGNAL(timeout()), this, TQT_SLOT(timeout()));
#endif
    }
}

KDialogD::~KDialogD()
{
    if(-1!=itsFd)
        close(itsFd);
    if(theirConfig)
        delete theirConfig;
    theirConfig=NULL;
}

void KDialogD::newConnection()
{
    kdDebug() << "New connection" << endl;

    ksocklen_t         addrlen = 64;
    struct sockaddr_un clientname;
    int                connectedFD;

    if((connectedFD=::accept(itsFd, (struct sockaddr *) &clientname, &addrlen))>=0)
    {
        int appNameLen;

        if(readBlock(connectedFD, (char *)&appNameLen, 4))
        {
            bool     ok=true;
            TQCString appName;

            if(0==appNameLen)
                appName="Generic";
            else
            {
                appName.resize(appNameLen);
                ok=readBlock(connectedFD, appName.data(), appNameLen);
            }

            if(ok)
            {
                itsNumConnections++;
#ifdef KDIALOGD_APP
                if(itsTimer)
                    itsTimer->stop();
#endif
                connect(new KDialogDClient(connectedFD, appName, this),
                        TQT_SIGNAL(error(KDialogDClient *)),
                        this, TQT_SLOT(deleteConnection(KDialogDClient *)));
            }
        }
    }
}

void KDialogD::deleteConnection(KDialogDClient *client)
{
    kdDebug() << "Delete client" << endl;
    delete client;

#ifdef KDIALOGD_APP
    if(0==--itsNumConnections)
        if(itsTimeoutVal)
            itsTimer->start(itsTimeoutVal*1000, true);  // Only single shot...
        else
            timeout();
#endif
}

void KDialogD::timeout()
{
#ifdef KDIALOGD_APP
    if(0==itsNumConnections)
        if(grabLock(0)>0)   // 0=> no wait...
        {
            kdDebug() << "Timeout occured, and no connections, so exit" << endl;
            kapp->exit();
        }
        else     //...unlock lock file...
        {
            kdDebug() << "Timeout occured, but unable to grab lock file - app must be connecting" << endl;
            releaseLock();
        }
#endif
}

KDialogDClient::KDialogDClient(int sock, const TQString &an, TQObject *parent)
              : TQObject(parent),
                itsFd(sock),
                itsDlg(NULL),
                itsAccepted(false),
                itsAppName(an)
{
    kdDebug() << "new client..." << itsAppName << " (" << itsFd << ")" << endl;
    connect(new TQSocketNotifier(itsFd, TQSocketNotifier::Read, this), TQT_SIGNAL(activated(int)), this, TQT_SLOT(read()));
    connect(new TQSocketNotifier(itsFd, TQSocketNotifier::Exception, this), TQT_SIGNAL(activated(int)), this, TQT_SLOT(close()));
}

KDialogDClient::~KDialogDClient()
{
    kdDebug() << "Deleted client" << endl;
    if(-1!=itsFd)
        ::close(itsFd);
    if(KDialogD::config())
        KDialogD::config()->sync();
}

void KDialogDClient::close()
{
    kdDebug() << "close " << itsFd << endl;

    ::close(itsFd);
    itsFd=-1;
    if(itsDlg)
    {
        itsDlg->close();
        itsDlg->delayedDestruct();
        itsDlg=NULL;
    }
    emit error(this);
}

void KDialogDClient::read()
{
    kdDebug() << "read" << endl;

    if(-1==itsFd)
        return;

    char         request;
    TQString      caption;
    unsigned int xid=0;

    if(!itsDlg && readData(&request, 1) && request>=(char)OP_FILE_OPEN && request<=(char)OP_FOLDER &&
       readData((char *)&xid, 4) && readString(caption))
    {
        if("."==caption)
            switch((Operation)request)
            {
                case OP_FILE_OPEN:
                case OP_FILE_OPEN_MULTIPLE:
                    caption=i18n("Open");
                    break;
                case OP_FILE_SAVE:
                    caption=i18n("Save As");
                    break;
                case OP_FOLDER:
                    caption=i18n("Select Folder");
                    break;
                default:
                    break;
            }

        if(OP_FOLDER==(Operation)request)
        {
            TQString intialFolder;

            if(readString(intialFolder))
            {
                initDialog(caption, new KDialogDDirSelectDialog(itsAppName, intialFolder, true, NULL,
                           "folderdialog", false), xid);
                return;
            }
        }
        else
        {
            TQString intialFolder,
                    filter;
            char    overW=0;

            if(readString(intialFolder) && readString(filter) &&
               (OP_FILE_SAVE!=(Operation)request || readData(&overW, 1)))
            {
                initDialog(caption, new KDialogDFileDialog(itsAppName, (Operation)request, intialFolder,
                                                           filter, overW ? true : false), xid);
                return;
            }
        }
    }

    kdDebug() << "Comms error, closing connection..." << itsFd << endl;
    // If we get here something was wrong, close connection...
    close();
}

void KDialogDClient::finished()
{
    if(-1==itsFd)
        return;

    //
    // * finished is emitted when a dialog is ok'ed/cancel'ed/closed
    // * if the user just closes the dialog - neither ok nor cancel are emitted
    // * the dir select dialog doesnt seem to set the TQDialog result parameter
    //   when it is accepted - so for this reason if ok is clicked we store an
    //   'accepted' value there, and check for that after the dialog is finished.
    kdDebug() << "finished" << endl;
    if(itsDlg && !(itsAccepted || TQDialog::Accepted==itsDlg->result()))
        cancel();
}

void KDialogDClient::ok(const TQStringList &items)
{
    kdDebug() << "ok" << endl;

    int                        num=items.count();
    TQStringList::ConstIterator it(items.begin()),
                               end(items.end());
    bool                       error=!writeData((char *)&num, 4);

    for(; !error && it!=end; ++it)
        error=!writeString(*it);

    if(error)
        close();
    else
        itsAccepted=true;
    if(itsDlg)
        itsDlg->delayedDestruct();
    itsDlg=NULL;
}

void KDialogDClient::cancel()
{
    if(itsDlg)
    {
        kdDebug() << "cancel" << endl;

        int rv=0;

        if(!writeData((char *)&rv, 4))
            close();
        if(itsDlg)
            itsDlg->delayedDestruct();
        itsDlg=NULL;
    }
}

bool KDialogDClient::readData(TQCString &buffer, int size)
{
    buffer.resize(size);
    return ::readBlock(itsFd, buffer.data(), size);
}

bool KDialogDClient::readString(TQString &str)
{
    int size;

    if(!readData((char *)&size, 4))
        return false;

    TQCString buffer;
    buffer.resize(size);

    if(!readData(buffer.data(), size))
        return false;

    str=TQString::fromUtf8(buffer.data());
    return true;
}

bool KDialogDClient::writeString(const TQString &str)
{
    TQCString utf8(str.utf8());

    int size=utf8.length()+1;

    return writeData((char *)&size, 4) && writeData(utf8.data(), size);
}

void KDialogDClient::initDialog(const TQString &caption, KDialogBase *d, unsigned int xid)
{
    itsAccepted=false;
    itsDlg=d;

    if(!caption.isEmpty())
        itsDlg->setPlainCaption(caption);

    if(xid)
    {
#ifdef USE_KWIN
        KWin::setMainWindow(itsDlg, xid);
        KWin::setState(itsDlg->winId(), NET::Modal);

        KWin::WindowInfo wi(KWin::windowInfo(xid, NET::WMGeometry, NET::WM2UserTime));
        TQRect            geom(wi.geometry());
        int              rx=geom.x(),
                         ry=geom.y();

        rx=(rx+(geom.width()/2))-(itsDlg->width()/2);
        if(rx<0)
            rx=0;
        ry=(ry+(geom.height()/2))-(itsDlg->height()/2);
        if(ry<0)
            ry=0;
        itsDlg->move(rx, ry);
#else
        XWindowAttributes attr;
        int               rx, ry;
        Window            juntwin;

        XSetTransientForHint(tqt_xdisplay(), itsDlg->winId(), xid);
        if(XGetWindowAttributes(tqt_xdisplay(), xid, &attr))
        {
            XTranslateCoordinates(tqt_xdisplay(), xid, attr.root,
                                  -attr.border_width, -16,
                                  &rx, &ry, &juntwin);

            rx=(rx+(attr.width/2))-(itsDlg->width()/2);
            if(rx<0)
                rx=0;
            ry=(ry+(attr.height/2))-(itsDlg->height()/2);
            if(ry<0)
                ry=0;
            itsDlg->move(rx, ry);
        }
#endif
    }

    connect(itsDlg, TQT_SIGNAL(ok(const TQStringList &)), this, TQT_SLOT(ok(const TQStringList &)));
    connect(itsDlg, TQT_SIGNAL(finished()), this, TQT_SLOT(finished()));
    itsDlg->show();
}

KDialogDFileDialog::KDialogDFileDialog(TQString &an, Operation op, const TQString &startDir,
                                       const TQString &filter, bool confirmOw)
                  : KFileDialog(startDir.isEmpty() || "~"==startDir ? TQDir::homeDirPath() : startDir,
                                filter, NULL, NULL, false),
                    itsConfirmOw(confirmOw),
                    itsAppName(an)
{
    kdDebug() << "startDir:" << startDir << endl;

    switch(op)
    {
        case OP_FILE_OPEN:
            setOperationMode(KFileDialog::Opening);
            setMode((KFile::Mode)(KFile::File | KFile::ExistingOnly));
            break;
        case OP_FILE_OPEN_MULTIPLE:
            setOperationMode(KFileDialog::Opening);
            setMode((KFile::Mode)(KFile::Files | KFile::ExistingOnly));
            break;
        case OP_FILE_SAVE:
            setOperationMode(KFileDialog::Saving);
            setMode(KFile::File);
            break;
        default:
            break;
    }

    if(KDialogD::config())
    {
        TQString oldGrp(KDialogD::config()->group()),
                grp(groupName(itsAppName));
        TQSize   defaultSize(600, 400);

        readConfig(KDialogD::config(), grp);
        KDialogD::config()->setGroup(grp);
        resize(KDialogD::config()->readSizeEntry(CFG_KEY_DIALOG_SIZE, &defaultSize));
        KDialogD::config()->setGroup(oldGrp);
    }

    ops->clearHistory();
}

void KDialogDFileDialog::accept()
{
    kdDebug() << "KDialogDFileDialog::accept" << endl;
}

void KDialogDFileDialog::slotOk()
{
    setResult(TQDialog::Accepted);
    KFileDialog::slotOk();

    kdDebug() << "KDialogDFileDialog::slotOk" << endl;
    KURL::List  urls;
    TQStringList items;
    bool        good=true;

    if(mode()&KFile::Files)
        urls=selectedURLs();
    else if(!locationEdit->currentText().isEmpty())
        urls.append(selectedURL());

    if(urls.count())
    {
        urls2Local(urls, items, this);

        if(urls.count()!=items.count())
        {
            KMessageBox::sorry(this, i18n("You can only select local files."),
                            i18n("Remote Files Not Accepted"));
            good=false;
        }
        else if(itsConfirmOw && KFileDialog::Saving==operationMode())
            good=!TDEIO::NetAccess::exists(urls.first(), false, this) ||
                 KMessageBox::Continue==KMessageBox::warningContinueCancel(this,
                                            i18n("File %1 exists.\nDo you want to replace it?")
                                                 .arg(urls.first().prettyURL()),
                                            i18n("File Exists"),
                                            KGuiItem(i18n("Replace"), "document-save-as"), TQString(),
                                            KMessageBox::Notify|KMessageBox::PlainCaption);

        if(good)
        {
            TQString filter(currentFilter());

            if(!filter.isEmpty())
                items.append(filter);

            emit ok(items);
            hide();
            KFileDialog::accept();
       }
       else
            setResult(TQDialog::Rejected);
    }
}

KDialogDFileDialog::~KDialogDFileDialog()
{
    kdDebug() << "~KDialogDFileDialog" << endl;

    if(KDialogD::config())
    {
        TQString oldGrp(KDialogD::config()->group()),
                grp(groupName(itsAppName));

        writeConfig(KDialogD::config(), grp);
        KDialogD::config()->setGroup(grp);
        KDialogD::config()->writeEntry(CFG_KEY_DIALOG_SIZE, size());
        KDialogD::config()->setGroup(oldGrp);
    }
}

KDialogDDirSelectDialog::KDialogDDirSelectDialog(TQString &an, const TQString &startDir, bool localOnly,
                                                 TQWidget *parent, const char *name, bool modal)
                       : KDirSelectDialog(startDir.isEmpty() || "~"==startDir
                                              ? TQDir::homeDirPath() : startDir,
                                          localOnly, parent, name, modal),
                         itsAppName(an)
{
    kdDebug() << "startDir:" << startDir << endl;

    if(KDialogD::config())
    {
        TQString oldGrp(KDialogD::config()->group()),
                grp(groupName(itsAppName, false));
        TQSize   defaultSize(600, 400);

        //readConfig(KDialogD::config(), grp);
        KDialogD::config()->setGroup(grp);
        resize(KDialogD::config()->readSizeEntry(CFG_KEY_DIALOG_SIZE, &defaultSize));
        KDialogD::config()->setGroup(oldGrp);
    }
}

KDialogDDirSelectDialog::~KDialogDDirSelectDialog()
{
    kdDebug() << "~KDialogDDirSelectDialog" << endl;

    if(KDialogD::config())
    {
        TQString oldGrp(KDialogD::config()->group()),
                grp(groupName(itsAppName, false));

        //writeConfig(KDialogD::config(), grp);
        KDialogD::config()->setGroup(grp);
        KDialogD::config()->writeEntry(CFG_KEY_DIALOG_SIZE, size());
        KDialogD::config()->setGroup(oldGrp);
    }
}

void KDialogDDirSelectDialog::slotOk()
{
    kdDebug() << "KDialogDDirSelectDialog::slotOk" << endl;

    KURL::List  urls;
    TQStringList items;

    urls.append(url());
    urls2Local(urls, items, this);

    if(urls.count()!=items.count())
        KMessageBox::sorry(this, i18n("You can only select local folders."),
                           i18n("Remote Folders Not Accepted"));
    else
    {
        emit ok(items);
        hide();
    }
}

#ifdef KDIALOGD_APP
static TDEAboutData aboutData("kdialogd3", I18N_NOOP("KDialog Daemon"), VERSION,
                            I18N_NOOP("Use TDE dialogs from non-TDE apps."),
                            TDEAboutData::License_GPL,
                            I18N_NOOP("(c) Craig Drummond, 2006-2007"));

int main(int argc, char **argv)
{
    TDECmdLineArgs::init(argc, argv, &aboutData);

    KUniqueApplication *app=new KUniqueApplication;
    KDialogD           kdialogd;
    int                rv=app->exec();

    delete app;

    unlink(getSockName());
    releaseLock();
    return rv;
}
#else
extern "C"
{
    KDE_EXPORT KDEDModule *create_kdialogd(const TQCString &obj)
    {
        return new KDialogDKDED(obj);
    }
};

KDialogDKDED::KDialogDKDED(const TQCString &obj)
            : KDEDModule(obj)
{
    new KDialogD(this);
}
#endif

#include "kdialogd.moc"