/*
    KNode, the KDE newsreader
    Copyright (c) 1999-2006 the KNode authors.
    See file AUTHORS for details

    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.
    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, US
*/


#include <kconfig.h>
#include <klocale.h>
#include <kdebug.h>

#include "knglobals.h"
#include "kncollectionviewitem.h"
#include "knconfig.h"
#include "kngrouppropdlg.h"
#include "utilities.h"
#include "knmainwidget.h"
#include "knscoring.h"
#include "knarticlemanager.h"
#include "kngroupmanager.h"
#include "knnntpaccount.h"
#include "headerview.h"
#include "settings.h"

#include <QTextStream>
#include <QByteArray>


#define SORT_DEPTH 5

KNGroup::KNGroup(KNCollection *p)
  : KNArticleCollection(p), n_ewCount(0), l_astFetchCount(0), r_eadCount(0), i_gnoreCount(0),
    f_irstNr(0), l_astNr(0), m_axFetch(0), d_ynDataFormat(1), f_irstNew(-1), l_ocked(false),
    u_seCharset(false), s_tatus(unknown), i_dentity(0)
{
  mCleanupConf = new KNode::Cleanup( false );
}


KNGroup::~KNGroup()
{
  delete i_dentity;
  delete mCleanupConf;
}


QString KNGroup::path()
{
  return p_arent->path();
}


const QString& KNGroup::name()
{
  static QString ret;
  if(n_ame.isEmpty()) ret=g_roupname;
  else ret=n_ame;
  return ret;
}


void KNGroup::updateListItem()
{
  if(!l_istItem) return;
  l_istItem->setTotalCount( c_ount );
  l_istItem->setUnreadCount( c_ount - r_eadCount - i_gnoreCount );
  l_istItem->repaint();
}


bool KNGroup::readInfo(const QString &confPath)
{
  KConfigGroup info( KSharedConfig::openConfig(confPath, KConfig::SimpleConfig), QString() );

  g_roupname = info.readEntry("groupname");
  d_escription = info.readEntry("description");
  n_ame = info.readEntry("name");
  c_ount = info.readEntry("count",0);
  r_eadCount = info.readEntry("read",0);
  if (r_eadCount > c_ount) r_eadCount = c_ount;
  f_irstNr = info.readEntry("firstMsg",0);
  l_astNr = info.readEntry("lastMsg",0);
  d_ynDataFormat = info.readEntry("dynDataFormat",0);
  u_seCharset = info.readEntry("useCharset", false);
  d_efaultChSet = info.readEntry("defaultChSet").toLatin1();
  QString s = info.readEntry("status","unknown");
  if (s=="readOnly")
    s_tatus = readOnly;
  else if (s=="postingAllowed")
    s_tatus = postingAllowed;
  else if (s=="moderated")
    s_tatus = moderated;
  else
    s_tatus = unknown;
  c_rosspostIDBuffer = info.readEntry("crosspostIDBuffer", QStringList() );

  i_dentity=new KNode::Identity(false);
  i_dentity->loadConfig(info);
  if(!i_dentity->isEmpty()) {
    kDebug(5003) <<"KNGroup::readInfo(const QString &confPath) : using alternative user for" << g_roupname;
  }
  else {
    delete i_dentity;
    i_dentity=0;
  }

  mCleanupConf->loadConfig( info );

  return (!g_roupname.isEmpty());
}


