/* ****************************************************************************
  This file is part of KBabel

  Copyright (C) 2002-2003 by Marco Wegner <mail@marcowegner.de>

  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.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************************** */


// System include files
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
// Qt include files
#include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qregexp.h>
#include <qstring.h>
#include <qstringlist.h>
// KDE include files
#include <klocale.h>
#include <kmessagebox.h>
// project specific include files
#include "cvshandler.h"


CVSHandler::CVSHandler( const QString& poBaseDir, const QString& potBaseDir )
{
  setPOBaseDir( poBaseDir );
  setPOTBaseDir( potBaseDir );
  _autoUpdateTemplates = false;
}

void CVSHandler::setPOBaseDir( const QString& dir )
{
  // check if 'CVS/Entries' exists in the PO base directory
  if ( QFileInfo( dir + "/CVS/Entries" ).exists( ) ) {
    _isPORepository = true;
    _poBaseDir = dir;
  } else
    _isPORepository = false;
  emit signalIsPORepository( _isPORepository );
}

void CVSHandler::setPOTBaseDir( const QString& dir )
{
  // check if 'CVS/Entries' exists in the POT base directory
  if ( QFileInfo( dir + "/CVS/Entries" ).exists( ) ) {
    _isPOTRepository = true;
    _potBaseDir = dir;
  } else
    _isPOTRepository = false;
  emit signalIsPOTRepository( _isPOTRepository );
}

QString CVSHandler::fileStatus( const QString& filename )
{
  switch ( fstatus( filename ) ) {
    case NO_REPOSITORY:
      return i18n( "No repository" );
      break;
    case NOT_IN_CVS:
      return i18n( "Not in CVS" );
      break;
    case LOCALLY_ADDED:
      return i18n( "Locally added" );
      break;
    case LOCALLY_REMOVED:
      return i18n( "Locally removed" );
      break;
    case LOCALLY_MODIFIED:
      return i18n( "Locally modified" );
      break;
    case UP_TO_DATE:
      return i18n( "Up-to-date" );
      break;
    case CONFLICT:
      return i18n( "Conflict" );
      break;
    default:
      return i18n( "Unknown" );
      break;
  }
}

CVSHandler::FileStatus CVSHandler::fstatus( const QString& filename )
{
  // no valid repository
  if ( !_isPORepository )
    return NO_REPOSITORY;

  QString fn( filename );
  fn = fn.remove( QRegExp( "/$" ) );
    
  QFileInfo info( fn );

  // check if 'CVS/Entries' exists and can be read
  QFile entries( info.dir( true ).path( ) + "/CVS/Entries" );
  if ( !entries.open( IO_ReadOnly ) )
    return NOT_IN_CVS;  // we already know that it's a repository

  // a line in CVS/Entries has the following format:
  // [D]/NAME/REVISION/[CONFLICT+]TIMESTAMP/OPTIONS/TAGDATE
  QRegExp rx( QString( "^D?/%1/" ).arg( info.fileName( ) ) );

  QString temp;
  QStringList fields;
  QTextStream stream( &entries );

  bool isInRepository = false;
  while ( !( stream.atEnd( ) || isInRepository ) ) {
    temp = stream.readLine( );
    if ( temp.find( rx ) == 0 )
      isInRepository = true;
  }
  entries.close( );

  // no entry found
  if ( !isInRepository )
    return NOT_IN_CVS;

  fields = QStringList::split( '/', temp, true );
  // bool isDir = ( fields[0] == "D" );
  QString cvsname( fields[1] );
  QString revision( fields[2] );
  QString timestamp( fields[3] );
  // ignore the other fields for now

  if ( revision == "0" && timestamp == "dummy timestamp" )
    return LOCALLY_ADDED;
  if ( revision.startsWith( "-" ) && timestamp == "dummy timestamp" )
    return LOCALLY_REMOVED;

  // check for conflicts
  bool hasConflicts = false;
  if ( timestamp.find( '+' ) >= 0 ) {
    hasConflicts = true;
    timestamp.remove( QRegExp( ".*\\+" ) );
  }

  if ( hasConflicts )
    return CONFLICT;

  // calculate the UTC time from the file's last modified date  
  struct stat st;
  lstat( fn.local8Bit( ), &st );
  struct tm * tm_p = gmtime( &st.st_mtime );
  QString ftime = QString( asctime( tm_p ) );
  ftime.truncate( ftime.length( ) - 1 );
  if ( ftime != timestamp )
    return LOCALLY_MODIFIED;

  return UP_TO_DATE;
}

QString CVSHandler::cvsStatus( const QString& filename ) const
{
  return map[filename];
}

