/***************************************************************************
    smb4ksearch  -  This class searches for custom search strings.
                             -------------------
    begin                : So Apr 27 2008
    copyright            : (C) 2008 by Alexander Reinholdt
    email                : dustpuppy@users.berlios.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                                                    *
 ***************************************************************************/

// Qt includes
#include <QHostAddress>
#include <QDesktopWidget>

// KDE includes
#include <kshell.h>
#include <kdebug.h>
#include <kapplication.h>

// application specific includes
#include <smb4ksearch.h>
#include <smb4kdefs.h>
#include <smb4ksambaoptionshandler.h>
#include <smb4kglobal.h>
#include <smb4kauthinfo.h>
#include <smb4kworkgroup.h>
#include <smb4ksettings.h>
#include <smb4kcoremessage.h>
#include <smb4kipaddressscanner.h>
#include <smb4khomesshareshandler.h>
#include <smb4kwalletmanager.h>

using namespace Smb4KGlobal;

Smb4KSearch::Smb4KSearch( QObject *parent ) : QObject( parent )
{
  m_timer_id = -1;
  m_working = false;
  m_aborted = false;
  m_retry = false;
  m_state = Idle;
  m_process_error = (QProcess::ProcessError)(-1);
  m_master_browser = Smb4KHost();
  m_workgroup_string = QString();

  m_proc = new KProcess( this );

  connect( m_proc, SIGNAL( readyReadStandardError() ),
           this,   SLOT( slotReadyReadStandardError() ) );

  connect( m_proc, SIGNAL( readyReadStandardOutput() ),
           this,   SLOT( slotReadyReadStandardOutput() ) );

  connect( m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ),
           this,   SLOT( slotProcessFinished( int, QProcess::ExitStatus ) ) );

  connect( m_proc, SIGNAL( error( QProcess::ProcessError ) ),
           this,   SLOT( slotProcessError( QProcess::ProcessError ) ) );

  connect( Smb4KIPAddressScanner::self(), SIGNAL( ipAddress( Smb4KHost * ) ),
           this,                          SLOT( slotReceiveIPAddress( Smb4KHost * ) ) );
}


Smb4KSearch::~Smb4KSearch()
{
}


void Smb4KSearch::search( const QString &string )
{
  m_list.append( string );

  if ( m_timer_id == -1 )
  {
    m_timer_id = startTimer( TIMER_INTERVAL );
  }
  else
  {
    // Do nothing
  }
}


void Smb4KSearch::abort()
{
  if ( !m_list.isEmpty() )
  {
    m_list.clear();
  }

  if ( m_proc->state() == QProcess::Running )
  {
    m_proc->kill();
  }

  m_aborted = true;
}


void Smb4KSearch::timerEvent( QTimerEvent */*e*/ )
{
  if ( !m_list.isEmpty() && !m_working )
  {
    // We have started working.
    m_working = true;

    // The command.
    QString command;

    // Check if this is an IP address.
    QHostAddress address( m_list.first() );

    if ( address.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol )
    {
      // This is not an IP address. Use the smbtree program.
      command.append( "smbtree -d2 " );
      command.append( Smb4KSambaOptionsHandler::self()->smbtreeOptions() );

      // Include the authentication for the current master browser.
      Smb4KAuthInfo authInfo;

      if ( m_master_browser.isEmpty() )
      {
        Smb4KWorkgroup *workgroup = findWorkgroup( Smb4KSettings::domainName() );

        if ( workgroup )
        {
          authInfo.setWorkgroupName( workgroup->name() );
          authInfo.setUNC( "//"+workgroup->masterBrowserName() );

          Smb4KWalletManager::self()->readAuthInfo( &authInfo );
        }
        else
        {
          // Do nothing
        }
      }
      else
      {
        authInfo.setWorkgroupName( m_master_browser.workgroup() );
        authInfo.setUNC( "//"+m_master_browser.name() );

        Smb4KWalletManager::self()->readAuthInfo( &authInfo );
      }


      if ( !authInfo.login().isEmpty() )
      {
        command.append( QString( " -U %1% " ).arg( KShell::quoteArg( authInfo.login() ) ) );

        if ( !authInfo.password().isEmpty() )
        {
          m_proc->setEnv( "PASSWD", authInfo.password() );
        }
        else
        {
          // Do nothing
        }
      }
      else
      {
        command.append( " -U guest% " );
      }

      m_state = FullSearch;
    }
    else
    {
      // This is an IP address. Use the nmblookup program.
      command.append( "nmblookup " );
      command.append( Smb4KSambaOptionsHandler::self()->nmblookupOptions() );

      if ( !Smb4KSambaOptionsHandler::self()->winsServer().isEmpty() )
      {
        command.append( " -R -U "+KShell::quoteArg( Smb4KSambaOptionsHandler::self()->winsServer() ) );
        command.append( " "+KShell::quoteArg( m_list.first() ) );
        command.append( " -A | grep '<00>' | sed -e 's/<00>.*//'" );
      }
      else
      {
        command.append( " "+KShell::quoteArg( m_list.first() ) );
        command.append( " -A | grep '<00>' | sed -e 's/<00>.*//'" );
      }

      m_state = IPSearch;
    }

    m_master_browser = Smb4KHost();
    m_workgroup_string.clear();

    m_proc->setShellCommand( command );
    m_proc->setOutputChannelMode( KProcess::SeparateChannels );

    emit state( SEARCH_START );

    QApplication::setOverrideCursor( Qt::WaitCursor );
    m_proc->start();
  }
  else
  {
    if ( m_list.isEmpty() )
    {
      killTimer( m_timer_id );
      m_timer_id = -1;
    }
    else
    {
      // Do nothing
    }
  }
}


