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

  Copyright (C) 1999-2000 by Matthias Kiefer
                            <matthias.kiefer@gmx.de>
		2001-2003 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

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

#include "pluralforms.h"
#include "itempart.h"
#include "msgfmt.h"
#include "poinfo.h"
#include "catalogfileplugin.h"

#include <qptrlist.h>
#include <qvaluevector.h>

class KDataTool;
class QString;
class QTextStream;
class KFileInfo;
class QTextCodec;
class QFile;
class KConfig;
class KDataTool;
class KURL;


namespace KBabel
{

class CatalogView;
class EditCommand;
class FindOptions;
class CatalogItem;
class IdentitySettings;
class MiscSettings;
class SaveSettings;
class TagSettings;


class CatalogPrivate;

/**
* This struct represents a position in a catalog.
* A position is a tuple (index,pluralform,textoffset).
*
* @short Structure, that represents a position in a catalog.
* @author Matthias Kiefer <matthias.kiefer@gmx.de>
* @author Stanislav Visnovsky <visnovsky@kde.org>
*/
struct DocPosition
{
	DocPosition( ) : offset(0), item(0), form(0) { }
	uint offset;
	
	Part part;
	uint item;	
	uint form;
};

/**
* This struct represents an entry in diff-algorithm sresults.
*
* @short Structure, that represents a diff result.
*/
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 DiffResult{DiffOk, DiffNotFound, DiffNeedList};

   /**
   * reads header information from the file and searches for charset
   * information.
   * @param gettextHeader text containing gettext headers
   *
   * @return Codec for found charset or 0, if no information has been found
   */
   static QTextCodec* codecForFile(QString gettextHeader);
   
   static PoInfo headerInfo(const CatalogItem headerItem);

   /**
   * A constructor for an empty message catalog.
   * @param parent parent @ref QObject for this catalog
   * @param name   unique name for this object
   * @param configFile configuration file to read config from
   */
   Catalog(QObject* parent=0, const char* name=0, QString configFile = QString::null);

   /**
   * Deprecated. A copy constructor. Do not use since each Catalog registers
   * its views and there should be no more than one Catalog for a given file.
   */
   Catalog(const Catalog&);
   virtual ~Catalog();

   /**
   * Get list of texts for a given message in original language. Each entry in the list
   * represents a single singular/plural form.
   *
   * @param index  index of the requested message
   * @param noNewLines set true if the new lines should be eliminated in result
   *        (useful for searching etc)
   * @return list of singular/plural forms of the original text
   */
   QStringList msgid(uint index, const bool noNewlines=false) const;

   /**
   * Get list of translated texts for a given message. Each entry in the list
   * represents a single singular/plural form.
   *
   * @param index  index of the requested message
   * @param noNewLines set true if the new lines should be eliminated in result
   *        (useful for searching etc)
   * @return list of translated singular/plural forms
   */
   QStringList msgstr(uint index, const bool noNewlines=false) const;

   /**
   * Get the comment for a given message.
   *
   * @param index  index of the requested message
   * @return       comment for the given message
   */
   QString comment(uint index) const;

   /**
   * Get the context information for a given message. This works
   * for GNU gettext PO files comments only. Finds and returns comment lines,
   * which start with #:
   *
   * @param index  index of the requested message
   * @return       context information found in the message comment
   */
   QString context(uint index) const;

   /**
   * Get the header for the current file.
   *
   * @return       @ref CatalogItem representing the header
   */
   CatalogItem header() const;

   /**
   * Get the name and e-mail address of the last translator of the current file.
   *
   * @return       string representation of the name and e-mail address
   */
   QString lastTranslator() const;
   
