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

  Copyright (C) 1999-2000 by Matthias Kiefer
                            <matthias.kiefer@gmx.de>
		2001	  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.

**************************************************************************** */
#ifndef CATALOG_H
#define CATALOG_H

#include <qintcache.h>
#include <qobject.h>
#include <qlist.h>
#include <qmap.h>
#include <kurl.h>

#include "catalogsettings.h"
#include "catalogitem.h"
#include "msgfmt.h"

class QString;
class QTextStream;
class KFileInfo;
class QTextCodec;
class QFile;
class CatalogView;
class EditCommand;
class FindOptions;

enum ItemPart{ Msgid, Msgstr, Comment };

struct DocPosition
{
	uint offset;
	
	ItemPart part;
	uint item;	
};


struct PoInfo
{
   int total;
   int fuzzy;
   int untranslated;

   QString project;
   QString creation;
   QString revision;
   QString lastTranslator;
   QString languageTeam;
   QString mimeVersion;
   QString contentType;
   QString encoding;
   QString others;

   QString headerComment;
};

struct DiffEntry
{
    QString msgid;
    QString msgstr;
};

/**
* This class represents a catalog, saved in a po-file.
* It has the ability to load from and save to a po-file.
* Also it defines all necessary functions to set and get the entries
*
* @short Class, that represents a translation catalog(po-file)
* @author Matthias Kiefer <matthias.kiefer@gmx.de>
*/
class Catalog : public QObject
{
    Q_OBJECT

public:
   enum IOStatus{OK,OS_ERROR,NO_PERMISSIONS,RECOVERED_PARSE_ERROR,PARSE_ERROR,NO_FILE};
   enum DiffResult{DiffOk, DiffNotFound, DiffNeedList};

   static IOStatus info(const QString url,PoInfo& info, QStringList &wordList, bool updateWordList);
   static PoInfo headerInfo(const CatalogItem);
   static bool findInFile(const QString url, FindOptions options );
   
   static bool stopStaticRead;

   /**
   * reads header information from the file and searches for charset
   * information.
   * @param codecFound contains information, if charset information was
   * found in the header or if the default codec was used
   *
   * @return Codec for found charset or 0, if no information has been found
   */
   static QTextCodec* codecForFile(QFile& file, bool *codecFound=0);

   Catalog(QObject* parent=0, const char* name=0);
   virtual ~Catalog();

   QString msgid(uint index) const;
   QString msgstr(uint index) const;
   QString comment(uint index) const;
   CatalogItem header() const;

   /**
    * @return The index of the item, that has the msgid id.
    * If no item is found, -1 is returned.
    */
   int indexForMsgid(const QString& id) const;
   
   /** @return a list of tags in entry #index */
   QStringList tagList(uint index);

   /**
	* returns the status of item index, which means 
	* @ref CatalogItem::Error orred together
	* @param recheck flag, if the item status should be checked now
	* @param whatToCheck what checks to do (CatalogItem::Error orred together)
	*/
   int itemStatus(uint index, bool recheck=false, int whatToCheck=0);

   /**
   * replaces msgstr in catalog item at index index with msgstr
   * returns true, if untranslated status changed
   */
   //bool setMsgstr(uint index,QString msgstr);
   /**
   * replaces comment in catalog item at index index with comment
   * returns true, if fuzzy status changed
   */
   //bool setComment(uint index,QString comment);
   /**
   * replaces header with given String
   * @return false if the header is not valid
   */
   bool setHeader(CatalogItem header);

   /** removes string ", fuzzy" from comment */
   void removeFuzzyStatus(uint index);

   /** sets or unsets fuzzy status of entry index */
   void setFuzzy(uint index, bool on);

   /**
   * used for session managment when modified file is
   * saved in a temp file
   */
   void setCurrentURL(const KURL& url);
   KURL currentURL() const;
   QString packageName() const;
   QString packageDir() const;
   QString encoding() const;

   /**
   * opens file url by using KDE's network downlad and calls
   * openFile with a local filename
   * @param errorInHeader contains information, if there was
   * an error in reading the header
   */
   IOStatus openURL(const KURL& url,bool& errorInHeader);

   /**
   * opens file openURL by using KDE's network downlad and calls
   * openFile with a local filename
   * sets current URL to saveURL
   * @param errorInHeader contains information, if there was
   * an error in reading the header
   */
   IOStatus openURL(const KURL& openURL, const KURL& saveURL, bool& errorInHeader);

