/***************************************************************************
 *   Copyright (C) 2001-2002 by Bernd Gehrmann                             *
 *   bernd@kdevelop.org                                                    *
 *   Copyright (C) 2003 by Mario Scalas (VCS Support)                      *
 *   mario.scalas@libero.it                                                *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "filetreewidget.h"

#include <tqheader.h>
#include <tqpainter.h>
#include <tqregexp.h>
#include <tqstringlist.h>
#include <tqcolor.h>

#include <kdebug.h>
#include <tdelocale.h>
#include <tdepopupmenu.h>
#include <tdefileitem.h>
#include <kurl.h>
#include <tdeaction.h>

#include "kdevcore.h"
#include "kdevproject.h"
#include "kdevpartcontroller.h"
#include "kdevmainwindow.h"
#include "kdevversioncontrol.h"
#include "domutil.h"
#include "urlutil.h"

#include "fileviewpart.h"
#include "fileitemfactory.h"
#include "vcsfiletreewidgetimpl.h"
#include "stdfiletreewidgetimpl.h"

using namespace filetreeview;

///////////////////////////////////////////////////////////////////////////////
// class FileTreeWidget
///////////////////////////////////////////////////////////////////////////////

FileTreeWidget::FileTreeWidget( FileViewPart *part, TQWidget *parent, KDevVCSFileInfoProvider *infoProvider )
    : KFileTreeView( parent, "filetreewidget" ), m_part( part ), m_rootBranch( 0 )
{
    kdDebug(9017) << "Requested FileTree for: " << projectDirectory() << endl;
    if (versionControl() && infoProvider)
        kdDebug(9017) << "Valid VCS directory: " << versionControl()->isValidDirectory( projectDirectory() ) << endl;

    if (infoProvider && versionControl() && versionControl()->isValidDirectory( projectDirectory() ))
        m_impl = new VCSFileTreeWidgetImpl( this, infoProvider );
    else
        m_impl = new StdFileTreeWidgetImpl( this );

    //setResizeMode( TQListView::LastColumn );
    setSorting( 0 );
    setAllColumnsShowFocus( true );
    setSelectionMode( TQListView::Extended ); // Enable multiple items selection by use of Ctrl/Shift
    setDragEnabled( false );

    // Slot connections
    connect( this, TQ_SIGNAL(executed(TQListViewItem*)), this, TQ_SLOT(slotItemExecuted(TQListViewItem*)) );
    connect( this, TQ_SIGNAL(returnPressed(TQListViewItem*)), this, TQ_SLOT(slotItemExecuted(TQListViewItem*)) );
    connect( this, TQ_SIGNAL(contextMenu(TDEListView*, TQListViewItem*, const TQPoint&)),
             this, TQ_SLOT(slotContextMenu(TDEListView*, TQListViewItem*, const TQPoint&)) );
    // Intercepts KDevelop core signals and VCS notifications (if available)
    connect( m_part->project(), TQ_SIGNAL( activeDirectoryChanged( const TQString&, const TQString& ) ),
             this, TQ_SLOT( changeActiveDirectory( const TQString&, const TQString& ) ) );
    connect( m_part->project(), TQ_SIGNAL( addedFilesToProject( const TQStringList & ) ),
             this, TQ_SLOT( addProjectFiles( const TQStringList & ) ) );
    connect( m_part->project(), TQ_SIGNAL( removedFilesFromProject( const TQStringList & ) ),
             this, TQ_SLOT( removeProjectFiles( const TQStringList & ) ) );
    // Safeguard against VCS plug-in unloading at run-time
    connect( m_impl, TQ_SIGNAL(implementationInvalidated()), this, TQ_SLOT(slotImplementationInvalidated()) );

    // Hide pattern for files
    TQDomDocument &dom = *m_part->projectDom();
    TQString defaultHidePattern = "*.o,*.lo,CVS";
    TQString hidePattern = DomUtil::readEntry( dom, "/kdevfileview/tree/hidepatterns", defaultHidePattern );
    m_hidePatterns = TQStringList::split( ",", hidePattern );
}

///////////////////////////////////////////////////////////////////////////////

FileTreeWidget::~FileTreeWidget()
{
    kdDebug(9017) << "FileTreeWidget::~FileTreeWidget()" << endl;

    TQDomDocument &dom = *m_part->projectDom();
    DomUtil::writeEntry( dom, "/kdevfileview/tree/hidepatterns", hidePatterns() );

//    delete m_impl;
}

///////////////////////////////////////////////////////////////////////////////

void FileTreeWidget::openDirectory( const TQString& dirName )
{
    kdDebug(9017) << "FileTreeWidget::openDirectory(): " + dirName << endl;

    // if we're reloading
    if (m_rootBranch)
    {
        disconnect( m_rootBranch, TQ_SIGNAL(populateFinished(KFileTreeViewItem*)), this, TQ_SLOT(finishPopulate(KFileTreeViewItem*)) );
        removeBranch( m_rootBranch );
        m_projectFiles.clear();
    }

    addProjectFiles( m_part->project()->allFiles(), true );

    KURL url = KURL::fromPathOrURL( dirName );
    const TQPixmap& pix = KMimeType::mimeType("inode/directory")->pixmap( TDEIcon::Small );

    // this is a bit odd, but the order of these calls seems to be important
    //FileTreeBranch *b = new FileTreeBranch( this, url, url.prettyURL(), pix );
    FileTreeBranchItem *b = m_impl->branchItemFactory()->makeBranchItem( this, url, url.prettyURL(), pix );
    b->setChildRecurse( false );
    m_rootBranch = addBranch( b );
    m_rootBranch->setOpen( true );
    connect( m_rootBranch, TQ_SIGNAL(populateFinished(KFileTreeViewItem*)), this, TQ_SLOT(finishPopulate(KFileTreeViewItem*)) );
}

///////////////////////////////////////////////////////////////////////////////

bool FileTreeWidget::shouldBeShown( KFileTreeViewItem* item )
{
    FileTreeViewItem * i = static_cast<FileTreeViewItem *>( item );
    return ( i->isDir() || ( (m_impl->showNonProjectFiles() || i->isProjectFile() )
             && !matchesHidePattern( i->url().fileName() ) ) ) ;
}

///////////////////////////////////////////////////////////////////////////////

bool FileTreeWidget::matchesHidePattern(const TQString &fileName)
{
    TQStringList::ConstIterator it;
    for (it = m_hidePatterns.begin(); it != m_hidePatterns.end(); ++it) {
        TQRegExp re(*it, true, true);
        if (re.search(fileName) == 0 && (uint)re.matchedLength() == fileName.length())
            return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////

void FileTreeWidget::hideOrShow()
{
    // This is called the first time the tree branch is expanded
    FileTreeViewItem* item = static_cast<FileTreeViewItem*>(firstChild());
    if( !item )
      return;

    // Need to skip the root item (which is the sub-directory)
    // i.e. "/home/devmario/src/tdevelop/parts/cvsservice"
    item = static_cast<FileTreeViewItem*>( item->firstChild() );
    // Now fill the sub-tree
    while (item)
    {
        item->hideOrShow();
        item = static_cast<FileTreeViewItem*>(item->nextSibling());
    }
}

void FileTreeWidget::finishPopulate(KFileTreeViewItem* item)
{
    if( item == firstChild() )
    {
        changeActiveDirectory( "", m_part->project()->activeDirectory() );
    }
}

///////////////////////////////////////////////////////////////////////////////

void FileTreeWidget::slotItemExecuted( TQListViewItem* item )
{
    if (!item)
        return;

    KFileTreeViewItem* ftitem = static_cast<KFileTreeViewItem*>(item);

    if (ftitem->isDir())
        return;

    m_part->partController()->editDocument( ftitem->url() );
}

///////////////////////////////////////////////////////////////////////////////

void FileTreeWidget::slotContextMenu( TDEListView *, TQListViewItem* item, const TQPoint &p )
{
    kdDebug(9017) << "FileTreeWidget::slotContextMenu(...)" << endl;

    TDEPopupMenu popup( this );
    popup.insertTitle( i18n("File Tree") );

    // If an item is selected, fill the file context with selected files' list
    if (item)
    {
        m_impl->fillPopupMenu( &popup, item );
        FileContext context( m_impl->selectedPathUrls() );
        m_part->core()->fillContextMenu( &popup, &context );
    }


    popup.exec( p );
}

///////////////////////////////////////////////////////////////////////////////

TQString FileTreeWidget::projectDirectory()
{
    return m_part->project()->projectDirectory();
}

///////////////////////////////////////////////////////////////////////////////

/**
 * @brief Test whether given file (or a directory) is part of this project.
 *
 * @param fileName or directory to test for presence.
 */
