/************************************************************************
 *
 * All dialogs opened are created and used modal.
 *
 ************************************************************************
 * (C) Craig Drummond, 2006
 ************************************************************************
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 ************************************************************************/

#define KTQT_OVERLOAD_NON_STATIC_FILEDIALOGS

#define _GNU_SOURCE
#include <dlfcn.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <tqstring.h>
#include <tqstringlist.h>
#include <tqwidget.h>
#include <tqapplication.h>
#ifdef KTQT_OVERLOAD_NON_STATIC_FILEDIALOGS
#include <tqcombobox.h>
#include <tqlineedit.h>
#include <tqobjectlist.h>
#define private public   // HACK HACK HACK!!!
#endif
#include <tqfiledialog.h>
#include <tqthread.h>
#include <tqnamespace.h>
#include <tqeventloop.h>
#include "connect.h"
#include "config.h"
#include "mangled.h"

static bool useKde=false;

#define MAX_LINE_LEN 1024
#define MAX_APP_NAME_LEN 32

static char * getAppNameFromPid(int pid)
{
    static char appName[MAX_APP_NAME_LEN+1]="\0";

    int  procFile=-1;
    char cmdline[MAX_LINE_LEN+1];

    sprintf(cmdline, "/proc/%d/cmdline",pid);

    if(-1!=(procFile=open(cmdline, O_RDONLY)))
    {
        if(read(procFile, cmdline, MAX_LINE_LEN)>7)
        {
            int len=strlen(cmdline),
                pos=0;

            for(pos=len-1; pos>0 && cmdline[pos] && cmdline[pos]!='/'; --pos)
                ;

            if(pos>=0 && pos<len)
            {
                strncpy(appName, &cmdline[pos ? pos+1 : 0], MAX_APP_NAME_LEN);
                appName[MAX_APP_NAME_LEN]='\0';
            }
        }
        close(procFile);
    }

    return appName;
}

static const char * getAppName(bool useTQt=true)
{
    static const char *appName=NULL;

    if(!appName)
    {
        const char *a=useTQt && tqApp ? tqApp->argv()[0] : getAppNameFromPid(getpid());
        const char *slash;

        // Was the cmdline app java? if so, try to use its parent name - just in case
        //   its run from a shell script, etc. - e.g. as eclipse does
        if(a && 0==strcmp(a, "java"))
            a=getAppNameFromPid(getppid());

        if(a && a[0]=='\0')
            a=NULL;

        appName=a && (slash=strrchr(a, '/')) && '\0'!=slash[1]
                    ? &(slash[1])
                    : a ? a : "TQtApp";
    }

    return appName;
}

int TQApplication::exec()
{
	static bool init=false;

	if(!init)
	{
		connectToKDialogD(getAppName(false));
		init=true;
	}

	static int (*realFunction)(void *);

	if(!realFunction)
		realFunction = (int (*)(void *)) dlsym(RTLD_NEXT, KQT_QAPPLICATION_EXEC);
	if (realFunction)
		return (int)realFunction(this);
	else
	{
		tqWarning("kgtk-qt3 tqt TQApplication::exec() realFunction not found!!");
		return 255;
	}
};

static TQString qt2KdeFilter(const TQString &f)
{
    TQString               filter;
    TQTextOStream          str(&filter);
    TQStringList           list(TQStringList::split(";;", f));
    TQStringList::Iterator it(list.begin()),
                          end(list.end());
    bool                  first=true;

    for(; it!=end; ++it)
    {
        int ob=(*it).findRev('('),
            cb=(*it).findRev(')');

        if(-1!=cb && ob<cb)
        {
            if(first)
                first=false;
            else
                str << '\n';
            str << (*it).mid(ob+1, (cb-ob)-1) << '|' << (*it).mid(0, ob);
        }
    }

    return filter;
}

static void kde2TQtFilter(const TQString &orig, TQString *sel)
{
    if(sel)
    {
        TQStringList           list(TQStringList::split(";;", orig));
        TQStringList::Iterator it(list.begin()),
                              end(list.end());
        int                   pos;

        for(; it!=end; ++it)
            if(-1!=(pos=(*it).find(*sel)) && pos>0 &&
               ('('==(*it)[pos-1] || ' '==(*it)[pos-1]) &&
               (*it).length()>=sel->length()+pos &&
               (')'==(*it)[pos+sel->length()] || ' '==(*it)[pos+sel->length()]))
            {
                *sel=*it;
                return;
            }
    }
}