   /**
    * @return The list of obsolete entries. These cannot be changed at
    * all and are stored for import/export purposes.
    */
   QValueList<CatalogItem> obsoleteEntries() 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);

   /** @return a list of arguments in entry #index */
   QStringList argList(uint index);
   
   /**
    * @return the list of errors found for a given item index
    * @param index index of the item to check
    */
   QStringList itemStatus(uint index);

   /**
    * @return the list of errors found for a given item index
    * @param index index of the item to check
    * @param recheck flag, if the item status should be checked now
    * @param whatToCheck what checks to do (a list of tools to be used
    */
   QStringList itemStatus(uint index, bool recheck, QPtrList<KDataTool> whatToCheck);

   /**
   * 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);
   /**
   * @return URL currently represented by the catalog
   */
   KURL currentURL() const;

   /**
   * @return package name for the current catalog
   */
   QString packageName() const;
   /**
   * @return directory for the package of the current catalog
   */
   QString packageDir() const;
   /**
   * @return package name and directory for the current catalog
   */
   QString package() const;

   /**
   * Setup the package for the catalog. It tries to parse the
   * package and split it to name and directory
   * @param package the name and directory to be set as package
   */
   void setPackage(const QString& package);

   /**
   * @return encoding for the current catalog
   */
   QString encoding() const;

   /**
   * opens file url by using KDE's network downlad and calls
   * openFile with a local filename
   */
   ConversionStatus openURL(const KURL& url, const QString& package=QString::null);

   /**
   * opens file openURL by using KDE's network downlad and calls
   * openFile with a local filename
   * sets current URL to saveURL
   */
   ConversionStatus openURL(const KURL& openURL, const KURL& saveURL, const QString& package=QString::null);

   /** save the file under the old filename */
   ConversionStatus saveFile();
   /** saves the file under a new filename */
   ConversionStatus 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();

   /**
    * @return extra data for the catalog as set by the import filter
    */
   QStringList catalogExtraData() const;
   /**
    * @return ID string of the used import filter
    */
   QString importPluginID() const;
   /**
    * @return list of MIME types (separated by comma) this catalog
    * can be viewed as. It is set by the used import filter.
    */
   QString mimeTypes() const;
   /**
    * @return the file codec used for the current catalog
    */
   QTextCodec* fileCodec() const;

   /**
    * Check syntax of the GNU gettext PO file using 'msgfmt'.
    * 
    * @param output the result as returned by msgfmt
    * @param clearError should the errors be cleared before running msgfmt
    * @return the file codec used for the current catalog
    */
   Msgfmt::Status checkSyntax(QString& output, bool clearErrors=true);

   /**
    * checks using an external tool. The tool must provide the "validate" command
    * with the datatype expected to be CatalogItem *. The MIME type
    * is application/x-kbabel-catalogitem.
    */
   bool checkUsingTool(KDataTool* tool, bool clearErrors=true);
   
   /**
    * modifies catalog by applying an external tool on all items. The tool must provide the "validate" command
    * with the datatype expected to be CatalogItem *. The MIME type
    * is application/x-kbabel-catalogitem.
    */
   void modifyUsingTool(KDataTool* tool, const QString& command);
   
   /**
    * Returns true, if there is an ongoing activity, such as load/save
    */
   bool isActive();

   /**
    * Stop the current activity, for example load/save or validation
    */
   void stop();

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

   /** @return whether the catalog has been modified since the last save */
   bool isModified() const;
   /** sets modified flag */
   void setModified(bool flag);

   /** @return whether the catalog is read-only */
   bool isReadOnly() const;

   /** set if file is generated from a docbook source file (with xml2pot) */
   bool isGeneratedFromDocbook() const;

   /** @return current number of entries */
   uint numberOfEntries() const;
   /** @return current number of fuzzies */
   uint numberOfFuzzies() const;
   /** @return current number of untranslated entries */
   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;
   /** @return whether the entry has an error
    *  @param index index of the queried entry
    *  @param pos   position of the error (currently only form is filled)
    */
   bool hasError(uint index, DocPosition& pos) const;
   /** @return type of plural forms for the given entry
    *  @param index index of the queried entry
    */
   PluralFormType pluralForm(uint index) const;

   /** @return type of plural forms this file uses.
    */
   PluralFormType pluralFormType() const;

   /** @return number of the plural forms for a given entry
    *  @param index index of the queried entry
    */
   int numberOfPluralForms(uint index) const;
   
   /**
   * returns the expected number of plural forms
   */
   int defaultNumberOfPluralForms() const;

   /**
   * returns index of next fuzzy entry behind startIndex
   * -1 if there is no entry behind. Further, it fills the
   * pos parameter with correct index and sets the form to 0.
   */
   int nextFuzzy(uint startIndex, DocPosition &pos) const;
   /**
   * returns index of previous fuzzy entry before startIndex
   * -1 if there is no entry before. Further, it fills the
   * pos parameter with correct index and sets the form to 0.
   */
   int prevFuzzy(uint startIndex, DocPosition &pos) const;
   /**
   * returns index of next untranslated entry behind startIndex
   * -1 if there is no entry behind. Further, it fills the
   * pos parameter with correct index and the form.
   */
   int nextUntranslated(uint startIndex, DocPosition &pos) const;
   /**
   * returns index of previous untranslated entry before startIndex
   * -1 if there is no entry before. Further, it fills the
   * pos parameter with correct index and the form.
   */
   int prevUntranslated(uint startIndex, DocPosition &pos) const;
   /**
   * returns index of next error entry behind startIndex
   * -1 if there is no entry behind
   */
   int nextError(uint startIndex, DocPosition &pos) const;
   /**
   * returns index of previous error entry before startIndex
   * -1 if there is no entry before
   */
   int prevError(uint startIndex, DocPosition &pos) 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(KConfig* config);
   /** save settings to the apps config file*/
   void savePreferences(KConfig* config);

   SaveSettings saveSettings() const;
   IdentitySettings identitySettings() const;
   MiscSettings miscSettings() const;
   TagSettings tagSettings() const;

   /**
   * 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 ConversionStatus 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);

   /**
    * Remove the index from the error list 
    * @param index index to be removed
    */
   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);
   
