/*
    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 <tqfileinfo.h>

#include <ksimpleconfig.h>
#include <kstandarddirs.h>
#include <kdebug.h>
#include <tdelocale.h>

#include <kqcstringsplitter.h>

#include "articlewidget.h"
#include "knarticlemanager.h"
#include "kncollectionviewitem.h"
#include "knhdrviewitem.h"
#include "utilities.h"
#include "knglobals.h"
#include "knarticlefactory.h"
#include "knfolder.h"
#include "knarticlewindow.h"
#include "knmainwidget.h"

using namespace KNode;


KNFolder::KNFolder()
  : KNArticleCollection(0), i_d(-1), p_arentId(-1), i_ndexDirty(false), w_asOpen(true)
{
}


KNFolder::KNFolder(int id, const TQString &name, KNFolder *parent)
  : KNArticleCollection(parent), i_d(id), i_ndexDirty(false), w_asOpen(true)
{
  TQString fname=path()+TQString("custom_%1").arg(i_d);

  n_ame = name;
  m_boxFile.setName(fname+".mbox");
  i_ndexFile.setName(fname+".idx");
  i_nfoPath=fname+".info";

  p_arentId=parent?parent->id():-1;

  if(i_ndexFile.exists())
    c_ount=i_ndexFile.size()/sizeof(DynData);
  else
    c_ount=0;
}


KNFolder::KNFolder(int id, const TQString &name, const TQString &prefix, KNFolder *parent)
  : KNArticleCollection(parent), i_d(id), i_ndexDirty(false), w_asOpen(true)
{
  TQString fname=path()+TQString("%1_%2").arg(prefix).arg(i_d);

  n_ame = name;
  m_boxFile.setName(fname+".mbox");
  i_ndexFile.setName(fname+".idx");
  i_nfoPath=fname+".info";

  p_arentId=parent?parent->id():-1;

  if(i_ndexFile.exists())
    c_ount=i_ndexFile.size()/sizeof(DynData);
  else
    c_ount=0;
}


KNFolder::~KNFolder()
{
  closeFiles();
}


void KNFolder::updateListItem()
{
  if(l_istItem) {
    l_istItem->setText(0, n_ame);
    if (!isRootFolder())
      l_istItem->setTotalCount( c_ount );
  }
}


TQString KNFolder::path()
{
  TQString dir(locateLocal("data","knode/")+"folders/");
  /*if (dir.isNull())
    KNHelper::displayInternalFileError();*/
  return dir;
}


bool KNFolder::readInfo(const TQString &infoPath)
{
  if(infoPath.isEmpty())
    return false;

  i_nfoPath=infoPath;

  KSimpleConfig info(i_nfoPath);
  if (!isRootFolder() && !isStandardFolder()) {
    n_ame=info.readEntry("name");
    i_d=info.readNumEntry("id", -1);
    p_arentId=info.readNumEntry("parentId", -1);
  }
  w_asOpen=info.readBoolEntry("wasOpen", true);

  if(i_d>-1) {
    TQFileInfo fi(infoPath);
    TQString fname=fi.dirPath(true)+"/"+fi.baseName();
    closeFiles();
    clear();

    m_boxFile.setName(fname+".mbox");
    i_ndexFile.setName(fname+".idx");
    c_ount=i_ndexFile.exists() ? (i_ndexFile.size()/sizeof(DynData)) : 0;
  }

  return (i_d!=-1);
}


bool KNFolder::readInfo()
{
  return readInfo(i_nfoPath);
}


void KNFolder::saveInfo()
{
  if(!i_nfoPath.isEmpty()) {
    KSimpleConfig info(i_nfoPath);
    if (!isRootFolder() && !isStandardFolder()) {
      info.writeEntry("name", n_ame);
      info.writeEntry("id", i_d);
      info.writeEntry("parentId", p_arentId);
    }
    if(l_istItem)
      info.writeEntry("wasOpen", l_istItem->isOpen());
  }
}


void KNFolder::setParent(KNCollection *p)
{
  p_arent = p;
  p_arentId = p ? (static_cast<KNFolder*>(p))->id() : -1;
}