   /** save the file under the old filename */
   IOStatus saveFile();
   /** saves the file under a new filename */
   IOStatus saveFileAs(const KURL& url,bool overwrite=false);

   /**
   * saves the current catalog in a temporary file and
   * returns the name and path of the file.
   */
   QString saveTempFile();

   Msgfmt::Status checkSyntax(QString& output);

   /** 
	* checks, if number and type of arguments (%1, %s,...)
	* are the same in msgid and msgstr
	* @param clearErrors flag, if the errrorlist has to be cleared
	*/
   bool checkArgs(bool clearErrors=true);

   /**
	* checks if msgid and msgstr have the same number of occurences of &
	* @param clearErrors flag, if the errrorlist has to be cleared
	*/
   bool checkAccelerators(bool clearErrors=true);

   /**
	* when the msgid is of the form abc=yxz it checks if abc is 
	* exactly the same in msgstr. Useful for checking KDE's desktop.po
	* files.
	* @param clearErrors flag, if the errrorlist has to be cleared
	*/
   bool checkEquations(bool clearErrors=true);
   
   /**
 	* checks if the context of a message is translated.
	* This is a patch to gettext which is used by KDE. It adds one line
	* in front of the actual text, which starts with _: and then contains
	* a small description. The msgstr must not translate this.
    * @param clearErrors flag, if the errrorlist has to be cleared
   */
   bool checkForContext(bool clearErrors=true);

   /**
    * checks if a string which contains both singular and plural form
    * is translated correctly
    */
   bool checkSingularPlural(bool clearErrors=true);

   /** closes the file and deletes all entries */
   void clear();

   bool isModified() const{return _modified;}
   /** sets modified flag */
   void setModified(bool flag);

   bool isReadOnly() const{return _readOnly;}
   uint numberOfEntries() const;
   uint numberOfFuzzies() const;
   uint numberOfUntranslated() const;

   bool hasFuzzyInFront(uint index) const;
   bool hasFuzzyAfterwards(uint index) const;
   bool hasUntranslatedInFront(uint index) const;
   bool hasUntranslatedAfterwards(uint index) const;
   bool hasErrorInFront(uint index) const;
   bool hasErrorAfterwards(uint index) const;
   bool isFuzzy(uint index) const;
   bool isUntranslated(uint index) const;
   bool hasError(uint index) const;
   bool isPluralForm(uint index) const;

   /**
   * returns index of next fuzzy entry behind startIndex
   * -1 if there is no entry behind
   */
   int nextFuzzy(uint startIndex) const;
   /**
   * returns index of previous fuzzy entry before startIndex
   * -1 if there is no entry before
   */
   int prevFuzzy(uint startIndex) const;
   /**
   * returns index of next untranslated entry behind startIndex
   * -1 if there is no entry behind
   */
   int nextUntranslated(uint startIndex) const;
   /**
   * returns index of previous untranslated entry before startIndex
   * -1 if there is no entry before
   */
   int prevUntranslated(uint startIndex) const;
   /**
   * returns index of next error entry behind startIndex
   * -1 if there is no entry behind
   */
   int nextError(uint startIndex) const;
   /**
   * returns index of previous error entry before startIndex
   * -1 if there is no entry before
   */
   int prevError(uint startIndex) const;



   /**
   * returns the header with Information updated, but does _not_ change
   * the header in the catalog
   */
   CatalogItem updatedHeader(CatalogItem oldHeader, bool usePrefs=true) const;

   /** read settings from the apps config file*/
   void readPreferences();
   /** save settings to the apps config file*/
   void savePreferences();

   SaveSettings saveSettings() const {return _saveSettings;}
   IdentitySettings identitySettings() const {return _identitySettings;}
   MiscSettings miscSettings() const {return _miscSettings;}

   /**
   * reads the header from QTextstream and puts it in header
   * I made it static to be able to use this function for
   * the search in po-files. This way, I can easily find the first
   * catalog entry in the textstream
   */
   static IOStatus readHeader(QTextStream& stream,CatalogItem& header);


   void registerView(CatalogView* view);
   void removeView(CatalogView* view);
   bool hasView() const;
   bool isLastView() const;

   bool isUndoAvailable();
   bool isRedoAvailable();
   /**
   * undo the last edit command
   * @return the index of the item, which was modified or -1, if no undo available
   */
   int undo();
   /**
   * redo the last undo command
   * @return the index of the item, which was modified or -1, if no redo available
   */
   int redo();

