/*

 ark -- archiver for the KDE project

 Copyright (C)

 2002: Helio Chissini de Castro <helio@conectiva.com.br>
 2001: Corel Corporation (author: Michael Jarrett, michaelj@corel.com)
 1999-2000: Corel Corporation (author: Emily Ezust, emilye@corel.com)
 1999: Francois-Xavier Duranceau duranceau@kde.org
 1997-1999: Rob Palmbos palm9744@kettering.edu

 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

// C includes
#include <stdlib.h>
#include <time.h>

// QT includes
#include <tqapplication.h>
#include <tqfile.h>
#include <tqtextcodec.h>

// KDE includes
#include <kdebug.h>
#include <tdemessagebox.h>
#include <kmimetype.h>
#include <tdelocale.h>
#include <kpassdlg.h>
#include <tdeprocess.h>
#include <tdestandarddirs.h>

// ark includes
#include "arch.h"
#include "arkwidget.h"
#include "arkutils.h"
#include "filelistview.h"

// the archive types
#include "tar.h"
#include "zip.h"
#include "lha.h"
#include "compressedfile.h"
#include "zoo.h"
#include "rar.h"
#include "ar.h"
#include "sevenzip.h"
#include "ace.h"
#include "arj.h"

Arch::ArchColumns::ArchColumns( int col, TQRegExp reg, int length, bool opt )
  : colRef( col ), pattern( reg ), maxLength( length ), optional( opt )
{
}

Arch::Arch( ArkWidget *gui, const TQString &filename )
  : m_filename( filename ), m_buffer( "" ), m_gui( gui ),
    m_bReadOnly( false ), m_bNotifyWhenDeleteFails( true ),
    m_header_removed( false ), m_finished( false ),
    m_numCols( 0 ), m_dateCol( -1 ), m_fixYear( -1 ), m_fixMonth( -1 ),
    m_fixDay( -1 ), m_fixTime( -1 ), m_repairYear( -1 ), m_repairMonth( -1 ),
    m_repairTime( -1 ), m_currentProcess( 0 )
{
  m_archCols.setAutoDelete( true ); // To check: it still leaky here???
}

Arch::~Arch()
{
    if ( m_currentProcess )
        m_currentProcess->kill();
}

//Check if a compress utility exists
void Arch::verifyCompressUtilityIsAvailable( const TQString &utility )
{
  // see if the utility is in the PATH of the user.
  TQString cmd = TDEGlobal::dirs()->findExe( utility );
  m_bArchUtilityIsAvailable = !cmd.isEmpty();
}

//Check if a utility can uncompress files
void Arch::verifyUncompressUtilityIsAvailable( const TQString &utility )
{
  // see if the utility is in the PATH of the user.
  TQString cmd = TDEGlobal::dirs()->findExe( utility );
  m_bUnarchUtilityIsAvailable = !cmd.isEmpty();
}

void Arch::slotOpenExited( TDEProcess* _kp )
{
 bool success = ( _kp->normalExit() && ( _kp->exitStatus() == 0 ) );

  if( !success )
  {
    if ( passwordRequired() )
    {
        TQString msg;
        if ( !m_password.isEmpty() )
            msg = i18n("The password was incorrect. ");
        if (KPasswordDialog::getPassword( m_password, msg+i18n("You must enter a password to open the file:") ) == KPasswordDialog::Accepted )
        {
            delete _kp;
            _kp = m_currentProcess = 0;
            clearShellOutput();
            open(); // try to open the file again with a password
            return;
        }
        m_password = "";
        emit sigOpen( this, false, TQString(), 0 );
        delete _kp;
        _kp = m_currentProcess = 0;
        return;
    }
  }

  int exitStatus = 100; // arbitrary bad exit status

  if ( _kp->normalExit() )
    exitStatus = _kp->exitStatus();

  if ( exitStatus == 1 )
  {
    exitStatus = 0;    // because 1 means empty archive - not an error.
                       // Is this a safe assumption?
  }

  if ( !exitStatus )
    emit sigOpen( this, true, m_filename,
                  Arch::Extract | Arch::Delete | Arch::Add | Arch::View );
  else
    emit sigOpen( this, false, TQString(), 0 );

  delete _kp;
  _kp = m_currentProcess = 0;
}

void Arch::slotDeleteExited( TDEProcess *_kp )
{
  bool success = ( _kp->normalExit() && ( _kp->exitStatus() == 0 ) );

  if ( !success )
  {
    TQApplication::restoreOverrideCursor();

    TQString msg = i18n( "The deletion operation failed." );

    if ( !getLastShellOutput().isNull() )
    {
      TQStringList list = TQStringList::split( "\n", getLastShellOutput() );
      KMessageBox::errorList( m_gui, msg, list );
      clearShellOutput();
    }
    else
    {
      KMessageBox::error( m_gui, msg );
    }
  }

  emit sigDelete( success );
  delete _kp;
  _kp = m_currentProcess = 0;
}

void Arch::slotExtractExited( TDEProcess *_kp )
{
  bool success = ( _kp->normalExit() && ( _kp->exitStatus() == 0 ) );

  if( !success )
  {
    if ( passwordRequired() )
    {
        TQString msg;
        if ( !m_password.isEmpty() )
            msg = i18n("The password was incorrect. ");
        if (KPasswordDialog::getPassword( m_password, msg+i18n("You must enter a password to extract the file:") ) == KPasswordDialog::Accepted )
        {
            delete _kp;
            _kp = m_currentProcess = 0;
            clearShellOutput();
            unarchFileInternal(); // try to extract the file again with a password
            return;
        }
        m_password = "";
        emit sigExtract( false );
        delete _kp;
        _kp = m_currentProcess = 0;
        return;
    }
    else if ( m_password.isEmpty() || _kp->exitStatus() > 1 )
    {
        TQApplication::restoreOverrideCursor();

        TQString msg = i18n( "The extraction operation failed." );

        if ( !getLastShellOutput().isNull() )
        {
            //getLastShellOutput() is a TQString. errorList is expecting TQStringLists to show in multiple lines
            TQStringList list = TQStringList::split( "\n", getLastShellOutput() );
            KMessageBox::errorList( m_gui, msg, list );
            clearShellOutput();
        }
        else
        {
            KMessageBox::error( m_gui, msg );
        }
    }
  }
  m_password = "";
  delete _kp;
  _kp = m_currentProcess = 0;
  emit sigExtract( success );
}

void Arch::unarchFile( TQStringList *fileList, const TQString & destDir,
                         bool viewFriendly )
{
    m_fileList = fileList;
    m_destDir = destDir;
    m_viewFriendly = viewFriendly;
    unarchFileInternal();
}

void Arch::slotAddExited( TDEProcess *_kp )
{
  bool success = ( _kp->normalExit() && ( _kp->exitStatus() == 0 ) );

  if( !success )
  {
    TQApplication::restoreOverrideCursor();

    TQString msg = i18n( "The addition operation failed." );

    if ( !getLastShellOutput().isNull() )
    {
      TQStringList list = TQStringList::split( "\n", getLastShellOutput() );
      KMessageBox::errorList( m_gui, msg, list );
      clearShellOutput();
    }
    else
    {
      KMessageBox::error( m_gui, msg );
    }
  }

  emit sigAdd( success );
  delete _kp;
  _kp = m_currentProcess = 0;
}

void Arch::slotReceivedOutput( TDEProcess*, char* data, int length )
{
  char c = data[ length ];
  data[ length ] = '\0';

  appendShellOutputData( data );
  data[ length ] = c;
}


void Arch::slotReceivedTOC( TDEProcess*, char* data, int length )
{
  char c = data[ length ];
  data[ length ] = '\0';

  appendShellOutputData( data );

  int lfChar, startChar = 0;

  while ( !m_finished )
  {
    for ( lfChar = startChar; data[ lfChar ] != '\n' && lfChar < length;
         lfChar++ );

    if ( data[ lfChar ] != '\n')
      break; // We are done all the complete lines

    data[ lfChar ] = '\0';

    m_buffer.append( data + startChar );

    data[ lfChar ] = '\n';
    startChar = lfChar + 1;

    if ( m_headerString.isEmpty() )
    {
      processLine( m_buffer );
    }
    else if ( m_buffer.find( m_headerString.data() ) == -1 )
    {
      if ( m_header_removed && !m_finished )
      {
        if ( !processLine( m_buffer ) )
        {
          // Have faith - maybe it wasn't a header?
          m_header_removed = false;
          m_error = true;
        }
      }
    }
    else if ( !m_header_removed )
    {
      m_header_removed = true;
    }
    else
    {
      m_finished = true;
    }

    m_buffer = "";
  }

  if ( !m_finished )
    m_buffer.append( data + startChar);	// Append what's left of the buffer

  data[ length ] = c;
}

bool Arch::processLine( const TQCString &line )
{
  TQString columns[ 11 ];
  unsigned int pos = 0;
  int strpos, len;

  TQTextCodec::setCodecForCStrings(TQTextCodec::codecForLocale());
  TQTextCodec *codec = TQTextCodec::codecForLocale();
  TQString tqunicode_line = codec->toUnicode( line );

  // Go through our columns, try to pick out data, return silently on failure
  for ( TQPtrListIterator <ArchColumns>col( m_archCols ); col.current(); ++col )
  {
    ArchColumns *curCol = *col;

    strpos = curCol->pattern.search( tqunicode_line, pos );
    len = curCol->pattern.matchedLength();

    if ( ( strpos == -1 ) || ( len > curCol->maxLength ) )
    {
      if ( curCol->optional )
        continue; // More?
      else
      {
        kdDebug(1601) << "processLine failed to match critical column" << endl;
        return false;
      }
    }

    pos = strpos + len;

    columns[curCol->colRef] = tqunicode_line.mid(strpos, len).utf8();
  }


  if ( m_dateCol >= 0 )
  {
    TQString year = ( m_repairYear >= 0 ) ?
                   ArkUtils::fixYear( columns[ m_repairYear ].ascii())
                   : columns[ m_fixYear ];
    TQString month = ( m_repairMonth >= 0 ) ?
                   TQString( "%1" )
                   .arg( ArkUtils::getMonth( columns[ m_repairMonth ].ascii() ) )
                   : columns[ m_fixMonth ];
    TQString timestamp = TQString::fromLatin1( "%1-%2-%3 %4" )
                        .arg( year )
                        .arg( month )
                        .arg( columns[ m_fixDay ] )
                        .arg( columns[ m_fixTime ] );

    columns[ m_dateCol ] = timestamp;
  }

  TQStringList list;

  for ( int i = 0; i < m_numCols; ++i )
  {
    list.append( columns[ i ] );
  }

  m_gui->fileList()->addItem( list ); // send the entry to the GUI

  return true;
}

void Arch::test()
{
    emit sigTest(false);
    KMessageBox::information(0, i18n("Not implemented."));
}

void Arch::slotTestExited( TDEProcess *_kp )
{
  bool success = ( _kp->normalExit() && ( _kp->exitStatus() == 0 ) );

  if( !success )
  {
    if ( passwordRequired() )
    {
        TQString msg;
        if ( !m_password.isEmpty() )
            msg = i18n("The password was incorrect. ");
        if (KPasswordDialog::getPassword( m_password, msg+i18n("You must enter a password to extract the file:") ) == KPasswordDialog::Accepted )
        {
            delete _kp;
            _kp = m_currentProcess = 0;
            clearShellOutput();
            test(); // try to test the archive again with a password
            return;
        }
        m_password = "";
        emit sigTest( false );
        delete _kp;
        _kp = m_currentProcess = 0;
        return;
    }
    else if ( m_password.isEmpty() || _kp->exitStatus() > 1 )
    {
        TQApplication::restoreOverrideCursor();

        TQString msg = i18n( "The test operation failed." );

        if ( !getLastShellOutput().isNull() )
        {
            //getLastShellOutput() is a TQString. errorList is expecting TQStringLists to show in multiple lines
            TQStringList list = TQStringList::split( "\n", getLastShellOutput() );
            KMessageBox::errorList( m_gui, msg, list );
            clearShellOutput();
        }
        else
        {
            KMessageBox::error( m_gui, msg );
        }
    }
  }
  delete _kp;
  _kp = m_currentProcess = 0;
  emit sigTest( success );
}

Arch *Arch::archFactory( ArchType aType,
                         ArkWidget *parent, const TQString &filename,
                         const TQString &openAsMimeType )
{
  switch( aType )
  {
    case TAR_FORMAT:
      return new TarArch( parent, filename, openAsMimeType );

    case ZIP_FORMAT:
      return new ZipArch( parent, filename );

    case LHA_FORMAT:
      return new LhaArch( parent, filename );

    case COMPRESSED_FORMAT:
      return new CompressedFile( parent, filename, openAsMimeType );

    case ZOO_FORMAT:
      return new ZooArch( parent, filename );

    case RAR_FORMAT:
    {
      RarArch *rarArch = new RarArch( parent, filename );

      if ( rarArch->unarchUtilityIsAvailable() )
        return rarArch;

      const TQString sevenZipProgram = SevenZipArch::findRarSupportProgram();
      if ( !sevenZipProgram.isEmpty() )
      {
        delete rarArch;
        SevenZipArch *sevenZipArch =
          new SevenZipArch( parent, filename, sevenZipProgram );
        sevenZipArch->setReadOnly( true );
        return sevenZipArch;
      }

      return rarArch;
    }

    case AA_FORMAT:
      return new ArArch( parent, filename );

    case SEVENZIP_FORMAT:
      return new SevenZipArch( parent, filename );

    case ACE_FORMAT:
      return new AceArch( parent, filename );

    case ARJ_FORMAT:
      return new ArjArch( parent, filename );

    case UNKNOWN_FORMAT:
    default:
      return 0;
  }
}
#include "arch.moc"
