/*
    This file is part of Kiten, a KDE Japanese Reference Tool
    SPDX-FileCopyrightText: 2006 Joseph Kerian <jkerian@gmail.com>
    SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#ifndef KITEN_DICTQUERY_H
#define KITEN_DICTQUERY_H

#include <QHash>
#include <QHashIterator>
#include <QString>
#include <QStringList>

class QChar;

#include "kiten_export.h"

 /**
  * @short A class to allow users of libkiten to properly setup a database
  * query.
  *
  * In general, you either pass or parse in parameters from users or automated
  * programs to be later sent to the dictionary manager.
  *
  * This class is one of the three critical classes (along with
  * dictionary and EntryList) that are needed to use libkiten. Essentially...
  * you feed the dictionary class a DictQuery, and dictionary will return an
  * EntryList that matches the query.
  *
  * @code
  * dictionary dictManager();
  * //Load some dictionaries via dictionary class methods
  * EntryList *results;
  * DictQuery myQuery("kanji");
  * results = dictManager.doSearch(myQuery);
  * //Print results (if any)
  * @endcode
  *
  * The internal implementation understands four distinct types of data:
  * Japanese Kanji
  * Japanese Kana
  * English Characters
  * Property Pairs of the form \<i\>name\</i\>:\<i\>value\</i\>
  *
  * It is left up to the individual dictionary types to parse these values
  * for matching and appropriateness to each dictionary.
  * You can use the setDictionaries() method to narrow the range of the
  * dictionaries that it will apply to.
  *
  * A DictQuery object can be considered an "unordered set" of values.
  * When testing for equality or comparison, each property or text entry above
  * is seen as a unique item in a set. Order is not important for these
  * operations. The object will attempt to preserve the order from parsed
  * strings, but it does not consider this order to be important.
  *
  * In general, application level programs are expected to use the
  * QString based interfaces, and dictionary implementations and other
  * parts of libkiten are expected to use the direct accessors and mutators,
  * although specialized interfaces (such as kitenradselect) may use property
  * mutators for a limited set of properties. (in this case, radicals)
  *
  * The query string input is of the following format:
  * &lt;QS&gt; ::= &lt;M&gt;DictQuery::mainDelimiter&lt;QS&gt;|&lt;R&gt;DictQuery::mainDelimiter&lt;QS&gt;|
  *       &lt;O&gt;DictQuery::mainDelimiter&lt;QS&gt;|NULL
  * &lt;M&gt;  ::= kana&lt;M&gt;|kana
  * &lt;R&gt;  ::= character&lt;R&gt;|character
  * &lt;O&gt;  ::= &lt;C&gt;DictQuery::propertySeparator&lt;D&gt;
  * &lt;C&gt;  ::= character&lt;C&gt;|character
  * &lt;D&gt;  ::= character&lt;D&gt;|character
  *
  * @author Joseph Kerian \<jkerian@gmail.com>
  */
class KITEN_EXPORT DictQuery
{
public:
  /**
   * This is the main delimiter that the DictQuery uses when parsing strings.
   * It is set to "space" at the moment.
   */
  static const QString mainDelimiter;
  /**
   * This is the delimiter that DictQuery uses when parsing property strings
   * of the form <i>strokes:4</i>. It is set to ":" at the moment.
   */
  static const QString propertySeperator;
  /**
    * Normal constructor.
    *
    * This will create an empty query object.
    */
  DictQuery();
  /**
    * Constructor with a given QString.
    *
    * @param str the QString will be parsed as described below in operator=(const QString&)
    */
  explicit DictQuery( const QString& str );
  /**
   * Copy constructor
   *
   * @param orig the original DictQuery to be copied
   */
  DictQuery( const DictQuery& orig );
  /**
   * Destructor
   */
  ~DictQuery();

  /**
   * @return true if the DictQuery is completely empty
   */
  bool isEmpty() const;
  /**
   * Removes all text/entries from the DictQuery
   */
  void clear();
  /**
   * The assignment copy operator
   */
  DictQuery &operator=( const DictQuery &old );
  /**
   * The clone method
   */
  DictQuery *clone() const;
  /**
   * This returns a QString that represents the query. This may be the same
   * as the original string, but some slight changes may have occurred if you
   * have done any manipulations on the DictQuery.
   */
  const QString toString() const;
  /**
   * This is a conversion to a QString... useful in a surprising
   * number of cases.
   */
  operator QString() const;

  /**
   * Use this to get a list of all the property keys in the query
   */
  const QList<QString> listPropertyKeys() const;
  /**
   * Returns a given extended attribute
   */
  const QString operator[]( const QString &key ) const;
  /**
   * Sets a given extended attribute
   */
  QString operator[]( const QString &key );
  /**
   * Get a specific property by key (is the same as using operator[] const)
   */
  QString getProperty( const QString &key ) const;
  /**
   * Verify if a given DictQuery object has a search parameter of a
   * particular property.
   */
  bool hasProperty( const QString &key ) const;
  /**
   * Set a particular property... this does significantly more error checking
   * than the operator[] version, and will return false if there was a
   * problem (an empty value or bad key)
   *
   * @param key the key for this entry
   * @param value the value to set this to, will overwrite the current contents of this location
   *
   * @returns false on failure
   */
  bool setProperty( const QString& key, const QString& value );
  /**
   * Remove all instances of a property.
   *
   * @returns true if the DictQuery had properties of the given type
   */
  bool removeProperty( const QString &key );
  /**
   * Returns and removes the property
   */
  QString takeProperty( const QString &key );

