/***************************************************************************
 *   Copyright (C) 2001-2002 by Bernd Gehrmann                             *
 *   bernd@kdevelop.org                                                    *
 *                                                                         *
 *   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 "scriptprojectpart.h"

#include <tqdir.h>
#include <tqregexp.h>
#include <tqstringlist.h>
#include <tqvaluestack.h>
#include <tqvbox.h>
#include <tqwhatsthis.h>
#include <tdeaction.h>
#include <kdebug.h>
#include <kdialogbase.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kdevgenericfactory.h>
#include <kdevcreatefile.h>
#include <kdirwatch.h>

#include "domutil.h"
#include "kdevcore.h"
#include "kdevmainwindow.h"
#include "kdevpartcontroller.h"
#include "kdevlanguagesupport.h"
#include "scriptoptionswidget.h"
#include "scriptnewfiledlg.h"
#include "kdevplugininfo.h"

typedef KDevGenericFactory<ScriptProjectPart> ScriptProjectFactory;
static const KDevPluginInfo pluginData("kdevscriptproject");
K_EXPORT_COMPONENT_FACTORY( libkdevscriptproject, ScriptProjectFactory( pluginData ) )

ScriptProjectPart::ScriptProjectPart(TQObject *parent, const char *name, const TQStringList &)
    : KDevBuildTool(&pluginData, parent, name ? name : "ScriptProjectPart")
{
    setInstance(ScriptProjectFactory::instance());

    setXMLFile("kdevscriptproject.rc");

    // only create new file action if file creation part not available
    if (!extension<KDevCreateFile>("TDevelop/CreateFile")) {
      TDEAction *action;
      action = new TDEAction( i18n("New File..."), 0,
                            this, TQ_SLOT(slotNewFile()),
                            actionCollection(), "file_newfile" );
      action->setWhatsThis( i18n("<b>New file</b><p>Creates a new file.") );
      action->setToolTip( i18n("Create a new file") );
    }
    new TDEAction( i18n("Rescan Project"), 0, CTRL+ALT+Key_R,
                            this, TQ_SLOT(rescan()),
                            actionCollection(), "rescan" );

    connect( core(), TQ_SIGNAL(projectConfigWidget(KDialogBase*)),
             this, TQ_SLOT(projectConfigWidget(KDialogBase*)) );
}


ScriptProjectPart::~ScriptProjectPart()
{}


void ScriptProjectPart::projectConfigWidget(KDialogBase *dlg)
{
    TQVBox *vbox;
    vbox = dlg->addVBoxPage(i18n("Script Project Options"), i18n("Script Project Options"), BarIcon("tdevelop", TDEIcon::SizeMedium));
    ScriptOptionsWidget *w = new ScriptOptionsWidget(this, vbox);
    connect( dlg, TQ_SIGNAL(okClicked()), w, TQ_SLOT(accept()) );
}


/**
 * @todo This should really be merged with FileTreeWidget::matchesHidePattern()
 * and put in its own class. Currently this is repeated in scriptprojectpart.cpp, pascalproject_part.cpp, adaproject_part.cpp
 */
static bool matchesPattern(const TQString &fileName, const TQStringList &patternList)
{
    TQStringList::ConstIterator it;
    for (it = patternList.begin(); it != patternList.end(); ++it) {
        TQRegExp re(*it, true, true);
        if (re.search(fileName) == 0 && re.matchedLength() == (int)fileName.length())
            return true;
    }

    return false;
}


void ScriptProjectPart::openProject(const TQString &dirName, const TQString &projectName)
{
    if (!languageSupport())
        kdDebug(9015) << "ScriptProjectPart::openProject: no language support found!" << endl;

    m_projectDirectory = dirName;
    m_projectName = projectName;

    TQDomDocument &dom = *projectDom();

    // Set the default directory radio to "executable"
    if (DomUtil::readEntry(dom, "/kdevscriptproject/run/directoryradio") == "" ) {
        DomUtil::writeEntry(dom, "/kdevscriptproject/run/directoryradio", "executable");
    }

    // Put all files from all subdirectories into file list
    TQValueStack<TQString> s;
    int prefixlen = m_projectDirectory.length()+1;
    s.push(m_projectDirectory);

    TQDir dir;
    do {
        dir.setPath(s.pop());
        kdDebug(9015) << "Examining: " << dir.path() << endl;
        const TQFileInfoList *dirEntries = dir.entryInfoList();
        if ( dirEntries )
        {
            TQPtrListIterator<TQFileInfo> it(*dirEntries);
            for (; it.current(); ++it) {
                TQString fileName = it.current()->fileName();
                if (fileName == "." || fileName == "..")
                   continue;
                TQString path = it.current()->absFilePath();
                if (it.current()->isDir()) {
                    //do not follow symlinks which point to the themselves
                    if (it.current()->isSymLink())
                    {
                        TQString symLink = it.current()->readLink();
                        if ((symLink == path) || (symLink == "./"))
                            continue;
                    } else if (canAddDirectoryToProject(path)) {
                        kdDebug(9015) << "Pushing: " << path << endl;
                        s.push(path);
                    }
                }
                else {
                    if (canAddToProject(path))
                        m_sourceFiles.append(path.mid(prefixlen));
                }
            }
        }
    } while (!s.isEmpty());

    KDevProject::openProject( dirName, projectName );
}