   /**
   * finds next occurence of searchstring using opts, starting at pos.
   * @param pos starting position and if successful contains position
   * of found string.
   * @param len contains len of found string
   * @ return true, if a string was found. false, if end was reached
   */
   bool findNext(const FindOptions* opts, DocPosition& pos, int& len);
   /**
   * finds previous occurence of searchstring using opts, starting at pos.
   * @param pos starting position and if successful contains position
   * of found string.
   * @param len contains len of found string
   * @ return true, if a string was found. false, if begin was reached
   */
   bool findPrev(const FindOptions* opts, DocPosition& pos, int& len);

   void removeFromErrorList(uint index);

   /**
    * tries to find a corresponding entry for entry entry 
    * from the list of old messages and calculates the diff for it
    */
   DiffResult diff(uint entry, QString* result);

   /**
    * sets a list of entries to generate a diff from
    */
   void setDiffList( const QValueList<DiffEntry>& );

   /**
    * @return the contents of this catalog as list for diffs
    */
   QValueList<DiffEntry> asDiffList();
   
   /**
    * @return how many plural forms are used in language lang.
    * If nothing is found -1 is returned.
    */
   static int getNumberOfPluralForms(const QString& lang);

   /**
    * @return true, if this item has gettext plural forms
    */
   bool hasPluralForms() const;

public slots:
   void setSettings(SaveSettings settings);
   void setSettings(IdentitySettings settings);
   void setSettings(MiscSettings settings);

   void applyEditCommand(EditCommand* cmd,CatalogView* source);


private:
   /** calls @ref CatalogView::update(EditCommand*) */
   void updateViews(EditCommand* cmd,CatalogView* view2exclude=0);

   /**
   * takes over changes, but does not change undo/redo list
   */
   void processCommand(EditCommand* cmd, CatalogView* view2exclude=0, bool undo=false);

   IOStatus writeFile(QString localfile, bool overwrite=false);
   /**
   * closes the current catalog,opens file from a local file and create
   * new CatalogItems
   * @param errorInHeader contains information, if there was
   * an error in reading the header
   */
   IOStatus openFile(QString filename, bool& errorInHeader);

   /**
   * generates  lists that contain indexes of all fuzzy and untranslated entries
   */
   void generateIndexLists();

   /**
   * returns value in list that is lower than parameter index
   * or -1 if there is none
   */
   int findPrevInList(const QValueList<uint>& list,uint index) const;
   /**
   * returns value in list that is bigger than parameter index
   * or -1 if there is none
   */
   int findNextInList(const QValueList<uint>& list,uint index) const;

   /** returns the current date and time in the format of the users choice */
   QString dateTime() const;

   void clearErrorList();

   /**
    * Tries to find out how many plural forms are used in this language and
    * sets numberOfPluralForms accordingly. If nothing is found,
    * numberOfPLuralForms is set to -1.
    */
   void getNumberOfPluralForms();

private:
   /** url of the po-file, that belongs to this catalog */
   KURL _url;

   /** holds the entries in the catalog */
   QValueList<CatalogItem> _entries;
   /** The header of the po-file. */
   CatalogItem _header;
   
   /** list of comments with obsolete entries */
   QValueList<QString> _obsolete;

   bool _modified;
   bool _readOnly;

   QValueList<uint> _fuzzyIndex;
   QValueList<uint> _untransIndex;
   QValueList<uint> _errorIndex;

   QPtrList<CatalogView> _views;

   SaveSettings _saveSettings;
   IdentitySettings _identitySettings;
   MiscSettings _miscSettings;

   QPtrList<EditCommand> _undoList;
   QPtrList<EditCommand> _redoList;

   QTextCodec *fileCodec;

   QStringList msgidDiffList;
   QMap< QString, QStringList > msgstr2MsgidDiffList;
   QIntCache<QString> diffCache;
   
   int numberOfPluralForms;

signals:
   void signalError(QString);
   void signalResetProgressBar(QString,int);
   void signalProgress(int);
   void signalClearProgressBar();

   void signalModified(bool);
   /** emitted when the header was changed, maybe when saving */
   void signalHeaderChanged();
   /** emitted when a file was opened or saved under another name */
   void signalFileOpened(bool readOnly);

   void signalNumberOfFuzziesChanged(uint number);
   void signalNumberOfUntranslatedChanged(uint number);
   void signalTotalNumberChanged(uint number);

   void signalSettingsChanged(SaveSettings);
   void signalSettingsChanged(IdentitySettings);
   void signalSettingsChanged(MiscSettings);

   void signalUndoAvailable(bool);
   void signalRedoAvailable(bool);
};

#endif //CATALOG_H