  /**
   * Returns a list of the dictionaries that this particular query
   * will target. An empty list (the default) will search all dictionaries
   * that the user has selected.
   */
  QStringList getDictionaries() const;
  /**
   * Set the list of dictionaries to search. This will be read and used
   * by the dictionary manager.
   */
  void setDictionaries( const QStringList &newDictionaries );

  /**
   * Accessor for the non-japanese meaning field
   */
  QString getMeaning() const;
  /**
   * Mutator for the Meaning field
   */
  bool setMeaning( const QString &newMeaning );
  /**
   * Accessor for the Pronunciation field (generally kana)
   */
  QString getPronunciation() const;
  /**
   * Mutator for the Pronunciation field
   */
  bool setPronunciation( const QString &newPronunciation );
  /**
   * Accessor for the Word/Kanji field (this is usually used for anything
   * containing kanji).
   */
  QString getWord() const;
  /**
   * Mutator for the Word/Kanji field. If what you are setting contains
   * only kana, consider using the setPronunciation instead.
   */
  bool setWord( const QString &newWord );

  /**
   * A simple setwise comparison of two DictQuery objects
   * Note that order is not important here... only each element
   * that is one of the DictQuery objects appears in the other.
   */
  KITEN_EXPORT friend bool operator==( const DictQuery &a, const DictQuery &b );
  /**
   * Convenient inverted override of operator==( DictQuery, DictQuery )
   */
  KITEN_EXPORT friend bool operator!=( const DictQuery &other, const DictQuery &query );
  /**
   * Set-wise strictly less than. A better way to think of this
   * might be the "subset" operator
   */
  KITEN_EXPORT friend bool operator<( const DictQuery &a, const DictQuery &b );
  /**
   * Convenient override of operator<( DictQuery, DictQuery ) and operator==
   */
  KITEN_EXPORT friend bool operator<=( const DictQuery &a, const DictQuery &b );
  /**
   * This will append the properties and other elements of the added kanji
   * onto the elements of the current element. If regenerated as a string,
   * it should look something like concatenation
   */
  DictQuery &operator+=( const DictQuery &old );
  /**
   * A simple string parser, look above for examples and explanations
   */
  DictQuery &operator=( const QString &old );
  /**
   * A simple override of operator+=( const DictQuery& )
   */
  DictQuery &operator+=( const QString &old );
  /**
   * Simple addition... similar to operator+=
   */
  KITEN_EXPORT friend DictQuery operator+( const DictQuery &a, const DictQuery &b );
#ifndef QT_NO_CAST_ASCII
  /**
   * An ascii cast variant of the operator=
   * Only available if QT_NO_CAST_ASCII is not defined on lib compilation
   */
  DictQuery &operator=( const char* );
#endif

   //Specify the type of matching
  /**
   * This enum is used to define the type of matching this query is supposed
   * to do. The names are fairly self-explanatory
   */
  enum MatchType
  {
    Exact,
    Beginning,
    Ending,
    Anywhere
  };
  /**
   * Get which match type is currently set on the DictQuery.
   */
  MatchType getMatchType() const;
  /**
   * Set a match type. If this is not called, the default is matchExact.
   */
  void setMatchType( MatchType newType );

  /**
   * This enum is used to define the type of matching this query is supposed
   * to do.
   */
  enum MatchWordType
  {
    Any,
    Verb,
    Noun,
    Adjective,
    Adverb,
    Prefix,
    Suffix,
    Expression
  };
  /**
   * Get which word type is currently set on the DictQuery.
   */
  MatchWordType getMatchWordType() const;
  /**
   * Set a word type. If this is not called, the default value is 'Any'.
   */
  void setMatchWordType( MatchWordType newType );

  enum FilterType
  {
    NoFilter,
    Rare,
    CommonUncommon
  };
  /**
   * Get which filter is currently set on the DictQuery.
   */
  FilterType getFilterType() const;
  /**
   * Set whether or not the query should output results separated in
   * common and uncommon sections.
   */
  void setFilterType( FilterType newType );

  /**
   * This enum is used as the return type for the two utility functions,
   * stringTypeCheck and charTypeCheck.
   */
  enum StringTypeEnum
  {
    Kanji,
    Kana,
    Latin,
    Mixed,
    ParseError
  };
  /**
   * A simple utility routine to tell us what sort of string we have
   * If the string contains only kanji, kana or non-kanji/kana characters, the result is strTypeKanji,
   * strTypeKana or strTypeLatin (perhaps a misnomer... but so far it's valid).
   * If the string contains both kanji and kana, the type returned is strTypeKanji
   * If the string contains any other combination, the return type is mixed.
   */
  static StringTypeEnum stringTypeCheck( const QString &in );
  /**
   * This utility does the same thing for QChar as stringTypeCheck does for QString. At the moment
   * the implementation is rather simple, and it assumes that anything that is not latin1 or kana is
   * a kanji.
   */
  static StringTypeEnum charTypeCheck( const QChar &ch );

private:
  class Private;
  Private* const d;
};

//Currently... KDE doesn't seem to want to use exceptions
#ifdef LIBKITEN_USING_EXCEPTIONS
class InvalidQueryException
{
  public:
    InvalidQueryException( QString x ) { m_val = x; }
    InvalidQueryException( QString m = "Invalid Query String", QString x ) { m_val = x; m_msg = m; }
    QString value() { return m_val; }
    QString message() { return m_msg; }

  protected:
    QString m_val;
    QString m_msg;
};
#endif

#endif
