/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2004-06-15
 * Description : digiKam album types
 *
 * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
 * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * 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, 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.
 *
 * ============================================================ */

/** @file album.h */

#ifndef ALBUM_H
#define ALBUM_H

// Qt includes

#include <QList>
#include <QString>
#include <QDateTime>
#include <QMap>

// KDE includes

#include <kurl.h>

// Local includes

#include "albuminfo.h"
#include "databaseurl.h"

namespace Digikam
{

/**
 * \class Album
 * \brief Abstract base class for all album types
 *
 * A class which provides an abstraction for a type Album. This class is meant to
 * be derived and every time a new Album Type is defined add a enum corresponding
 * to that to Album::Type
 *
 * This class provides a means of building a tree representation for
 * Albums @see Album::setParent().
 */
class Album
{
public:

    enum Type
    {
        PHYSICAL=0, /**<  PHYSICAL: A physical album type @see PAlbum */
        TAG,        /**<  TAG:      A tag      album type @see TAlbum */
        DATE,       /**<  DATE:     A date     album type @see DAlbum */
        SEARCH      /**<  SEARCH:   A search   album type @see SAlbum */
    };

    /**
     * @return the parent album for this album
     */
    Album*  parent() const;

    /**
     * @return the first child of this album or 0 if no children
     */
    Album*  firstChild() const;

    /**
     * @return the last child of this album or 0 if no children
     */
    Album*  lastChild() const;

    /**
     * @return the next sibling of this album of this album or 0
     * if no next sibling
     * @see AlbumIterator
     */
    Album*  next() const;

    /**
     * @return the previous sibling of this album of this album or 0 if no
     * previous sibling
     * @see AlbumIterator
     */
    Album*  prev() const;

    /**
     * @return the type of album
     * @see Type
     */
    Type    type() const;

    /**
     * Each album has a @p ID uniquely identifying it in the set of Albums of
     * a Type
     *
     * \note The @p ID for a root Album is always 0
     *
     * @return the @p ID of the album
     * @see globalID()
     */
    int     id() const;

    /**
     * An album ID is only unique among the set of all Albums of its Type.
     * This is a global Identifier which will uniquely identifying the Album
     * among all Albums
     *
     * \note If you are adding a new Album Type make sure to update
     * this implementation.
     *
     * You can always get the @p ID of the album using something like
     *
     * \code
     * int albumID = rootAlbum->globalID() - album->globalID();
     * \endcode
     *
     * @return the @p globalID of the album
     * @see id()
     */
    int     globalID() const;

    /**
     * @return the @p title aka name of the album
     */
    QString title() const;

    /**
     * @return the kde url of the album
     */
    virtual DatabaseUrl databaseUrl() const = 0;

    /**
     * @return true is the album is a Root Album
     */
    bool    isRoot() const;

    /**
     * @return true if the @p album is in the parent hierarchy
     *
     * @param album Album to check whether it belongs in the child
     * hierarchy
     */
    bool    isAncestorOf(Album* album) const;

    /**
     * This allows to associate some "extra" data to a Album. As one
     * Album can be used by several objects (often views) which all need
     * to add some data, you have to use a key to reference your extra data
     * within the Album.
     *
     * That way a Album can hold and provide access to all those views
     * separately.
     *
     * for eg,
     *
     * \code
     * album->setExtraData( this, searchFolderItem );
     * \endcode
     *
     * and can later access the searchFolderItem by doing
     *
     * \code
     * SearchFolderItem *item = static_cast<SearchFolderItem*>(album->extraData(this));
     * \endcode
     *
     * Note: you have to remove and destroy the data you associated yourself
     * when you don't need it anymore!
     *
     * @param key the key of the extra data
     * @param value the value of the extra data
     * @see extraData
     * @see removeExtraData
     */
    void    setExtraData(const void* key, void *value);

    /**
     * Remove the associated extra data associated with @p key
     *
     * @param key the key of the extra data
     * @see setExtraData
     * @see extraData
     */
    void    removeExtraData(const void* key);

    /**
     * Retrieve the associated extra data associated with @p key
     *
     * @param key the key of the extra data
     * @see setExtraData
     * @see extraData
     */
    void*   extraData(const void* key) const;

protected:

    /**
     * Constructor
     */
    Album(Album::Type type, int id, bool root);

    /**
     * Destructor
     *
     * this will also recursively delete all child Albums
     */
    virtual ~Album();

    /**
     * Delete all child albums and also remove any associated extra data
     */
    void    clear();

    /**
     * @internal use only
     *
     * Set a new title for the album
     *
     * @param title new title for the album
     */
    void setTitle(const QString& title);

    /**
     * @internal use only
     *
     * Set the parent of the album
     *
     * @param parent set the parent album of album to @p parent
     */
    void setParent(Album* parent);

    /**
     * @internal use only
     *
     * Insert an Album as a child for this album
     *
     * @param child the Album to add as child
     */
    void insertChild(Album* child);