public slots:
   /** 
    * Update the save setting using this one.
    * @param settings the new settings.
    */
   void setSettings(KBabel::SaveSettings settings);
   /** 
    * Update the identity setting using this one.
    * @param settings the new settings.
    */
   void setSettings(KBabel::IdentitySettings settings);
   /** 
    * Update the miscelanous setting using this one.
    * @param settings the new settings.
    */
   void setSettings(KBabel::MiscSettings settings);
   /** 
    * Update the tag setting using this one.
    * @param settings the new settings.
    */
   void setSettings(KBabel::TagSettings settings);

   /**
    * Apply the given edit command.
    * 
    * @param cmd the command to be applied
    * @param souce the view which applies the command
    */
   void applyEditCommand(EditCommand* cmd,CatalogView* source);
   /**
    * Convenience method. Apply a begin edit command for a given message and catalog
    * source.
    * 
    * @param index index of to be changed message
    * @param part  part (msgid,msgstr,comment) of to be changed message
    * @param source the view which applies the command
    */
   void applyBeginCommand(uint index, Part part, CatalogView* source);
   /**
    * Convenience method. Apply an end edit command for a given message and catalog
    * source.
    * 
    * @param index index of to be changed message
    * @param part  part (msgid,msgstr,comment) of to be changed message
    * @param source the view which applies the command
    */
   void applyEndCommand(uint index, Part part, CatalogView* source);

private slots:
   /** slot used for internal long-term activity */
   void stopInternal();

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);

   /** do the actual file write using plugin */
   ConversionStatus writeFile(QString localfile, bool overwrite=false);

   /**
   * 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;

   /** clear the list of all errors */
   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();
   
   // this class can fill our contents
   friend class CatalogImportPlugin;
   
   /** set the flag that the catalog is generated from docbook */
   void setGeneratedFromDocbook(const bool generated);
   /** set the entries of the catalog */
   void setEntries( QValueVector<CatalogItem> entries);
   /** set the obsolete entries of the catalog */
   void setObsoleteEntries( QValueList<CatalogItem> entries);
   /** set extra data for the catalog as defined by import plugin */
   void setCatalogExtraData(const QStringList& data);
   /** set file codec for the catalog */
   void setFileCodec(QTextCodec* codec);
   /** set the list of import errors */
   void setErrorIndex(const QValueList<uint>&errors);
   /** set ID of the used import plugin */
   void setImportPluginID(const QString& id);
   /** set the MIME types for the current catalog as defined by import plugin */
   void setMimeTypes(const QString& mimeTypes);
   
private:
   CatalogPrivate* d;
   
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(KBabel::SaveSettings);
   void signalSettingsChanged(KBabel::IdentitySettings);
   void signalSettingsChanged(KBabel::MiscSettings);
   void signalSettingsChanged(KBabel::TagSettings);

   void signalUndoAvailable(bool);
   void signalRedoAvailable(bool);
   
   /** internal signal sent to external part to stop current ongoing activity*/
   void signalStopActivity();
};

}

#endif //CATALOG_H