void KNGroup::saveInfo()
{
  QString dir(path());

  if (!dir.isNull()) {
    KConfig _info(dir+g_roupname+".grpinfo", KConfig::SimpleConfig);
    KConfigGroup info( &_info, QString() );

    info.writeEntry("groupname", g_roupname);
    info.writeEntry("description", d_escription);
    info.writeEntry("firstMsg", f_irstNr);
    info.writeEntry("lastMsg", l_astNr);
    info.writeEntry("count", c_ount);
    info.writeEntry("read", r_eadCount);
    info.writeEntry("dynDataFormat", d_ynDataFormat);
    info.writeEntry("name", n_ame);
    info.writeEntry("useCharset", u_seCharset);
    info.writeEntry("defaultChSet", QString::fromLatin1(d_efaultChSet));
    switch (s_tatus) {
      case unknown: info.writeEntry("status","unknown");
                    break;
      case readOnly: info.writeEntry("status","readOnly");
                     break;
      case postingAllowed: info.writeEntry("status","postingAllowed");
                           break;
      case moderated: info.writeEntry("status","moderated");
                           break;
    }
    info.writeEntry("crosspostIDBuffer", c_rosspostIDBuffer);

    if(i_dentity)
      i_dentity->saveConfig(info);
    else if(info.hasKey("Email")) {
      info.deleteEntry("Name", false);
      info.deleteEntry("Email", false);
      info.deleteEntry("Reply-To", false);
      info.deleteEntry("Mail-Copies-To", false);
      info.deleteEntry("Org", false);
      info.deleteEntry("UseSigFile", false);
      info.deleteEntry("UseSigGenerator", false);
      info.deleteEntry("sigFile", false);
      info.deleteEntry("sigText", false);
    }

    mCleanupConf->saveConfig( info );
  }
}


KNNntpAccount* KNGroup::account()
{
  KNCollection *p=parent();
  while(p->type()!=KNCollection::CTnntpAccount) p=p->parent();

  return (KNNntpAccount*)p_arent;
}


bool KNGroup::loadHdrs()
{
  if(isLoaded()) {
    kDebug(5003) <<"KNGroup::loadHdrs() : nothing to load";
    return true;
  }

  kDebug(5003) <<"KNGroup::loadHdrs() : loading headers";
  QByteArray buffer, hdrValue;
  QFile f;
  int cnt=0, id, lines, fileFormatVersion, artNumber;
  unsigned int timeT;
  KNRemoteArticle *art;

  QString dir(path());
  if (dir.isNull())
    return false;

  f.setFileName(dir+g_roupname+".static");

  if(f.open(QIODevice::ReadOnly)) {

    if(!resize(c_ount)) {
      f.close();
      return false;
    }

    while ( !f.atEnd() ) {
      buffer = f.readLine();
      if ( buffer.isEmpty() ){
        if ( f.error() == QFile::NoError ) {
          kWarning(5003) <<"Found broken line in static-file: Ignored!";
          continue;
        } else {
          kError(5003) <<"Corrupted static file, IO-error!";
          clear();
          return false;
        }
      }

      QList<QByteArray> splits = buffer.split( '\t' );
      if ( splits.size() < 4 ) {
        kWarning(5003) <<"Found broken line in static-file: Ignored!";
        continue;
      }
      QList<QByteArray>::ConstIterator it = splits.constBegin();

      art = new KNRemoteArticle( this );

      art->messageID()->from7BitString( *it );
      ++it;

      art->subject()->from7BitString( *it );
      ++it;

      KMime::Types::Mailbox mbox;
      mbox.setAddress( *it );
      ++it;

      if ( (*it) != "0\n" ) // last item has the line ending
        mbox.setNameFrom7Bit( (*it).trimmed() );
      art->from()->addAddress( mbox );

      buffer = f.readLine().trimmed();
      if ( buffer != "0" )
        art->references()->from7BitString( buffer );

      buffer = f.readLine();
      if ( sscanf( buffer, "%d %d %u %d", &id, &lines, &timeT, &fileFormatVersion) < 4 )
        fileFormatVersion = 0;          // KNode <= 0.4 had no version number
      art->setId(id);
      art->lines()->setNumberOfLines(lines);
      KDateTime dt;
      dt.setTime_t( timeT );
      art->date()->setDateTime( dt );

      if ( fileFormatVersion > 0 ) {
        buffer = f.readLine();
        sscanf( buffer, "%d", &artNumber );
        art->setArticleNumber(artNumber);
      }

      // optional headers
      if ( fileFormatVersion > 1 ) {
        // first line is the number of addiotion headers
        buffer = f.readLine().trimmed();
        // following lines contain one header per line
        for ( uint i = buffer.toUInt(); i > 0; --i ) {
          buffer = f.readLine().trimmed();
          int pos = buffer.indexOf( ':' );
          QByteArray hdrName = buffer.left( pos );
          // skip headers we already set above and which we actually never should
          // find here, but however it still happens... (eg. #101355)
          if ( hdrName == "Subject" || hdrName == "From" || hdrName == "Date"
              || hdrName == "Message-ID" || hdrName == "References"
              || hdrName == "Bytes" || hdrName == "Lines" )
            continue;
          hdrValue = buffer.right( buffer.length() - (pos + 2) );
          if ( hdrValue.length() > 0 )
            art->setHeader( new KMime::Headers::Generic( hdrName, art, hdrValue ) );
        }
      }

      if(append(art)) cnt++;
      else {
        f.close();
        clear();
        return false;
      }
    }

    setLastID();
    f.close();
  }
  else {
    clear();
    return false;
  }


  f.setFileName(dir+g_roupname+".dynamic");

  if (f.open(QIODevice::ReadOnly)) {

    dynDataVer0 data0;
    dynDataVer1 data1;
    int readCnt=0,byteCount,dataSize;
    if (d_ynDataFormat==0)
      dataSize = sizeof(data0);
    else
      dataSize = sizeof(data1);

    while(!f.atEnd()) {

      if (d_ynDataFormat==0)
        byteCount = f.read((char*)(&data0), dataSize);
      else
        byteCount = f.read((char*)(&data1), dataSize);
      if ((byteCount == -1)||(byteCount!=dataSize)) {
        if ( f.error() == QFile::NoError ) {
          kWarning(5003) <<"Found broken entry in dynamic-file: Ignored!";
          continue;
        } else {
          kError(5003) <<"Corrupted dynamic file, IO-error!";
          clear();
          return false;
        }
      }

      if (d_ynDataFormat==0)
        art=byId(data0.id);
      else
        art=byId(data1.id);

      if(art) {
        if (d_ynDataFormat==0)
          data0.getData(art);
        else
          data1.getData(art);

      if (art->isRead()) readCnt++;
      }

    }

    f.close();

    r_eadCount=readCnt;

  }

  else  {
    clear();
    return false;
  }

  kDebug(5003) << cnt <<" articles read from file";
  c_ount=length();

  // convert old data files into current format:
  if (d_ynDataFormat!=1) {
    saveDynamicData(length(), true);
    d_ynDataFormat=1;
  }

  // restore "New" - flags
  if( f_irstNew > -1 ) {
    for( int i = f_irstNew; i < length(); i++ ) {
      at(i)->setNew(true);
    }
  }

  updateThreadInfo();
  processXPostBuffer(false);
  return true;
}