bool KNFolder::loadHdrs()
{
  if(isLoaded()) {
    kdDebug(5003) << "KNFolder::loadHdrs() : already loaded" << endl;
    return true;
  }

  if(!i_ndexFile.open(IO_ReadOnly)) {
    kdError(5003) << "KNFolder::loadHdrs() : cannot open index-file!" << endl;
    closeFiles();
    return false;
  }

  if(!m_boxFile.open(IO_ReadOnly)) {
    kdError(5003) << "KNFolder::loadHdrs() : cannot open mbox-file!" << endl;
    closeFiles();
    return false;
  }

  if(!resize(c_ount)) {
    closeFiles();
    return false;
  }

  TQCString tmp;
  KTQCStringSplitter split;
  KNLocalArticle *art;
  DynData dynamic;
  int pos1=0, pos2=0, cnt=0, byteCount;

  knGlobals.top->setCursorBusy(true);
  knGlobals.setStatusMsg(i18n(" Loading folder..."));
  knGlobals.top->secureProcessEvents();

  while(!i_ndexFile.atEnd()) {

    //read index-data
    byteCount=i_ndexFile.readBlock((char*)(&dynamic), sizeof(DynData));
    if(byteCount!=sizeof(DynData))
      if(i_ndexFile.status() == IO_Ok) {
        kdWarning(5003) << "KNFolder::loadHeaders() : found broken entry in index-file: Ignored!" << endl;
        continue;
      }
      else {
        kdError(5003) << "KNFolder::loadHeaders() : corrupted index-file, IO-error!" << endl;
        closeFiles();
        clear();
        knGlobals.top->setCursorBusy( false );
        return false;
      }

    art=new KNLocalArticle(this);

    //set index-data
    dynamic.getData(art);

    //read overview
    if(!m_boxFile.at(art->startOffset())) {
      kdError(5003) << "KNFolder::loadHdrs() : cannot set mbox file-pointer!" << endl;
      closeFiles();
      clear();
      knGlobals.top->setCursorBusy( false );
      return false;
    }
    tmp=m_boxFile.readLine(); //KNFile::readLine()
    if(tmp.isEmpty()) {
      if(m_boxFile.status() == IO_Ok) {
        kdWarning(5003) << "found broken entry in mbox-file: Ignored!" << endl;
        delete art;
        continue;
      }
      else {
        kdError(5003) << "KNFolder::loadHdrs() : corrupted mbox-file, IO-error!"<< endl;
        closeFiles();
        clear();
        knGlobals.top->setCursorBusy( false );
        return false;
      }
    }

    //set overview
    bool end=false;
    pos1=tmp.find(' ')+1;
    pos2=tmp.find('\t', pos1);
    if (pos2 == -1) {
      pos2=tmp.length();
      end=true;
    }
    art->subject()->from7BitString(tmp.mid(pos1, pos2-pos1));

    if (!end) {
      pos1=pos2+1;
      pos2=tmp.find('\t', pos1);
      if (pos2 == -1) {
        pos2=tmp.length();
        end=true;
      }
      art->newsgroups()->from7BitString(tmp.mid(pos1, pos2-pos1));
    }

    if (!end) {
      pos1=pos2+1;
      pos2=tmp.find('\t', pos1);
      if (pos2 == -1) {
        pos2=tmp.length();
        end=true;
      }
      art->to()->from7BitString(tmp.mid(pos1,pos2-pos1));
    }

    if (!end) {
      pos1=pos2+1;
      pos2=tmp.length();
      art->lines()->from7BitString(tmp.mid(pos1,pos2-pos1));
    }

    if(!append(art)) {
      kdError(5003) << "KNFolder::loadHdrs() : cannot append article!"<< endl;
      delete art;
      clear();
      closeFiles();

      knGlobals.setStatusMsg(TQString());
      knGlobals.top->setCursorBusy(false);
      return false;
    }

    cnt++;
  }

  closeFiles();
  setLastID();
  c_ount=cnt;
  updateListItem();

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

  return true;
}


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

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

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

  return true;
}

bool KNFolder::loadArticle(KNLocalArticle *a)
{
  if(a->hasContent())
    return true;

  closeFiles();
  if(!m_boxFile.open(IO_ReadOnly)) {
    kdError(5003) << "KNFolder::loadArticle(KNLocalArticle *a) : cannot open mbox file: "
                  << m_boxFile.name() << endl;
    return false;
  }

  //set file-pointer
  if(!m_boxFile.at(a->startOffset())) {
    kdError(5003) << "KNFolder::loadArticle(KNLocalArticle *a) : cannot set mbox file-pointer!" << endl;
    closeFiles();
    return false;
  }

  //read content
  m_boxFile.readLine(); //skip X-KNode-Overview

  unsigned int size=a->endOffset()-m_boxFile.at()-1;
  TQCString buff(size+10);
  int readBytes=m_boxFile.readBlock(buff.data(), size);
  closeFiles();
  if(readBytes < (int)(size) && m_boxFile.status() != IO_Ok) {  //cannot read file
    kdError(5003) << "KNFolder::loadArticle(KNLocalArticle *a) : corrupted mbox file, IO-error!" << endl;
    return false;
  }

  //set content
  buff.at(readBytes)='\0'; //terminate string
  a->setContent(buff);
  a->parse();

  return true;
}


