/*
    KNode, the KDE newsreader
    Copyright (c) 1999-2005 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 <ksimpleconfig.h>
#include <tdelocale.h>
#include <kdebug.h>

#include <kqcstringsplitter.h>

#include "knprotocolclient.h"
#include "knglobals.h"
#include "kncollectionviewitem.h"
#include "kngrouppropdlg.h"
#include "utilities.h"
#include "knconfigmanager.h"
#include "knmainwidget.h"
#include "knscoring.h"
#include "knarticlemanager.h"
#include "kngroupmanager.h"
#include "knnntpaccount.h"
#include "headerview.h"


#define SORT_DEPTH 5

KNGroup::KNGroup(KNCollection *p)
  : KNArticleCollection(p), n_ewCount(0), l_astFetchCount(0), r_eadCount(0), i_gnoreCount(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 KNConfig::Cleanup( false );
}


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


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


const TQString& KNGroup::name()
{
  static TQString 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 TQString &confPath)
{
  KSimpleConfig info(confPath);

  g_roupname = info.readEntry("groupname");
  d_escription = info.readEntry("description");
  n_ame = info.readEntry("name");
  c_ount = info.readNumEntry("count",0);
  r_eadCount = info.readNumEntry("read",0);
  if (r_eadCount > c_ount) r_eadCount = c_ount;
  f_irstNr = info.readNumEntry("firstMsg",0);
  l_astNr = info.readNumEntry("lastMsg",0);
  d_ynDataFormat = info.readNumEntry("dynDataFormat",0);
  u_seCharset = info.readBoolEntry("useCharset", false);
  d_efaultChSet = info.readEntry("defaultChSet").latin1();
  TQString 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.readListEntry("crosspostIDBuffer");

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

  mCleanupConf->loadConfig( &info );

  return (!g_roupname.isEmpty());
}


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

  if (!dir.isNull()) {
    KSimpleConfig info(dir+g_roupname+".grpinfo");

    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", TQString::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()) {
    kdDebug(5003) << "KNGroup::loadHdrs() : nothing to load" << endl;
    return true;
  }

  kdDebug(5003) << "KNGroup::loadHdrs() : loading headers" << endl;
  TQCString buff, hdrValue;
  KNFile f;
  KTQCStringSplitter split;
  int cnt=0, id, lines, fileFormatVersion, artNumber;
  unsigned int timeT;
  KNRemoteArticle *art;

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

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

  if(f.open(IO_ReadOnly)) {

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

    while(!f.atEnd()) {
      buff=f.readLine();
      if(buff.isEmpty()){
        if (f.status() == IO_Ok) {
          kdWarning(5003) << "Found broken line in static-file: Ignored!" << endl;
          continue;
        } else {
          kdError(5003) << "Corrupted static file, IO-error!" << endl;
          clear();
          return false;
        }
      }

      split.init(buff, "\t");

      art=new KNRemoteArticle(this);

      split.first();
      art->messageID()->from7BitString(split.string());

      split.next();
      art->subject()->from7BitString(split.string());

      split.next();
      art->from()->setEmail(split.string());
      split.next();
      if(split.string()!="0")
        art->from()->setNameFrom7Bit(split.string());

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

      buff=f.readLine();
      if (sscanf(buff,"%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);
      art->date()->setUnixTime(timeT);

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

      // optional headers
      if (fileFormatVersion > 1) {
        // first line is the number of addiotion headers
        buff = f.readLine();
        // following lines contain one header per line
        for (uint i = buff.toUInt(); i > 0; --i) {
          buff = f.readLine();
          int pos = buff.find(':');
          TQCString hdrName = buff.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 = buff.right( buff.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.setName(dir+g_roupname+".dynamic");

  if (f.open(IO_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.readBlock((char*)(&data0), dataSize);
      else
        byteCount = f.readBlock((char*)(&data1), dataSize);
      if ((byteCount == -1)||(byteCount!=dataSize))
        if (f.status() == IO_Ok) {
          kdWarning(5003) << "Found broken entry in dynamic-file: Ignored!" << endl;
          continue;
        } else {
          kdError(5003) << "Corrupted dynamic file, IO-error!" << endl;
          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;
  }

  kdDebug(5003) << cnt << " articles read from file" << endl;
  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(TQStrList *hdrs, TQStrList *hdrfmt, KNProtocolClient *client)
{
  KNRemoteArticle *art=0, *art2=0;
  TQCString data, hdr, hdrName;
  KTQCStringSplitter split;
  split.setIncludeSep(false);
  int new_cnt=0, added_cnt=0, todo=hdrs->count();
  TQTime timer;

  l_astFetchCount=0;

  if(!hdrs || hdrs->count()==0)
    return;

  timer.start();

  //resize the list
  if(!resize(size()+hdrs->count())) return;

  // recreate msg-ID index
  syncSearchIndex();

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

  for(char *line=hdrs->first(); line; line=hdrs->next()) {
    split.init(line, "\t");

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

    //Article Number
    split.first();
    art->setArticleNumber(split.string().toInt());

    //Subject
    split.next();
    art->subject()->from7BitString(split.string());
    if(art->subject()->isEmpty())
      art->subject()->fromUnicodeString(i18n("no subject"), art->defaultCharset());

    //From and Email
    split.next();
    art->from()->from7BitString(split.string());

    //Date
    split.next();
    art->date()->from7BitString(split.string());

    //Message-ID
    split.next();
    art->messageID()->from7BitString(split.string().simplifyWhiteSpace());

    //References
    split.next();
    if(!split.string().isEmpty())
      art->references()->from7BitString(split.string()); //use TQCString::copy() ?

    // Bytes
    split.next();

    //Lines
    split.next();
    art->lines()->setNumberOfLines(split.string().toInt());

    // optinal additional headers
    mOptionalHeaders = *hdrfmt;
    for (hdr = hdrfmt->first(); !hdr.isNull(); hdr = hdrfmt->next()) {
      if (!split.next())
        break;
      data = split.string();
      int pos = hdr.find(':');
      hdrName = hdr.left( pos );
      // if the header format is 'full' we have to strip the header name
      if (hdr.findRev("full") == (int)(hdr.length() - 4))
        data = data.right( data.length() - (hdrName.length() + 2) );

      // add header
      art->setHeader( new KMime::Headers::Generic( hdrName, art, data ) );
    }

    // 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 (client) client->updatePercentage((new_cnt*30)/todo);
    }
  }

  // now we build the threads
  syncSearchIndex(); // recreate the msgId-index so it contains the appended headers
  buildThreads(added_cnt, client);
  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, mode;
  KNRemoteArticle *art;

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

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

  if(ovr) mode=IO_WriteOnly;
  else mode=IO_WriteOnly | IO_Append;

  if(f.open(mode)) {

    TQTextStream ts(&f);
    ts.setEncoding(TQTextStream::Latin1);

    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';
      ts << art->from()->email() << '\t';

      if(art->from()->hasName())
        ts << art->from()->nameAs7Bit() << '\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()->unixTime() << ' ';
      ts << "2\n";       // version number to achieve backward compatibility easily

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

      // optional headers
      ts << mOptionalHeaders.count() << '\n';
      for (TQCString hdrName = mOptionalHeaders.first(); !hdrName.isNull(); hdrName = mOptionalHeaders.next()) {
        hdrName = hdrName.left( hdrName.find(':') );
        KMime::Headers::Base *hdr = art->getHeaderByType( hdrName );
        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;
  int mode;
  KNRemoteArticle *art;

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

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

    if(ovr) mode=IO_WriteOnly;
    else mode=IO_WriteOnly | IO_Append;

    if(f.open(mode)) {

      for(int idx=length()-cnt; idx<length(); idx++) {
        art=at(idx);
        if(art->isExpired()) continue;
        data.setData(art);
        f.writeBlock((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) {

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

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

    if(f.open(IO_ReadWrite)) {

      sOfData=sizeof(data);

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

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

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

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

      f.close();

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

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


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


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

  for (TQStringList::Iterator it = c_rosspostIDBuffer.begin(); it != c_rosspostIDBuffer.end(); ++it) {
    if ((xp=byMessageId((*it).local8Bit())))
      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, KNProtocolClient *client)
{
  int end=length(),
      start=end-cnt,
      foundCnt=0, bySubCnt=0, refCnt=0,
      resortCnt=0, idx, oldRef; // idRef;
  KNRemoteArticle *art, *ref;
  TQTime timer;

  timer.start();

  // this method is called from the nntp-thread!!!
#ifndef NDEBUG
  tqDebug("knode: KNGroup::buildThreads() : start = %d  end = %d",start,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
          tqDebug("knode: %d: old %d  new %d",art->id(), oldRef, 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(client)
        client->updatePercentage(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()->unixTime() < oldest->date()->unixTime() )
              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 (client) client->updatePercentage(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
      tqDebug("knode: Sorting : loop in %d",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);
         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
  tqDebug("knode: Sorting : %d headers resorted", resortCnt);
  tqDebug("knode: Sorting : %d references of %d found", foundCnt, refCnt);
  tqDebug("knode: Sorting : %d references of %d sorted by subject", bySubCnt, refCnt);
#endif
}


KNRemoteArticle* KNGroup::findReference(KNRemoteArticle *a)
{
  int found=false;
  TQCString ref_mid;
  int ref_nr=0;
  KNRemoteArticle *ref_art=0;

  ref_mid=a->references()->first();

  while(!found && !ref_mid.isNull() && ref_nr < SORT_DEPTH) {
    ref_art=byMessageId(ref_mid);
    if(ref_art) {
      found=true;
      a->setThreadingLevel(ref_nr+1);
      a->setIdRef(ref_art->id());
    }
    ref_nr++;
    ref_mid=a->references()->next();
  }

  return ref_art;
}


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

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

    kdDebug(5003) << "scoring " << newCount() << " articles" << endl;
    kdDebug(5003) << "(total " << length() << " article in group)" << endl;
    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 ) {
        kdWarning( 5003 ) << "found no article at " << len-idx-1 << endl;
        continue;
      }

      defScore = 0;
      if (a->isIgnored())
        defScore = knGlobals.configManager()->scoring()->ignoredThreshold();
      else if (a->isWatched())
        defScore = knGlobals.configManager()->scoring()->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(TQString());
    knGlobals.top->setCursorBusy(false);

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


void KNGroup::reorganize()
{
  kdDebug(5003) << "KNGroup::reorganize()" << endl;

  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(TQString());
  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) {
    kdWarning(5003) << "KNGroup::updateThreadInfo() : Found broken threading infos! Restoring ..." << endl;
    reorganize();
    updateThreadInfo();
  }
}


void KNGroup::showProperties()
{
  if(!i_dentity) i_dentity=new KNConfig::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;
}

TQString KNGroup::prepareForExecution()
{
  if (knGlobals.groupManager()->loadHeaders(this))
    return TQString();
  else
    return i18n("Cannot load saved headers: %1").arg(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);
}


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