bool KNGroup::unloadHdrs(bool force)
{
  if(l_ockedArticles>0)
    return false;

  if (!force && isNotUnloadable())
    return false;

  KNRemoteArticle *a;
  for(int idx=0; idx<length(); idx++) {
    a=at(idx);
    if (a->hasContent() && !knGlobals.articleManager()->unloadArticle(a, force))
      return false;
  }
  syncDynamicData();
  clear();

  return true;
}


// Attention: this method is called from the network thread!
void KNGroup::insortNewHeaders( const KIO::UDSEntryList &list, KNJobData *job)
{
  KNRemoteArticle *art=0, *art2=0;
  int new_cnt=0, added_cnt=0;
  int todo = list.count();
  QTime timer;

  l_astFetchCount=0;

  if ( list.isEmpty() )
    return;

  timer.start();

  //resize the list
  if ( !resize( size() + list.count() ) ) return;

  // recreate msg-ID index
  syncSearchIndex();

  // remember index of first new
  if(f_irstNew == -1)
    f_irstNew = length(); // index of last + 1

  // get a list of additional headers provided in the listing
  if ( mOptionalHeaders.isEmpty() ) {
    KIO::UDSEntry entry = list.first();
    const QList<uint> fields = entry.listFields();
    foreach ( uint field, fields ) {
      if ( field < KIO::UDSEntry::UDS_EXTRA || field > KIO::UDSEntry::UDS_EXTRA_END )
        continue;
      QString value = entry.stringValue( field );
      kDebug(5003) << value;
      QString hdrName = value.left( value.indexOf( ':' ) );
      if ( hdrName == "Subject" || hdrName == "From" || hdrName == "Date"
           || hdrName == "Message-ID" || hdrName == "References"
           || hdrName == "Bytes" || hdrName == "Lines" )
        continue;
      kDebug(5003) <<"Adding optional header:" << hdrName;
      mOptionalHeaders.append( hdrName.toLatin1() );
    }
  }

  QString hdrName, hdrValue;
  for( KIO::UDSEntryList::ConstIterator it = list.begin(); it != list.end(); ++it ) {

    //new Header-Object
    art=new KNRemoteArticle(this);
    art->setNew(true);

    const QList<uint> fields = (*it).listFields();
    foreach ( uint field, fields ) {

      if ( field == KIO::UDSEntry::UDS_NAME ) {
        //Article Number
        art->setArticleNumber( (*it).stringValue( field ).toInt() );
      } else if ( field >= KIO::UDSEntry::UDS_EXTRA && field <= KIO::UDSEntry::UDS_EXTRA_END ) {
        QString value = (*it).stringValue( field );
        int pos = value.indexOf( ':' );
        if ( pos >= value.length() - 1 )
          continue; // value is empty
        hdrName = value.left( pos );
        hdrValue = value.right( value.length() - ( hdrName.length() + 2 ) );

        if ( hdrName == "Subject" ) {
          art->subject()->from7BitString( hdrValue.toLatin1() );
          if ( art->subject()->isEmpty() )
            art->subject()->fromUnicodeString( i18n("no subject"), art->defaultCharset() );
        } else if ( hdrName == "From" ) {
          art->from()->from7BitString( hdrValue.toLatin1() );
        } else if ( hdrName == "Date" ) {
          art->date()->from7BitString( hdrValue.toLatin1() );
        } else if ( hdrName == "Message-ID" ) {
          art->messageID()->from7BitString( hdrValue.simplified().toLatin1() );
        } else if ( hdrName == "References" ) {
          if( !hdrValue.isEmpty() )
            art->references()->from7BitString( hdrValue.toLatin1() );
        } else if ( hdrName == "Lines" ) {
          art->lines()->setNumberOfLines( hdrValue.toInt() );
        } else {
          // optional extra headers
          art->setHeader( new KMime::Headers::Generic( hdrName.toLatin1(), art, hdrValue.toLatin1() ) );
        }
      }
    }

    // check if we have this article already in this group,
    // if so mark it as new (useful with leafnodes delay-body function)
    art2=byMessageId(art->messageID()->as7BitString(false));
    if(art2) { // ok, we already have this article
      art2->setNew(true);
      art2->setArticleNumber(art->articleNumber());
      delete art;
      new_cnt++;
    }
    else if (append(art)) {
      added_cnt++;
      new_cnt++;
    }
    else {
      delete art;
      return;
    }

    if (timer.elapsed() > 200) {           // don't flicker
      timer.restart();
      if(job) {
        job->setProgress((new_cnt*30)/todo);
      }
    }
  }

  // now we build the threads
  syncSearchIndex(); // recreate the msgId-index so it contains the appended headers
  buildThreads(added_cnt, job);
  updateThreadInfo();

  // save the new headers
  saveStaticData(added_cnt);
  saveDynamicData(added_cnt);

  // update group-info
  c_ount=length();
  n_ewCount+=new_cnt;
  l_astFetchCount=new_cnt;
  updateListItem();
  saveInfo();
}