bool FileTreeWidget::isInProject(const TQString &fileName) const
{
    return m_projectFiles.contains(fileName);
}

///////////////////////////////////////////////////////////////////////////////

/**
 * @brief Add a bunch of files to this project.
 *
 * Whenever we load a project or user chooses to add a bunch of files using UI,
 * we end in this method.
 * We merge the list of files already in the project (if any) with the incoming set.
 *
 * @param fileList
 * @param constructing
 *
 * @see m_projectFiles
 */
void FileTreeWidget::addProjectFiles( TQStringList const & fileList, bool constructing )
{
    kdDebug(9017) << "files added to project: " << fileList << endl;

    TQStringList::ConstIterator it;
    for ( it = fileList.begin(); it != fileList.end(); ++it )
    {
        if( (*it).isEmpty() )
            continue;
        kdDebug(9017) << "adding file: " << *it << endl;
        const TQString file = projectDirectory() + "/" + ( *it );
        if ( !m_projectFiles.contains( file ) )
        {
            // We got a new file to add to this project.
            // Ensure all the parent directories are part of the project set, too.
            TQStringList paths = TQStringList::split( "/", *it);
            paths.pop_back();
            while( !paths.isEmpty() )
            {
                // We are adding the directories from longest (the one containing our file),
                // to the shortest, measured from root directory of our project.
                // Whenever we find out that a directory is already recorded as part of our project,
                // we may stop adding, because its parent directories were already added -
                // in some previous addition.
                TQString joinedPaths = paths.join("/");
                if( m_projectFiles.contains( joinedPaths ) )
                    break;
                m_projectFiles.insert( projectDirectory() + "/" + joinedPaths, true );
                paths.pop_back();
            }
            m_projectFiles.insert( file, false );
//            kdDebug(9017) << "file added: " << file << endl;
        }

        if ( !constructing )
        {
            FileTreeViewItem* item = static_cast<FileTreeViewItem*>(firstChild());
            if( item )
            {
                item->setProjectFile( file, true );
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////////

void FileTreeWidget::removeProjectFiles( TQStringList const & fileList )
{
    kdDebug(9017) << "files removed from project: " << fileList.count() << endl;

    TQStringList::ConstIterator it;
    for ( it = fileList.begin(); it != fileList.end(); ++it )
    {
        TQString file = m_part->project()->projectDirectory() + "/" + ( *it );
        m_projectFiles.remove( file );
        kdDebug(9017) << "file removed: " << file << endl;

        FileTreeViewItem* item = static_cast<FileTreeViewItem*>(firstChild());
        if( item )
        {
            item->setProjectFile( file, false );
        }
    }
}

void FileTreeWidget::changeActiveDirectory( const TQString& olddir, const TQString& newdir )
{
    FileTreeViewItem* item = static_cast<FileTreeViewItem*>(firstChild());
    if( item )
    {
      item->changeActiveDir( projectDirectory() + "/" + olddir, projectDirectory() + "/" + newdir );
    }
}

///////////////////////////////////////////////////////////////////////////////

void FileTreeWidget::applyHidePatterns( const TQString &hidePatterns )
{
    m_hidePatterns = TQStringList::split( ",", hidePatterns );
    hideOrShow();
}

///////////////////////////////////////////////////////////////////////////////

TQString FileTreeWidget::hidePatterns() const
{
    return m_hidePatterns.join( "," );
}

///////////////////////////////////////////////////////////////////////////////

KDevVersionControl *FileTreeWidget::versionControl() const
{
    if (part() && part()->versionControl())
        return part()->versionControl();
    else
        return 0;
}

///////////////////////////////////////////////////////////////////////////////

bool FileTreeWidget::showNonProjectFiles() const
{
    return m_impl->showNonProjectFiles();
}

///////////////////////////////////////////////////////////////////////////////

void FileTreeWidget::slotImplementationInvalidated()
{
    kdDebug(9017) << "FileTreeWidget::slotImplementationInvalidated()" << endl;

    // Destroy old implementation, create the simpler default impl. and
    // reload list view
    // remove old branch
    removeBranch( m_rootBranch );
    m_rootBranch = 0; // avoid openDirectory() trying to release the branch

    // Restore a clean situation for an eventual new & different implementation
    /** \FIXME this for-loop should really go in ~FileTreeViewWidgetImpl() but
      * it crashes there: here it works :-/
      */
    for (int i=columns()-1; i>=0; --i)
    {
        kdDebug(9017) << "Removing column: " << i << endl;
        removeColumn( i );
    }

    delete m_impl;
    m_impl = new StdFileTreeWidgetImpl( this );
    openDirectory( projectDirectory() );
}

#include "filetreewidget.moc"