#ifdef KTQT_OVERLOAD_NON_STATIC_FILEDIALOGS
#ifdef KTQT_USE_TQFILEDIALOG_PRIVATE
// HACK HACK HACK!!!

// KGtk versions <=0.9.1 used this copied TQFileDialogPrivate to access the file filters
// newer versions walk the file dialogs children...
class TQFileDialogPrivate {
public:
    ~TQFileDialogPrivate();

    TQStringList history;

    bool geometryDirty;
    TQComboBox * paths;
    TQComboBox * types;
};
#endif

static const TQString getFilters(TQFileDialog *dlg, bool scribusSave=false)
{
    TQString filter;

#if KTQT_USE_TQFILEDIALOG_PRIVATE
    if(dlg && dlg->d && dlg->d->types)
    {
        TQTextOStream str(&filter);

        for(int i=0; i<dlg->d->types->count(); ++i)
        {
            if(i)
                str << ";;";

            if(scribusSave && -1!=dlg->d->types->text(i).find("(*.sla *.sla.gz *.scd *scd.gz)"))
                str << "Compressed Documents (*.sla.gz *scd.gz);;Documents (*.sla *.scd)";
            else
                str << dlg->d->types->text(i);
        }
    }
#else
    if(dlg)
    {
        const TQObjectList children=((TQObject *)dlg)->childrenListObject();

        if(!children.isEmpty())
        {
            TQObjectList::ConstIterator it(children.begin()),
                                       end(children.end());

            for(; it!=end; ++it)
                if(::tqt_cast<TQComboBox *>(*it) && 0==qstrcmp((*it)->name(), "file types"))
                {
                    TQComboBox    *types=(TQComboBox *)(*it);
                    TQTextOStream str(&filter);

                    for(int i=0; i<types->count(); ++i)
                    {
                        if(i)
                            str << ";;";

                        if(scribusSave && -1!=types->text(i).find("(*.sla *.sla.gz *.scd *scd.gz)"))
                            str << "Compressed Documents (*.sla.gz *scd.gz);;Documents (*.sla *.scd)";
                        else
                            str << types->text(i);
                    }

                    break;
                }
        }
    }
#endif

    return filter;
}

static TQString getCurrentFileName(TQFileDialog *dlg)
{
    if(dlg)
    {
        const TQObjectList children=((TQObject *)dlg)->childrenListObject();

        if(!children.isEmpty())
        {
            TQObjectList::ConstIterator it(children.begin()),
                                       end(children.end());

            for(; it!=end; ++it)
                 if(::tqt_cast<TQLineEdit *>(*it)) // && 0==qstrcmp((*it)->name(), "name/filter editor"))
                    return ((TQLineEdit *)(*it))->text();
        }
    }

    return TQString();
}

static TQString getDir(const TQString &f)
{
    TQString d(f);

    int slashPos=d.findRev('/');

    if(slashPos!=-1)
        d.remove(slashPos+1, d.length());

    return d;
}
#endif