int KNGroup::saveStaticData(int cnt,bool ovr)
{
  int idx, savedCnt = 0;
  KNRemoteArticle *art;

  QString dir(path());
  if (dir.isNull())
    return 0;

  QFile f(dir+g_roupname+".static");

  QIODevice::OpenMode mode;
  if(ovr) mode=QIODevice::WriteOnly;
  else mode=QIODevice::WriteOnly | QIODevice::Append;

  if(f.open(mode)) {

    QTextStream ts(&f);
    ts.setCodec( "ISO 8859-1" );

    for(idx=length()-cnt; idx<length(); idx++) {

      art=at(idx);

      if(art->isExpired()) continue;

      ts << art->messageID()->as7BitString(false) << '\t';
      ts << art->subject()->as7BitString(false) << '\t';

      KMime::Types::Mailbox mbox;
      if ( !art->from()->isEmpty() )
        mbox = art->from()->mailboxes().first();
      ts << mbox.address() << '\t';

      if ( mbox.hasName() )
        ts << KMime::encodeRFC2047String( mbox.name(), art->from()->rfc2047Charset() ) << '\n';
      else
        ts << "0\n";

      if(!art->references()->isEmpty())
        ts << art->references()->as7BitString(false) << "\n";
      else
        ts << "0\n";

      ts << art->id() << ' ';
      ts << art->lines()->numberOfLines() << ' ';
      ts << art->date()->dateTime().toTime_t() << ' ';
      ts << "2\n";       // version number to achieve backward compatibility easily

      ts << art->articleNumber() << '\n';

      // optional headers
      ts << mOptionalHeaders.count() << '\n';
      Q_FOREACH( QByteArray hdrName, mOptionalHeaders ) {
        KMime::Headers::Base *hdr = art->getHeaderByType( hdrName.data() );
        if ( hdr )
          ts << hdrName << ": " << hdr->asUnicodeString() << '\n';
        else
          ts << hdrName << ": \n";
      }

      savedCnt++;
    }

    f.close();
  }

  return savedCnt;
}