    /**
     * @internal use only
     *
     * Remove a Album from the children list for this album
     *
     * @param child the Album to remove
     */
    void removeChild(Album* child);

private:

    /**
     * Disable copy and default constructor
     */
    Album();
    Album(const Album&);
    Album& operator==(const Album&);

private:

    bool                     m_root;
    bool                     m_clearing;

    int                      m_id;

    QString                  m_title;
    QMap<const void*, void*> m_extraMap;

    Type                     m_type;

    Album*                   m_parent;
    Album*                   m_firstChild;
    Album*                   m_lastChild;
    Album*                   m_next;
    Album*                   m_prev;

    friend class AlbumManager;
};

/**
 * \class PAlbum
 *
 * A Physical Album representation
 */
class PAlbum : public Album
{
public:

    /// Constructor for root album
    PAlbum(const QString& title);
    /// Constructor for album root albums
    PAlbum(int albumRoot, const QString &label);
    /// Constructor for normal albums
    PAlbum(int albumRoot, const QString &parentPath, const QString& title, int id);
    ~PAlbum();

    void setCaption(const QString& caption);
    void setCategory(const QString& category);
    void setDate(const QDate& date);

    QString     albumRootPath() const;
    int         albumRootId() const;
    QString     caption() const;
    QString     category() const;
    QDate       date() const;
    QString     albumPath() const;
    QString     prettyUrl() const;
    QString     folderPath() const;
    DatabaseUrl databaseUrl() const;
    KUrl        fileUrl() const;
    QString     icon() const;
    KUrl        iconKURL() const;

    bool        isAlbumRoot() const;

private:

    bool       m_isAlbumRootAlbum;

    int        m_albumRootId;

    QString    m_path;
    QString    m_parentPath;
    QString    m_category;
    QString    m_caption;
    QString    m_icon;

    QDate      m_date;

    friend class AlbumManager;
};

/**
 * \class TAlbum
 *
 * A Tag Album representation
 */
class TAlbum : public Album
{
public:

    TAlbum(const QString& title, int id, bool root=false);
    ~TAlbum();

    /**
     * @return The tag path, e.g. "/People/Friend/John" if leadingSlash is true,
               "People/Friend/John" if leadingSlash if false.
     *         The root TAlbum returns "/" resp. "".
     */
    QString     tagPath(bool leadingSlash = true) const;
    DatabaseUrl databaseUrl() const;
    QString     prettyUrl() const;
    QString     icon() const;
    QList<int>  tagIDs() const;

private:

    int     m_pid;

    QString m_icon;

    friend class AlbumManager;
};

/**
 * \class DAlbum
 *
 * A Date Album representation
 */
class DAlbum : public Album
{
public:

    enum Range
    {
        Month = 0,
        Year
    };

    DAlbum(const QDate& date, bool root=false, Range range=Month);
    ~DAlbum();

    QDate date() const;
    Range range() const;
    DatabaseUrl databaseUrl() const;

private:

    static int  m_uniqueID;

    QDate       m_date;

    Range       m_range;

    friend class AlbumManager;
};

/**
 * \class SAlbum
 *
 * A Search Album representation
 */
class SAlbum : public Album
{
public:

    SAlbum(const QString &title, int id, bool root=false);
    ~SAlbum();

    DatabaseUrl          databaseUrl() const;
    QString              query() const;
    DatabaseSearch::Type type() const;
    bool                 isNormalSearch() const;
    bool                 isAdvancedSearch() const;
    bool                 isKeywordSearch() const;
    bool                 isTimelineSearch() const;
    bool                 isHaarSearch() const;
    bool                 isMapSearch() const;
    bool                 isDuplicatesSearch() const;

private:

    void setSearch(DatabaseSearch::Type type, const QString &query);

private:

    QString              m_query;
    DatabaseSearch::Type m_type;

    friend class AlbumManager;
};

/**
 *  \class AlbumIterator
 *
 *  Iterate over all children of this Album.
 *  \note It will not include the specified album
 *
 *  Example usage:
 *  \code
 *  AlbumIterator it(album);
 *  while ( it.current() )
 *  {
 *     kDebug(50003) << "Album: " << it.current()->title() << endl;
 *     ++it;
 *  }
 * \endcode
 *
 *  \warning Do not delete albums using this iterator.
 */
class AlbumIterator
{
public:

    AlbumIterator(Album *album);
    ~AlbumIterator();

    AlbumIterator& operator++();
    Album*         operator*();
    Album*         current() const;

private:

    AlbumIterator() {}
    AlbumIterator(const AlbumIterator&) {}
    AlbumIterator& operator=(const AlbumIterator&){ return *this; }

private:

    Album* m_current;
    Album* m_root;
};

}  // namespace Digikam

Q_DECLARE_METATYPE(Digikam::Album*)

#endif /* ALBUM_H */