void CVSHandler::execCVSCommand(CVS::Command cmd, const QString& filename, bool templates)
{
  if ( !_isPORepository ) {
    // This message box should never be visible but who knows... ;-)
    KMessageBox::sorry( 0, i18n( "This is not a valid CVS repository. "
      "The CVS commands cannot be executed." ) );
    return;
  }

  QFileInfo info( filename );
  if ( !info.isDir( ) ) {
    execCVSCommand(cmd, QStringList( filename ), templates);
    return;
  }

  // it's a dir
  QString command( "cd " + filename + " && cvs " );
  switch ( cmd ) {
    case CVS::Update:
      command += "update -dP";
      break;
    case CVS::Commit:
      command += "commit -m @LOGMESSAGE@";
      checkToAdd( QStringList( filename ) );
      break;
    case CVS::Status:
      command += "status";
      break;
  }

  showDialog( cmd, QStringList( filename ), command );
}

void CVSHandler::execCVSCommand(CVS::Command cmd, const QStringList& files, bool templates)
{
  if ( !_isPORepository ) {
    // This message box should never be visible but who knows... ;-)
    KMessageBox::sorry( 0, i18n( "This is not a valid CVS repository. "
      "The CVS commands cannot be executed." ) );
    return;
  }

  QStringList::ConstIterator it;
  QString command("cd " + (templates ? _potBaseDir : _poBaseDir) + " && cvs ");
  switch ( cmd ) {
    case CVS::Update:
      command += "update -dP";
      break;
    case CVS::Commit:
      command += "commit -m @LOGMESSAGE@";
      checkToAdd( files );
      break;
    case CVS::Status:
      command += "status";
      break;
  }
    
  QRegExp rx;
  if (templates)
    rx.setPattern(_potBaseDir + "/?");
  else
    rx.setPattern(_poBaseDir + "/?");

  for ( it = files.begin( ); it != files.end( ); ++it ) {
    QString temp = *it;
    temp.remove(rx);
    command += " \'" + temp + "\'";
  }

  showDialog( cmd, files, command );
}

void CVSHandler::setAutoUpdateTemplates( bool update )
{
  _autoUpdateTemplates = update;
}

void CVSHandler::showDialog( CVS::Command cmd, const QStringList& files, const QString& commandLine )
{
  CVSDialog * dia = new CVSDialog( cmd, 0, "CVS DIALOG", true );
  dia->setFiles( files );
  dia->setCommandLine( commandLine );
  if ( cmd == CVS::Commit ) {
    dia->setAddCommand( _addCommand );
  }
  
  if ( dia->exec( ) == KDialog::Accepted ) {
    if ( cmd == CVS::Status )
      processStatusOutput( dia->statusOutput( ) );
  }

  delete dia;

  // file status display update necessary in Catalog Manager
  if ( cmd == CVS::Commit )
    emit signalFilesCommitted( files );
}

void CVSHandler::checkToAdd( const QStringList& files )
{
  if ( files.isEmpty( ) )
    return;
    
  QStringList toBeAdded;
    
  QStringList::ConstIterator it;
  for ( it = files.begin( ); it != files.end( ); ++it ) {
    // check for every entry if it needs to be added
    if ( fstatus( *it ) == NOT_IN_CVS ) {
      QFileInfo info( *it );
      QString temp;    // will hold the dir path
      if ( info.isDir( ) ) {
        toBeAdded << *it;
        temp = *it;
      } else {
        toBeAdded << *it;
        temp = QFileInfo( *it ).dirPath( true );
      }
      // check recursivlely if parent dirs have to be added as well
      while ( fstatus( temp ) == NOT_IN_CVS && toBeAdded.findIndex( temp ) == -1 ) {
        toBeAdded << temp;
        temp = QFileInfo( temp ).dirPath( true );
      }
    }
  }

  // remove an old command
  _addCommand.setLength( 0 );
    
  // make sure the diectories are added before the files  
  toBeAdded.sort( );

  // create a command line for adding the files and dirs
  for ( it = toBeAdded.begin( ); it != toBeAdded.end( ); ++it ) {
    QFileInfo info( *it );
    _addCommand += "cd " + info.dirPath( true ) + " && cvs add " + info.fileName( ) + "; ";
  }
}

void CVSHandler::processStatusOutput( const QString& status )
{
  if ( !_isPORepository )
    return;
    
  // at first we need to extract the name of the base directory on the server
  QFile f( _poBaseDir + "/CVS/Root" );
  if ( !f.open( IO_ReadOnly ) )
    return;
    
  QTextStream stream( &f );
  // extract the string after the last colon in the first line
  QString basedir = stream.readLine( ).section( ':', -1 );
  
  f.close( );

  // divide the complete status output in little chunks for every file  
  QStringList entries = QStringList::split( QRegExp( "={67,67}" ), status );
  QStringList::Iterator it;
  for ( it = entries.begin( ); it != entries.end( ); ++it ) {
    QString entr = *it;
    // translate the filename from repository to local
    QRegExp rx( basedir + ".*,v" );
    int pos = entr.find( rx );
    QString file = _poBaseDir + entr.mid( pos + basedir.length( ), 
      rx.matchedLength( ) - basedir.length( ) - 2 );
    
    entr = "<qt>" + entr + "</qt>";
    
    // TODO: do some markup
    
    map.replace( file, entr );
  }
}

#include "cvshandler.moc"
