#include "valgrind_part.h"

#include <tqwhatsthis.h>
#include <tqregexp.h>
#include <tqfile.h>

#include <kiconloader.h>
#include <tdelocale.h>
#include <kdevgenericfactory.h>
#include <tdeaction.h>
#include <kprocess.h>
#include <tdemessagebox.h>
#include <tdefiledialog.h>
#include <kdebug.h>

#include "kdevcore.h"
#include "kdevmainwindow.h"
#include "kdevproject.h"
#include "kdevplugininfo.h"

#include "valgrind_widget.h"
#include "valgrind_dialog.h"
#include "valgrinditem.h"

typedef KDevGenericFactory<ValgrindPart> ValgrindFactory;
static const KDevPluginInfo pluginData("kdevvalgrind");
K_EXPORT_COMPONENT_FACTORY( libkdevvalgrind, ValgrindFactory( pluginData ) )

ValgrindPart::ValgrindPart( TQObject *parent, const char *name, const TQStringList& )
  : KDevPlugin( &pluginData, parent, name ? name : "ValgrindPart" )
{
  setInstance( ValgrindFactory::instance() );
  setXMLFile( "kdevpart_valgrind.rc" );

  proc = new KShellProcess();
  connect( proc, TQT_SIGNAL(receivedStdout( TDEProcess*, char*, int )),
           this, TQT_SLOT(receivedStdout( TDEProcess*, char*, int )) );
  connect( proc, TQT_SIGNAL(receivedStderr( TDEProcess*, char*, int )),
           this, TQT_SLOT(receivedStderr( TDEProcess*, char*, int )) );
  connect( proc, TQT_SIGNAL(processExited( TDEProcess* )),
           this, TQT_SLOT(processExited( TDEProcess* )) );
  connect( core(), TQT_SIGNAL(stopButtonClicked(KDevPlugin*)),
           this, TQT_SLOT(slotStopButtonClicked(KDevPlugin*)) );
  connect( core(), TQT_SIGNAL(projectOpened()),
           this, TQT_SLOT(projectOpened()) );

  m_widget = new ValgrindWidget( this );
  m_widget->setIcon( SmallIcon("fork") );
  m_widget->setCaption(i18n("Valgrind Output"));

  TQWhatsThis::add( m_widget, i18n( "<b>Valgrind</b><p>Shows the output of the valgrind. Valgrind detects<br>"
    "use of uninitialized memory<br>"
    "reading/writing memory after it has been free'd<br>"
    "reading/writing off the end of malloc'd blocks<br>"
    "reading/writing inappropriate areas on the stack<br>"
    "memory leaks -- where pointers to malloc'd blocks are lost forever<br>"
    "passing of uninitialised and/or unaddressable memory to system calls<br>"
    "mismatched use of malloc/new/new [] vs free/delete/delete []<br>"
    "some abuses of the POSIX pthread API." ) );

  TDEAction* action = new TDEAction( i18n("&Valgrind Memory Leak Check"), 0, this,
	       TQT_SLOT(slotExecValgrind()), actionCollection(), "tools_valgrind" );
  action->setToolTip(i18n("Valgrind memory leak check"));
  action->setWhatsThis(i18n("<b>Valgrind memory leak check</b><p>Runs Valgrind - a tool to help you find memory-management problems in your programs."));

  action = new TDEAction( i18n("P&rofile with KCachegrind"), 0, this,
	       TQT_SLOT(slotExecCalltree()), actionCollection(), "tools_calltree" );
  action->setToolTip(i18n("Profile with KCachegrind"));
  action->setWhatsThis(i18n("<b>Profile with KCachegrind</b><p>Runs your program in calltree and then displays profiler information in KCachegrind."));

  mainWindow()->embedOutputView( m_widget, "Valgrind", i18n("Valgrind memory leak check") );
}


ValgrindPart::~ValgrindPart()
{
  if ( m_widget )
    mainWindow()->removeView( m_widget );
  delete m_widget;
  delete proc;
}

void ValgrindPart::projectOpened()
{
  _lastExec.truncate( 0 );
}

void ValgrindPart::loadOutput()
{
  TQString fName = KFileDialog::getOpenFileName(TQString(), "*", 0, i18n("Open Valgrind Output"));
  if ( fName.isEmpty() )
    return;

  TQFile f( fName );
  if ( !f.open( IO_ReadOnly ) ) {
    KMessageBox::sorry( 0, i18n("Could not open valgrind output: %1").arg(fName) );
    return;
  }

  clear();
  getActiveFiles();

  TQTextStream stream( &f );
  while ( !stream.atEnd() ) {
    receivedString( stream.readLine() + "\n" );
  }
  f.close();
}