/////////////////////////////////////////////////////////////////////////////
// SLOT IMPLEMENTATIONS
/////////////////////////////////////////////////////////////////////////////

void Smb4KSearch::slotReadyReadStandardError()
{
  QString stderr_output = QString::fromLocal8Bit( m_proc->readAllStandardError(), -1 );

  // Process authentication errors:
  if ( (stderr_output.contains( "The username or password was not correct." ) ||
       stderr_output.contains( "NT_STATUS_ACCOUNT_DISABLED" ) /* AD error */ ||
       stderr_output.contains( "NT_STATUS_ACCESS_DENIED" ) ||
       stderr_output.contains( "NT_STATUS_LOGON_FAILURE" )) &&
       !m_master_browser.isEmpty() )
  {
    Smb4KAuthInfo authInfo( &m_master_browser );

    if ( Smb4KWalletManager::self()->showPasswordDialog( &authInfo ) )
    {
      m_retry = true;
    }
    else
    {
      // Do nothing
    }
  }
  else
  {
    Smb4KCoreMessage::error( ERROR_SEARCHING, QString(), stderr_output );
  }
}


void Smb4KSearch::slotReadyReadStandardOutput()
{
  QString stdout_output = QString::fromLocal8Bit( m_proc->readAllStandardOutput(), -1 );
  QStringList output = stdout_output.split( "\n", QString::SkipEmptyParts, Qt::CaseSensitive );

  switch ( m_state )
  {
    case FullSearch:
    {
      // Get the local master browser in case an error occurs and we
      // have to do another search.
      if ( stdout_output.contains( "Got a positive name query response from" ) &&
           m_master_browser.isEmpty() )
      {
        QString tmp = stdout_output.section( "response from", 1, 1 ).section( "(", 0, 0 ).trimmed();

        QHostAddress address( tmp );

        if ( address.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol )
        {
          // This is the local host. So, the workgroup is the
          // one the user is in.
          m_master_browser = Smb4KHost( tmp );
          m_master_browser.setWorkgroup( Smb4KSettings::domainName() );
        }
        else
        {
          // Find the master browser in the host list by searching
          // for the IP address.
          for ( int i = 0; i < hostsList()->size(); ++i )
          {
            QHostAddress host_ip( hostsList()->at( i )->ip() );

            if ( host_ip == address )
            {
              m_master_browser = Smb4KHost( *hostsList()->at( i ) );

              break;
            }
            else
            {
              continue;
            }
          }
        }
      }
      else
      {
        // Do nothing
      }

      // Process the search results.
      QString workgroup;

      for ( int i = 0; i < output.size(); ++i )
      {
        if ( !output.at( i ).contains( "added interface", Qt::CaseInsensitive ) &&
             !output.at( i ).contains( "tdb(", Qt::CaseInsensitive ) &&
             !output.at( i ).contains( "Got a positive", Qt::CaseInsensitive ) &&
             !output.at( i ).contains( "error connecting", Qt::CaseInsensitive ) &&
             !output.at( i ).isEmpty() )
        {
          if ( !output.at( i ).contains( "\\" ) &&
               !output.at( i ).trimmed().isEmpty() )
          {
            m_workgroup_string = output.at( i ).trimmed();

            continue;
          }
          else if ( output.at( i ).count( "\\" ) == 2 )
          {
            // We definetly need the host, so we will check for
            // the compliance with the search string only before
            // the ipAddress() signal is to be emitted.
            QString host = output.at( i ).trimmed().section( "\t", 0, 0 ).trimmed().remove( 0, 2 );
            QString comment = output.at( i ).trimmed().section( "\t", 1, -1 ).trimmed();

            bool known = false;
            Smb4KHost hostItem;
            Smb4KHost *tempHost = findHost( host, m_workgroup_string );

            if ( tempHost )
            {
              hostItem = Smb4KHost( *tempHost );
              hostItem.setComment( comment );
              known = true;
            }
            else
            {
              hostItem.setWorkgroup( m_workgroup_string );
              hostItem.setName( host );
              hostItem.setComment( comment );
            }

            if ( !hostItem.ip().isEmpty() )
            {
              if ( host.contains( m_list.first(), Qt::CaseInsensitive ) )
              {
                emit result( &hostItem, known );
              }
              else
              {
                // Do nothing
              }
            }
            else
            {
              Smb4KIPAddressScanner::self()->getIPAddress( &hostItem );
            }

            m_hosts_list.append( HostEntry( hostItem, known ) );

            continue;
          }
          else if ( output.at( i ).count( "\\" ) == 3 )
          {
            QString unc = output.at( i ).trimmed().section( "\t", 0, 0 ).trimmed().replace( "\\", "/" );

            if ( unc.contains( m_list.first(), Qt::CaseInsensitive ) )
            {
              QString comment = output.at( i ).trimmed().section( "\t", 1, -1 ).trimmed();

              bool mounted = false;
              Smb4KShare shareItem;
              Smb4KShare *tempShare = NULL;

              QList<Smb4KShare *> shares = findShareByUNC( unc );

              for ( int j = 0; j < shares.size(); ++j )
              {
                tempShare = shares.at( j );

                if ( tempShare->isForeign() )
                {
                  continue;
                }
                else
                {
                  break;
                }
              }

              if ( tempShare && (!tempShare->isForeign() || Smb4KSettings::showAllShares()) )
              {
                shareItem = Smb4KShare( *tempShare );
                shareItem.setUNC( unc );
                shareItem.setComment( comment );
                mounted = true;
              }
              else
              {
                shareItem.setUNC( unc );
                shareItem.setComment( comment );
                shareItem.setWorkgroup( m_workgroup_string );
              }

              // The IP address should already be found. Include it,
              // if this is necessary.
              if ( shareItem.hostIP().isEmpty() )
              {
                for ( int j = 0; j < m_hosts_list.size(); ++j )
                {
                  if ( QString::compare( m_hosts_list.at( j ).first.name(), shareItem.host(), Qt::CaseInsensitive ) == 0 &&
                       (!shareItem.workgroup().isEmpty() &&
                       QString::compare( m_hosts_list.at( j ).first.workgroup(), shareItem.workgroup(), Qt::CaseInsensitive ) == 0) )
                  {
                    shareItem.setHostIP( m_hosts_list.at( j ).first.ip() );

                    break;
                  }
                  else
                  {
                    continue;
                  }
                }
              }
              else
              {
                // Do nothing
              }

              // In case this is a 'homes' share, set also the user names.
              if ( QString::compare( shareItem.name(), "homes" ) == 0 )
              {
                Smb4KHomesSharesHandler::self()->setHomesUsers( &shareItem );
              }
              else
              {
                // Do nothing
              }

              emit result( &shareItem, mounted );

              continue;
            }
            else
            {
              continue;
            }
          }
          else
          {
            continue;
          }
        }
        else
        {
          continue;
        }
      }

      break;
    }
    case IPSearch:
    {
      if ( !output.isEmpty() )
      {
        Smb4KHost hostItem( output.first().trimmed() );
        hostItem.setWorkgroup( output.last().trimmed() );
        hostItem.setIP( m_list.first() );

        bool known = false;

        if ( findHost( output.first().trimmed(), output.last().trimmed() ) )
        {
          known = true;
        }
        else
        {
          // Do nothing
        }

        emit result( &hostItem, known );
      }
      else
      {
        // Do nothing
      }

      break;
    }
    default:
    {
      break;
    }
  }
}


