/* This file is part of the KDE project
   Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
                 2000, 2001 Werner Trobin <trobin@kde.org>
   Copyright (C) 2004 Nicolas Goutte <goutte@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/


#include <KoFilterManager.h>
#include <KoFilterManager_p.h>

#include <tqfile.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqptrlist.h>
#include <tqapplication.h>

#include <tdelocale.h>
#include <tdemessagebox.h>
#include <KoDocument.h>
#include <klibloader.h>
#include <tdelistbox.h>
#include <kmimetype.h>
#include <kdebug.h>

#include <queue>

#include <unistd.h>

class KoFilterManager::Private
{
public:
    bool m_batch;
};


KoFilterChooser::KoFilterChooser (TQWidget *parent, const TQStringList &mimeTypes, const TQString &nativeFormat)
    : KDialogBase (parent, "kofilterchooser", true, i18n ("Choose Filter"),
                   KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true),
    m_mimeTypes (mimeTypes)
{
    setInitialSize (TQSize (300, 350));

    TQWidget *page = new TQWidget (this);
    setMainWidget (page);

    // looks too squashed together without * 2
    TQVBoxLayout *layout = new TQVBoxLayout (page, marginHint (), spacingHint () * 2);

    TQLabel *filterLabel = new TQLabel (i18n ("Select a filter:"), page, "filterlabel");
    layout->addWidget (filterLabel);

    m_filterList = new TDEListBox (page, "filterlist");
    layout->addWidget (m_filterList);

    Q_ASSERT (!m_mimeTypes.isEmpty ());
    for (TQStringList::ConstIterator it = m_mimeTypes.begin ();
            it != m_mimeTypes.end ();
            it++)
    {
        KMimeType::Ptr mime = KMimeType::mimeType (*it);
        m_filterList->insertItem (mime->comment ());
    }

    if (nativeFormat == "application/x-kword")
    {
        const int index = m_mimeTypes.findIndex ("text/plain");
        if (index > -1)
            m_filterList->setCurrentItem (index);
    }

    if (m_filterList->currentItem () == -1)
        m_filterList->setCurrentItem (0);

    m_filterList->centerCurrentItem ();
    m_filterList->setFocus ();

    connect (m_filterList, TQT_SIGNAL (selected (int)), this, TQT_SLOT (slotOk ()));
}

KoFilterChooser::~KoFilterChooser ()
{
}

TQString KoFilterChooser::filterSelected ()
{
    const int item = m_filterList->currentItem ();

    if (item > -1)
        return m_mimeTypes [item];
    else
        return TQString();
}


// static cache for filter availability
TQMap<TQString, bool> KoFilterManager::m_filterAvailable;

const int KoFilterManager::s_area = 30500;


KoFilterManager::KoFilterManager( KoDocument* document ) :
    m_document( document ), m_parentChain( 0 ), m_graph( "" ), d( 0 )
{
    d = new KoFilterManager::Private;
    d -> m_batch = false;
    if ( document )
        TQObject::connect( this, TQT_SIGNAL( sigProgress( int ) ),
                          document, TQT_SIGNAL( sigProgress( int ) ) );
}


KoFilterManager::KoFilterManager( const TQString& url, const TQCString& mimetypeHint,
                                  KoFilterChain* const parentChain ) :
    m_document( 0 ), m_parentChain( parentChain ), m_importUrl( url ), m_importUrlMimetypeHint( mimetypeHint ),
    m_graph( "" ), d( 0 )
{
    d = new KoFilterManager::Private;
    d -> m_batch = false;
}

KoFilterManager::~KoFilterManager()
{
    delete d;
}

TQString KoFilterManager::import( const TQString& url, KoFilter::ConversionStatus& status )
{
    // Find the mime type for the file to be imported.
    KURL u;
    u.setPath( url );
    KMimeType::Ptr t = KMimeType::findByURL( u, 0, true );

    m_graph.setSourceMimeType( t->name().latin1() );  // .latin1() is okay here (Werner)
    if ( !m_graph.isValid() ) {
        bool userCancelled = false;

        kdWarning(s_area) << "Can't open " << t->name () << ", trying filter chooser" << endl;
        if ( m_document )
        {
	    if ( !m_document->isAutoErrorHandlingEnabled() )
	    {
		status = KoFilter::BadConversionGraph;
		return TQString();
	    }
            TQCString nativeFormat = m_document->nativeFormatMimeType ();

            TQApplication::setOverrideCursor( arrowCursor );
            KoFilterChooser chooser(0,
                                    KoFilterManager::mimeFilter (nativeFormat, KoFilterManager::Import, m_document->extraNativeMimeTypes()),
                                    nativeFormat);
            if (chooser.exec ())
            {
                TQCString f = chooser.filterSelected ().latin1();

                if (f == nativeFormat)
                {
                    status = KoFilter::OK;
                    TQApplication::restoreOverrideCursor();
                    return url;
                }

                m_graph.setSourceMimeType (f);
            }
            else
                userCancelled = true;
            TQApplication::restoreOverrideCursor();
        }

        if (!m_graph.isValid())
        {
            kdError(s_area) << "Couldn't create a valid graph for this source mimetype: "
                            << t->name() << endl;
            importErrorHelper( t->name(), userCancelled );
            status = KoFilter::BadConversionGraph;
            return TQString();
        }
    }

    KoFilterChain::Ptr chain( 0 );
    // Are we owned by a KoDocument?
    if ( m_document ) {
        TQCString mimeType = m_document->nativeFormatMimeType();
        TQStringList extraMimes = m_document->extraNativeMimeTypes();
        int i=0, n = extraMimes.count();
        chain = m_graph.chain( this, mimeType );
        while( !chain && i<n) {
            mimeType = extraMimes[i].utf8();
            chain = m_graph.chain( this, mimeType );
            ++i;
        }
    }
    else {
        kdError(s_area) << "You aren't supposed to use import() from a filter!" << endl;
        status = KoFilter::UsageError;
        return TQString();
    }

    if ( !chain ) {
        kdError(s_area) << "Couldn't create a valid filter chain!" << endl;
        importErrorHelper( t->name() );
        status = KoFilter::BadConversionGraph;
        return TQString();
    }

    // Okay, let's invoke the filters one after the other
    m_direction = Import; // vital information!
    m_importUrl = url;  // We want to load that file
    m_exportUrl = TQString();  // This is null for sure, as embedded stuff isn't
                                  // allowed to use that method
    status = chain->invokeChain();

    m_importUrl = TQString();  // Reset the import URL

    if ( status == KoFilter::OK )
        return chain->chainOutput();
    return TQString();
}

KoFilter::ConversionStatus KoFilterManager::exp0rt( const TQString& url, TQCString& mimeType )
{
    bool userCancelled = false;

    // The import url should already be set correctly (null if we have a KoDocument
    // file manager and to the correct URL if we have an embedded manager)
    m_direction = Export; // vital information!
    m_exportUrl = url;

    KoFilterChain::Ptr chain = 0;
    if ( m_document ) {
        // We have to pick the right native mimetype as source.
        TQStringList nativeMimeTypes;
        nativeMimeTypes.append( m_document->nativeFormatMimeType() );
        nativeMimeTypes += m_document->extraNativeMimeTypes();
        TQStringList::ConstIterator it = nativeMimeTypes.begin();
        const TQStringList::ConstIterator end = nativeMimeTypes.end();
        for ( ; !chain && it != end; ++it )
        {
            m_graph.setSourceMimeType( (*it).latin1() );
            if ( m_graph.isValid() )
                chain = m_graph.chain( this, mimeType );
        }
    }
    else if ( !m_importUrlMimetypeHint.isEmpty() ) {
        kdDebug(s_area) << "Using the mimetype hint: '" << m_importUrlMimetypeHint << "'" << endl;
        m_graph.setSourceMimeType( m_importUrlMimetypeHint );
    }
    else {
        KURL u;
        u.setPath( m_importUrl );
        KMimeType::Ptr t = KMimeType::findByURL( u, 0, true );
        if ( t->name() == KMimeType::defaultMimeType() ) {
            kdError(s_area) << "No mimetype found for " << m_importUrl << endl;
            return KoFilter::BadMimeType;
        }
        m_graph.setSourceMimeType( t->name().latin1() );

        if ( !m_graph.isValid() ) {
            kdWarning(s_area) << "Can't open " << t->name () << ", trying filter chooser" << endl;

            TQApplication::setOverrideCursor( arrowCursor );
            KoFilterChooser chooser(0, KoFilterManager::mimeFilter ());
            if (chooser.exec ())
                m_graph.setSourceMimeType (chooser.filterSelected ().latin1 ());
            else
                userCancelled = true;

            TQApplication::restoreOverrideCursor();
        }
    }

    if (!m_graph.isValid ())
    {
        kdError(s_area) << "Couldn't create a valid graph for this source mimetype." << endl;
        if (!userCancelled) KMessageBox::error( 0L, i18n("Could not export file."), i18n("Missing Export Filter") );
        return KoFilter::BadConversionGraph;
    }

    if ( !chain ) // already set when coming from the m_document case
        chain = m_graph.chain( this, mimeType );

    if ( !chain ) {
        kdError(s_area) << "Couldn't create a valid filter chain to " << mimeType << " !" << endl;
        KMessageBox::error( 0L, i18n("Could not export file."), i18n("Missing Export Filter") );
        return KoFilter::BadConversionGraph;
    }

    return chain->invokeChain();
}

namespace  // in order not to mess with the global namespace ;)
{
    // This class is needed only for the static mimeFilter method
    class Vertex
    {
    public:
        Vertex( const TQCString& mimeType ) : m_color( White ), m_mimeType( mimeType ) {}

        enum Color { White, Gray, Black };
        Color color() const { return m_color; }
        void setColor( Color color ) { m_color = color; }

        TQCString mimeType() const { return m_mimeType; }

        void addEdge( Vertex* vertex ) { if ( vertex ) m_edges.append( vertex ); }
        TQPtrList<Vertex> edges() const { return m_edges; }

    private:
        Color m_color;
        TQCString m_mimeType;
        TQPtrList<Vertex> m_edges;
    };

    // Some helper methods for the static stuff
    // This method builds up the graph in the passed ascii dict
    void buildGraph( TQAsciiDict<Vertex>& vertices, KoFilterManager::Direction direction )
    {
        TQStringList stopList; // Lists of mimetypes that are considered end of chains
        stopList << "text/plain";
        stopList << "text/csv";
        stopList << "text/x-tex";
        stopList << "text/html";

        vertices.setAutoDelete( true );

        // partly copied from build graph, but I don't see any other
        // way without crude hacks, as we have to obey the direction here
        TQValueList<KoDocumentEntry> parts( KoDocumentEntry::query(false, TQString()) );
        TQValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
        TQValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );

        while ( partIt != partEnd ) {
            TQStringList nativeMimeTypes = ( *partIt ).service()->property( "X-TDE-ExtraNativeMimeTypes" ).toStringList();
            nativeMimeTypes += ( *partIt ).service()->property( "X-TDE-NativeMimeType" ).toString();
            TQStringList::ConstIterator it = nativeMimeTypes.begin();
            const TQStringList::ConstIterator end = nativeMimeTypes.end();
            for ( ; it != end; ++it )
                if ( !(*it).isEmpty() )
                    vertices.insert( (*it).latin1(), new Vertex( (*it).latin1() ) );
            ++partIt;
        }

        TQValueList<KoFilterEntry::Ptr> filters = KoFilterEntry::query(); // no constraint here - we want *all* :)
        TQValueList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin();
        const TQValueList<KoFilterEntry::Ptr>::ConstIterator end = filters.end();

        for ( ; it != end; ++it ) {

            TQStringList impList; // Import list
            TQStringList expList; // Export list

            const TQStringList::Iterator stopEnd = stopList.end();
            // Now we have to exclude the "stop" mimetypes (in the right direction!)
            if ( direction == KoFilterManager::Import ) {
                // Import: "stop" mime type should not appear in export
                TQStringList::ConstIterator testIt = ( *it )->export_.begin();
                TQStringList::ConstIterator testEnd = ( *it )->export_.end();
                for ( ; testIt != testEnd ; ++testIt ) {
                    if ( stopList.find( *testIt ) == stopEnd ) {
                        expList.append( *testIt );
                    }
                }
                impList = ( *it )->import;
            }
            else {
                // Export: "stop" mime type should not appear in import
                TQStringList::ConstIterator testIt = ( *it )->import.begin();
                const TQStringList::ConstIterator testEnd = ( *it )->import.end();
                for ( ; testIt != testEnd ; ++testIt ) {
                    if ( stopList.find( *testIt ) == stopEnd ) {
                        impList.append( *testIt );
                    }
                }
                expList = ( *it )->export_;
            }

            if ( impList.empty() || expList.empty() )
            {
                // This filter cannot be used under these conditions
                kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " ruled out" << endl;
                continue;
            }

            // First add the "starting points" to the dict
            TQStringList::ConstIterator importIt = impList.begin();
            const TQStringList::ConstIterator importEnd = impList.end();
            for ( ; importIt != importEnd; ++importIt ) {
                const TQCString key = ( *importIt ).latin1();  // latin1 is okay here (werner)
                // already there?
                if ( !vertices[ key ] )
                    vertices.insert( key, new Vertex( key ) );
            }

            // Are we allowed to use this filter at all?
            if ( KoFilterManager::filterAvailable( *it ) ) {
                TQStringList::ConstIterator exportIt = expList.begin();
                const TQStringList::ConstIterator exportEnd = expList.end();

                for ( ; exportIt != exportEnd; ++exportIt ) {
                    // First make sure the export vertex is in place
                    const TQCString key = ( *exportIt ).latin1();  // latin1 is okay here
                    Vertex* exp = vertices[ key ];
                    if ( !exp ) {
                        exp = new Vertex( key );
                        vertices.insert( key, exp );
                    }
                    // Then create the appropriate edges depending on the
                    // direction (import/export)
                    // This is the chunk of code which actually differs from the
                    // graph stuff (apart from the different vertex class)
                    importIt = impList.begin(); // ### TODO: why only the first one?
                    if ( direction == KoFilterManager::Import ) {
                        for ( ; importIt != importEnd; ++importIt )
                            exp->addEdge( vertices[ ( *importIt ).latin1() ] );
                    } else {
                        for ( ; importIt != importEnd; ++importIt )
                            vertices[ ( *importIt ).latin1() ]->addEdge( exp );
                    }
                }
            }
            else
                kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " does not apply." << endl;
        }
    }

    // This method runs a BFS on the graph to determine the connected
    // nodes. Make sure that the graph is "cleared" (the colors of the
    // nodes are all white)
    TQStringList connected( const TQAsciiDict<Vertex>& vertices, const TQCString& mimetype )
    {
        if ( mimetype.isEmpty() )
            return TQStringList();
        Vertex *v = vertices[ mimetype ];
        if ( !v )
            return TQStringList();

        v->setColor( Vertex::Gray );
        std::queue<Vertex*> queue;
        queue.push( v );
        TQStringList connected;

        while ( !queue.empty() ) {
            v = queue.front();
            queue.pop();
            TQPtrList<Vertex> edges = v->edges();
            TQPtrListIterator<Vertex> it( edges );
            for ( ; it.current(); ++it ) {
                if ( it.current()->color() == Vertex::White ) {
                    it.current()->setColor( Vertex::Gray );
                    queue.push( it.current() );
                }
            }
            v->setColor( Vertex::Black );
            connected.append( v->mimeType() );
        }
        return connected;
    }
}

// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
TQStringList KoFilterManager::mimeFilter( const TQCString& mimetype, Direction direction, const TQStringList& extraNativeMimeTypes )
{
    //kdDebug(s_area) << "mimetype=" << mimetype << " extraNativeMimeTypes=" << extraNativeMimeTypes << endl;
    TQAsciiDict<Vertex> vertices;
    buildGraph( vertices, direction );

    // TODO maybe use the fake vertex trick from the method below, to make the search faster?

    TQStringList nativeMimeTypes;
    nativeMimeTypes.append( TQString::fromLatin1( mimetype ) );
    nativeMimeTypes += extraNativeMimeTypes;

    // Add the native mimetypes first so that they are on top.
    TQStringList lst = nativeMimeTypes;

    // Now look for filters which output each of those natives mimetypes
    for( TQStringList::ConstIterator natit = nativeMimeTypes.begin(); natit != nativeMimeTypes.end(); ++natit ) {
        const TQStringList outMimes = connected( vertices, (*natit).latin1() );
        //kdDebug(s_area) << k_funcinfo << "output formats connected to mime " << *natit << " : " << outMimes << endl;
        for ( TQStringList::ConstIterator mit = outMimes.begin(); mit != outMimes.end(); ++mit )
            if ( lst.find( *mit ) == lst.end() ) // append only if not there already. TQt4: TQSet<TQString>?
                lst.append( *mit );
    }
    return lst;
}

TQStringList KoFilterManager::mimeFilter()
{
    TQAsciiDict<Vertex> vertices;
    buildGraph( vertices, KoFilterManager::Import );

    TQValueList<KoDocumentEntry> parts( KoDocumentEntry::query(false, TQString()) );
    TQValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
    TQValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );

    if ( partIt == partEnd )
        return TQStringList();

    // To find *all* reachable mimetypes, we have to resort to
    // a small hat trick, in order to avoid multiple searches:
    // We introduce a fake vertex, which is connected to every
    // single KOffice mimetype. Due to that one BFS is enough :)
    // Now we just need an... ehrm.. unique name for our fake mimetype
    Vertex *v = new Vertex( "supercalifragilistic/x-pialadocious" );
    vertices.insert( "supercalifragilistic/x-pialadocious", v );
    while ( partIt != partEnd ) {
        TQStringList nativeMimeTypes = ( *partIt ).service()->property( "X-TDE-ExtraNativeMimeTypes" ).toStringList();
        nativeMimeTypes += ( *partIt ).service()->property( "X-TDE-NativeMimeType" ).toString();
        TQStringList::ConstIterator it = nativeMimeTypes.begin();
        const TQStringList::ConstIterator end = nativeMimeTypes.end();
        for ( ; it != end; ++it )
            if ( !(*it).isEmpty() )
                v->addEdge( vertices[ (*it).latin1() ] );
        ++partIt;
    }
    TQStringList result = connected( vertices, "supercalifragilistic/x-pialadocious" );

    // Finally we have to get rid of our fake mimetype again
    result.remove( "supercalifragilistic/x-pialadocious" );
    return result;
}

// Here we check whether the filter is available. This stuff is quite slow,
// but I don't see any other convenient (for the user) way out :}
bool KoFilterManager::filterAvailable( KoFilterEntry::Ptr entry )
{
    if ( !entry )
        return false;
    if ( entry->available != "check" )
        return true;

    //kdDebug( 30500 ) << "Checking whether " << entry->service()->name() << " applies." << endl;
    // generate some "unique" key
    TQString key( entry->service()->name() );
    key += " - ";
    key += entry->service()->library();

    if ( !m_filterAvailable.contains( key ) ) {
        //kdDebug( 30500 ) << "Not cached, checking..." << endl;

        KLibrary* library = KLibLoader::self()->library( TQFile::encodeName( entry->service()->library() ) );
        if ( !library ) {
            kdWarning( 30500 ) << "Huh?? Couldn't load the lib: "
                               << KLibLoader::self()->lastErrorMessage() << endl;
            m_filterAvailable[ key ] = false;
            return false;
        }

        // This code is "borrowed" from klibloader ;)
        TQCString symname;
        symname.sprintf("check_%s", library->name().latin1() );
        void* sym = library->symbol( symname );
        if ( !sym )
        {
            kdWarning( 30500 ) << "The library " << library->name()
                               << " does not offer a check_" << library->name()
                               << " function." << endl;
            m_filterAvailable[ key ] = false;
        }
        else {
            typedef int (*t_func)();
            t_func check = (t_func)sym;
            m_filterAvailable[ key ] = check() == 1;
        }
    }
    return m_filterAvailable[ key ];
}

void KoFilterManager::importErrorHelper( const TQString& mimeType, const bool suppressDialog )
{
    TQString tmp = i18n("Could not import file of type\n%1").arg( mimeType );
    // ###### FIXME: use KLibLoader::lastErrorMessage() here
    if (!suppressDialog) KMessageBox::error( 0L, tmp, i18n("Missing Import Filter") );
}

void KoFilterManager::setBatchMode( const bool batch )
{
    d->m_batch = batch;
}

bool KoFilterManager::getBatchMode( void ) const
{
    return d->m_batch;
}

#include <KoFilterManager.moc>
#include <KoFilterManager_p.moc>