void ValgrindPart::getActiveFiles()
{
  activeFiles.clear();
  if ( project() ) {
    TQStringList projectFiles = project()->allFiles();
    TQString projectDirectory = project()->projectDirectory();
    KURL url;
    for ( TQStringList::Iterator it = projectFiles.begin(); it != projectFiles.end(); ++it ) {
      KURL url( projectDirectory + "/" + (*it) );
      url.cleanPath( true );
      activeFiles += url.path();
      kdDebug() << "set project file: " << url.path().latin1() << endl;
    }
  }
}

static void guessActiveItem( ValgrindItem& item, const TQStringList activeFiles )
{
  if ( activeFiles.isEmpty() && item.backtrace().isEmpty() )
    return;
  for ( ValgrindItem::BacktraceList::Iterator it = item.backtrace().begin(); it != item.backtrace().end(); ++it ) {
    // active: first line of backtrace that lies in project source file
    for ( TQStringList::ConstIterator it2 = activeFiles.begin(); it2 != activeFiles.end(); ++it2 ) {
      if ( (*it).url() == (*it2) ) {
        (*it).setHighlighted( true );
        return;
      }
    }
  }
}

void ValgrindPart::appendMessage( const TQString& message )
{
  if ( message.isEmpty() )
    return;

  ValgrindItem item( message );
  guessActiveItem( item, activeFiles );
  m_widget->addMessage( item );
}

void ValgrindPart::slotExecValgrind()
{
  ValgrindDialog* dlg = new ValgrindDialog(ValgrindDialog::Memcheck);
  if ( project() && _lastExec.isEmpty() ) {
    dlg->setExecutable( project()->mainProgram() );
  } else {
    dlg->setExecutable( _lastExec );
  }
  dlg->setParameters( _lastParams );
  dlg->setValExecutable( _lastValExec );
  dlg->setValParams( _lastValParams );
  kcInfo.runKc = false;
  _lastValExec = dlg->valExecutable();
  _lastValParams = dlg->valParams();
  if ( dlg->exec() == TQDialog::Accepted ) {
    runValgrind( dlg->executableName(), dlg->parameters(), dlg->valExecutable(), dlg->valParams() );
  }
}

void ValgrindPart::slotExecCalltree()
{
  ValgrindDialog* dlg = new ValgrindDialog(ValgrindDialog::Calltree);
  if ( project() && _lastExec.isEmpty() ) {
    dlg->setExecutable( project()->mainProgram() );
  } else {
    dlg->setExecutable( _lastExec );
  }
  dlg->setParameters( _lastParams );
  dlg->setCtExecutable( _lastCtExec );
  dlg->setKcExecutable( _lastKcExec );
  dlg->setCtParams( _lastCtParams );
  kcInfo.runKc = true;
  kcInfo.kcPath = dlg->kcExecutable();
//  kcInfo.kcWorkDir = KURL(dlg->executableName()).directory();
  if ( dlg->exec() == TQDialog::Accepted ) {
    runValgrind( dlg->executableName(), dlg->parameters(), dlg->ctExecutable(), dlg->ctParams() );
  }
  _lastKcExec = dlg->kcExecutable();
  _lastCtExec = dlg->ctExecutable();
  _lastCtParams = dlg->ctParams();
}

void ValgrindPart::slotKillValgrind()
{
  if ( proc )
    proc->kill();
}

void ValgrindPart::slotStopButtonClicked( KDevPlugin* which )
{
  if ( which != 0 && which != this )
    return;
  slotKillValgrind();
}

void ValgrindPart::clear()
{
  m_widget->clear();
  currentMessage = TQString();
  currentPid = -1;
  lastPiece = TQString();
}

void ValgrindPart::runValgrind( const TQString& exec, const TQString& params, const TQString& valExec, const TQString& valParams )
{
  if ( proc->isRunning() ) {
    KMessageBox::sorry( 0, i18n( "There is already an instance of valgrind running." ) );
    return;
    /// @todo - ask for forced kill
  }

  clear();

  getActiveFiles();

//  proc->setWorkingDirectory(KURL(exec).directory());
  proc->clearArguments();

  DomUtil::PairList run_envvars;
  if (project())
    run_envvars = project()->runEnvironmentVars();

  TQStringList envVarList;
  DomUtil::PairList::ConstIterator it;
  for (it = run_envvars.begin(); it != run_envvars.end(); ++it)
  {
    envVarList << TQString("%1=\"%2\" ").arg((*it).first).arg((*it).second);
  }

  *proc << envVarList.join("") << valExec << valParams << exec << params;
  proc->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
  mainWindow()->raiseView( m_widget );
  core()->running( this, true );

  _lastExec = exec;
  _lastParams = params;
}