static bool writeString(int fd, const TQString &str)
{
    TQCString utf8(str.utf8());
    int      size=utf8.length()+1;

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

static bool writeBool(int fd, bool b)
{
    char bv=b ? 1 : 0;

    return writeBlock(fd, (char *)&bv, 1);
}

class KTQtDialog : public TQDialog
{
    public:

    KTQtDialog(TQWidget *parent) : TQDialog(parent, "kqt", true, WStyle_NoBorder|WX11BypassWM)
    {
        resize(1, 1);
        setWindowOpacity(0.0);
        setWindowState(WState_Minimized);
        move(32768, 32768);
    }

/*    void r() { TQDialog::reject(); }*/
};

class KTQtThread : public TQThread
{
    public:

    KTQtThread(TQStringList &l, TQString &s, int f, KTQtDialog *dlg) : dialog(dlg), kdialogdError(false), res(l), selFilter(s), fd(f)
    { }

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

    bool readString(TQString &str, int size)
    {
        TQCString buffer;
        buffer.resize(size);

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

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

    void run()
    {
        TQString buffer;
        int     num=0;

        if(readBlock(fd, (char *)&num, 4))
        {
            int n;

            for(n=0; n<num && !kdialogdError; ++n)
            {
                int size=0;

                if(readBlock(fd, (char *)&size, 4))
                {
                    if(size>0)
                    {
                        if(readString(buffer, size))
                        {
                            //buffer[size-1]='\0';
                            if('/'==buffer[0])
                                res.append(buffer);
                            else
                                selFilter=buffer;
                        }
                        else
                            kdialogdError=true;
                    }
                }
                else
                    kdialogdError=true;
            }
        }
        else
            kdialogdError=true;

        TQApplication::postEvent(dialog, new TQCloseEvent);
    }

    KTQtDialog   *dialog;
    bool        kdialogdError;
    TQStringList &res;
    TQString     &selFilter;
    int         fd;
};

static bool sendMessage(TQWidget *parent, Operation op, TQStringList &res, TQString &selFilter,
                        const TQString &title, const TQString &p1, const TQString *p2, bool ow)
{
    if(connectToKDialogD(getAppName()))
    {
        char o=(char)op;
        int  xid=parent ? parent->topLevelWidget()->winId() : tqApp->activeWindow()->winId();

        if(writeBlock(kdialogdSocket, &o, 1) &&
           writeBlock(kdialogdSocket, (char *)&xid, 4) &&
           writeString(kdialogdSocket, title) &&
           writeString(kdialogdSocket, p1) &&
           (p2? writeString(kdialogdSocket, *p2) : true) &&
           (OP_FILE_SAVE==op ? writeBool(kdialogdSocket, ow) : true))
        {
            KTQtDialog dlg(parent);
            KTQtThread thread(res, selFilter, kdialogdSocket, &dlg);

            thread.start();
            dlg.exec();
            thread.wait();
            if(thread.kdialogdError)
            {
                closeConnection();
                return false;
            }
            return true;
        }
    }

    return false;
}

static TQString getTitle(const TQString &title, Operation op)
{
    if(!title.isEmpty())
        return title;

    return ".";
}

static bool openKdeDialog(TQWidget *widget, const TQString &title, const TQString &p1, const TQString *p2,
                          Operation op, TQStringList &res, TQString *selFilter, bool ow=false)
{
    TQString filter;
    bool    rv=sendMessage(widget, op, res, filter, getTitle(title, op), p1, p2, ow);

    // If we failed to talk to, or start kdialogd, then dont keep trying - just fall back to TQt
    if(!rv)
        /*useKde=false*/;
    else if(selFilter)
        *selFilter=filter;

    return rv;
}

static void kqtExit()
{
    if(useKde)
        closeConnection();
}

static bool kqtInit()
{
    static bool initialised=false;

    if(!initialised)
    {
        initialised=true;
        useKde=NULL!=getenv("TDE_FULL_SESSION") && connectToKDialogD(getAppName());
        if(useKde)
            atexit(&kqtExit);
    }

    return useKde;
}

static TQString lastDir;

static void storeLastDir(const TQString &f)
{
    lastDir=f;

    int slashPos(lastDir.findRev('/'));

    if(slashPos!=-1)
        lastDir.remove(slashPos+1, lastDir.length());
}

static const TQString & startDir(const TQString &d)
{
    return d.isEmpty() ? lastDir : d;
}

TQString TQFileDialog::getOpenFileName(const TQString &initially, const TQString &filter,
                                     TQWidget *parent, const char *name, const TQString &caption,
                                     TQString *selectedFilter, bool resolveSymlinks)
{
    TQStringList res;
    TQString     f(qt2KdeFilter(filter));
    kqtInit();

    if(openKdeDialog(parent, caption, startDir(initially), &f, OP_FILE_OPEN, res, selectedFilter))
    {
        kde2TQtFilter(filter, selectedFilter);
        TQString fn(res.first());

        storeLastDir(fn);
        return fn;
    }
    return TQString();
}

TQString TQFileDialog::getSaveFileName(const TQString &initially, const TQString &filter, TQWidget *parent,
                                     const char *name, const TQString &caption,
                                     TQString *selectedFilter, bool resolveSymlinks)
{
    TQStringList res;
    TQString     f(qt2KdeFilter(filter));
    kqtInit();

    if (openKdeDialog(parent, caption, startDir(initially), &f, OP_FILE_SAVE, res, selectedFilter))
    {
        kde2TQtFilter(filter, selectedFilter);
        TQString fn(res.first());

        storeLastDir(fn);
        return fn;
    }
    return TQString();
}

TQString TQFileDialog::getExistingDirectory(const TQString &dir, TQWidget *parent, const char *name,
                                          const TQString &caption, bool dirOnly, bool resolveSymlinks)
{
    TQStringList res;
    TQString     dummy;

    kqtInit();

    return openKdeDialog(parent, caption, dir, NULL, OP_FOLDER, res, &dummy)
            ? res.first()
            : TQString();
}

TQStringList TQFileDialog::getOpenFileNames(const TQString &filter, const TQString &dir, TQWidget *parent,
                                          const char *name, const TQString &caption,
                                          TQString *selectedFilter, bool resolveSymlinks)
{
    TQStringList res;
    TQString     f(qt2KdeFilter(filter));
    kqtInit();

    openKdeDialog(parent, caption, startDir(dir), &f, OP_FILE_OPEN_MULTIPLE, res, selectedFilter);

    if(res.count())
    {
        kde2TQtFilter(filter, selectedFilter);
        storeLastDir(res.first());
    }
    return res;
}

#ifdef KTQT_OVERLOAD_NON_STATIC_FILEDIALOGS
static TQString getFile(const TQString &f)
{
    TQString d(f);

    int slashPos=d.findRev('/');

    if(slashPos!=-1)
        d.remove(0, slashPos+1);

    return d;
}

int TQDialog::exec()
{
    int res=TQDialog::Rejected;

    if(inherits("TQFileDialog"))
    {
        TQFileDialog *that=(TQFileDialog *)this;

        const TQDir  *dirp=that->dir();
        TQString     dir,
                    selectedFilter,
                    file,
                    initialDir(dirp ? dirp->absPath() : TQDir::homeDirPath());
        TQStringList files;

        if(dirp)
            delete dirp;

        TQApplication::eventLoop()->processEvents(TQEventLoop::ExcludeUserInput, 1);
        switch(that->mode())
        {
            case TQFileDialog::Directory:
            case TQFileDialog::DirectoryOnly:
                dir=TQFileDialog::getExistingDirectory(initialDir, parentWidget(), NULL,
                                                      caption(), true, true);

                if(!dir.isEmpty())
                    res=TQDialog::Accepted;
                break;
            case TQFileDialog::AnyFile:
            {
                TQString app(getFile(tqApp->argv()[0])),
                        initialFile(getCurrentFileName(that));

                if(!initialFile.isEmpty())
                    initialDir=initialDir+TQChar('/')+initialFile;

                file=TQFileDialog::getSaveFileName(initialDir,
                                                  getFilters(that, "scribus"==app ||
                                                                   "scribus-ng"==app),
                                                  parentWidget(), NULL, caption(), &selectedFilter,
                                                  true);

                if(!file.isEmpty())
                    res=TQDialog::Accepted;
                break;
            }
            case TQFileDialog::ExistingFile:
                file=TQFileDialog::getOpenFileName(initialDir, getFilters(that), parentWidget(),
                                                  NULL, caption(), &selectedFilter, true);

                if(!file.isEmpty())
                    res=TQDialog::Accepted;
                break;
            case TQFileDialog::ExistingFiles:
                files=TQFileDialog::getOpenFileNames(getFilters(that), initialDir, parentWidget(),
                                                    NULL, caption(), &selectedFilter, true);

                if(files.count())
                    res=TQDialog::Accepted;
                break;
        }

        if(TQDialog::Accepted==res)
        {
            if(file.isEmpty() && files.count())
                file=files.first();
            if(dir.isEmpty() && !file.isEmpty())
                dir=getDir(file);
            if(!dir.isEmpty())
                that->setDir(dir);
            if(!selectedFilter.isEmpty())
                that->setSelectedFilter(selectedFilter);
            if(!file.isEmpty())
                that->setSelection(file);

            if(files.count() && that->nameEdit)
            {
                TQStringList::Iterator it(files.begin()),
                                      end(files.end());
                TQString               filesStr;
                TQTextOStream          str(&filesStr);

                for(; it!=end; ++it)
                    str << "\"" << (*it) << "\" ";
                that->nameEdit->setText(filesStr);
            }
            TQApplication::eventLoop()->processEvents(TQEventLoop::ExcludeUserInput, 1);
        }
    }
    else
    {
        static int (*realFunction)(void *);

        if(!realFunction)
            realFunction = (int (*)(void *)) dlsym(RTLD_NEXT, KQT_QDIALOG_EXEC);
        return (int)realFunction(this);
    }

    return res;
}
#endif