void KNGroup::saveDynamicData(int cnt,bool ovr)
{
  dynDataVer1 data;
  KNRemoteArticle *art;

  if(length()>0) {
    QString dir(path());
    if (dir.isNull())
      return;

    QFile f(dir+g_roupname+".dynamic");

    QIODevice::OpenMode mode;
    if(ovr) mode=QIODevice::WriteOnly;
    else mode=QIODevice::WriteOnly | QIODevice::Append;

    if(f.open(mode)) {

      for(int idx=length()-cnt; idx<length(); idx++) {
        art=at(idx);
        if(art->isExpired()) continue;
        data.setData(art);
        f.write((char*)(&data), sizeof(data));
        art->setChanged(false);
      }
      f.close();
    }
    else KNHelper::displayInternalFileError();
  }
}


void KNGroup::syncDynamicData()
{
  dynDataVer1 data;
  int cnt=0, readCnt=0, sOfData;
  KNRemoteArticle *art;

  if(length()>0) {

    QString dir(path());
    if (dir.isNull())
      return;

    QFile f(dir+g_roupname+".dynamic");

    if(f.open(QIODevice::ReadWrite)) {

      sOfData=sizeof(data);

      for(int i=0; i<length(); i++) {
        art=at(i);

        if(art->hasChanged() && !art->isExpired()) {

          data.setData(art);
          f.seek( i * sOfData );
          f.write((char*) &data, sOfData);
          cnt++;
          art->setChanged(false);
        }

        if(art->isRead() && !art->isExpired()) readCnt++;
      }

      f.close();

      kDebug(5003) << g_roupname <<" => updated" << cnt <<" entries of dynamic data";

      r_eadCount=readCnt;
    }
    else KNHelper::displayInternalFileError();
  }
}


void KNGroup::appendXPostID(const QString &id)
{
  c_rosspostIDBuffer.append(id);
}


void KNGroup::processXPostBuffer(bool deleteAfterwards)
{
  QStringList remainder;
  KNRemoteArticle *xp;
  KNRemoteArticle::List al;

  for (QStringList::Iterator it = c_rosspostIDBuffer.begin(); it != c_rosspostIDBuffer.end(); ++it) {
    if ((xp=byMessageId((*it).toLocal8Bit())))
      al.append(xp);
    else
      remainder.append(*it);
  }
  knGlobals.articleManager()->setRead(al, true, false);

  if (!deleteAfterwards)
    c_rosspostIDBuffer = remainder;
  else
    c_rosspostIDBuffer.clear();
}


