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

#include <sys/utsname.h>

#include <tqregexp.h>

#include <krun.h>
#include <kdebug.h>
#include <tdeaction.h>
#include <tdelocale.h>
#include <tdeglobal.h>
#include <ktrader.h>
#include <tdeconfig.h>
#include <tdeio/job.h>
#include <kservice.h>
#include <kinstance.h>
#include <tdepopupmenu.h>
#include <dcopref.h>
#include <tdehtml_part.h>
#include <kgenericfactory.h>
#include <tdeprotocolmanager.h>
#include <tdeaboutdata.h>

#include "uachangerplugin.h"

typedef KGenericFactory<UAChangerPlugin> UAChangerPluginFactory;
static const TDEAboutData aboutdata("uachangerplugin", I18N_NOOP("Change Browser Identification") , "1.0" );
K_EXPORT_COMPONENT_FACTORY (libuachangerplugin, UAChangerPluginFactory (&aboutdata))


#define UA_PTOS(x) (*it)->property(x).toString()
#define TQFL1(x) TQString::fromLatin1(x)


UAChangerPlugin::UAChangerPlugin( TQObject* parent, const char* name,
                                  const TQStringList & )
                :KParts::Plugin( parent, name ),
                  m_bSettingsLoaded(false), m_part(0L), m_config(0L)
{
  setInstance(UAChangerPlugin::instance());

  m_pUAMenu = new TDEActionMenu( i18n("Change Browser &Identification"), "agent",
                               actionCollection(), "changeuseragent" );
  m_pUAMenu->setDelayed( false );
  connect( m_pUAMenu->popupMenu(), TQT_SIGNAL( aboutToShow() ),
           this, TQT_SLOT( slotAboutToShow() ) );

  m_pUAMenu->setEnabled ( false );

  if ( parent && parent->inherits( "TDEHTMLPart" ) )
  {
    m_part = static_cast<TDEHTMLPart*>(parent);
    connect( m_part, TQT_SIGNAL(started(TDEIO::Job*)), this,
             TQT_SLOT(slotStarted(TDEIO::Job*)) );
  }
}

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

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

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

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

  struct utsname utsn;
  uname( &utsn );

  TQStringList languageList = TDEGlobal::locale()->languageList();
  if ( languageList.count() )
  {
     TQStringList::Iterator it = languageList.find(TQFL1("C"));
     if( it != languageList.end() )
     {
       if( languageList.contains( TQFL1("en") ) > 0 )
         languageList.remove( it );
       else
         (*it) = TQFL1("en");
     }
  }

  TDETrader::OfferList::ConstIterator it = list.begin();
  TDETrader::OfferList::ConstIterator lastItem = list.end();

  for ( ; it != lastItem; ++it )
  {
    TQString tmp = UA_PTOS("X-TDE-UA-FULL");
    TQString tag = UA_PTOS("X-TDE-UA-TAG");

    if(tag != "IE" && tag != "NN" && tag != "MOZ")
      tag = "OTHER";

    if ( (*it)->property("X-TDE-UA-DYNAMIC-ENTRY").toBool() )
    {
      tmp.replace( TQFL1("appSysName"), TQFL1(utsn.sysname) );
      tmp.replace( TQFL1("appSysRelease"), TQFL1(utsn.release) );
      tmp.replace( TQFL1("appMachineType"), TQFL1(utsn.machine) );
      tmp.replace( TQFL1("appLanguage"), languageList.join(TQFL1(", ")) );
      tmp.replace( TQFL1("appPlatform"), TQFL1("X11") );
    }

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

    m_lstIdentity << tmp;
    tmp = TQString("%1 %2").arg(UA_PTOS("X-TDE-UA-SYSNAME")).arg(UA_PTOS("X-TDE-UA-SYSRELEASE"));

    if ( tmp.stripWhiteSpace().isEmpty() )
    {
      if(tag == "NN" || tag == "IE" || tag == "MOZ")
         tmp = i18n("Version %1").arg(UA_PTOS("X-TDE-UA-VERSION"));
      else
         tmp = TQString("%1 %2").arg(UA_PTOS("X-TDE-UA-NAME")).arg(UA_PTOS("X-TDE-UA-VERSION"));
    }
    else
    {
      if(tag == "NN" || tag == "IE" || tag == "MOZ")
         tmp = i18n("Version %1 on %2").arg(UA_PTOS("X-TDE-UA-VERSION")).arg(tmp);
      else
        tmp = i18n("%1 %2 on %3").arg(UA_PTOS("X-TDE-UA-NAME")).arg(UA_PTOS("X-TDE-UA-VERSION")).arg(tmp);
    }

    m_lstAlias << tmp;

    /* sort in this UA Alias alphabetically */
    BrowserGroup ualist = m_mapAlias[tag];
    BrowserGroup::Iterator e = ualist.begin();
    while ( !tmp.isEmpty() && e != ualist.end() )
    {
      if ( m_lstAlias[(*e)] > tmp ) {
         ualist.insert( e, m_lstAlias.count()-1 );
         tmp = TQString();
      }
      ++e;
    }

    if ( !tmp.isEmpty() )
      ualist.append( m_lstAlias.count()-1 );

    m_mapAlias[tag] = ualist;

    if(tag == "OTHER")
       m_mapBrowser[tag] = i18n("Other");
    else
       m_mapBrowser[tag] = UA_PTOS("X-TDE-UA-NAME");

  }
}

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

  // This plugin works on local files, http[s], and webdav[s].
  TQString proto = m_currentURL.protocol();
  if (m_currentURL.isLocalFile() ||
      proto.startsWith("http") || proto.startsWith("webdav"))
  {
    if (!m_pUAMenu->isEnabled())
      m_pUAMenu->setEnabled ( true );
  }
  else
  {
    m_pUAMenu->setEnabled ( false );
  }
}