bool KNFolder::saveArticles( KNLocalArticle::List &l )
{
  if(!isLoaded())  // loading should not be done here - keep the StorageManager in sync !!
    return false;

  if(!m_boxFile.open(IO_WriteOnly | IO_Append)) {
    kdError(5003) << "KNFolder::saveArticles() : cannot open mbox-file!" << endl;
    closeFiles();
    return false;
  }

  int addCnt=0;
  bool ret=true;
  bool clear=false;
  TQTextStream ts(&m_boxFile);
  ts.setEncoding(TQTextStream::Latin1);

  for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) {

    clear=false;
    if ( (*it)->id() == -1 || (*it)->collection() != this ) {
      if ( (*it)->id() != -1 ) {
        KNFolder *oldFolder = static_cast<KNFolder*>( (*it)->collection() );
        if ( !(*it)->hasContent() )
          if( !( clear = oldFolder->loadArticle( (*it) ) ) ) {
            ret = false;
            continue;
          }

        KNLocalArticle::List l;
        l.append( (*it) );
        oldFolder->removeArticles( l, false );
      }
      if ( !append( (*it) ) ) {
        kdError(5003) << "KNFolder::saveArticle(KNLocalArticle::List *l) : cannot append article!" << endl;
        ret = false;
        continue;
        (*it)->setCollection(0);
      }
      else {
        (*it)->setCollection(this);
        addCnt++;
      }
    }

    if ( byId( (*it)->id() ) == (*it) ) {

      //MBox
      ts << "From aaa@aaa Mon Jan 01 00:00:00 1997\n";
      (*it)->setStartOffset(m_boxFile.at()); //save offset

      //write overview information
      ts << "X-KNode-Overview: ";
      ts << (*it)->subject()->as7BitString(false) << '\t';

      KMime::Headers::Base* h;
      if( ( h = (*it)->newsgroups( false ) ) !=0 )
        ts << h->as7BitString(false);
      ts << '\t';

      if( (h = (*it)->to( false ) ) != 0 )
        ts << h->as7BitString(false);
      ts << '\t';

      ts << (*it)->lines()->as7BitString(false) << '\n';

      //write article
      (*it)->toStream( ts );
      ts << "\n";

      (*it)->setEndOffset( m_boxFile.at() ); //save offset

      //update
      ArticleWidget::articleChanged( (*it) );
      i_ndexDirty=true;

    }
    else {
      kdError(5003) << "KNFolder::saveArticle() : article not in folder!" << endl;
      ret=false;
    }

    if ( clear )
      (*it)->KMime::Content::clear();
  }

  closeFiles();
  syncIndex();

  if(addCnt>0) {
    c_ount=length();
    updateListItem();
    knGlobals.articleManager()->updateViewForCollection(this);
  }

  return ret;
}


void KNFolder::removeArticles( KNLocalArticle::List &l, bool del )
{
  if( !isLoaded() || l.isEmpty() )
    return;

  int idx = 0, delCnt = 0, *positions;
  positions = new int[l.count()];
  KNLocalArticle *a = 0;

  for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it, ++idx ) {
    if ( (*it)->isLocked() )
      positions[idx] = -1;
    else
      positions[idx] = a_rticles.indexForId( (*it)->id() );
  }

  for ( idx = 0; idx < (int)(l.count()); ++idx ) {
    if(positions[idx]==-1)
      continue;

    a=at(positions[idx]);

    //update
    knGlobals.artFactory->deleteComposerForArticle(a);
    KNArticleWindow::closeAllWindowsForArticle(a);
    ArticleWidget::articleRemoved( a );
    delete a->listItem();

    //delete article
    a_rticles.remove(positions[idx], del, false);
    delCnt++;
    if(!del)
      a->setId(-1);
  }

  if(delCnt>0) {
    compact();
    c_ount-=delCnt;
    updateListItem();
    i_ndexDirty=true;
  }
  delete[] positions;
}


void KNFolder::deleteAll()
{
  if(l_ockedArticles>0)
    return;

  if (!unloadHdrs(true))
    return;

  clear();
  c_ount=0;
  syncIndex(true);
  updateListItem();
}


void KNFolder::deleteFiles()
{
  m_boxFile.remove();
  i_ndexFile.remove();
  TQFile::remove(i_nfoPath);
}


void KNFolder::syncIndex(bool force)
{
  if(!i_ndexDirty && !force)
    return;

  if(!i_ndexFile.open(IO_WriteOnly)) {
    kdError(5003) << "KNFolder::syncIndex(bool force) : cannot open index-file!" << endl;
    closeFiles();
    return;
  }

  KNLocalArticle *a;
  DynData d;
  for(int idx=0; idx<length(); idx++) {
    a=at(idx);
    d.setData(a);
    i_ndexFile.writeBlock((char*)(&d), sizeof(DynData));
  }
  closeFiles();

  i_ndexDirty=false;
}


void KNFolder::closeFiles()
{
  if(m_boxFile.isOpen())
    m_boxFile.close();
  if(i_ndexFile.isOpen())
    i_ndexFile.close();
}


//==============================================================================


void KNFolder::DynData::setData(KNLocalArticle *a)
{
  id=a->id();
  so=a->startOffset();
  eo=a->endOffset();
  sId=a->serverId();
  ti=a->date()->unixTime();

  flags[0]=a->doMail();
  flags[1]=a->mailed();
  flags[2]=a->doPost();
  flags[3]=a->posted();
  flags[4]=a->canceled();
  flags[5]=a->editDisabled();
}


void KNFolder::DynData::getData(KNLocalArticle *a)
{
  a->setId(id);
  a->date()->setUnixTime(ti);
  a->setStartOffset(so);
  a->setEndOffset(eo);
  a->setServerId(sId);
  a->setDoMail(flags[0]);
  a->setMailed(flags[1]);
  a->setDoPost(flags[2]);
  a->setPosted(flags[3]);
  a->setCanceled(flags[4]);
  a->setEditDisabled(flags[5]);
}