void KNGroup::buildThreads(int cnt, KNJobData *job)
{
  int end=length(),
      start=end-cnt,
      foundCnt=0, bySubCnt=0, refCnt=0,
      resortCnt=0, idx, oldRef; // idRef;
  KNRemoteArticle *art, *ref;
  QTime timer;

  timer.start();

  // this method is called from the nntp-thread!!!
#ifndef NDEBUG
  kDebug(5003) << "start =" << start << "end =" << end;
#endif

  //resort old hdrs
  if(start>0)
    for(idx=0; idx<start; idx++) {
      art=at(idx);
      if(art->threadingLevel()>1) {
        oldRef=art->idRef();
        ref=findReference(art);
        if(ref) {
          // this method is called from the nntp-thread!!!
          #ifndef NDEBUG
          kDebug(5003) << art->id() << ": Old" << oldRef << "New" << art->idRef();
          #endif
          resortCnt++;
          art->setChanged(true);
        }
      }
    }


  for(idx=start; idx<end; idx++) {

    art=at(idx);

    if(art->idRef()==-1 && !art->references()->isEmpty() ){   //hdr has references
      refCnt++;
      if(findReference(art))
        foundCnt++;
    }
    else {
      if(art->subject()->isReply()) {
        art->setIdRef(0); //hdr has no references
        art->setThreadingLevel(0);
      }
      else if(art->idRef()==-1)
        refCnt++;
    }

    if (timer.elapsed() > 200) {           // don't flicker
      timer.restart();
      if(job) {
        job->setProgress(30+((foundCnt)*70)/cnt);
      }
    }
  }


  if(foundCnt<refCnt) {    // some references could not been found

    //try to sort by subject
    KNRemoteArticle *oldest;
    KNRemoteArticle::List list;

    for(idx=start; idx<end; idx++) {

      art=at(idx);

      if(art->idRef()==-1) {  //for all not sorted headers

        list.clear();
        list.append(art);

        //find all headers with same subject
        for(int idx2=0; idx2<length(); idx2++)
          if(at(idx2)==art) continue;
          else if(at(idx2)->subject()==art->subject())
            list.append(at(idx2));

        if(list.count()==1) {
          art->setIdRef(0);
          art->setThreadingLevel(6);
          bySubCnt++;
        }
        else {

          //find oldest
          oldest=list.first();
          for ( KNRemoteArticle::List::Iterator it = list.begin(); it != list.end(); ++it )
            if ( (*it)->date()->dateTime() < oldest->date()->dateTime() )
              oldest = (*it);

          //oldest gets idRef 0
          if(oldest->idRef()==-1) bySubCnt++;
          oldest->setIdRef(0);
          oldest->setThreadingLevel(6);

          for ( KNRemoteArticle::List::Iterator it = list.begin(); it != list.end(); ++it ) {
            if ( (*it) == oldest )
              continue;
            if ( (*it)->idRef() == -1 || ( (*it)->idRef() != -1 && (*it)->threadingLevel() == 6 ) ) {
              (*it)->setIdRef(oldest->id());
              (*it)->setThreadingLevel(6);
              if ( (*it)->id() >= at(start)->id() )
                bySubCnt++;
            }
          }
        }
      }

      if (timer.elapsed() > 200) {           // don't flicker
        timer.restart();
        if(job) {
          job->setProgress(30+((bySubCnt+foundCnt)*70)/cnt);
        }
      }
    }
  }

  //all not found items get refID 0
  for (int idx=start; idx<end; idx++){
    art=at(idx);
    if(art->idRef()==-1) {
      art->setIdRef(0);
      art->setThreadingLevel(6);   //was 0 !!!
    }
  }

  //check for loops in threads
  int startId;
  bool isLoop;
  int iterationCount;
  for (int idx=start; idx<end; idx++){
    art=at(idx);
    startId=art->id();
    isLoop=false;
    iterationCount=0;
    while(art->idRef()!=0 && !isLoop && (iterationCount < end)) {
      art=byId(art->idRef());
      isLoop=(art->id()==startId);
      iterationCount++;
    }

    if(isLoop) {
      // this method is called from the nntp-thread!!!
      #ifndef NDEBUG
      kDebug(5003) << "Sorting : loop in" << startId;
      #endif
      art=at(idx);
      art->setIdRef(0);
      art->setThreadingLevel(0);
    }
  }

  // propagate ignored/watched flags to new headers
  for(int idx=start; idx<end; idx++) {
    art=at(idx);
    int idRef=art->idRef();
    int tmpIdRef;

    if(idRef!=0) {
      while(idRef!=0) {
         art=byId(idRef);
	 if (art) {
            tmpIdRef=art->idRef();
            idRef = (idRef!=tmpIdRef)? tmpIdRef : 0;
	 }
      }
      if (art) {
        if (art->isIgnored()) {
          at(idx)->setIgnored(true);
          ++i_gnoreCount;
        }
        at(idx)->setWatched(art->isWatched());
      }
    }
  }

  // this method is called from the nntp-thread!!!
#ifndef NDEBUG
  kDebug(5003) << "Sorting :" << resortCnt << "headers resorted";
  kDebug(5003) << "Sorting :" << foundCnt << "references of" << refCnt << "found"; 
  kDebug(5003) << "Sorting :" << bySubCnt << "references of" << refCnt << "sorted by subject";
#endif
}


