/***************************************************************************
 *   Copyright (C) 2002 by Roberto Raggi                                   *
 *   roberto@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 "backgroundparser.h"
#include "cppsupportpart.h"
#include "cppsupport_events.h"
#include "codeinformationrepository.h"
#include "cppcodecompletion.h"
#include "driver.h"
#include "ast_utils.h"
#include "kdevdeepcopy.h"
#include "kdevdriver.h"

#if QT_VERSION < 0x030100
#include <kdevmutex.h>
#else
#include <qmutex.h>
#endif

#include <kparts/part.h>
#include <ktexteditor/editinterface.h>
#include <ktexteditor/document.h>
#include <ktexteditor/view.h>

#include <kdevpartcontroller.h>
#include <kdevproject.h>

#include <kurl.h>
#include <kdebug.h>
#include <kapplication.h>

#include <qfile.h>
#include <qfileinfo.h>
#include <qtextstream.h>
#include <qprocess.h>

class KDevSourceProvider: public SourceProvider
{
public:
    KDevSourceProvider( CppSupportPart* cppSupport )
        : m_cppSupport( cppSupport ),
          m_readFromDisk( false ) {}

    void setReadFromDisk( bool b ) { m_readFromDisk = b; }
    bool readFromDisk() const { return m_readFromDisk; }

    virtual QString contents( const QString& fileName )
    {
      QString contents = QString::null;

      if( !m_readFromDisk )
      {
            // GET LOCK
            kapp->lock();
            
            //kdDebug(9007) << "-------> kapp locked" << endl;
  
            QPtrList<KParts::Part> parts( *m_cppSupport->partController()->parts() );
            QPtrListIterator<KParts::Part> it( parts );
            while( it.current() ){
                KTextEditor::Document* doc = dynamic_cast<KTextEditor::Document*>( it.current() );
                ++it;
  
                KTextEditor::EditInterface* editIface = dynamic_cast<KTextEditor::EditInterface*>( doc );
                if( !doc || !editIface || doc->url().path() != fileName )
                    continue;
  
                contents = QString( editIface->text().ascii() ); // deep copy
  
                //kdDebug(9007) << "-------> kapp unlocked" << endl;
  
                break;
            }
  
            // RELEASE LOCK
            kapp->unlock();
            //kdDebug(9007) << "-------> kapp unlocked" << endl;
        }
        else
        {
          QFile f( fileName );
          if( f.open(IO_ReadOnly) ){
	      QTextStream stream( &f );
              contents = stream.read();
              f.close();
          }
        }

	return contents;
    }

    virtual bool isModified( const QString& fileName )
    {
	Q_UNUSED( fileName );
	return true;
    }

private:
    CppSupportPart*  m_cppSupport;
    bool m_readFromDisk;
private:
    KDevSourceProvider( const KDevSourceProvider& source );
    void operator = ( const KDevSourceProvider& source );
};

class SynchronizedFileList
{
public:
    SynchronizedFileList() {}

    bool isEmpty() const
    {
	QMutexLocker locker( &m_mutex );
	return m_fileList.isEmpty();
    }

    uint count() const
    {
	QMutexLocker locker( &m_mutex );
	return m_fileList.count();
    }

    QPair<QString, bool> front() const
    {
	QMutexLocker locker( &m_mutex );
	return m_fileList.front();
    }

    void clear()
    {
	QMutexLocker locker( &m_mutex );
	m_fileList.clear();
    }

    void push_back( const QString& fileName, bool readFromDisk=false )
    {
	QMutexLocker locker( &m_mutex );
	m_fileList.append( qMakePair(fileName, readFromDisk) ); /// \FIXME ROBE deepcopy?!
    }

    void pop_front()
    {
	QMutexLocker locker( &m_mutex );
	m_fileList.pop_front();
    }

    bool contains( const QString& fileName ) const
    {
	QMutexLocker locker( &m_mutex );
	QValueList< QPair<QString, bool> >::ConstIterator it = m_fileList.begin();
	while( it != m_fileList.end() ){
	    if( (*it).first == fileName )
		return true;
	    ++it;
	}
	return false;
    }

    void remove( const QString& fileName )
    {
	QMutexLocker locker( &m_mutex );
	QValueList< QPair<QString, bool> >::Iterator it = m_fileList.begin();
	while( it != m_fileList.end() ){
	    if( (*it).first == fileName )
		m_fileList.remove( it );
	    ++it;
	}
    }

private:
    mutable QMutex m_mutex;
    QValueList< QPair<QString, bool> > m_fileList;
};

BackgroundParser::BackgroundParser( CppSupportPart* part, QWaitCondition* consumed )
    : m_consumed( consumed ), m_cppSupport( part ), m_close( false )
{
    m_fileList = new SynchronizedFileList();
    m_driver = new KDevDriver( m_cppSupport );    
    m_driver->setSourceProvider( new KDevSourceProvider(m_cppSupport) );
    
    QString conf_file_name = m_cppSupport->specialHeaderName();
    if( QFile::exists(conf_file_name) )
        m_driver->parseFile( conf_file_name, true );
    
    //disabled for now m_driver->setResolveDependencesEnabled( true );
}

BackgroundParser::~BackgroundParser()
{
    removeAllFiles();

    delete( m_driver );
    m_driver = 0;

    delete m_fileList;
    m_fileList = 0;
}

void BackgroundParser::addFile( const QString& fileName, bool readFromDisk )
{
    QString fn = deepCopy( fileName );

    bool added = false;
    if( !m_fileList->contains(fn) ){
        m_fileList->push_back( fn, readFromDisk );
	added = true;
    }

    if( added )
        m_canParse.wakeAll();
}

void BackgroundParser::removeAllFiles()
{
    kdDebug(9007) << "BackgroundParser::removeAllFiles()" << endl;
    QMutexLocker locker( &m_mutex );

    QMap<QString, Unit*>::Iterator it = m_unitDict.begin();
    while( it != m_unitDict.end() ){
        Unit* unit = it.data();
	++it;
	delete( unit );
	unit = 0;
    }
    m_unitDict.clear();
    m_driver->reset();
    m_fileList->clear();

    m_isEmpty.wakeAll();
}

void BackgroundParser::removeFile( const QString& fileName )
{
    QMutexLocker locker( &m_mutex );

    Unit* unit = findUnit(fileName);
    if( unit ){
        m_driver->remove( fileName );
        m_unitDict.remove( fileName );
        delete( unit );
	unit = 0;
    }

    if( m_fileList->isEmpty() )
        m_isEmpty.wakeAll();
}

Unit* BackgroundParser::parseFile( const QString& fileName, bool readFromDisk, bool lock )
{

    static_cast<KDevSourceProvider*>( m_driver->sourceProvider() )->setReadFromDisk( readFromDisk );

    m_driver->remove( fileName );
    m_driver->parseFile( fileName , false, true );
    m_driver->removeAllMacrosInFile( fileName );  // romove all macros defined by this
						  // translation unit.
    TranslationUnitAST::Node translationUnit = m_driver->takeTranslationUnit( fileName );

    Unit* unit = new Unit;
    unit->fileName = fileName;
    unit->translationUnit = translationUnit.release();
    unit->problems = m_driver->problems( fileName );
    
    static_cast<KDevSourceProvider*>( m_driver->sourceProvider() )->setReadFromDisk( false );

    if( lock )
        m_mutex.lock();

    if( m_unitDict.find(fileName) != m_unitDict.end() ){
	Unit* u = m_unitDict[ fileName ];
	m_unitDict.remove( fileName );
	delete( u );
	u = 0;
    }

    m_unitDict.insert( fileName, unit );

    if( lock )
        m_mutex.unlock();

    KApplication::postEvent( m_cppSupport, new FileParsedEvent(fileName, unit->problems) );

    m_currentFile = QString::null;

    if( m_fileList->isEmpty() )
	m_isEmpty.wakeAll();

    return unit;
}

Unit* BackgroundParser::findUnit( const QString& fileName )
{
    QMap<QString, Unit*>::Iterator it = m_unitDict.find( fileName );
    return it != m_unitDict.end() ? *it : 0;
}

TranslationUnitAST* BackgroundParser::translationUnit( const QString& fileName )
{
    Unit* u = findUnit(fileName);
    if( u == 0 ){
	m_fileList->remove( fileName );
	u = parseFile( fileName, false );
    }

    return u->translationUnit;
}

QValueList<Problem> BackgroundParser::problems( const QString& fileName, bool readFromDisk, bool forceParse )
{
    Unit* u = findUnit(fileName);
    if( u == 0 || forceParse ){
	m_fileList->remove( fileName );
	u = parseFile( fileName, readFromDisk );
    }

    return u ? u->problems : QValueList<Problem>();
}

void BackgroundParser::close()
{
    QMutexLocker locker( &m_mutex );
    m_close = true;
    m_canParse.wakeAll();

    while (running())
        sleep(1);
}

bool BackgroundParser::filesInQueue()
{
    QMutexLocker locker( &m_mutex );

    return m_fileList->count() || !m_currentFile.isEmpty();
}

void BackgroundParser::updateParserConfiguration()
{
    QMutexLocker locker( &m_mutex );
    
    QString conf_file_name = m_cppSupport->specialHeaderName();
    m_driver->removeAllMacrosInFile( conf_file_name );
    m_driver->parseFile( conf_file_name, true );
}

void BackgroundParser::run()
{
    // (void) m_cppSupport->codeCompletion()->repository()->getEntriesInScope( QStringList(), false );

    while( !m_close ){

	while( m_fileList->isEmpty() ){
            m_canParse.wait();

            if( m_close )
                break;
        }

        if( m_close )
            break;

	QPair<QString, bool> entry = m_fileList->front();
        QString fileName = entry.first;
	bool readFromDisk = entry.second;
	m_currentFile = fileName;

	(void) parseFile( fileName, readFromDisk, true );
	m_fileList->pop_front();

	m_currentFile = QString::null;
    }

    kdDebug(9007) << "!!!!!!!!!!!!!!!!!! BG PARSER DESTROYED !!!!!!!!!!!!" << endl;

    QThread::exit();
}

// kate: space-indent on; indent-width 4; mixed-indent on;