void ValgrindPart::receivedStdout( TDEProcess*, char* /* msg */, int /* len */ )
{
  //kdDebug() << "got StdOut: " <<TQString::fromLocal8Bit( msg, len ) << endl;
}

void ValgrindPart::receivedStderr( TDEProcess*, char* msg, int len )
{
  receivedString( TQString::fromLocal8Bit( msg, len ) );
}

void ValgrindPart::receivedString( const TQString& str )
{
  TQString rmsg = lastPiece + str;
  TQStringList lines = TQStringList::split( "\n", rmsg );

//  kdDebug() << "got: " << TQString::fromLocal8Bit( msg, len ) << endl;

  if ( !rmsg.endsWith( "\n" ) ) {
    // the last message is trucated, we'll receive
    // the rest in the next call
    lastPiece = lines.back();
    lines.pop_back();
  } else {
    lastPiece = TQString();
  }
  appendMessages( lines );
}

void ValgrindPart::appendMessages( const TQStringList& lines )
{
  TQRegExp valRe( "==(\\d+)== (.*)" );

  for ( TQStringList::ConstIterator it = lines.begin(); it != lines.end(); ++it ) {
    if ( valRe.search( *it ) < 0 )
      continue;

    int cPid = valRe.cap( 1 ).toInt();

    if ( valRe.cap( 2 ).isEmpty() ) {
      appendMessage( currentMessage );
      currentMessage = TQString();
    } else if ( cPid != currentPid ) {
      appendMessage( currentMessage );
      currentMessage = *it;
      currentPid = cPid;
    } else {
      if ( !currentMessage.isEmpty() )
        currentMessage += "\n";
      currentMessage += *it;
    }
  }
}

void ValgrindPart::processExited( TDEProcess* p )
{
  if ( p == proc ) {
    appendMessage( currentMessage + lastPiece );
    currentMessage = TQString();
    lastPiece = TQString();
    core()->running( this, false );

    if (kcInfo.runKc)
    {
        TDEProcess *kcProc = new TDEProcess;
//        kcProc->setWorkingDirectory(kcInfo.kcWorkDir);
        *kcProc << kcInfo.kcPath;
        *kcProc << TQString("callgrind.out.%1").arg(p->pid());
        kcProc->start(TDEProcess::DontCare);
    }
  }
}

void ValgrindPart::restorePartialProjectSession( const TQDomElement* el )
{
  TQDomElement execElem = el->namedItem( "executable" ).toElement();
  _lastExec = execElem.attribute( "path", "" );
  _lastParams = execElem.attribute( "params", "" );

  TQDomElement valElem = el->namedItem( "valgrind" ).toElement();
  _lastValExec = valElem.attribute( "path", "" );
  _lastValParams = valElem.attribute( "params", "" );

  TQDomElement ctElem = el->namedItem( "calltree" ).toElement();
  _lastCtExec = ctElem.attribute( "path", "" );
  _lastCtParams = ctElem.attribute( "params", "" );

  TQDomElement kcElem = el->namedItem( "tdecachegrind" ).toElement();
  _lastKcExec = kcElem.attribute( "path", "" );
}

void ValgrindPart::savePartialProjectSession( TQDomElement* el )
{
  TQDomDocument domDoc = el->ownerDocument();
  if ( domDoc.isNull() )
    return;

  TQDomElement execElem = domDoc.createElement( "executable" );
  execElem.setAttribute( "path", _lastExec );
  execElem.setAttribute( "params", _lastParams );

  TQDomElement valElem = domDoc.createElement( "valgrind" );
  valElem.setAttribute( "path", _lastValExec );
  valElem.setAttribute( "params", _lastValParams );

  TQDomElement ctElem = domDoc.createElement( "calltree" );
  ctElem.setAttribute( "path", _lastCtExec );
  ctElem.setAttribute( "params", _lastCtParams );

  TQDomElement kcElem = domDoc.createElement( "tdecachegrind" );
  kcElem.setAttribute( "path", _lastKcExec );

  el->appendChild( execElem );
  el->appendChild( valElem );
  el->appendChild( ctElem );
  el->appendChild( kcElem );
}

#include "valgrind_part.moc"