KNRemoteArticle* KNGroup::findReference(KNRemoteArticle *a)
{
  QByteArray ref_mid;
  KNRemoteArticle *ref_art=0;

  QList<QByteArray> references = a->references()->identifiers();

  for ( int ref_nr = 0; ref_nr < references.count() && ref_nr < SORT_DEPTH; ++ref_nr ) {
    ref_mid = '<' + references.at( references.count() - ref_nr - 1 ) + '>';
    ref_art = byMessageId(ref_mid);
    if(ref_art) {
      a->setThreadingLevel(ref_nr+1);
      a->setIdRef(ref_art->id());
      break;
    }
  }

  return ref_art;
}


void KNGroup::scoreArticles(bool onlynew)
{
  kDebug(5003) <<"KNGroup::scoreArticles()";
  int len=length(),
      todo=(onlynew)? lastFetchCount():length();

  if (todo) {
    // reset the notify collection
    delete KNScorableArticle::notifyC;
    KNScorableArticle::notifyC = 0;

    kDebug(5003) <<"scoring" << newCount() <<" articles";
    kDebug(5003) <<"(total" << length() <<" article in group)";
    knGlobals.top->setCursorBusy(true);
    knGlobals.setStatusMsg(i18n(" Scoring..."));

    int defScore;
    KScoringManager *sm = knGlobals.scoringManager();
    sm->initCache(groupname());
    for(int idx=0; idx<todo; idx++) {
      KNRemoteArticle *a = at(len-idx-1);
      if ( !a ) {
        kWarning( 5003 ) <<"found no article at" << len-idx-1;
        continue;
      }

      defScore = 0;
      if (a->isIgnored())
        defScore = knGlobals.settings()->ignoredThreshold();
      else if (a->isWatched())
        defScore = knGlobals.settings()->watchedThreshold();

      if (a->score() != defScore) {
        a->setScore(defScore);
        a->setChanged(true);
      }

      bool read = a->isRead();

      KNScorableArticle sa(a);
      sm->applyRules(sa);

      if ( a->isRead() != read && !read )
        incReadCount();
    }

    knGlobals.setStatusMsg( QString() );
    knGlobals.top->setCursorBusy(false);

    //kDebug(5003) << KNScorableArticle::notifyC->collection();
    if (KNScorableArticle::notifyC)
      KNScorableArticle::notifyC->displayCollection(knGlobals.topWidget);
  }
}


void KNGroup::reorganize()
{
  kDebug(5003) <<"KNGroup::reorganize()";

  knGlobals.top->setCursorBusy(true);
  knGlobals.setStatusMsg(i18n(" Reorganizing headers..."));

  for(int idx=0; idx<length(); idx++) {
    KNRemoteArticle *a = at(idx);
    Q_ASSERT( a );
    a->setId(idx+1); //new ids
    a->setIdRef(-1);
    a->setThreadingLevel(0);
  }

  buildThreads(length());
  saveStaticData(length(), true);
  saveDynamicData(length(), true);
  knGlobals.top->headerView()->repaint();
  knGlobals.setStatusMsg( QString() );
  knGlobals.top->setCursorBusy(false);
}