void ScriptProjectPart::closeProject()
{
}


TQString ScriptProjectPart::projectDirectory() const
{
    return m_projectDirectory;
}


TQString ScriptProjectPart::buildDirectory() const
{
    return m_projectDirectory;
}

TQString ScriptProjectPart::projectName() const
{
    return m_projectName;
}


/** Retuns a PairList with the run environment variables */
DomUtil::PairList ScriptProjectPart::runEnvironmentVars() const
{
    return DomUtil::readPairListEntry(*projectDom(), "/kdevscriptproject/run/envvars", "envvar", "name", "value");
}


/** Retuns the currently selected run directory
  * The returned string can be:
  *   if run/directoryradio == executable
  *        The directory where the executable is
  *   if run/directoryradio == build
  *        The directory where the executable is relative to build directory
  *   if run/directoryradio == custom
  *        The custom directory absolute path
  */
TQString ScriptProjectPart::runDirectory() const
{
    TQString cwd = defaultRunDirectory("kdevscriptproject");
    if (cwd.isEmpty())
      cwd = buildDirectory();
    return cwd;
}


/** Retuns the currently selected main program
  * The returned string can be:
  *   if run/directoryradio == executable
  *        The executable name
  *   if run/directoryradio == build
  *        The path to executable relative to build directory
  *   if run/directoryradio == custom or relative == false
  *        The absolute path to executable
  */
TQString ScriptProjectPart::mainProgram() const
{
    TQDomDocument * dom = projectDom();

    if ( !dom ) return TQString();

    TQString DomMainProgram = DomUtil::readEntry( *dom, "/kdevscriptproject/run/mainprogram");

    if ( DomMainProgram.isEmpty() ) return TQString();

    if ( DomMainProgram.startsWith("/") )   // assume absolute path
    {
        return DomMainProgram;
    }
    else // assume project relative path
    {
        return projectDirectory() + "/" + DomMainProgram;
    }

    return TQString();
}

/** Retuns a TQString with the debug command line arguments */
TQString ScriptProjectPart::debugArguments() const
{
    return DomUtil::readEntry(*projectDom(), "/kdevscriptproject/run/globaldebugarguments");
}


/** Retuns a TQString with the run command line arguments */
TQString ScriptProjectPart::runArguments() const
{
    return DomUtil::readEntry(*projectDom(), "/kdevscriptproject/run/programargs");
}


TQString ScriptProjectPart::activeDirectory() const
{
    TQDomDocument &dom = *projectDom();

    return DomUtil::readEntry(dom, "/kdevscriptproject/general/activedir");
}


TQStringList ScriptProjectPart::allFiles() const
{
/*    TQStringList res;

    TQStringList::ConstIterator it;
    for (it = m_sourceFiles.begin(); it != m_sourceFiles.end(); ++it)
        res += (m_projectDirectory + "/" + (*it));

    return res;*/

	// return all files relative to the project directory!
	return m_sourceFiles;
}

void ScriptProjectPart::addFile(const TQString &fileName)
{
    kdDebug(9015) << "AddFile2" << fileName << endl;

	TQStringList fileList;
	fileList.append ( fileName );

	this->addFiles ( fileList );
}

void ScriptProjectPart::addFiles ( const TQStringList& fileList )
{
	TQStringList::ConstIterator it;

	for ( it = fileList.begin(); it != fileList.end(); ++it )
	{
		m_sourceFiles.append ( ( *it ) );
	}

	emit addedFilesToProject ( fileList );
}

void ScriptProjectPart::removeFile(const TQString &fileName)
{
	TQStringList fileList;
	fileList.append ( fileName );

	this->addFiles ( fileList );
}

void ScriptProjectPart::removeFiles ( const TQStringList& fileList )
{
	emit removedFilesFromProject ( fileList );

	TQStringList::ConstIterator it;

	for ( it = fileList.begin(); it != fileList.end(); ++it )
	{
		m_sourceFiles.remove ( ( *it ) );
	}
}