void UAChangerPlugin::slotAboutToShow()
{
  if (!m_config)
  {
    m_config = new TDEConfig( "tdeio_httprc" );
    parseDescFiles();
  }

  if (!m_bSettingsLoaded)
    loadSettings();

  int count = 0;
  m_pUAMenu->popupMenu()->clear();
  m_pUAMenu->popupMenu()->insertTitle(i18n("Identify As")); // imho title doesn't need colon..

  TQString host = m_currentURL.isLocalFile() ? TQFL1("localhost") : m_currentURL.host();
  m_currentUserAgent = KProtocolManager::userAgentForHost(host);
  //kdDebug(90130) << "User Agent: " << m_currentUserAgent << endl;

  int id = m_pUAMenu->popupMenu()->insertItem( i18n("Default Identification"), this,
                                      TQT_SLOT(slotDefault()), 0, ++count );
  if( m_currentUserAgent == KProtocolManager::defaultUserAgent() )
     m_pUAMenu->popupMenu()->setItemChecked(id, true);

  m_pUAMenu->popupMenu()->insertSeparator();

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

  m_pUAMenu->popupMenu()->insertSeparator();

  /* useless here, imho..
  m_pUAMenu->popupMenu()->insertItem( i18n("Reload Identifications"), this,
                                      TQT_SLOT(slotReloadDescriptions()),
                                      0, ++count );*/

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

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

}

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

void UAChangerPlugin::slotItemSelected( int id )
{
  if (m_lstIdentity[id] == m_currentUserAgent) return;

  TQString host;
  m_currentUserAgent = m_lstIdentity[id];
  host = m_currentURL.isLocalFile() ? TQFL1("localhost") : 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()
{
  if( m_currentUserAgent == KProtocolManager::defaultUserAgent() ) return; // don't flicker!
  // We have no choice but delete all higher domain level settings here since it
  // affects what will be matched.
  TQStringList partList = TQStringList::split('.', m_currentURL.host(), false);

  if ( !partList.isEmpty() )
  {
    partList.remove(partList.begin());

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

    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(TQFL1("."));
      partList.remove(partList.begin());
    }

    for (TQStringList::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);
    }
  }
  else
      if ( m_currentURL.isLocalFile() && m_config->hasGroup( "localhost" ) )
          m_config->deleteGroup( "localhost" );

  m_config->sync();

  // Reset some internal variables and inform the http io-slaves of the changes.
  m_currentUserAgent = KProtocolManager::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...
  if (!DCOPRef("*", "TDEIO::Scheduler").send("reparseSlaveConfiguration", TQString()))
    kdWarning() << "UAChangerPlugin::updateIOSlaves: Unable to update running application!" << endl;
}

TQString UAChangerPlugin::filterHost(const TQString &hostname)
{
  TQRegExp rx;

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

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

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

TQString UAChangerPlugin::findTLD (const TQString &hostname)
{
  TQStringList domains;
  TQStringList partList = TQStringList::split('.', hostname, false);

  if (partList.count())
      partList.remove(partList.begin()); // Remove hostname

  while(partList.count())
  {
    // We only have a TLD left.
    if (partList.count() == 1)
        break;

    if( partList.count() == 2 )
    {
        // The .name domain uses <name>.<surname>.name
        // Although the TLD is striclty speaking .name, for our purpose
        // it should be <surname>.name since people should not be able
        // to set cookies for everyone with the same surname.
        // Matches <surname>.name
        if( partList[1].lower() == TQFL1("name") )
        {
          break;
        }
        else if( partList[1].length() == 2 )
        {
          // If this is a TLD, we should stop. (e.g. co.uk)
          // We assume this is a TLD if it ends with .xx.yy or .x.yy
          if (partList[0].length() <= 2)
              break; // This is a TLD.

          // Catch some TLDs that we miss with the previous check
          // e.g. com.au, org.uk, mil.co
          TQCString t = partList[0].lower().utf8();
          if ((t == "com") || (t == "net") || (t == "org") || (t == "gov") ||
              (t == "edu") || (t == "mil") || (t == "int"))
              break;
        }
    }

    domains.append(partList.join(TQFL1(".")));
    partList.remove(partList.begin()); // Remove part
  }

  if( domains.isEmpty() )
    return hostname;

  return domains[0];
}

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

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

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

void UAChangerPlugin::loadSettings()
{
  TDEConfig 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"