void KNGroup::updateThreadInfo()
{
  KNRemoteArticle *ref;
  bool brokenThread=false;

  for(int idx=0; idx<length(); idx++) {
    at(idx)->setUnreadFollowUps(0);
    at(idx)->setNewFollowUps(0);
  }

  for(int idx=0; idx<length(); idx++) {
    int idRef=at(idx)->idRef();
    int tmpIdRef;
    int iterCount=1;         // control iteration count to avoid infinite loops
    while((idRef!=0) && (iterCount <= length())) {
      ref=byId(idRef);
      if(!ref) {
        brokenThread=true;
        break;
      }

      if(!at(idx)->isRead())  {
        ref->incUnreadFollowUps();
        if(at(idx)->isNew()) ref->incNewFollowUps();
      }
      tmpIdRef=ref->idRef();
      idRef= (idRef!=tmpIdRef) ? ref->idRef() : 0;
      iterCount++;
    }
    if(iterCount > length())
      brokenThread=true;
    if(brokenThread) break;
  }

  if(brokenThread) {
    kWarning(5003) <<"KNGroup::updateThreadInfo() : Found broken threading infos! Restoring ...";
    reorganize();
    updateThreadInfo();
  }
}


void KNGroup::showProperties()
{
  if(!i_dentity) i_dentity=new KNode::Identity(false);
  KNGroupPropDlg *d=new KNGroupPropDlg(this, knGlobals.topWidget);

  if(d->exec())
    if(d->nickHasChanged())
      l_istItem->setText(0, name());

  if(i_dentity->isEmpty()) {
    delete i_dentity;
    i_dentity=0;
  }

  delete d;
}


int KNGroup::statThrWithNew()
{
  int cnt=0;
  for(int i=0; i<length(); i++)
    if( (at(i)->idRef()==0) && (at(i)->hasNewFollowUps()) ) cnt++;
  return cnt;
}


int KNGroup::statThrWithUnread()
{
  int cnt=0;
  for(int i=0; i<length(); i++)
    if( (at(i)->idRef()==0) && (at(i)->hasUnreadFollowUps()) ) cnt++;
  return cnt;
}

QString KNGroup::prepareForExecution()
{
  if (knGlobals.groupManager()->loadHeaders(this))
    return QString();
  else
    return i18n("Cannot load saved headers: %1", groupname());
}

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

void KNGroup::dynDataVer0::setData(KNRemoteArticle *a)
{
  id=a->id();
  idRef=a->idRef();
  thrLevel=a->threadingLevel();
  read=a->getReadFlag();
  score=a->score();
}


void KNGroup::dynDataVer0::getData(KNRemoteArticle *a)
{
  a->setId(id);
  a->setIdRef(idRef);
  a->setRead(read);
  a->setThreadingLevel(thrLevel);
  a->setScore(score);
}


void KNGroup::dynDataVer1::setData(KNRemoteArticle *a)
{
  id=a->id();
  idRef=a->idRef();
  thrLevel=a->threadingLevel();
  read=a->getReadFlag();
  score=a->score();
  ignoredWatched = 0;
  if (a->isWatched())
    ignoredWatched = 1;
  else if (a->isIgnored())
    ignoredWatched = 2;
}


void KNGroup::dynDataVer1::getData(KNRemoteArticle *a)
{
  a->setId(id);
  a->setIdRef(idRef);
  a->setRead(read);
  a->setThreadingLevel(thrLevel);
  a->setScore(score);
  a->setWatched(ignoredWatched==1);
  a->setIgnored(ignoredWatched==2);
}


KNode::Cleanup * KNGroup::activeCleanupConfig()
{
  if (!cleanupConfig()->useDefault())
    return cleanupConfig();
  return account()->activeCleanupConfig();
}
