/*
    Copyright (c) 2001 Dawit Alemayehu <adawit@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License (LGPL) as published by the Free Software Foundation;
    either version 2 of the License, or (at your option) any later
    version.

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

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/

#include <sys/utsname.h>

#include <qregexp.h>

#include <krun.h>
#include <kdebug.h>
#include <kaction.h>
#include <klocale.h>
#include <kglobal.h>
#include <ktrader.h>
#include <kconfig.h>
#include <kio/job.h>
#include <kservice.h>
#include <kinstance.h>
#include <kpopupmenu.h>
#include <dcopclient.h>
#include <khtml_part.h>
#include <kgenericfactory.h>
#include <kprotocolmanager.h>

#include "uachangerplugin.h"

#define UA_PTOS(x) (*it)->property(x).toString()
#define QFL(x) QString::fromLatin1(x)

typedef KGenericFactory<UAChangerPlugin> UAChangerPluginFactory;
K_EXPORT_COMPONENT_FACTORY( libuachangerplugin,
	                    UAChangerPluginFactory( "uachangerplugin" ) );

UAChangerPlugin::UAChangerPlugin( QObject* parent, const char* name,
                                  const QStringList & )
                :KParts::Plugin( parent, name )
{
  m_pUAMenu = new KActionMenu( i18n("Change Browser &Identification"), "agent",
                               actionCollection(), "changeuseragent" );
  m_pUAMenu->setDelayed( false );
  connect( m_pUAMenu->popupMenu(), SIGNAL( aboutToShow() ),
           this, SLOT( slotAboutToShow() ) );

  m_bSettingsLoaded = false;
  m_part = 0L;
  m_config = 0L;
  m_pUAMenu->setEnabled ( false );

  if ( parent && parent->inherits( "KHTMLPart" ) )
  {
    m_part = static_cast<KHTMLPart*>(parent);
    connect( m_part, SIGNAL(started(KIO::Job*)), this,
             SLOT(slotStarted(KIO::Job*)) );
  }
}

UAChangerPlugin::~UAChangerPlugin()
{
  saveSettings();
  slotReloadDescriptions();
}

void UAChangerPlugin::slotReloadDescriptions()
{
  delete m_config;
  m_config = 0L;
}

void UAChangerPlugin::parseDescFiles()
{
  KTrader::OfferList list = KTrader::self()->query("UserAgentStrings");
  if ( list.count() == 0 )
    return;

  m_mapAlias.clear();
  m_lstAlias.clear();
  m_lstIdentity.clear();

  KTrader::OfferList::ConstIterator it = list.begin();
  for ( ; it != list.end(); ++it )
  {
    QString tmp = UA_PTOS("X-KDE-UA-FULL");
    if ( (*it)->property("X-KDE-UA-DYNAMIC-ENTRY").toBool() )
    {
      int pos;
      struct utsname utsn;
      uname( &utsn );

      if ( (pos=tmp.find("appSysName")) != -1 )
        tmp.replace( pos, 10, QString(utsn.sysname) );
      if ( (pos=tmp.find("appSysRelease")) != -1 )
        tmp.replace( pos, 13, QString(utsn.release) );
      if ( (pos=tmp.find("appMachineType")) != -1 )
        tmp.replace( pos, 14, QString(utsn.machine) );

      QStringList languageList = KGlobal::locale()->languageList();
      if ( languageList.count() )
      {
        QStringList::Iterator it = languageList.find(QString::fromLatin1("C"));
        if( it != languageList.end() )
        {
          if( languageList.contains( QString::fromLatin1("en") ) > 0 )
            languageList.remove( it );
          else
            (*it) = QString::fromLatin1("en");
        }
      }
      if ( (pos=tmp.find("appLanguage")) != -1 )
        tmp.replace( pos, 11, QString("%1").arg(languageList.join(", ")) );
      if ( (pos=tmp.find("appPlatform")) != -1 )
        tmp.replace( pos, 11, QString::fromLatin1("X11") );
    }

    if ( !m_lstIdentity.contains(tmp) )
      m_lstIdentity << tmp;
    else
      continue; // Ignore dups!

    tmp = QString("%1 %2").arg(UA_PTOS("X-KDE-UA-SYSNAME")).arg(UA_PTOS("X-KDE"
                  "-UA-SYSRELEASE"));
    if ( tmp.stripWhiteSpace().isEmpty() )
      tmp = QString("%1 %2").arg(UA_PTOS("X-KDE-UA-"
                    "NAME")).arg(UA_PTOS("X-KDE-UA-VERSION"));
    else
      tmp = QString("%1 %2 on %3").arg(UA_PTOS("X-KDE-UA-"
                    "NAME")).arg(UA_PTOS("X-KDE-UA-VERSION")).arg(tmp);
    m_lstAlias << tmp;
    m_mapAlias[UA_PTOS("X-KDE-UA-TAG")].append( m_lstAlias.count()-1 );
  }
}

void UAChangerPlugin::slotStarted( KIO::Job* )
{
  m_currentURL = m_part->url();

  if (m_currentURL.isEmpty () ||
      (m_currentURL.protocol().find("http", 0, false) != 0 &&
       m_currentURL.protocol().find("webdav", 0, false) != 0))
  {
    m_pUAMenu->setEnabled ( false );
  }
  else
  {
    if (!m_pUAMenu->isEnabled())
      m_pUAMenu->setEnabled ( true );
  }
}

void UAChangerPlugin::slotAboutToShow()
{
  KProtocolManager proto;

  if (!m_config)
  {
    m_config = new KConfig( "kio_httprc" );
    parseDescFiles();
  }

  if (!m_bSettingsLoaded)
    loadSettings();

  m_pUAMenu->popupMenu()->clear();
  m_pUAMenu->popupMenu()->insertTitle(i18n("Identify As:"));

  m_currentUserAgent = proto.userAgentForHost(m_currentURL.host());

  AliasConstIterator map = m_mapAlias.begin();
  for( ; map != m_mapAlias.end(); ++map )
  {
    BrowserGroup::ConstIterator e = map.data().begin();
    for( ; e != map.data().end(); ++e )
    {
      int id = m_pUAMenu->popupMenu()->insertItem( m_lstAlias[(*e)], this,
                                                   SLOT(slotItemSelected(int)),
                                                   0, (*e) );
      if (m_lstIdentity[(*e)] == m_currentUserAgent)
        m_pUAMenu->popupMenu()->setItemChecked(id, true);
    }
    m_pUAMenu->popupMenu()->insertSeparator();
  }

  int count = m_pUAMenu->popupMenu()->count();

  m_pUAMenu->popupMenu()->insertItem( i18n("Reload Identifications"), this,
                                      SLOT(slotReloadDescriptions()),
                                      0, ++count );
  m_pUAMenu->popupMenu()->insertItem( i18n("Default Identification"), this,
                                      SLOT(slotDefault()), 0, ++count );
  m_pUAMenu->popupMenu()->setItemEnabled(count, (proto.defaultUserAgent()!=
                                                 m_currentUserAgent));
  m_pUAMenu->popupMenu()->insertSeparator();

  m_pUAMenu->popupMenu()->insertItem( i18n("Apply to Entire Site"), this,
                                      SLOT(slotApplyToDomain()),
                                      0, ++count );  
  m_pUAMenu->popupMenu()->setItemChecked(count, m_bApplyToDomain);

  m_pUAMenu->popupMenu()->insertItem( i18n("Configure..."), this,
                                      SLOT(slotConfigure()));

}

void UAChangerPlugin::slotConfigure()
{
  KService::Ptr service = KService::serviceByDesktopName ("useragent");
  if (service)
    KRun::runCommand (service->exec ());
}

void UAChangerPlugin::slotItemSelected( int id )
{
  QString host;

  if ( !m_pUAMenu->popupMenu()->isItemChecked(id) )
  {
    m_currentUserAgent = m_lstIdentity[id];
    host = filterHost( m_currentURL.host() );

    m_config->setGroup( host.lower() );
    m_config->writeEntry( "UserAgent", m_currentUserAgent );
    m_config->sync();

    // Update the io-slaves...
    updateIOSlaves ();

    // Reload the page with the new user-agent string
    m_part->openURL( m_currentURL );
  }
}

void UAChangerPlugin::slotDefault()
{
  QStringList domains;
  QStringList partList;
  KProtocolManager proto;

  // Remove the exact name match...
  domains << m_currentURL.host ();

  // We have no choice but delete all higher domain level
  // settings here since it affects what will be matched.
  partList = QStringList::split('.', m_currentURL.host(), false);
  partList.remove(partList.begin());

  while (partList.count())
  {
    if (partList.count() == 2)
      if (partList[0].length() <=2 && partList[1].length() ==2)
        break;

    if (partList.count() == 1)
      break;

    domains << partList.join(".");
    partList.remove(partList.begin());
  }

  for (QStringList::Iterator it = domains.begin(); it != domains.end(); it++)
  {
    kdDebug () << "Domain to remove: " << *it << endl;
    if ( m_config->hasGroup(*it) )
      m_config->deleteGroup(*it);
    else if( m_config->hasKey(*it) )
      m_config->deleteEntry(*it);
  }

  m_config->sync();

  // Reset some internal variables and inform the http io-slaves of
  // the changes.
  m_currentUserAgent = proto.defaultUserAgent();

  // Update the http io-slaves.
  updateIOSlaves();

  // Reload the page with the default user-agent
  m_part->openURL( m_currentURL );
}

void UAChangerPlugin::updateIOSlaves ()
{
  // Inform running http(s) io-slaves about the change...
  DCOPClient * client = new DCOPClient ();

  if (!client->attach())
    kdDebug() << "Can't connect with DCOP server." << endl;

  QByteArray data;
  QDataStream stream( data, IO_WriteOnly );
  stream << QString::null;
  client->send( "*", "KIO::Scheduler", "reparseSlaveConfiguration(QString)",
                data );
  delete client;
}

QString UAChangerPlugin::filterHost(const QString &hostname)
{
  QRegExp rx;
  bool isIPAddress;

  // Check for IPv4 address
  rx.setPattern ("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
  isIPAddress = rx.exactMatch (hostname);

  if (isIPAddress)
    return hostname;

  // Check for IPv6 address here...
  rx.setPattern ("^\\[.*\\]$");
  isIPAddress = rx.exactMatch (hostname);

  if (isIPAddress)
    return hostname;

  // Return the TLD if apply to domain or 
  return (m_bApplyToDomain ? findTLD(hostname): hostname);
}


// TODO: Probably need to move this code...
QString UAChangerPlugin::findTLD (const QString &hostname)
{
  QString tld = hostname;

  if ( !tld.isEmpty () )
  {
    QStringList partList = QStringList::split('.', tld, false);
    int count = partList.count();

    if (count > 2)
    {
      QStringList tlds;

      // Classical TLD's
      tlds << "com" << "net" << "org" << "gov" << "edu" << "mil" << "int";

      // The new seven TLD's
      tlds << "aero" << "biz" << "coop" << "info" << "museum" << "name" << "pro";

      QStringList::Iterator topTLD = partList.fromLast ();

      if ( (*topTLD).length() < 3 )
      {
        --topTLD;

        if ( (*topTLD).length () < 3 )
        {
          --topTLD;

          // Remove anymore known TLD's...
          while ( topTLD != partList.begin() && tlds.findIndex (*topTLD) != -1 )
            --topTLD;

          // Remove everything upto this point...
          partList.erase (partList.begin(), topTLD);

          // If we still have something, then create a TLD out of it.
          if (partList.count ()) 
            tld = partList.join (".");
        }
        else
        {
          while ( topTLD != partList.begin() && tlds.findIndex (*topTLD) != -1 )
            --topTLD;

          partList.erase (partList.begin(), topTLD);

          if (partList.count ())
            tld = partList.join (".");
        }
      }
      else
      {
        --topTLD;

        while ( topTLD != partList.begin () && tlds.findIndex (*topTLD) != -1 )
          --topTLD;

        partList.erase (partList.begin (), topTLD);

        if (partList.count())
          tld = partList.join (".");
      }
    }
  }

  return tld;
}

void UAChangerPlugin::saveSettings()
{
  if(!m_bSettingsLoaded) return;

  KConfig cfg ("uachangerrc", false, false);
  cfg.setGroup ("General");

  cfg.writeEntry ("applyToDomain", m_bApplyToDomain);
}

void UAChangerPlugin::loadSettings()
{
  KConfig cfg ("uachangerrc", false, false);
  cfg.setGroup ("General");

  m_bApplyToDomain = cfg.readBoolEntry ("applyToDomain", true);
  m_bSettingsLoaded = true;
}

void UAChangerPlugin::slotApplyToDomain()
{
  m_bApplyToDomain = !m_bApplyToDomain;
}
#include "uachangerplugin.moc"
