/* ****************************************************************************
  This file is part of KBabel

  Copyright (C) 1999-2000 by Matthias Kiefer
                            <matthias.kiefer@gmx.de>
		2001-2002 by Stanislav Visnovsky
			    <visnovsky@kde.org>

  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.

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

  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., 675 Mass Ave, Cambridge, MA 02139, USA.

**************************************************************************** */
#include <qtextstream.h>
#include <qfile.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qregexp.h>
#include <qstring.h>
#include <qtextcodec.h>
#include <qdatetime.h>

#include <kconfig.h>
#include <kglobal.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kapplication.h>
#include <kio/netaccess.h>
#include <krfcdate.h>
#include <kurl.h>

#include "catalog.h"
#include "catalog_private.h"
#include "catalogitem.h"
#include "diff.h"
#include "findoptions.h"
#include "catalogview.h"
#include "editcmd.h"

#include "resources.h"
#include "version.h"

// from libgettext
extern "C"
{
#include "libgettext/fstrcmp.h"
}

#include "libgettext/pofiles.h"
#include <FlexLexer.h>

#include <fstream>

bool Catalog::stopStaticRead = 0;

Catalog::Catalog(QObject* parent, const char* name, QString configFile) 
        : QObject(parent,name)
{
   d = new CatalogPrivate();

   d->_configFile=configFile;
   KConfig *config;
   if(d->_configFile.isEmpty() ) config = KGlobal::config();
   else config=new KConfig(d->_configFile);
   readPreferences(config);
}

Catalog::Catalog(const Catalog& c): QObject(c.parent(),c.name()
)
{
   d = new CatalogPrivate();
   d->_modified=c.d->_modified;
   d->_configFile=c.d->_configFile;
   d->_readOnly=c.d->_readOnly;
   d->_generatedFromDocbook=c.d->_generatedFromDocbook;
   d->_packageName=c.d->_packageName;
   d->_packageDir=c.d->_packageDir;
   d->fileCodec=c.d->fileCodec;
   d->numberOfPluralForms=c.d->numberOfPluralForms;

   KConfig *config;
   if(d->_configFile.isEmpty() ) config = KGlobal::config();
   else config=new KConfig(d->_configFile);
   readPreferences(config);

   d->_views.clear(); // FIXME: loosing _views list

   d->_undoList.clear(); // FIXME: loosing undo list
   d->_redoList.clear(); // FIXME: loosing redo list
}

Catalog::~Catalog()
{
    delete d;
}

QString Catalog::msgid(uint index, const bool noNewlines) const
{
   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].msgid(noNewlines);
}

QString Catalog::msgstr(uint index, const bool noNewlines) const
{
   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].msgstr(noNewlines);
}

QString Catalog::comment(uint index) const
{
   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].comment();
}

QString Catalog::context(uint index) const
{
    QString c = comment(index);
    kdDebug() << "Comment is " << c << endl;
    
    QStringList lines = QStringList::split("\n",c);
    
    kdDebug() << "There is " << lines.size() << " lines in there" << endl;
    
    QString result;
    for( QStringList::Iterator it=lines.begin(); it!=lines.end(); it++)
    {
	kdDebug() << "Examining " << (*it) << endl;
	if( (*it).startsWith( "#:") )
	{
	    kdDebug() << "I will add " << (*it) << endl;
	    result+=(*it)+"\n";
	}
    }
    kdDebug() << "Catalog::context is " << result << endl;
    return result.stripWhiteSpace();
}

CatalogItem Catalog::header() const
{
   return d->_header;
}


int Catalog::indexForMsgid(const QString& id) const
{
    int i=0;
	QValueList<CatalogItem>::ConstIterator it = d->_entries.begin(); 

    while(it != d->_entries.end() && (*it).msgid() != id)
    {
        ++it; 
        i++;
    }

    if(it == d->_entries.end())
        i=-1;

    return i;
}

QStringList Catalog::tagList(uint index)
{
   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].tagList();
}


QStringList Catalog::argList(uint index)
{
   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].argList();
}


/*
bool Catalog::setMsgstr(uint index,QString msgstr)
{
    kdWarning() << "Catalog::setMsgstr()" << endl;

   bool untranslatedChanged=false;

   if(_entries[index].isUntranslated() && !msgstr.isEmpty())
   {
      _untransIndex.remove(index);
      untranslatedChanged=true;
   }
   else if(msgstr.isEmpty())
   {
      QValueList<uint>::Iterator it;

      // insert index in the right place in the list
      it = _untransIndex.begin();
      while(it != _untransIndex.end() && index > (*it))
      {
         ++it;
      }
      _untransIndex.insert(it,index);

      untranslatedChanged=true;
   }

   _entries[index].setMsgstr(msgstr);

   setModified(true);

   if(untranslatedChanged)
      emit signalNumberOfUntranslatedChanged(numberOfUntranslated());

   return untranslatedChanged;
}
*/

/*
bool Catalog::setComment(uint index,QString comment)
{
    kdWarning() << "Catalog::setComment()" << endl;
   bool fuzziesChanged=false;


   bool wasFuzzy=_entries[index].isFuzzy();

   _entries[index].setComment(comment);

   bool isFuzzy=_entries[index].isFuzzy();

   if(wasFuzzy && !isFuzzy)
   {
      _fuzzyIndex.remove(index);
      fuzziesChanged=true;
   }
   else if(isFuzzy)
   {
      QValueList<uint>::Iterator it;

      // insert index in the right place in the list
      it = _fuzzyIndex.begin();
      while(it != _fuzzyIndex.end() && index > (*it))
      {
         ++it;
      }
      _fuzzyIndex.insert(it,index);

      fuzziesChanged=true;
   }

   setModified(true);

   if(fuzziesChanged)
      emit signalNumberOfFuzziesChanged(numberOfFuzzies());


   return fuzziesChanged;
}
*/

bool Catalog::setHeader(CatalogItem newHeader)
{
   if(newHeader.isValid())
   {
      d->_header=newHeader;
      setModified(true);

      emit signalHeaderChanged();
      
      return true;
   }

   return false;
}

KURL Catalog::currentURL() const
{
   return d->_url;
}

void Catalog::setCurrentURL(const KURL& url)
{
   d->_url=url;
}


