/***************************************************************************
 *   Copyright (C) 2006 by Andreas Gungl <a.gungl@gmx.de>                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library 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 Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#ifndef ENTITY_H
#define ENTITY_H

#include <QtCore/Qt>
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QString>
#include <QtCore/QStringList>

class QVariant;
class QSqlDatabase;

namespace Akonadi {
namespace Server {


enum Tristate {
  False = 0,
  True = 1,
  Undefined = 2
};

/**
  Base class for classes representing database records. It also contains
  low-level data access and manipulation template methods.
*/
class Entity
{
  public:
    typedef qint64 Id;

  protected:
    qint64 id() const;
    void setId( qint64 id );

    bool isValid() const;

  public:
    template <typename T> static QString joinByName( const QVector<T> &list, const QString &sep )
    {
      QStringList tmp;
      Q_FOREACH ( const T &t, list ) {
        tmp << t.name();
      }
      return tmp.join( sep );
    }

    /**
      Returns the number of records having @p value in @p column.
      @param column The name of the key column.
      @param value The value used to identify the record.
    */
    template <typename T> inline static int count( const QString &column, const QVariant &value )
    {
      return Entity::countImpl( T::tableName(), column, value );
    }

    /**
      Deletes all records having @p value in @p column.
    */
    template <typename T> inline static bool remove( const QString &column, const QVariant &value )
    {
      return Entity::removeImpl( T::tableName(), column, value );
    }

    /**
      Checks whether an entry in a n:m relation table exists.
      @param leftId Identifier of the left part of the relation.
      @param rightId Identifier of the right part of the relation.
     */
    template <typename T> inline static bool relatesTo( qint64 leftId, qint64 rightId )
    {
      return Entity::relatesToImpl( T::tableName(), T::leftColumn(), T::rightColumn(), leftId, rightId );
    }

    /**
      Adds an entry to a n:m relation table (specified by the template parameter).
      @param leftId Identifier of the left part of the relation.
      @param rightId Identifier of the right part of the relation.
    */
    template <typename T> inline static bool addToRelation( qint64 leftId, qint64 rightId )
    {
      return Entity::addToRelationImpl( T::tableName(), T::leftColumn(), T::rightColumn(), leftId, rightId );
    }

    /**
      Removes an entry from a n:m relation table (specified by the template parameter).
      @param leftId Identifier of the left part of the relation.
      @param rightId Identifier of the right part of the relation.
    */
    template <typename T> inline static bool removeFromRelation( qint64 leftId, qint64 rightId )
    {
      return Entity::removeFromRelationImpl( T::tableName(), T::leftColumn(), T::rightColumn(), leftId, rightId );
    }

    enum RelationSide {
      Left,
      Right
    };

    /**
      Clears all entries from a n:m relation table (specified by the given template parameter).
      @param id Identifier on the relation side.
      @param side The side of the relation.
    */
    template <typename T> inline static bool clearRelation( qint64 id, RelationSide side = Left )
    {
      return Entity::clearRelationImpl( T::tableName(), T::leftColumn(), T::rightColumn(), id, side );
    }

  protected:
    Entity();
    explicit Entity( qint64 id );
    ~Entity();

  private:
    static int countImpl( const QString &tableName, const QString &column, const QVariant &value );
    static bool removeImpl( const QString &tableName, const QString &column, const QVariant &value );
    static bool relatesToImpl( const QString &tableName, const QString &leftColumn, const QString &rightColumn, qint64 leftId, qint64 rightId );
    static bool addToRelationImpl( const QString &tableName, const QString &leftColumn, const QString &rightColumn, qint64 leftId, qint64 rightId );
    static bool removeFromRelationImpl( const QString &tableName, const QString &leftColumn, const QString &rightColumn, qint64 leftId, qint64 rightId );
    static bool clearRelationImpl( const QString &tableName, const QString &leftColumn, const QString &rightColumn, qint64 id, RelationSide side );

  private:
    static QSqlDatabase database();
    qint64 m_id;
};

namespace _detail {

    /*!
      Binary predicate to sort collections of Entity subclasses by
      their id.

      Example for sorting:
      \code
      std::sort( coll.begin(), coll.end(), _detail::ById<std::less>() );
      \endcode

      Example for finding by id:
      \code
      // linear:
      std::find_if( coll.begin(), coll.end(), bind( _detail::ById<std::equal_to>(), _1, myId ) );
      // binary:
      std::lower_bound( coll.begin(), coll.end(), myId, _detail::ById<std::less>() );
      \end
    */
    template <template <typename U> class Op>
    struct ById {
        typedef bool result_type;
        bool operator()( Entity::Id lhs, Entity::Id rhs ) const
        {
            return Op<Entity::Id>()( lhs, rhs );
        }
        template <typename E>
        bool operator()( const E &lhs, const E &rhs ) const
        {
            return this->operator()( lhs.id() , rhs.id() );
        }
        template <typename E>
        bool operator()( const E &lhs, Entity::Id rhs ) const
        {
            return this->operator()( lhs.id(), rhs );
        }
        template <typename E>
        bool operator()( Entity::Id lhs, const E &rhs ) const
        {
            return this->operator()( lhs, rhs.id() );
        }
    };
}

} // namespace Server
} // namespace Akonadi

Q_DECLARE_METATYPE(Akonadi::Server::Tristate);

#endif