void ScriptProjectPart::slotNewFile()
{
    ScriptNewFileDialog dlg(this);
    dlg.exec();
}

/*!
    \fn ScriptProjectPart::distFiles() const
 */
TQStringList ScriptProjectPart::distFiles() const
{
   	TQStringList sourceList = allFiles();
	// Scan current source directory for any .pro files.
	TQString projectDir = projectDirectory();
	TQDir dir(projectDir);
	TQStringList files = dir.entryList( "*README*");
	return sourceList + files;
}

void ScriptProjectPart::rescan()
{
//     kdDebug() << "Directory " << path << " changed, scanning for new files in the project" << endl;

    // Put all files from all subdirectories into file list
    TQValueStack<TQString> s;
    int prefixlen = m_projectDirectory.length()+1;
    s.push(m_projectDirectory);

    TQDir dir;
    do {
        dir.setPath(s.pop());
        kdDebug(9015) << "Examining: " << dir.path() << endl;
        const TQFileInfoList *dirEntries = dir.entryInfoList();
        if ( dirEntries )
        {
            TQPtrListIterator<TQFileInfo> it(*dirEntries);
            for (; it.current(); ++it) {
                TQString fileName = it.current()->fileName();
                if (fileName == "." || fileName == "..")
                   continue;
                TQString path = it.current()->absFilePath();
                if (it.current()->isDir()) {
                    //do not follow symlinks which point to the themselves
                    if (it.current()->isSymLink())
                    {
                        TQString symLink = it.current()->readLink();
                        if ((symLink == path) || (symLink == "./"))
                            continue;
                    } else if (canAddDirectoryToProject(path)) {
                        kdDebug(9015) << "Pushing: " << path << endl;
                        s.push(path);
                    }
                }
                else {
                    if (!isProjectFile(path) && canAddToProject(path))
                        addFile(path.mid(prefixlen));
//                         m_sourceFiles.append(path.mid(prefixlen));
                }
            }
        }
    } while (!s.isEmpty());

/*    const TQFileInfoList *dirEntries = TQDir(path).entryInfoList();
    if ( dirEntries )
    {
        kdDebug() << "1" << endl;
        TQPtrListIterator<TQFileInfo> it(*dirEntries);
        for (; it.current(); ++it) {
            kdDebug() << "2" << endl;
            TQString fileName = it.current()->fileName();
            if (fileName == "." || fileName == "..")
                continue;
            kdDebug() << "3" << endl;
            TQString filePath = it.current()->absFilePath();
            if (!it.current()->isDir() && canAddToProject(filePath) && !isProjectFile(filePath)) {
                kdDebug() << "=== adding " << filePath << endl;
                addFile(filePath);
            }
        }
    }*/
}

bool ScriptProjectPart::canAddToProject(const TQString & path)
{
    TQDomDocument &dom = *projectDom();
    TQString includepatterns
        = DomUtil::readEntry(dom, "/kdevscriptproject/general/includepatterns");
    TQStringList includepatternList;
    if ( includepatterns.isNull() ) {
    if ( languageSupport() ){
        KMimeType::List list = languageSupport()->mimeTypes();
        KMimeType::List::Iterator it = list.begin();
        while( it != list.end() ){
        includepatternList += (*it)->patterns();
        ++it;
        }
    }
    } else {
        includepatternList = TQStringList::split(",", includepatterns);
    }

    TQString excludepatterns
        = DomUtil::readEntry(dom, "/kdevscriptproject/general/excludepatterns");
    if (excludepatterns.isNull())
        excludepatterns = "*~";
    TQStringList excludepatternList = TQStringList::split(",", excludepatterns);

    if (matchesPattern(path, includepatternList)
        && !matchesPattern(path, excludepatternList)) {
        kdDebug(9015) << "Adding: " << path << endl;
        return true;
    } else {
        kdDebug(9015) << "Ignoring: " << path << endl;
        return false;
    }
}

bool ScriptProjectPart::canAddDirectoryToProject(const TQString & path)
{
    TQDomDocument &dom = *projectDom();
    TQString excludepatterns
        = DomUtil::readEntry(dom, "/kdevscriptproject/general/excludepatterns");
    if (excludepatterns.isNull()) {
        return true;
    }
    TQStringList excludepatternList = TQStringList::split(",", excludepatterns);

    if (!matchesPattern(path, excludepatternList)) {
        return true;
    } else {
        kdDebug(9015) << "Ignoring Directory: " << path << endl;
        return false;
    }
}

#include "scriptprojectpart.moc"