CatalogItem Catalog::updatedHeader(CatalogItem oldHeader, bool usePrefs) const
{
   QStringList headerList=oldHeader.msgstrAsList();
   QStringList commentList=QStringList::split('\n',oldHeader.comment());

   QStringList::Iterator it,ait;
   QString temp;
   bool found;
   if(!usePrefs || d->_saveSettings.updateLastTranslator)
   {
      found=false;

      temp="Last-Translator: "+d->_identitySettings.authorName;
      if(!d->_identitySettings.authorEmail.isEmpty())
      {
         temp+=(" <"+d->_identitySettings.authorEmail+">");
      }
      temp+="\\n";
      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Last-Translator:.*")))
         {
            (*it).replace(QRegExp("^ *Last-Translator:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateRevisionDate)
   {
      found=false;

      temp="PO-Revision-Date: "+dateTime()+"\\n";

      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *PO-Revision-Date:.*")))
         {
            (*it).replace(QRegExp("^ *PO-Revision-Date:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateProject)
   {
      found=false;

      temp="Project-Id-Version: "+d->_saveSettings.projectString+"\\n";
      temp.replace( QRegExp("@PACKAGE@"), packageName());

      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Project-Id-Version:.*")))
         {
            (*it).replace(QRegExp("^ *Project-Id-Version:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateLanguageTeam)
   {
      found=false;

      temp="Language-Team: "+d->_identitySettings.languageName;
      if(!d->_identitySettings.mailingList.isEmpty())
      {
         temp+=(" <"+d->_identitySettings.mailingList+">");
      }
      temp+="\\n";
      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Language-Team:.*")))
         {
            (*it).replace(QRegExp("^ *Language-Team:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateCharset)
   {
      found=false;

	  QString encodingStr;
      if(d->_saveSettings.useOldEncoding && d->fileCodec)
      {
		  encodingStr = charsetString(d->fileCodec);
      }
      else
      {
          encodingStr=charsetString(d->_saveSettings.encoding);
      }
      temp="Content-Type: text/plain; charset="+encodingStr+"\\n";
      QString charsettemp="; charset="+encodingStr+"\\n";

      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Content-Type:.*;\\s*charset=")))
         {
            (*it).replace(QRegExp(";\\s*charset\\s*=\\s*[^\\\"\\n]+"),charsettemp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateEncoding)
   {
      found=false;

      temp="Content-Transfer-Encoding: 8bit\\n";

      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Content-Transfer-Encoding:.*")))
         {
            (*it).replace(QRegExp("^ *Content-Transfer-Encoding:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }

   temp="X-Generator: KBabel %1\\n";
   temp=temp.arg(VERSION);
   found=false;

   for( it = headerList.begin(); it != headerList.end(); ++it )
   {
      if((*it).contains(QRegExp("^ *X-Generator:.*")))
      {
         (*it).replace(QRegExp("^ *X-Generator:.*"),temp);
         found=true;
      }
    }
    if(!found)
    {
       headerList.append(temp);
    }

   QString msgstr;
   for( it = headerList.begin(); it != headerList.end(); ++it )
   {
      msgstr+=("\n"+(*it));
   }

   msgstr.remove(0,1);// remove first newline

   oldHeader.setMsgstr(msgstr);

   //comment = description, copyrights
   if(!usePrefs || (d->_saveSettings.FSFCopyright != NoChange))
   {
      found=false;

      for( it = commentList.begin(); it != commentList.end(); ++it )
      {
         if((*it).contains(QRegExp("^# *Copyright \\(C\\).*Free Software Foundation, Inc")))
         {
            found=true;
	    break;
         }
       }
       if(found)
       {
	    if((*it).contains(QRegExp("^# *Copyright \\(C\\) YEAR Free Software Foundation, Inc\\.")))
	    {
		//template string
    		if(d->_saveSettings.FSFCopyright == Remove) (*it).replace(QRegExp(" YEAR Free Software Foundation, Inc"),"");
		else (*it).replace(QRegExp("YEAR"), QDate::currentDate().toString("yyyy"));
	    } else 
	    if( d->_saveSettings.FSFCopyright == Update )
	    {
		    //update years
		    QString cy = QDate::currentDate().toString("yyyy");
		    if( !(*it).contains( QRegExp(cy)) ) // is the year already included?
		    {
			int index = (*it).findRev( QRegExp("[\\d]+[\\d\\-, ]*") );
			if( index == -1 ) 
			{
			    KMessageBox::information(0,i18n("Free Software Foundation Copyright does not contain any year. "
			    "It will not be updated."));
			} else {
			    (*it).insert(index+1, QString(", ")+cy);
			}
		    }
	    }
	} 
   }

   if(!usePrefs || d->_saveSettings.updateDescription)
   {
      temp="# "+d->_saveSettings.descriptionString;
      temp.replace( QRegExp("@PACKAGE@"), packageName());
      temp.replace( QRegExp("@LANGUAGE@"), d->_identitySettings.languageName);

      found=false;
      bool foundTemplate=false;

      for( it = commentList.begin(); it != commentList.end(); ++it )
      {
         if((*it).contains(QRegExp("^"+temp+"$")))
         {
            found=true;
         }
	 if((*it).contains(QRegExp("^# SOME DESCRIPTIVE TITLE.$")))
	 {
	    ait = it;
	    foundTemplate = true;
	 }
       }
       if(foundTemplate) commentList.remove(ait);
       if(!found) commentList.prepend(temp);
   }

   if(!usePrefs || d->_saveSettings.updateTranslatorCopyright)
   {
      QStringList foundAuthors;

      temp="# "+d->_identitySettings.authorName;
      if(!d->_identitySettings.authorEmail.isEmpty())
      {
         temp+=(" <"+d->_identitySettings.authorEmail+">");
      }
      temp+=", "+QDate::currentDate().toString("yyyy")+".";
      
      for( it = commentList.begin(); it != commentList.end(); ++it )
      {
         if((*it).contains(QRegExp("^#.*<.+@.+>,\\s*([\\d]+[\\d\\-, ]*)|(YEAR)"))) // email address followed by year
         {
	    foundAuthors.append( (*it) );
         }
       }
       
       for( it = foundAuthors.begin() ; it != foundAuthors.end() ; ++it )
    	    commentList.remove( (*it) );
	    
	it=qFind(foundAuthors.begin(), foundAuthors.end(), QString("# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR."));
	if( it != foundAuthors.end() ) foundAuthors.remove( it );
       
       if(foundAuthors.size()>0)
       {
          found = false;
	  bool foundAuthor = false;

	  QString cy = QDate::currentDate().toString("yyyy");
	  
	  for( it = foundAuthors.begin() ; it!=foundAuthors.end(); ++it )
	  {
		if( (*it).contains(QRegExp(d->_identitySettings.authorName+".*"
		    +d->_identitySettings.authorEmail)))
		{
		    foundAuthor = true;
		    if( (*it).contains(QRegExp(cy))) found = true;
		    else ait = it;
		}
	   }
	   if( !found )
		if( !foundAuthor ) foundAuthors.append(temp);
		else
		{
		    //update years
		    int index = (*ait).findRev( QRegExp("[\\d]+[\\d\\-, ]*") );
		    if( index == -1 ) (*ait)+=", "+cy;
		    else (*ait).insert(index+1, QString(", ")+cy);
		}
		
	}
	else foundAuthors.append(temp);
	it=commentList.end();
	do 
	    --it;
	while( (*it).contains( QRegExp( "^#\\s*$")) || (*it).contains( QRegExp( "^#[:,\\.]")));
	it++;
	for( ait = foundAuthors.begin() ; ait != foundAuthors.end() ; ++ait )
	{
	    QString s = (*ait);

	    // ensure dot at the end of copyright
	    if( !s.endsWith(".") ) s += ".";
	    commentList.insert(it, s);
	}
   }

   QString comment;
   for( it = commentList.begin(); it != commentList.end(); ++it )
   {
      comment+=("\n"+(*it));
   }

   comment.remove(0,1);// remove first newline
   
   oldHeader.setComment(comment);
   
   return oldHeader;
}

void Catalog::setFuzzy(uint index, bool on)
{
   uint max=d->_entries.count()-1;
   if(index > max)
       return;

   if(d->_entries[index].isFuzzy() != on)
   {
      EditCommand *cmd;
      cmd=new BeginCommand();
      cmd->setPart(EditCommand::Comment);
      cmd->setIndex(index);
      applyEditCommand( cmd, 0 );
      
      QPtrList<EditCommand> editList;
      if(on)
      {
          editList=d->_entries[index].addFuzzy(false);
      }
      else
      {
          editList=d->_entries[index].removeFuzzy(false);
          d->_fuzzyIndex.remove(index);
      }

      for ( cmd=editList.first(); cmd != 0; cmd=editList.next() )
      {
         cmd->setIndex(index);
         applyEditCommand(cmd,0);
      }

      setModified(true);

      cmd=new EndCommand();
      cmd->setPart(EditCommand::Comment);
      cmd->setIndex(index);
      applyEditCommand( cmd ,0);

      emit signalNumberOfFuzziesChanged(numberOfFuzzies());
   }

}

void Catalog::removeFuzzyStatus(uint index)
{
    setFuzzy(index,false);
}


void Catalog::setModified(bool flag)
{
    bool old=d->_modified;
    d->_modified=flag;

    if(old!=d->_modified)
       emit signalModified(flag);
}


QString Catalog::packageName() const
{
    if( !d->_packageName.isNull() ) return d->_packageName;
    
    QString package=d->_url.fileName();

    int index=package.find(QRegExp("(\\."+d->_identitySettings.languageCode+")?\\.pot?$"));

    if(index>0)
      package=package.left(index);

    return package;
}

void Catalog::setPackage(const QString& package )
{
    int pos=package.findRev("/");
    if( pos<0 )
    {
	d->_packageDir = "";
	d->_packageName = package;
	d->_packageName.replace( QRegExp("^/+"),"");
    }
    else
    {
	d->_packageDir = package.left(pos);
	if( !d->_packageDir.endsWith("/") ) d->_packageDir+="/";
	d->_packageName = package.right(package.length()-pos);
	d->_packageName.replace( QRegExp("^/+"),"");
	kdDebug() << "package dir " << d->_packageDir << " and package name " << d->_packageName << endl; 
    }
}

QString Catalog::packageDir() const
{
    QString result;
    if( !d->_packageDir.isNull() ) result=d->_packageDir;
    else result=d->_url.directory(false);
    
    return result;
}

QString Catalog::encoding() const
{
    QString encodingStr;
    if(d->_saveSettings.useOldEncoding && d->fileCodec)
    {
	    encodingStr = charsetString(d->fileCodec);
    }
    else
    {
        encodingStr= charsetString(d->_saveSettings.encoding);
    }

    return encodingStr;
}

Catalog::IOStatus Catalog::openURL(const KURL& url, bool& errorInHeader, const QString& package)
{
   QString target;

   if(KIO::NetAccess::download(url, target))
   {
        // load in the file (target is always local)
        IOStatus success=openFile(target,errorInHeader);

        // and remove the temp file
        KIO::NetAccess::removeTempFile(target);

        // store current url
        if(success==OK || success==RECOVERED_PARSE_ERROR)
        {
           setModified(false);
           d->_url=url;
	   if( package.isNull() )
	   {
		d->_packageName=QString::null;
		d->_packageDir=QString::null;
	   }
	   else setPackage(package);

           emit signalFileOpened(d->_readOnly);
           emit signalNumberOfFuzziesChanged(numberOfFuzzies());
           emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
           emit signalTotalNumberChanged(numberOfEntries());
        }

        return success;
   }
   else
   {
      return OS_ERROR;
   }
}

Catalog::IOStatus Catalog::openURL(const KURL& openUrl, const KURL& saveURL, bool& errorInHeader, const QString& package)
{
   QString target;

   if(KIO::NetAccess::download(openUrl, target))
   {
        // load in the file (target is always local)
        IOStatus success=openFile(target,errorInHeader);

        // and remove the temp file
        KIO::NetAccess::removeTempFile(target);

        // store current url
        if(success==OK || success==RECOVERED_PARSE_ERROR)
        {
           setModified(false);
           d->_url = saveURL;
	   if( package.isNull() )
	   {
		d->_packageName=QString::null;
		d->_packageDir=QString::null;
	   }
	   else setPackage(package);

           emit signalFileOpened(d->_readOnly);
           emit signalNumberOfFuzziesChanged(numberOfFuzzies());
           emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
           emit signalTotalNumberChanged(numberOfEntries());
        }

        return success;
   }
   else
   {
      return OS_ERROR;
   }
}

Msgfmt::Status Catalog::checkSyntax(QString& output, bool clearErrors)
{
   QString filename;
   bool tempFileUsed=false;

   if(d->_url.isLocalFile() && !isModified())
   {
      filename=d->_url.path(0);
   }
   else
   {
      tempFileUsed=true;
      filename=saveTempFile();
   }

   Msgfmt msgfmt;
   Msgfmt::Status result = msgfmt.checkSyntax( filename , output );

   if( clearErrors) clearErrorList();

   if( result==Msgfmt::SyntaxError )
   {
      int currentIndex=-1;
      int currentLine=0;

      if( !d->_header.msgstr().isEmpty() )
         currentLine=d->_header.totalLines()+1;

      QStringList lines = QStringList::split("\n",output);
      for ( QStringList::Iterator it = lines.begin(); it != lines.end(); ++it )
      {
         if( (*it).contains(QRegExp("^.+:\\d+:")) )
         {
            int begin=(*it).find(":",0)+1;
            int end=(*it).find(":",begin);

            QString line=(*it).mid(begin,end-begin);

            while( line.toInt() > currentLine )
            {
               currentIndex++;
               currentLine += ( d->_entries[currentIndex].totalLines() + 1 );
            }

            if( !d->_errorIndex.contains(currentIndex) )
            {
               d->_errorIndex.append(currentIndex);
			   d->_entries[currentIndex].setSyntaxError(true);
            }
         }
      }
   }

   if(tempFileUsed)
      QFile::remove(filename);

   return result;
}

void Catalog::clearErrorList()
{
	QValueList<uint>::Iterator it;
	for(it = d->_errorIndex.begin(); it != d->_errorIndex.end(); ++it)
	{
		d->_entries[(*it)].setSyntaxError(false);
	}

	d->_errorIndex.clear();
}

void Catalog::removeFromErrorList(uint index)
{
	if(d->_errorIndex.contains(index))
	{
		d->_errorIndex.remove(index);
		d->_entries[index].setSyntaxError(false);
	}
}

int Catalog::itemStatus(uint index, bool recheck, int whatToCheck)
{
	uint max=d->_entries.count()-1;
	if(index > max)
		index=max;

	CatalogItem& item = d->_entries[index]; 

	if(recheck)
	{
		if(whatToCheck & CatalogItem::Args)
			item.checkArgs(d->_miscSettings.contextInfo);

		if(whatToCheck & CatalogItem::Accel)
			item.checkAccelerator(d->_miscSettings.accelMarker,d->_miscSettings.contextInfo);

		if(whatToCheck & CatalogItem::Equation)
			item.checkEquation();

		if(whatToCheck & CatalogItem::Context)
			item.checkForContext(d->_miscSettings.contextInfo);
        
		if((whatToCheck & CatalogItem::SingularPlural))
        {
			item.checkSingularPlural(d->_miscSettings.singularPlural
                    ,d->numberOfPluralForms);
        }

		if((whatToCheck & CatalogItem::XmlTags))
			item.checkXmlTags();
	}

	return item.errors();
}

bool Catalog::checkArgs(bool clearErrors)
{
	if(clearErrors)
		clearErrorList();

	int index = 0;
	bool hasErrors=false;
	for ( QValueList<CatalogItem>::Iterator it = d->_entries.begin(); 
					it != d->_entries.end(); ++it, index++ )
	{
		if(!(*it).checkArgs(d->_miscSettings.contextInfo))
		{
			if( !d->_errorIndex.contains(index) )
			{
				d->_errorIndex.append(index);
				hasErrors=true;
			}
		}
	}
	
	if( hasErrors && !clearErrors ) qHeapSort(d->_errorIndex);

	return !hasErrors;
}


bool Catalog::checkAccelerators(bool clearErrors)
{
	if(clearErrors)
		clearErrorList();

	int index = 0;
	bool hasErrors=false;
	for ( QValueList<CatalogItem>::Iterator it = d->_entries.begin(); 
					it != d->_entries.end(); ++it , index++)
	{
		if(!(*it).checkAccelerator(d->_miscSettings.accelMarker,d->_miscSettings.contextInfo))
		{
			if( !d->_errorIndex.contains(index) )
			{
				d->_errorIndex.append(index);
				hasErrors=true;
			}
		}
	}

	if( hasErrors && !clearErrors ) qHeapSort(d->_errorIndex);

	return !hasErrors;
}

bool Catalog::checkEquations(bool clearErrors)
{
	if(clearErrors)
		clearErrorList();

	int index = 0;
	bool hasErrors=false;
	for ( QValueList<CatalogItem>::Iterator it = d->_entries.begin(); 
					it != d->_entries.end(); ++it , index++)
	{
		if(!(*it).checkEquation())
		{
			if( !d->_errorIndex.contains(index) )
			{
				d->_errorIndex.append(index);
				hasErrors=true;
			}
		}
	}

	if( hasErrors && !clearErrors ) qHeapSort(d->_errorIndex);

	return !hasErrors;
}


bool Catalog::checkForContext(bool clearErrors)
{
	if(clearErrors)
		clearErrorList();

	int index = 0;
	bool hasErrors=false;
	for ( QValueList<CatalogItem>::Iterator it = d->_entries.begin(); 
					it != d->_entries.end(); ++it , index++)
	{
		if(!(*it).checkForContext(d->_miscSettings.contextInfo))
		{
			if( !d->_errorIndex.contains(index) )
			{
				d->_errorIndex.append(index);
				hasErrors=true;
			}
		}
	}

	if( hasErrors && !clearErrors ) qHeapSort(d->_errorIndex);

	return !hasErrors;
}

bool Catalog::checkSingularPlural(bool clearErrors)
{
    if(clearErrors)
		clearErrorList();
    
	int index = 0;
	bool hasErrors=false;
	for ( QValueList<CatalogItem>::Iterator it = d->_entries.begin(); 
					it != d->_entries.end(); ++it , index++)
	{
		if(!(*it).checkSingularPlural(d->_miscSettings.singularPlural
                    ,d->numberOfPluralForms))
		{
			if( !d->_errorIndex.contains(index) )
			{
				d->_errorIndex.append(index);
				hasErrors=true;
			}
		}
	}

	if( hasErrors && !clearErrors ) qHeapSort(d->_errorIndex);

	return !hasErrors;
}


bool Catalog::checkXmlTags(bool clearErrors)
{
    if(clearErrors)
		clearErrorList();
    
	int index = 0;
	bool hasErrors=false;
	for ( QValueList<CatalogItem>::Iterator it = d->_entries.begin(); 
					it != d->_entries.end(); ++it , index++)
	{
		if(!(*it).checkXmlTags())
		{
			if( !d->_errorIndex.contains(index) )
			{
				d->_errorIndex.append(index);
				hasErrors=true;
			}
		}
	}

	if( hasErrors && !clearErrors ) qHeapSort(d->_errorIndex);

	return !hasErrors;
}


void Catalog::clear()
{
    d->_entries.clear();
    d->_url=KURL();
    d->_obsolete.clear();

    if(d->_undoList.count() > 0)
       emit signalUndoAvailable(false);
    if(d->_redoList.count() > 0)
       emit signalRedoAvailable(false);

    d->_undoList.clear();
    d->_redoList.clear();

    d->msgidDiffList.clear();
    d->msgstr2MsgidDiffList.clear();
    d->diffCache.clear();
}


uint Catalog::numberOfEntries() const
{
   return d->_entries.count();
}

uint Catalog::numberOfFuzzies() const
{
   return d->_fuzzyIndex.count();
}

uint Catalog::numberOfUntranslated() const
{
   return d->_untransIndex.count();
}


bool Catalog::hasFuzzyInFront(uint index)  const
{
   if(findPrevInList(d->_fuzzyIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasFuzzyAfterwards(uint index) const
{
   if(findNextInList(d->_fuzzyIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasUntranslatedInFront(uint index) const
{
   if(findPrevInList(d->_untransIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasUntranslatedAfterwards(uint index) const
{
   if(findNextInList(d->_untransIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasErrorInFront(uint index)  const
{
   if(findPrevInList(d->_errorIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasErrorAfterwards(uint index) const
{
   if(findNextInList(d->_errorIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::isFuzzy(uint index) const
{
   if(index > numberOfEntries())
      return false;

   return d->_entries[index].isFuzzy();
}


bool Catalog::isUntranslated(uint index) const
{
   if(index > numberOfEntries())
      return false;

   return d->_entries[index].isUntranslated();
}

bool Catalog::hasError(uint index) const
{
   return d->_errorIndex.contains(index);
}

bool Catalog::isPluralForm(uint index) const
{
    if(index > numberOfEntries())
        return false;

    return d->_entries[index].isPluralForm();
}

int Catalog::nextFuzzy(uint startIndex) const
{
   return findNextInList(d->_fuzzyIndex,startIndex);
}

int Catalog::prevFuzzy(uint startIndex) const
{
   return findPrevInList(d->_fuzzyIndex,startIndex);
}

int Catalog::nextUntranslated(uint startIndex) const
{
   return findNextInList(d->_untransIndex,startIndex);
}

int Catalog::prevUntranslated(uint startIndex) const
{
   return findPrevInList(d->_untransIndex,startIndex);
}


int Catalog::nextError(uint startIndex) const
{
   return findNextInList(d->_errorIndex,startIndex);
}

int Catalog::prevError(uint startIndex) const
{
   return findPrevInList(d->_errorIndex,startIndex);
}


void Catalog::registerView(CatalogView* view)
{
   if(d->_views.containsRef(view)==0)
   {
      d->_views.append(view);
   }
}


void Catalog::removeView(CatalogView* view)
{
   d->_views.removeRef(view);
}


void Catalog::updateViews(EditCommand* cmd,CatalogView* view2exclude)
{
    CatalogView* view;
    for ( view=d->_views.first(); view != 0; view=d->_views.next())
    {
       if(view!=view2exclude)
       {
          view->update(cmd);
       }
    }
}



bool Catalog::hasView() const
{
    if(d->_views.count()==0)
           return false;

    return true;
}

bool Catalog::isLastView() const
{
    if(d->_views.count()<=1)
           return true;

    return false;
}


void Catalog::readPreferences(KConfig *config)
{
   KConfigGroupSaver groupSaver(config,"Header");

   d->_saveSettings.autoUpdate=config->readBoolEntry("AutoUpdate"
				   ,Defaults::Save::autoUpdate);
   d->_saveSettings.updateLastTranslator=config->readBoolEntry("Update-Last-Translator"
                       ,Defaults::Save::updateLastTranslator);
   d->_saveSettings.updateRevisionDate=config->readBoolEntry("Update-Revision-Date"
                       ,Defaults::Save::updateRevisionDate);
   d->_saveSettings.updateLanguageTeam=config->readBoolEntry("Update-Language-Team"
                       ,Defaults::Save::updateLanguageTeam);
   d->_saveSettings.updateCharset=config->readBoolEntry("Update-Charset"
                       ,Defaults::Save::updateCharset);
   d->_saveSettings.updateEncoding=config->readBoolEntry("Update-Encoding"
                       ,Defaults::Save::updateEncoding);
   d->_saveSettings.encoding=(FileEncoding)(config->readNumEntry("Encoding"
						   ,(int)Defaults::Save::encoding));
   d->_saveSettings.useOldEncoding=config->readBoolEntry("UseOldEncoding"
                           ,Defaults::Save::useOldEncoding);
			   
   d->_saveSettings.updateProject=config->readBoolEntry("Update-Project"
                       ,Defaults::Save::updateProject);
   d->_saveSettings.projectString=config->readEntry("ProjectString"
                       ,Defaults::Save::projectString());
		       
   d->_saveSettings.autoSyntaxCheck = config->readBoolEntry("AutoSyntaxCheck"
                 ,Defaults::Save::autoSyntaxCheck);
   d->_saveSettings.saveObsolete = config->readBoolEntry("SaveObsolete"
                 ,Defaults::Save::saveObsolete);
   d->_saveSettings.customDateFormat = config->readEntry("CustomDateFormat"
                 ,Defaults::Save::customDateFormat());
   d->_saveSettings.dateFormat = (Qt::DateFormat)( config->readNumEntry("DateFormat"
                 ,(int)Defaults::Save::dateFormat) );
   d->_saveSettings.updateDescription = config->readBoolEntry("UpdateDescription"
		 ,Defaults::Save::updateDescription);
   d->_saveSettings.descriptionString = config->readEntry("DescriptionString"
		 ,Defaults::Save::descriptionString());
   d->_saveSettings.updateTranslatorCopyright = config->readBoolEntry("UpdateTranslatorCopyright"
		 ,Defaults::Save::updateTranslatorCopyright);
   d->_saveSettings.FSFCopyright=(CopyrightUpdate)(config->readNumEntry("FSFCopyright"
						   ,(int)Defaults::Save::FSFCopyright));

    Defaults::Identity defaultIdentity;
   d->_identitySettings.authorName=config->readEntry("Author-Name"
				   ,defaultIdentity.authorName());
   d->_identitySettings.authorLocalizedName=config->readEntry("Local-Author-Name"
				   ,defaultIdentity.authorName());
   d->_identitySettings.authorEmail=config->readEntry("Author-Email"
				   ,defaultIdentity.authorEmail());
   d->_identitySettings.languageName=config->readEntry("Language"
				   ,defaultIdentity.languageName());
   d->_identitySettings.languageCode=config->readEntry("LanguageCode"
				   ,defaultIdentity.languageCode());
   d->_identitySettings.mailingList=config->readEntry("Mailinglist"
				   ,defaultIdentity.mailingList());
   d->_identitySettings.timeZone=config->readEntry("Timezone"
				   ,defaultIdentity.timezone());

   config->setGroup("Misc");
   
   Defaults::Misc defaultMisc;
   QString temp=config->readEntry("AccelMarker"
				   ,defaultMisc.accelMarker());
   if(temp.length() > 0)
		   d->_miscSettings.accelMarker=temp[0];

   temp = config->readEntry("ContextInfo"
				   ,defaultMisc.contextInfo().pattern());
   d->_miscSettings.contextInfo.setPattern(temp);
   
   temp = config->readEntry("SingularPlural"
				   ,defaultMisc.singularPlural().pattern());
   d->_miscSettings.singularPlural.setPattern(temp);

   d->_identitySettings.numberOfPluralForms=config->readNumEntry("PluralForms"
           , defaultIdentity.numberOfPluralForms);

   getNumberOfPluralForms();

   d->_miscSettings.useBzip = config->readBoolEntry("BZipCompression", defaultMisc.useBzip);
   d->_miscSettings.compressSingleFile = config->readBoolEntry("CompressSingleFile", defaultMisc.compressSingleFile);
}

void Catalog::savePreferences(KConfig *config)
{
   KConfigGroupSaver groupSaver(config,"Header");

   config->writeEntry("AutoUpdate",d->_saveSettings.autoUpdate);
   config->writeEntry("Update-Last-Translator",d->_saveSettings.updateLastTranslator);
   config->writeEntry("Update-Revision-Date",d->_saveSettings.updateRevisionDate);
   config->writeEntry("Update-Language-Team",d->_saveSettings.updateLanguageTeam);
   config->writeEntry("Update-Charset",d->_saveSettings.updateCharset);
   config->writeEntry("Update-Encoding",d->_saveSettings.updateEncoding);
   config->writeEntry("Encoding",(int)d->_saveSettings.encoding);
   config->writeEntry("UseOldEncoding",d->_saveSettings.useOldEncoding);

   config->writeEntry("Update-Project", d->_saveSettings.updateProject);
   config->writeEntry("ProjectString", d->_saveSettings.projectString);

   config->writeEntry("AutoSyntaxCheck",d->_saveSettings.autoSyntaxCheck);
   config->writeEntry("SaveObsolete",d->_saveSettings.saveObsolete);
   config->writeEntry("CustomDateFormat",d->_saveSettings.customDateFormat);
   config->writeEntry("DateFormat",(int)d->_saveSettings.dateFormat);

   config->writeEntry("UpdateDescription", d->_saveSettings.updateDescription);
   config->writeEntry("DescriptionString", d->_saveSettings.descriptionString);
   config->writeEntry("UpdateTranslatorCopyright",d->_saveSettings.updateTranslatorCopyright);
   config->writeEntry("FSFCopyright", (int)d->_saveSettings.FSFCopyright);

   config->writeEntry("Author-Name",d->_identitySettings.authorName);
   config->writeEntry("Local-Author-Name",d->_identitySettings.authorLocalizedName);
   config->writeEntry("Author-Email",d->_identitySettings.authorEmail);
   config->writeEntry("Language",d->_identitySettings.languageName);
   config->writeEntry("LanguageCode",d->_identitySettings.languageCode);
   config->writeEntry("Mailinglist",d->_identitySettings.mailingList);
   config->writeEntry("Timezone",d->_identitySettings.timeZone);


   config->setGroup("Misc");
   QString temp(d->_miscSettings.accelMarker);
   config->writeEntry("AccelMarker",temp);
   config->writeEntry("ContextInfo",d->_miscSettings.contextInfo.pattern());
   config->writeEntry("SingularPlural",d->_miscSettings.singularPlural.pattern());
   config->writeEntry("PluralForms",d->_identitySettings.numberOfPluralForms);
   config->writeEntry("BZipCompression", d->_miscSettings.useBzip);
   config->writeEntry("CompressSingleFile", d->_miscSettings.compressSingleFile);
   
   config->sync();
}

IdentitySettings Catalog::identitySettings() const
{
    return d->_identitySettings;
    
}

SaveSettings Catalog::saveSettings() const
{
    return d->_saveSettings;
    
}

MiscSettings Catalog::miscSettings() const
{
    return d->_miscSettings;
    
}

bool Catalog::isGeneratedFromDocbook() const
{
    return d->_generatedFromDocbook;
}

QString Catalog::package() const 
{
    return packageDir()+packageName();
}

bool Catalog::isReadOnly() const
{
    return d->_readOnly;
}

void Catalog::setSettings(SaveSettings settings)
{
   d->_saveSettings=settings;

   emit signalSettingsChanged(settings);
}

void Catalog::setSettings(IdentitySettings settings)
{
   QString oldLanguageCode = d->_identitySettings.languageCode;
   int oldForms =d->_identitySettings.numberOfPluralForms;
    
   
   d->_identitySettings=settings;

   if(oldLanguageCode != d->_identitySettings.languageCode)
   {
       getNumberOfPluralForms();
   }

    if(oldForms != d->_identitySettings.numberOfPluralForms)
    {
        getNumberOfPluralForms();
    }

   emit signalSettingsChanged(settings);
}

void Catalog::setSettings(MiscSettings settings)
{
	d->_miscSettings=settings;

	emit signalSettingsChanged(settings);
}

void Catalog::generateIndexLists()
{
   d->_fuzzyIndex.clear();
   d->_untransIndex.clear();
   clearErrorList();

   uint counter=0;
   for ( QValueList<CatalogItem>::Iterator it = d->_entries.begin(); it != d->_entries.end(); ++it )
   {
       if((*it).isUntranslated())
       {
          d->_untransIndex.append(counter);
       }
       else if((*it).isFuzzy())
       {
          d->_fuzzyIndex.append(counter);
       }

       counter++;
   }

}

int Catalog::findNextInList(const QValueList<uint>& list,uint index) const
{
    QValueList<uint>::ConstIterator it;

    int nextIndex=-1;

    // find index in List
    it=list.find(index);

    // if the given index is found in the list and not the last entry
    // in the list, return the next listentry
    if(it!=list.end() && it!=list.fromLast())
    {
       ++it;
       return (*it);
    }

    // if the index is not in the list, search the index in the list, that
    // is the nearest to the given index
    for( it = list.begin(); it != list.end(); ++it )
    {
       if((*it) > index)
       {
          nextIndex=(*it);
          break;
       }
    }


    return nextIndex;
}

int Catalog::findPrevInList(const QValueList<uint>& list,uint index) const
{
    QValueList<uint>::ConstIterator it;

    int prevIndex=-1;

    it=list.find(index);

    // if the given index is found in the list and not the last entry
    // in the list, return the next listentry
    if(it!=list.end() && it!=list.begin())
    {
       --it;
       return (*it);
    }


    // if the index is not in the list, search the index in the list, that
    // is the nearest to the given index
    for( it = list.fromLast(); it != list.end(); --it )
    {
       if((*it) < index)
       {
          prevIndex=(*it);
          break;
       }
    }


    return prevIndex;
}


QString Catalog::dateTime() const
{
    QString dateTimeString;
    QDate date=QDate::currentDate();
    QTime time=QTime::currentTime();

    switch(d->_saveSettings.dateFormat)
    {
       case Qt::LocalDate:
       {
          return KGlobal::locale()->formatDateTime(QDateTime::currentDateTime());
       }
       case Qt::ISODate:
          dateTimeString = Defaults::Save::customDateFormat();
          break;
       case Qt::TextDate:
          dateTimeString = d->_saveSettings.customDateFormat;
          break;
    }

    // the year
    dateTimeString.replace( QRegExp("%Y"), QString::number( date.year() ) );
    dateTimeString.replace( QRegExp("%y"), QString::number( date.year() ).right(2) );

    // the month
    if(date.month()<10)
    {
       dateTimeString.replace( QRegExp("%m"), "0"+QString::number( date.month() ) );
    }
    else
    {
       dateTimeString.replace( QRegExp("%m"), QString::number( date.month() ) );
    }

    dateTimeString.replace( QRegExp("%f"), QString::number( date.month() ) );

    dateTimeString.replace( QRegExp("%b"), date.longMonthName(date.month()) );
    dateTimeString.replace( QRegExp("%h"), date.longMonthName(date.month()) );

    // the day
    dateTimeString.replace( QRegExp("%j"), QString::number( date.dayOfYear() ) );
    dateTimeString.replace( QRegExp("%e"), QString::number( date.day() ) );
    if(date.day() < 10)
    {
       dateTimeString.replace( QRegExp("%d"), "0"+QString::number( date.day() ) );
    }
    else
    {
       dateTimeString.replace( QRegExp("%d"), QString::number( date.day() ) );
    }

    dateTimeString.replace( QRegExp("%a"), date.longDayName( date.dayOfWeek() ) );


    // hour
    dateTimeString.replace( QRegExp("%k"), QString::number( time.hour() ) );

    if(time.hour() < 10)
    {
       dateTimeString.replace( QRegExp("%H"), "0"+QString::number( time.hour() ) );
    }
    else
    {
       dateTimeString.replace( QRegExp("%H"), QString::number( time.hour() ) );
    }

    QString zone;
    int hour;
    if( time.hour() > 12 )
    {
       zone="PM";
       hour=time.hour()-12;
    }
    else
    {
       zone="AM";
       hour=time.hour();
    }

    dateTimeString.replace( QRegExp("%I"), QString::number( hour ) );

    if(hour < 10)
    {
       dateTimeString.replace( QRegExp("%i"), "0"+QString::number( hour ) );
    }
    else
    {
       dateTimeString.replace( QRegExp("%i"), QString::number( hour ) );
    }

    dateTimeString.replace( QRegExp("%p"), zone );

    // minutes
    if(time.minute() < 10)
    {
       dateTimeString.replace( QRegExp("%M"), "0"+QString::number( time.minute() ) );
    }
    else
    {
       dateTimeString.replace( QRegExp("%M"), QString::number( time.minute() ) );
    }

    // seconds
    if(time.second() < 10)
    {
       dateTimeString.replace( QRegExp("%S"), "0"+QString::number( time.second() ) );
    }
    else
    {
       dateTimeString.replace( QRegExp("%S"), QString::number( time.second() ) );
    }

    // timezone
    dateTimeString.replace( QRegExp("%Z"), d->_identitySettings.timeZone );
    QTime t;
    int sgn = KRFCDate::localUTCOffset() < 0 ? -1 : 1 ; 
    t = t.addSecs( sgn*KRFCDate::localUTCOffset()*60 );
    dateTimeString.replace( QRegExp("%z"), (sgn<0 ? "-" : "+") +t.toString("hhmm"));

    return dateTimeString;
}


Catalog::IOStatus Catalog::readHeader(QTextStream& stream, CatalogItem& header)
{
   CatalogItem temp;
   int filePos=stream.device()->at();
   CatalogItem::IOStatus status=temp.read(stream);

   if(status==CatalogItem::Ok || status==CatalogItem::RecoveredParseError)
   {
      // test if this is the header
      if(temp.msgid().isEmpty())
      {
          header=temp;
          if(header.isFuzzy())
          {
             header.removeFuzzy();
          }
      }
      else
      {
         stream.device()->at(filePos);
      }

      if(status==CatalogItem::RecoveredParseError)
      	return RECOVERED_PARSE_ERROR;
      else
	      return OK;
   }

   return PARSE_ERROR;

}

Catalog::IOStatus Catalog::openFile(QString filename, bool& errorInHeader)
{
	errorInHeader=false;

   if ( filename.isEmpty() )
    kdDebug() << "fatal error: empty filename to open" << endl;

   QFileInfo info(filename);

   if(!info.exists() || info.isDir())
      return NO_FILE;

   if(!info.isReadable())
      return NO_PERMISSIONS;

   QFile file(filename);


   bool recoveredError=false;
   bool docbookContent=false;
   bool docbookFile=false;
   	
   if(file.open(IO_ReadOnly))
   {
	  uint oldPercent = 0;
      emit signalResetProgressBar(i18n("loading file"),100);
      
      QByteArray ba = file.readAll();
      
      // find codec for file
	  bool hadCodec;
      QTextCodec* codec=codecForFile(file,&hadCodec);

      file.close();

      QTextStream stream(ba,IO_ReadOnly);
      if(codec)
          stream.setCodec(codec);
      QIODevice *dev = stream.device();
	  int fileSize = dev->size();

      // if somethings goes wrong with the parsing, we don't have deleted the old contents
      QValueList<CatalogItem> tempEntries;
      CatalogItem tempHeader;
      QValueList<QString> tempObsolete;


      kdDebug(KBABEL) << "start parsing..." << endl;

      // first read header
      IOStatus status = readHeader(stream,tempHeader);
      if(status == RECOVERED_PARSE_ERROR) {
      	errorInHeader=true;
      }
      else if(status!=OK)
      {
          emit signalClearProgressBar();

          return status;
      }
      // check if header seems to indicate docbook content generated by xml2pot
      docbookContent = tempHeader.msgstr().contains("application/x-xml2pot");

      // now parse the rest of the file
      CatalogItem tempCatItem;
      CatalogItem::IOStatus success=CatalogItem::Ok;
      uint counter=0;
      QValueList<uint> errorIndex;

      while(!stream.eof() && success==CatalogItem::Ok )
      {
         kapp->processEvents(10);

         success=tempCatItem.read(stream);

         if(success==CatalogItem::Ok)
         {
            // add new entry to the list of entries
            tempEntries.append(tempCatItem);
            // check if first comment seems to indicate a docbook source file
            if(counter==0)
                docbookFile = tempCatItem.comment().contains(".docbook");
            tempCatItem.clear();
         }
         else if(success==CatalogItem::RecoveredParseError)
         {
         	success=CatalogItem::Ok;
         	recoveredError=true;
         	errorIndex.append(counter);
         	
            // add new entry to the list of entries
            tempEntries.append(tempCatItem);
            tempCatItem.clear();
         }
	 
	 if( success==CatalogItem::Obsolete )
	 {
	    success=CatalogItem::Ok;
	    tempObsolete.append(tempCatItem.comment());
	 }
	 else counter++;

         if((100*dev->at())/fileSize > oldPercent)
		 {
			oldPercent = (100*dev->at())/fileSize;
            emit signalProgress(oldPercent);
		 }
      }


      // to be sure it is set to 100, if someone don't connect to
      // signalClearProgressBar()
      emit signalProgress(100);

      emit signalClearProgressBar();

      kdDebug(KBABEL) << "ready." << endl;


      if(success!=CatalogItem::ParseError)
      {
         clear();

         // check if file is readOnly
         QFileInfo fi(file);
         d->_readOnly=!fi.isWritable();

         d->_generatedFromDocbook=docbookContent || docbookFile;

         d->_entries=tempEntries;
         d->_header=tempHeader;
         emit signalHeaderChanged();
	 
	 d->_obsolete=tempObsolete;

         generateIndexLists();
         d->_errorIndex=errorIndex;

         if(hadCodec)
            d->fileCodec=codec;
         else
            d->fileCodec=0;
      }
      else
      {
         return PARSE_ERROR;
      }
   }
   else
   {
      return NO_PERMISSIONS;
   }

   if(recoveredError)
   	return RECOVERED_PARSE_ERROR;

   return OK;
}

Catalog::IOStatus Catalog::saveFile()
{
   if(d->_url.isEmpty())
   {
      kdFatal(KBABEL) << "fatal error: empty filename" << endl;
      return NO_FILE;
   }

   return saveFileAs(d->_url,true);
}

Catalog::IOStatus Catalog::saveFileAs(const KURL &url, bool overwrite)
{
   IOStatus status=OK;

   bool newName=false;
   KURL targetURL=d->_url;

   if(url != d->_url)
   {
      newName = true;
      targetURL=url;
   }


   if(d->_saveSettings.autoUpdate)
   {
      d->_header=updatedHeader(d->_header);
      emit signalHeaderChanged();
   }


   if(targetURL.isLocalFile())
   {
      // test if the directory exists. If not, create it.
      QDir dir( targetURL.directory());
      
      QStringList dirList;
      while(!dir.exists() && !dir.dirName().isEmpty())
      {
         dirList.prepend(dir.dirName());
         dir.setPath(dir.path()+"/..");
      }
      for ( QStringList::Iterator it = dirList.begin(); it != dirList.end(); ++it )
      {
         if(!dir.mkdir(*it))
         {
            status=OS_ERROR;
            break;
         }
         dir.cd(*it);
      }
      
      if(status==OK)
      {
         status=writeFile(targetURL.path(0),overwrite);
      }
   }
   else
   {
      QString tempFile=kapp->tempSaveName(targetURL.path(0));

      status = writeFile(tempFile,overwrite);

      if(status == OK)
      {
         KURL temp(tempFile);
         if( !KIO::NetAccess::upload( temp.url(), targetURL.url() ) )
         {
            status = OS_ERROR;
         }
      }

      QFile::remove(tempFile);
   }

   if(status == OK)
   {
      setModified(false);

      if(newName)
      {
         // if we saved a file, the catalog can not be any longer readOnly;
         d->_readOnly=false;

         d->_url=targetURL;

         emit signalFileOpened(d->_readOnly);
      }
   }

   return status;
}

QString Catalog::saveTempFile()
{
   QString filename = kapp->tempSaveName("/temp/kbabel_temp.po");
   if( writeFile(filename) != OK )
   {
      filename = QString::null;
   }

   return filename;
}


Catalog::IOStatus Catalog::writeFile(QString localFile , bool overwrite)
{
   QFileInfo info(localFile);

   if(info.isDir())
      return NO_FILE;

   if(info.exists())
   {
      if(!overwrite || !info.isWritable())
      {
         return NO_PERMISSIONS;
      }
   }
   else // check if the directory is writable
   {
      QFileInfo dir(info.dirPath());
      if(!dir.isWritable())
      {
         return NO_PERMISSIONS;
      }
   }

   QFile file(localFile);

   if(file.open(IO_WriteOnly))
   {
      int progressRatio = QMAX(100/numberOfEntries(), 1);
      emit signalResetProgressBar(i18n("saving file"),100);

      QTextStream stream(&file);
	  if(d->_saveSettings.useOldEncoding && d->fileCodec)
	  {
		   stream.setCodec(d->fileCodec);
	  }
      else
      {
         switch(d->_saveSettings.encoding)
         {
            case UTF8:
	       d->fileCodec = QTextCodec::codecForName("utf-8");
               stream.setCodec(d->fileCodec);
               break;
            case UTF16:
	       d->fileCodec = QTextCodec::codecForName("utf-16");
               stream.setEncoding(QTextStream::Unicode);
               break;
            default:
	       d->fileCodec = QTextCodec::codecForLocale();
               break;
         }
      }

      // only save header if it is not empty
      if(!d->_header.comment().isEmpty() || !d->_header.msgstr().isEmpty())
      {
         d->_header.write(stream);
         stream << "\n";
      }

      QValueList<CatalogItem>::ConstIterator it;

      int counter=1;
      QStringList list;
      for( it = d->_entries.begin(); it != d->_entries.end(); ++it )
      {
          if(counter%10==0) {
             emit signalProgress(counter/progressRatio);
	  }
	  
          counter++;

          (*it).write(stream);
          stream << "\n";

	 kapp->processEvents(10);
      }

      if( d->_saveSettings.saveObsolete )
      {
          QValueList<QString>::ConstIterator oit;

          for( oit = d->_obsolete.begin(); oit != d->_obsolete.end(); ++oit )
    	  {
              stream << (*oit) << "\n\n";

	      kapp->processEvents(10);
          }
      }
      
      emit signalProgress(100);
      file.close();

      emit signalClearProgressBar();
   }
   else
   {
      //emit signalError(i18n("Wasn't able to open file %1").arg(filename.ascii()));
      return OS_ERROR;
   }

   return OK;
}

QTextCodec* Catalog::codecForFile(QFile& file, bool* hadCodec)
{
   bool wasOpen=true;
   int fileIndex=0;
   
   if(hadCodec)
   {
      *hadCodec=false;
   }

   if(!file.isOpen())
   {
      wasOpen=false;

      if(!file.open(IO_ReadOnly))
      {
         kdDebug(KBABEL) << "wasn't able to open file" << endl;
         return 0;
      }
   }
   else
   {
      fileIndex=file.at();
		file.at(0);
   }

   QTextStream stream(&file);
   CatalogItem tempHeader;

   // first read header
   IOStatus status = readHeader(stream,tempHeader);
   if(status!=OK && status != RECOVERED_PARSE_ERROR)
   {
       kdDebug(KBABEL) << "wasn't able to read header" << endl;
       if(!wasOpen)
          file.close();
       return 0;
   }

   QString charset;

   QString head = tempHeader.msgstr();

   QRegExp r("Content-Type:\\s*\\w+/[-\\w]+;\\s*charset\\s*=\\s*[^\\\"\\n]+");
   int begin=r.search(head);
   int len=r.matchedLength();
   if(begin<0) {
   	kdDebug() << "no charset entry found" << endl;
   	return 0;
   }	
   	
   head = head.mid(begin,len);

   QRegExp regexp("charset *= *([^\\\\\\\"]+)");
   if( regexp.search( head ) > -1 )
   {
       charset = regexp.cap(1);
       kdDebug(KBABEL) << QString("charset: ")+charset << " for " << file.name() << endl;
   }

   QTextCodec* codec=0;

   if(!charset.isEmpty())
   {
      // "CHARSET" is the default charset entry in a template (pot).
      // characters in a template should be either pure ascii or 
      // at least utf8, so utf8-codec can be used for both.
      if( charset == "CHARSET")
      {
          if(hadCodec)
             *hadCodec=false;

          codec=QTextCodec::codecForName("utf8");
          kdDebug(KBABEL) 
              << QString("file seems to be a template: using utf8 encoding.")
              << endl;
      }
      else
      {
         codec=QTextCodec::codecForName(charset.latin1());
         if(hadCodec)
            *hadCodec=true;
      }

      if(!codec)
      {
         kdWarning() << "charset found, but no codec available, using UTF8 instead" << endl;
	 codec=QTextCodec::codecForName("utf8");
      }
   }

   if(!wasOpen)
   {
       file.close();
   }
   else
   {
       file.at(fileIndex);
   }

   return codec;
}

PoInfo Catalog::headerInfo(const CatalogItem headerItem)
{
   QStringList header=headerItem.msgstrAsList();

   QStringList::Iterator it;

   PoInfo info;

   // extract information from the header
   for(it=header.begin();it!=header.end();++it)
   {
      if((*it).contains(QRegExp("^\\s*Project-Id-Version\\s*:\\s*.+\\s*$")))
      {
         info.project=(*it).replace(QRegExp("^\\s*Project-Id-Version\\s*:\\s*"),"");

         if(info.project.right(2)=="\\n")
            info.project.remove(info.project.length()-2,2);
         
         info.project=info.project.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*POT-Creation-Date\\s*:\\s*.+\\s*$")))
      {
         info.creation=(*it).replace(QRegExp("^\\s*POT-Creation-Date\\s*:\\s*"),"");

         if(info.creation.right(2)=="\\n")
            info.creation.remove(info.creation.length()-2,2);

         info.creation=info.creation.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*PO-Revision-Date\\s*:\\s*.+\\s*$")))
      {
         info.revision=(*it).replace(QRegExp("^\\s*PO-Revision-Date\\s*:\\s*"),"");

         if(info.revision.right(2)=="\\n")
            info.revision.remove(info.revision.length()-2,2);

         info.revision=info.revision.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*Last-Translator\\s*:\\s*.+\\s*$")))
      {
         info.lastTranslator=(*it).replace(QRegExp("^\\s*Last-Translator\\s*:\\s*"),"");

         if(info.lastTranslator.right(2)=="\\n")
            info.lastTranslator.remove(info.lastTranslator.length()-2,2);

         info.lastTranslator=info.lastTranslator.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*Language-Team\\s*:\\s*.+\\s*")))
      {
         info.languageTeam=(*it).replace(QRegExp("^\\s*Language-Team\\s*:\\s*"),"");

         if(info.languageTeam.right(2)=="\\n")
            info.languageTeam.remove(info.languageTeam.length()-2,2);

         info.languageTeam=info.languageTeam.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*MIME-Version\\s*:\\s*.+\\s*")))
      {
         info.mimeVersion=(*it).replace(QRegExp("^\\s*MIME-Version\\s*:\\s*"),"");

         if(info.mimeVersion.right(2)=="\\n")
            info.mimeVersion.remove(info.mimeVersion.length()-2,2);

         info.mimeVersion=info.mimeVersion.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*Content-Type\\s*:\\s*.+\\s*")))
      {
         info.contentType=(*it).replace(QRegExp("^\\s*Content-Type\\s*:\\s*"),"");

         if(info.contentType.right(2)=="\\n")
            info.contentType.remove(info.contentType.length()-2,2);

         info.contentType=info.contentType.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*Content-Transfer-Encoding\\s*:\\s*.+\\s*")))
      {
         info.encoding=(*it).replace(QRegExp("^\\s*Content-Transfer-Encoding\\s*:\\s*"),"");

         if(info.encoding.right(2)=="\\n")
            info.encoding.remove(info.encoding.length()-2,2);

         info.encoding=info.encoding.simplifyWhiteSpace();
      }
      else
      {
          QString line=(*it);

         if(line.right(2)=="\\n")
            line.remove(line.length()-2,2);

         line=line.simplifyWhiteSpace();
         if(!info.others.isEmpty())
             info.others+='\n';

         info.others+=line;
      }


   }

   info.headerComment=headerItem.comment();

   return info;
}


Catalog::IOStatus Catalog::info(const QString url, PoInfo& info, QStringList &wordList, bool updateWordList, bool interactive)
{
   stopStaticRead = false;
   QString target;
   if(KIO::NetAccess::download(url, target))
   {
       QFile file(target);

	   // first check file with msgfmt to be sure, it is 
	   // syntactically correct
	   Msgfmt msgfmt;
	   QString output;
	   Msgfmt::Status stat = msgfmt.checkSyntax( target , output );
	   if(stat == Msgfmt::SyntaxError)
	   {
          KIO::NetAccess::removeTempFile(target);
		  return PARSE_ERROR;
	   }


       if(file.open(IO_ReadOnly))
       {
           CatalogItem temp;

           QTextCodec *codec = codecForFile(file);

           file.close();
	   
           info.total=0;
           info.fuzzy=0;
           info.untranslated=0;
	   
	   if( !codec ) return PARSE_ERROR;
	   
	   std::ifstream* stream = new std::ifstream( file.name().local8Bit());
	   FlexLexer* lexer = new yyFlexLexer( stream  );

	   lexer->yylex();
	   
           // now parse the rest of the file
           CatalogItem::IOStatus success=CatalogItem::Ok;
	   
           while( lastToken != T_EOF && success==CatalogItem::Ok)
           {
	       int save = lastToken;
               if( interactive ) kapp->processEvents(10);
	       lastToken = save;
	       
	       if( stopStaticRead )
	       {
	    	    delete lexer;
		    delete stream;
		    return OK;
		}
	       
	       success=temp.fastRead(lexer, codec, updateWordList );
	       
               if(success==CatalogItem::Ok || success==CatalogItem::RecoveredParseError)
               {
		  success=CatalogItem::Ok;

                  if( temp.msgid().isEmpty() ) //header
		  {
		      if( temp.isFuzzy() )  temp.removeFuzzy();
		      
		      PoInfo infoCounts = info;
		      info=Catalog::headerInfo(temp);
		      info.total = infoCounts.total;
		      info.fuzzy = infoCounts.fuzzy;
		      info.untranslated = infoCounts.untranslated;
		      continue; // do not update counters and word list for header
		  }
		  		  
                  info.total++;

                  if(temp.isFuzzy())
                     info.fuzzy++;
                  else if(temp.isUntranslated())
                     info.untranslated++;
		     
		  if( updateWordList )
		  {
		    QString st = temp.msgid().simplifyWhiteSpace().lower();
		    QStringList sl = QStringList::split( ' ', st );
		    while(!sl.isEmpty())
		    {
			QString w = sl.first();
			sl.pop_front();
			if( !wordList.contains(w) ) wordList.append( w );
		    }
		    st = temp.msgstr().simplifyWhiteSpace().lower();
		    sl = QStringList::split( ' ', st );
		    while(!sl.isEmpty())
		    {
			QString w = sl.first();
			sl.pop_front();
			if( !wordList.contains(w) ) wordList.append( w );
		    }
		    st = temp.comment().simplifyWhiteSpace().lower();
		    sl = QStringList::split( ' ', st );
		    while(!sl.isEmpty())
		    {
			QString w = sl.first();
			sl.pop_front();
			if( !wordList.contains(w) ) wordList.append( w );
		    }
                }
	      }
           }

	   delete lexer;
	   delete stream;

           if(success==CatalogItem::ParseError)
           {
	       KIO::NetAccess::removeTempFile(target);
               return PARSE_ERROR;
           }
       }
       else
       {
          KIO::NetAccess::removeTempFile(target);
          return NO_PERMISSIONS;
       }

	KIO::NetAccess::removeTempFile(target);
        return OK;
   }
   else
   {
      return OS_ERROR;
   }

   return OK;
}

bool Catalog::findInFile( const QString url, FindOptions options )
{
   enum {Begin, Comment, Msgid, Msgstr} part = Begin;
   
   stopStaticRead = false;
   QString target;
   if(KIO::NetAccess::download(url, target))
   {
       QFile file(target);

       if(file.open(IO_ReadOnly))
       {
           QTextCodec *codec = codecForFile(file);

           file.close();
           KIO::NetAccess::removeTempFile(target);
	   
	   if( !codec ) return false;
	   
	   std::ifstream* stream = new std::ifstream( file.name().local8Bit()); 
	   FlexLexer* lexer = new yyFlexLexer( stream );

	   lexer->yylex();

           // prepare the search
	   
	   QString searchStr = options.findStr;
	   QRegExp regexp( searchStr );
	   
	   if( options.isRegExp ) 
		regexp.setCaseSensitive( options.caseSensitive );

           // first read header
	   CatalogItem temp;
	   
	   CatalogItem::IOStatus status = temp.fastRead( lexer, codec );
	   if( status != CatalogItem::Ok || !temp.msgid().isEmpty() ) 
	   {
		delete lexer;
		delete stream;
		return false; // header is not at the beginning, broken file
	   }
	   
	   // now parse the rest of the file
	   QString text;
	   int pos,len;
	   
           while(lastToken != T_EOF)
           {
	       switch( lastToken ) {
	           case T_COMMENT: {
			part = Comment;
			if( !options.inComment ) break;
			text = codec->toUnicode(lexer->YYText()); 
			if( options.isRegExp )
			    pos=regexp.search(text, 0 );
			else 
			    pos=text.find(searchStr,0,options.caseSensitive);
			if( pos >= 0)
			{
			    if( options.wholeWords) {
				len = searchStr.length();
				QString pre = text.mid(pos-1,1);
				QString post = text.mid(pos+len,1);
				if( !pre.contains( QRegExp("[a-zA-Z0-9]")) &&
				    !post.contains( QRegExp("[a-zA-Z0-9]") )
				) {
				    delete lexer;
				    delete stream;
				    return true;
				}
			    }
			    else {
				delete lexer;
				delete stream;
				return true;
			    };
			}
			break;
		   }
		   case T_STRING: {
			if( part == Msgid && !options.inMsgid ) break;
			if( part == Msgstr && !options.inMsgstr ) break;
			
			text = codec->toUnicode(lexer->YYText()); 
			if( options.ignoreContextInfo )
			{
			    pos = options.contextInfo.search(text);
			    len = options.contextInfo.matchedLength();
			    if( pos >= 0 )
		    		text.remove( pos, len );
			}
		    
			if( options.ignoreAccelMarker )
			{
			    pos = text.find( options.accelMarker );
			    if( pos >= 0 )
				text.remove( pos, 1 );
			}
			
			if( options.isRegExp )
			    pos=regexp.search(text, 0 );
			else 
			    pos=text.find(searchStr,0,options.caseSensitive);

			if( pos >= 0)
			{
			    if( options.wholeWords) {
				len = searchStr.length();
				QString pre = text.mid(pos-1,1);
				QString post = text.mid(pos+len,1);
				if( !pre.contains( QRegExp("[a-zA-Z0-9]")) &&
				    !post.contains( QRegExp("[a-zA-Z0-9]") )
				) {
				    delete lexer;
				    delete stream;
				    return true;
				}
			    }
			    else {
				delete lexer;
				delete stream;
				return true;
			    };
			}
			break;
		   }
		   case T_MSGSTR: {
			part = Msgstr;
			break;
		   }
		   case T_MSGID: 
		   case T_MSGIDPLURAL: {
	    		kapp->processEvents(10);
			
			// if stopped, return not found
			if( stopStaticRead ) 
			{
			    delete lexer;
			    delete stream;
			    return false;
			}
			part = Msgid;
			break;
		   }
	       }
	       lexer->yylex();
           }
	   delete lexer;
	   delete stream;
       }
    }
    return false;
}

bool Catalog::isUndoAvailable()
{
   return !d->_undoList.isEmpty();
}

bool Catalog::isRedoAvailable()
{
   return !d->_redoList.isEmpty();
}

int Catalog::undo()
{
   if(!isUndoAvailable())
      return -1;

   int macroLevel = 0;

   EditCommand *command=0;
   do
   {
      command = d->_undoList.take();
      if ( !command )
      {
         kdError() << "undo command is NULL?" << endl;
         return -1;
      }

      processCommand( command, 0, true );

      macroLevel += command->terminator();

      if ( d->_undoList.isEmpty() )
      {
         emit signalUndoAvailable( false );
      }
      if(d->_redoList.isEmpty())
      {
         emit signalRedoAvailable(true);
      }
      d->_redoList.append(command);

    }
    while(macroLevel != 0);

    return command->index();
}

int Catalog::redo()
{
   if(!isRedoAvailable())
      return -1;

   int macroLevel = 0;
   EditCommand *command=0;

   do
   {
      command = d->_redoList.take();
      if ( !command )
      {
         kdError() << "undo command is NULL?" << endl;
         return -1;
      }

      processCommand( command, 0,false );

      macroLevel += command->terminator();
      if ( d->_redoList.isEmpty() )
      {
         emit signalRedoAvailable( false );
      }
      if ( d->_undoList.isEmpty() )
      {
         emit signalUndoAvailable( true );
      }

      d->_undoList.append( command );
    }
    while (macroLevel != 0);

    return command->index();
}

void Catalog::applyEditCommand(EditCommand* cmd, CatalogView* view)
{

    processCommand(cmd,view);
    setModified(true);

    if ( d->_undoList.isEmpty() )
    {
       emit signalUndoAvailable(true);	
    }
    else if ( cmd->merge( d->_undoList.last() ) )
    {
       delete cmd;
       return;
    }


    d->_undoList.append(cmd);


    if ( !d->_redoList.isEmpty() )
    {
       d->_redoList.clear();
       emit signalRedoAvailable( false );
    }

}

void Catalog::processCommand(EditCommand* cmd,CatalogView* view, bool undo)
{
    if(cmd->terminator()==0)
    {
       bool checkUntranslated=false;
       bool checkFuzzy=false;
       bool wasFuzzy=false;
       
       CatalogItem &item=d->_entries[cmd->index()];

       if(cmd->part() == EditCommand::Msgstr)
       {
          if( item.isUntranslated() )
          {
             d->_untransIndex.remove(cmd->index());

             emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
          }
          else
          {
             checkUntranslated=true;
          }
       }
       else if(cmd->part() == EditCommand::Comment)
       {
          checkFuzzy=true;
          wasFuzzy=item.isFuzzy();
       }



       item.processCommand(cmd,undo);

       if(undo)
       {
          EditCommand* tmpCmd=0;
          DelTextCmd* delcmd = (DelTextCmd*) cmd;
          if (delcmd->type() == EditCommand::Delete )
          {
             tmpCmd = new InsTextCmd(delcmd->offset,delcmd->str);
          }
          else
          {
             tmpCmd = new DelTextCmd(delcmd->offset,delcmd->str);
          }

          tmpCmd->setIndex(cmd->index());
          tmpCmd->setPart(cmd->part());

          updateViews(tmpCmd,view);

          delete tmpCmd;
       }
       else
       {
          updateViews(cmd,view);
       }
       
       if(checkUntranslated && item.isUntranslated())
       {
          QValueList<uint>::Iterator it;

          // insert index in the right place in the list
          it = d->_untransIndex.begin();
          while(it != d->_untransIndex.end() && cmd->index() > (int)(*it))
          {
             ++it;
          }
          d->_untransIndex.insert( it,(uint)(cmd->index()) );
	  
          emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
       }
       else if(checkFuzzy)
       {
          if(wasFuzzy != item.isFuzzy())
          {
             if(wasFuzzy)
             {
                d->_fuzzyIndex.remove(cmd->index());
                emit signalNumberOfFuzziesChanged(numberOfFuzzies());
             }
             else
             {
                QValueList<uint>::Iterator it;

                // insert index in the right place in the list
                it = d->_fuzzyIndex.begin();
                while(it != d->_fuzzyIndex.end() && cmd->index() > (int)(*it))
                {
                   ++it;
                }
                d->_fuzzyIndex.insert( it,(uint)(cmd->index()) );

                emit signalNumberOfFuzziesChanged(numberOfFuzzies());
             }
          }
       }

    }
}

bool Catalog::findNext(const FindOptions* findOpts, DocPosition& docPos, int& len)
{
	bool success = false; // true, when string found
	bool endReached=false;
	
	len=0;
	int pos=0;
		
	QString searchStr = findOpts->findStr;	
	QRegExp regexp(searchStr);
	
	if( findOpts->isRegExp ) {
		regexp.setCaseSensitive(findOpts->caseSensitive);
	}
		
	if( docPos.item == numberOfEntries()-1) {
		switch(docPos.part) {
			case Msgid:
				if(!findOpts->inMsgstr && !findOpts->inComment
					&& docPos.offset >= msgid(docPos.item).length() ) {
					endReached=true;
				}
				break;
			case Msgstr:
				if(!findOpts->inComment && docPos.offset >= msgstr(docPos.item).length() ) {
					endReached=true;
				}
				break;
			case Comment:
				if(docPos.offset >= comment(docPos.item).length() ) {
					endReached=true;
				}
				break;
		}
	}
	
	while(!success) {
        int accelMarkerPos = -1;
        int contextInfoLength = 0;
        int contextInfoPos = -1;
		QString targetStr;
		
		kapp->processEvents(10);
		
		if(endReached) {
			return false;
		}
		
		switch(docPos.part) {
			case Msgid:
				targetStr = msgid(docPos.item);
				break;
			case Msgstr:
				targetStr = msgstr(docPos.item);
				break;
			case Comment:
				targetStr = comment(docPos.item);
				break;
		}

        if(findOpts->ignoreContextInfo)
        {
            contextInfoPos = d->_miscSettings.contextInfo.search(targetStr);
	    contextInfoLength = d->_miscSettings.contextInfo.matchedLength();
            if(contextInfoPos >= 0)
            {
                targetStr.remove(contextInfoPos,contextInfoLength);
                
                if(docPos.offset > (uint)contextInfoPos)
                    docPos.offset -= contextInfoLength;
            }
        }

        if(findOpts->ignoreAccelMarker 
                && targetStr.contains(d->_miscSettings.accelMarker))
        {
            accelMarkerPos = targetStr.find(d->_miscSettings.accelMarker);
            targetStr.remove(accelMarkerPos,1);

            if(docPos.offset > (uint)accelMarkerPos)
                docPos.offset--;
        }
		
		if( findOpts->isRegExp ) {
			if ((pos=regexp.search(targetStr,docPos.offset)) >= 0 ) {
			    len = regexp.matchedLength();
				if(findOpts->wholeWords) {
					QString pre=targetStr.mid(pos-1,1);
					QString post=targetStr.mid(pos+len,1);
					if(!pre.contains(QRegExp("[a-zA-Z0-9]")) && !post.contains(QRegExp("[a-zA-Z0-9]")) ){
						success=true;
						docPos.offset=pos;
					}
				}
				else {	
					success=true;
					docPos.offset=pos;
				}
			}
		}
		else {
			if( (pos=targetStr.find(searchStr,docPos.offset,findOpts->caseSensitive)) >= 0 ) {
				len=searchStr.length();
				
				if(findOpts->wholeWords) {
					QString pre=targetStr.mid(pos-1,1);
					QString post=targetStr.mid(pos+len,1);
					if(!pre.contains(QRegExp("[a-zA-Z0-9]")) && !post.contains(QRegExp("[a-zA-Z0-9]")) ){
						success=true;
						docPos.offset=pos;
					}
				}
				else {	
					success=true;
					docPos.offset=pos;
				}
			}
		}
		
		
		if(!success) {					
			docPos.offset=0;
			switch(docPos.part) {
				case Msgid:
				{
					if(findOpts->inMsgstr) {
						docPos.part = Msgstr;
					}
					else if(findOpts->inComment) {
						docPos.part = Comment;
					}
					else
					{
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.item++;
						}
					}
					break;
				}
				case Msgstr:
					if(findOpts->inComment) {
						docPos.part = Comment;
					}
					else if(findOpts->inMsgid) {
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.part = Msgid;
							docPos.item++;
						}
					}
					else {
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.item++;
						}
					}
					break;
				case Comment:
					if(findOpts->inMsgid) {
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.part = Msgid;
							docPos.item++;
						}
					}
					else if(findOpts->inMsgstr){
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.part = Msgstr;
							docPos.item++;
						}
					}
					else {
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.item++;
						}
					}
					break;
			}
		}
        else
        {
            if(accelMarkerPos >= 0)
            {
                if(docPos.offset >= (uint)accelMarkerPos)
                {
                    docPos.offset++;
                }
                else if(docPos.offset+len > (uint)accelMarkerPos)
                {
                    len++;
                }
            }

            if(contextInfoPos >= 0)
            {
                if(docPos.offset >= (uint)contextInfoPos)
                {
                    docPos.offset+=contextInfoLength;
                }
                else if(docPos.offset+len > (uint)contextInfoPos)
                {
                    len+=contextInfoLength;
                }

            }
        }
	}	
	
	
	return true;
}

bool Catalog::findPrev(const FindOptions* findOpts, DocPosition& docPos, int& len)
{
	bool success = false;  // true, when found
	bool beginReached = false;
	
	len=0;
	int pos=0;
	
	QString searchStr = findOpts->findStr;	
	QRegExp regexp(searchStr);
	
	if( findOpts->isRegExp ) {
		regexp.setCaseSensitive(findOpts->caseSensitive);
	}
	while(!success) {
        int accelMarkerPos = -1;
        int contextInfoLength = 0;
        int contextInfoPos = -1;
		QString targetStr;
		
		kapp->processEvents(10);
		
		if(beginReached) {
			return false;				
		}
		
		switch(docPos.part) {
			case Msgid:
				targetStr = msgid(docPos.item);
				break;
			case Msgstr:
				targetStr = msgstr(docPos.item);
				break;
			case Comment:
				targetStr = comment(docPos.item);
				break;
		}
		
        if(findOpts->ignoreContextInfo)
        {
            contextInfoPos = d->_miscSettings.contextInfo.search(targetStr);
	    contextInfoLength = d->_miscSettings.contextInfo.matchedLength();
            if(contextInfoPos >= 0)
            {
                targetStr.remove(contextInfoPos,contextInfoLength);

                if(docPos.offset > (uint)contextInfoPos)
                    docPos.offset -= contextInfoLength;
            }
        }

        if(findOpts->ignoreAccelMarker 
                && targetStr.contains(d->_miscSettings.accelMarker))
        {
            accelMarkerPos = targetStr.find(d->_miscSettings.accelMarker);
            targetStr.remove(accelMarkerPos,1);

            if(docPos.offset > (uint)accelMarkerPos)
                docPos.offset--;
        }

		if(docPos.offset <= 0) {
			success=false;
		}	
		else if( findOpts->isRegExp ) {
			/*
			don't work!?
			if((pos=targetStr.findRev(regexp,docPos.offset)) >= 0 ) {
				regexp.match(targetStr,pos,&len); // to get the length of the string
			*/
			bool found=false;
			int tmpPos=docPos.offset;
			while(!found && tmpPos>=0)
			{
				if( (pos=regexp.search(targetStr,tmpPos)) >= 0 && (uint)pos < docPos.offset)
					found=true;
				else
					tmpPos--;
				len = regexp.matchedLength();
			}		
			if(found) {
				if(findOpts->wholeWords) {
					QString pre=targetStr.mid(pos-1,1);
					QString post=targetStr.mid(pos+len,1);
					if(!pre.contains(QRegExp("[a-zA-Z0-9]")) && !post.contains(QRegExp("[a-zA-Z0-9]")) ){
						success=true;
						docPos.offset=pos;
					}
				}
				else {	
					success=true;
					docPos.offset=pos;
				}
			}
		}
		else if( (pos=targetStr.findRev(searchStr,docPos.offset-1,findOpts->caseSensitive)) >= 0
		          && (uint)pos < docPos.offset) {
			len=searchStr.length();
			if(findOpts->wholeWords) {
				QString pre=targetStr.mid(pos-1,1);
				QString post=targetStr.mid(pos+len,1);
				if(!pre.contains(QRegExp("[a-zA-Z0-9]")) && !post.contains(QRegExp("[a-zA-Z0-9]")) ){
					success=true;
					docPos.offset=pos;
				}
			}
			else {	
				success=true;
				docPos.offset=pos;
			}			
		}
			
		if(!success) {	
			switch(docPos.part) {
				case Comment:
				{
					if(findOpts->inMsgstr) {
						docPos.part = Msgstr;
						docPos.offset = msgstr(docPos.item).length();
					}
					else if(findOpts->inMsgid) {
						docPos.part = Msgid;
						docPos.offset = msgid(docPos.item).length();
					}
					else
					{
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.item--;
							docPos.offset = comment(docPos.item).length();
						}
					}
					break;
				}
				case Msgstr:
					if(findOpts->inMsgid) {
						docPos.part = Msgid;
						docPos.offset = msgid(docPos.item).length();
					}
					else if(findOpts->inComment) {
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.part = Comment;
							docPos.item--;
							docPos.offset = comment(docPos.item).length();
						}
					}
					else {
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.item--;
							docPos.offset = msgstr(docPos.item).length();
						}
					}
					break;
				case Msgid:
					if(findOpts->inComment) {
						if(docPos.item <= 0 )
						{
							beginReached=true;
						}
						else
						{
							docPos.part = Comment;
							docPos.item--;
							docPos.offset = comment(docPos.item).length();
						}
					}
					else if(findOpts->inMsgstr){
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.part = Msgstr;
							docPos.item--;
							docPos.offset = msgstr(docPos.item).length();
						}
					}
					else {
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.item--;
							docPos.offset = msgid(docPos.item).length();
						}
					}
					break;
			}
		}
        else
        {
            if(accelMarkerPos >= 0)
            {
                if(docPos.offset >= (uint)accelMarkerPos)
                {
                    docPos.offset++;
                }
                else if(docPos.offset+len > (uint)accelMarkerPos)
                {
                    len++;
                }
            }

            if(contextInfoPos >= 0)
            {
                if(docPos.offset >= (uint)contextInfoPos)
                {
                    docPos.offset+=contextInfoLength;
                }
                else if(docPos.offset+len > (uint)contextInfoPos)
                {
                    len+=contextInfoLength;
                }

            }
        }
	}	
	
	return true;
}


Catalog::DiffResult Catalog::diff(uint entry, QString *result)
{
    if(!result)
    {
        kdWarning() << "0 pointer for result" << endl;
        return DiffNotFound;
    }

    if( d->msgidDiffList.isEmpty() )
    {
        return DiffNeedList;
    }

    // first look if the diff for this entry is in the cache
    QString *s = d->diffCache[entry];
    if(s)
    {
        if(s->isEmpty())
            return DiffNotFound;

        
        *result = *s;
        return DiffOk;
    }
    
    // then look if the same msgid is contained in the diff file
    QRegExp nlReg("\\n");
    QString id = msgid(entry);
    id.replace(nlReg,"");
    if(d->msgidDiffList.contains(id))
    {
        *result = msgid(entry);

        return DiffOk;
    }
    
    QString idForDiff;
    
    // then look if there are entries with the same translation
    QString str = msgstr(entry);
    str.replace(nlReg,"");
    if(d->msgstr2MsgidDiffList.contains(str))
    {
        QStringList list = d->msgstr2MsgidDiffList[str];
        
        if(list.count() == 1)
        {
            idForDiff = list.first();
        }
        else
        {
            // find the best matching id
            double bestWeight = 0.6;
            QString bestId;

            QStringList::ConstIterator it;
            for(it = list.begin(); it != list.end(); ++it)
            {
                double weight = fstrcmp( id.utf8(), (*it).utf8() );
                if(weight > bestWeight)
                {
                    bestWeight = weight;
                    bestId = (*it);
                }
            }

            if( !bestId.isEmpty() )
            {
                idForDiff = bestId;
            }
        }
    }
    else
    {
        emit signalResetProgressBar(i18n("searching matching message")
                ,100);
            
        // find the best matching id
        double bestWeight = 0.6;
        QString bestId;

        int counter=0;
        int oldPercent=0;
        int max = QMAX( d->msgidDiffList.count()-1, 1);
        
        QStringList::ConstIterator it;
        for(it = d->msgidDiffList.begin();
                it != d->msgidDiffList.end(); ++it)
        {
            counter++;
            int  percent = 100*counter/max;
            if(percent > oldPercent)
            {
                oldPercent = percent;
                emit signalProgress(percent);
            }
            
            double weight = fstrcmp( id.utf8(), (*it).utf8() );
            if(weight > bestWeight)
            {
                bestWeight = weight;
                bestId = (*it);
            }

            kapp->processEvents(10);
        }

        if( !bestId.isEmpty() )
        {
            idForDiff = bestId;
        }

        emit signalClearProgressBar();

    }

    if( idForDiff.isEmpty() )
    {
        s = new QString(*result);
        if( !d->diffCache.insert(entry,s) )
            delete s;

        return DiffNotFound;
    }

    QString r = Diff::charDiff(idForDiff,id);

    
    // restore the newlines
    QString orig = msgid(entry);
    if(orig.contains('\n'))
    {
        int pos=orig.find('\n');
        int oldPos=0;
        int rPos=0;
        int max = r.length();
        while(pos > 0)
        {
            int len = pos - oldPos;
            oldPos = pos+1;
            
            int p = 0;
            while(p < len && rPos < max)
            {
                QString m = r.mid(rPos,11);
                if(r[rPos]=='<' && m == "<KBABELDEL>")
                {
                    int endPos = r.find("</KBABELDEL>",rPos+11);
                    if(endPos > rPos)
                    {
                        rPos = endPos+12;
                    }
                    else
                    {
                        kdWarning(KBABEL) << "no closing diff remove tag found" 
                                          << endl;
                        rPos+=11;
                    }
                }
                else if(r[rPos] == '<' && m == "<KBABELADD>")
                {
                    rPos+=11;
                }
                else if(r[rPos] == '<' && m == "</KBABELADD")
                {
                    rPos+=12;
                }
                else
                {
                    rPos++;
                    p++;
                }
            }

            if(rPos < max)
            {
                r.insert(rPos,'\n');
                max++;
                rPos++;
            }

            
            pos = orig.find('\n',pos+1);
        }
    }

    
    int pos = r.find("\\n");
    while(pos >= 0 )
    {
        int slash=1;
        while(pos-slash >= 0 && r[pos-slash]=='\\')
        {
            slash++;
        }
        if(slash%2 == 1 && r[pos+2]!='\n')
        {
            r.insert(pos+2,'\n');
            pos+=3;
        }
        else
        {
            pos+=2;
        }

        pos = r.find("\\n",pos);
    }

    // add newlines if line has gotten to long;
    pos = r.find('\n');
    int oldPos=0;
    const int maxLength = 60;
    while(pos >= 0)
    {
        int length=pos-oldPos;
        QString tmp = r.mid(oldPos, pos-oldPos);
        if(!tmp.contains("KBABELDEL>") )
        {
            oldPos = pos;
            pos = r.find('\n',pos+1);
            continue;
        }
                
        int n = tmp.contains("<KBABELADD>");
        length -= n*11;
        n = tmp.contains("<KBABELDEL>");
        length -= n*11;
        n = tmp.contains("</KBABELADD>");
        length -= n*12;
        n = tmp.contains("</KBABELDEL>");
        length -= n*12;
        while(length > maxLength)
        {
            int counter=0;
            while(counter < maxLength-10 && oldPos < pos)
            {
                if(r[oldPos]=='<' && r.mid(oldPos,11)=="<KBABELADD>")
                {
                    oldPos+=11;
                }
                else if(r[oldPos]=='<' && r.mid(oldPos,11)=="<KBABELDEL>")
                {
                    oldPos+=11;
                }
                else if(r[oldPos]=='<' && r.mid(oldPos,12)=="</KBABELADD>")
                {
                    oldPos+=12;
                }
                else if(r[oldPos]=='<' && r.mid(oldPos,12)=="</KBABELDEL>")
                {
                    oldPos+=12;
                }
                else
                {
                    counter++;
                    oldPos++;
                }
            }
                
            while(oldPos < pos-5 && !r[oldPos].isSpace() )
            {
                oldPos++;
            }
            oldPos++;
            if(oldPos < pos-5)
            {
                r.insert(oldPos,'\n');
                pos++;
            }
            else
            {
                break;
            }
            
            length=pos-oldPos;
            QString tmp = r.mid(oldPos, pos-oldPos);
            n = tmp.contains("<KBABELADD>");
            length -= n*11;
            n = tmp.contains("<KBABELDEL>");
            length -= n*11;
            n = tmp.contains("</KBABELADD>");
            length -= n*12;
            n = tmp.contains("</KBABELDEL>");
            length -= n*12;
        }
            
        oldPos=pos;
        pos = r.find('\n',pos+1);
    }
    
    // now the last line
    pos = r.length();
    
    int length=pos-oldPos;
    QString tmp = r.mid(oldPos, pos-oldPos);
    int n = tmp.contains("<KBABELADD>");
    length -= n*11;
    n = tmp.contains("<KBABELDEL>");
    length -= n*11;
    n = tmp.contains("</KBABELADD>");
    length -= n*12;
    n = tmp.contains("</KBABELDEL>");
    length -= n*12;
    while(length > maxLength)
    {
        int counter=0;
        while(counter < maxLength-10 && oldPos < pos)
        {
            if(r[oldPos]=='<' && r.mid(oldPos,11)=="<KBABELADD>")
            {
                oldPos+=11;
            }
            else if(r[oldPos]=='<' && r.mid(oldPos,11)=="<KBABELDEL>")
            {
                oldPos+=11;
            }
            else if(r[oldPos]=='<' && r.mid(oldPos,12)=="</KBABELADD>")
            {
                oldPos+=12;
            }
            else if(r[oldPos]=='<' && r.mid(oldPos,12)=="</KBABELDEL>")
            {
                oldPos+=12;
            }
            else
            {
                counter++;
                oldPos++;
            }
        }
        
        while(oldPos < pos-5 && !r[oldPos].isSpace() )
        {
            oldPos++;
        }
        oldPos++;
        if(oldPos < pos-5)
        {
            r.insert(oldPos,'\n');
            pos++;
        }
        else
        {
            break;
        }
        
        length=pos-oldPos;
        QString tmp = r.mid(oldPos, pos-oldPos);
        n = tmp.contains("<KBABELADD>");
        length -= n*11;
        n = tmp.contains("<KBABELDEL>");
        length -= n*11;
        n = tmp.contains("</KBABELADD>");
        length -= n*12;
        n = tmp.contains("</KBABELDEL>");
        length -= n*12;
    }
    
    *result = r;
    
    s = new QString(*result);
    if( !d->diffCache.insert(entry,s) )
        delete s;
    
    return DiffOk;
}

void Catalog::setDiffList( const QValueList<DiffEntry>& list)
{
    emit signalResetProgressBar(i18n("preparing messages for diff"),100);

    d->msgidDiffList.clear();
    d->msgstr2MsgidDiffList.clear();
    d->diffCache.clear();

    uint max = QMAX(list.count()-1,1);
    int oldPercent=0;
    uint counter=0;
    QRegExp nlReg("\\n");
    QValueList<DiffEntry>::ConstIterator it;
    for(it = list.begin(); it != list.end(); ++it)
    {
        int percent = (100*counter)/max;
        counter++;
        if(percent > oldPercent)
        {
            oldPercent = percent;
            emit signalProgress(percent);
            kapp->processEvents(10);
        }

        QString id = (*it).msgid;
        id.replace(nlReg,"");
        QString str = (*it).msgstr;
        str.replace(nlReg,"");
        d->msgidDiffList.append(id);
        
        if(!str.isEmpty())
        {
            if(d->msgstr2MsgidDiffList.contains(str))
            {           
                QStringList sl = d->msgstr2MsgidDiffList[str];
                sl.append(id);
            }
            else
            {
                QStringList sl;
                sl.append(id);
                d->msgstr2MsgidDiffList.insert(str,sl);
            }
        }
    }

    emit signalClearProgressBar();
}

QValueList<DiffEntry> Catalog::asDiffList()
{
    QValueList<DiffEntry> list;	
    
    for ( QValueList<CatalogItem>::Iterator it = d->_entries.begin(); 
					it != d->_entries.end(); ++it)
	{
        DiffEntry e;
        e.msgid = (*it).msgid();
        e.msgstr = (*it).msgstr();

        list.append(e);
    }

    return list;

}

void Catalog::getNumberOfPluralForms()
{
    if(d->_identitySettings.numberOfPluralForms > 0)
    {
        d->numberOfPluralForms = d->_identitySettings.numberOfPluralForms;
        return;
    }
    
    QString lang=d->_identitySettings.languageCode;
    if(lang.isEmpty())
    {
        d->numberOfPluralForms=-1;
        return;
    }

    d->numberOfPluralForms = getNumberOfPluralForms(lang);
}

int Catalog::getNumberOfPluralForms(const QString& lang)
{
    int nr=-1;

    KLocale locale("kdelibs");
    locale.setLanguage(lang);

    const char* formsString =
    "_: Dear translator, please do not translate this string in any form, but "
    "pick the _right_ value out of NoPlural/TwoForms/French... If not sure what "
    "to do mail thd@kde.org and coolo@kde.org, they will tell you. Better leave "
    "that out if unsure, the programs will crash!!\n"
    "Definition of PluralForm - to be set by the translator of kdelibs.po";
    
    QString formsTranslation = locale.translate(formsString);

    // no translation found
    if(formsTranslation == formsString || formsTranslation.isEmpty())
    {
        kdDebug(KBABEL) << "no translation of PluralForms found" << endl;
        return -1;
    }
    if ( formsTranslation == "NoPlural" )
      nr = 1;
    else if ( formsTranslation == "TwoForms" )
      nr = 2;
    else if ( formsTranslation == "French" )
      nr = 2;
    else if ( formsTranslation == "Gaeilge" || formsTranslation == "OneTwoRest" )
      nr = 3;
    else if ( formsTranslation == "Russian" )
      nr = 3;
    else if ( formsTranslation == "Polish" )
      nr = 3;
    else if ( formsTranslation == "Slovenian" )
      nr = 4;
    else if ( formsTranslation == "Lithuanian" )
      nr = 3;
    else if ( formsTranslation == "Czech" )
      nr = 3;
    else if ( formsTranslation == "Slovak" )
      nr = 3;
    else if ( formsTranslation == "Maltese" )
      nr = 4;
    else if ( formsTranslation == "Arabic" )
      nr = 4;
    else if ( formsTranslation == "Balcan" )
      nr = 3;
    else
    {
        kdDebug(KBABEL) << "unknown translation of PluralForms: " 
            << formsTranslation << endl;
        nr=-1;
    }

    return nr;
}

bool Catalog::hasPluralForms() const
{
    bool have=false;
    for ( QValueList<CatalogItem>::ConstIterator it = d->_entries.begin(); 
					it != d->_entries.end(); ++it)
	{
		if((*it).isPluralForm())
		{
            have=true;
            break;
		}
	}

    return have;
}

bool Catalog::isModified() const
{
    return d->_modified;
}

#include "catalog.moc"