void Smb4KSearch::slotReceiveIPAddress( Smb4KHost *host )
{
  if ( host && !m_hosts_list.isEmpty() )
  {
    for ( int i = 0; i < m_hosts_list.size(); ++i )
    {
      if ( QString::compare( host->name(), m_hosts_list.at( i ).first.name(), Qt::CaseInsensitive ) == 0 &&
           QString::compare( host->workgroup(), m_hosts_list.at( i ).first.workgroup(), Qt::CaseInsensitive ) == 0 )
      {
        Smb4KHost hostItem = m_hosts_list.at( i ).first;
        hostItem.setIP( host->ip() );

        if ( hostItem.name().contains( m_list.first(), Qt::CaseInsensitive ) )
        {
          emit result( &hostItem, m_hosts_list.at( i ).second );
        }
        else
        {
          // Do nothing
        }

        // Do not remove the host item here, because we need it
        // to find the IP address for the shares.

        break;
      }
      else
      {
        continue;
      }
    }
  }
  else
  {
    // Do nothing
  }
}


void Smb4KSearch::slotProcessFinished( int /*exitCode*/, QProcess::ExitStatus exitStatus )
{
  if ( exitStatus == QProcess::CrashExit && !m_aborted )
  {
    if ( m_process_error != -1 )
    {
      Smb4KCoreMessage::processError( ERROR_PROCESS_ERROR, m_process_error );
    }
    else
    {
      Smb4KCoreMessage::processError( ERROR_PROCESS_EXIT, m_process_error );
    }
  }
  else
  {
    // Do nothing
  }

  if ( !m_retry && !m_aborted )
  {
    m_list.removeFirst();
  }
  else
  {
    // Do nothing
  }

  m_proc->clearProgram();
  QApplication::restoreOverrideCursor();

  m_hosts_list.clear();
  m_state = Idle;
  m_aborted = false;
  m_retry = false;
  m_working = false;

  emit state( SEARCH_STOP );
  emit finished();
}


void Smb4KSearch::slotProcessError( QProcess::ProcessError errorCode )
{
  m_process_error = errorCode;
}

#include "smb4ksearch.moc"
