/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#pragma once

#include <boost/optional.hpp>
#include <QObject>
#include <QSet>
#include <interfaces/core/ihookproxy.h>
#include <interfaces/core/itagsmanager.h>
#include "feed.h"

namespace LeechCraft
{
namespace Aggregator
{
	class StorageBackend;
	typedef std::shared_ptr<StorageBackend> StorageBackend_ptr;

	/** @brief Abstract base class for storage backends.
	 *
	 * Specifies interface for all storage backends. Includes functions for
	 * appending, modifying and retrieving feeds, channels and items.
	 */
	class StorageBackend : public QObject
	{
		Q_OBJECT
	public:
		struct ChannelNotFoundError {};
		struct ItemNotFoundError {};

		enum Type
		{
			SBSQLite,
			SBPostgres,
			SBMysql
		};

		using QObject::QObject;

		static StorageBackend_ptr Create (const QString&, const QString& = QString ());
		static StorageBackend_ptr Create (Type, const QString& = QString ());

		/** @brief Do post-initialization.
		 *
		 * This function is called by the Core after all the updates are
		 * checked and done, if required.
		 *
		 * @sa UpdateFeedsStorage
		 * @sa UpdateChannelsStorage
		 * @sa UpdateItemsStorage
		 */
		virtual void Prepare () = 0;

		/** @brief Returns all the feeds in the storage.
		 *
		 * @return All feed IDs.
		 */
		[[nodiscard]] virtual ids_t GetFeedsIDs () const = 0;

		/** @brief Returns the feed identified by its id.
		 *
		 * @param[in] id The ID of the feed to be returned.
		 * @return The full feed information.
		 */
		virtual boost::optional<Feed> GetFeed (const IDType_t& id) const = 0;

		/** @brief Returns the ID of the feed with the given url
		 * or -1 if it could not be found.
		 *
		 * @param[in] url The URL of the feed to be found.
		 * @return The ID of the feed or -1 if there is no such feed.
		 */
		virtual IDType_t FindFeed (const QString& url) const = 0;

		/** @brief Returns feed's settings.
		 *
		 * Returns invalid (default-constructed) FeedSettings if no settings
		 * exist in the storage.
		 *
		 * @param[in] feed Feed's ID.
		 * @return FeedSettings for the feed.
		 */
		virtual boost::optional<Feed::FeedSettings> GetFeedSettings (const IDType_t& feed) const = 0;

		/** @brief Sets feed's settings.
		 *
		 * Sets new feed settings replacing old ones if they exist.
		 *
		 * @param[in] settings New feed's settings.
		 */
		virtual void SetFeedSettings (const Feed::FeedSettings& settings) = 0;

		/** @brief Get all the channels of a feed in the container.
		 *
		 * Returns short information about channels in the storage which
		 * have feedParent as their parent's feed ID.
		 *
		 * @param[out] shorts The container with short information about the
		 * channels to which retrieved info would be appended.
		 * @param[in] feedParent Parent feed's ID identifying the feed.
		 */
		virtual channels_shorts_t GetChannels (const IDType_t& feedParent) const = 0;

		/** @brief Returns full information about a channel.
		 *
		 * Returns full information about a channel identified
		 * by its ID. The channel's Items_ field isn't filled
		 * with child items though, the items should be
		 * requested by a separate call to GetItems().
		 *
		 * @param[in] channelId The ID of the channel.
		 * @return Full information about the requested channel.
		 */
		virtual boost::optional<Channel> GetChannel (const IDType_t& channelId) const = 0;

		/** @brief Find channel with the given title, link and
		 * and given parent and returns its ID or -1 if it's not
		 * found.
		 *
		 * @param[in] title The channel's title.
		 * @param[in] link The channel's link.
		 * @param[in] feedId ID of the parent feed.
		 * @return ID of the found channel or -1 if it's not
		 * found.
		 */
		virtual IDType_t FindChannel (const QString& title, const QString& link, const IDType_t& feedId) const = 0;

		/** @brief Trims the channel to remove old items.
		 *
		 * Emits channelDataUpdated() after that.
		 *
		 * @param[in] channelId The ID of the channel to trim.
		 * @param[in] days Max number of days.
		 * @param[in] number Max number of items.
		 */
		virtual void TrimChannel (const IDType_t& channelId, int days, int number) = 0;

		/** @brief Returns short information about items in a channel.
		 *
		 * Returns short information about items in the storage which are
		 * identified by their channel's ID.
		 *
		 * @param[out] items The container to which short information about
		 * the items would be appended.
		 * @param[in] channelId The ID of the channel.
		 */
		virtual items_shorts_t GetItems (const IDType_t& channelId) const = 0;

		/** @brief Counts unread items number in a given channel.
		 *
		 * A possibly optimized version of getting items via
		 * GetItems() and counting unread items by hand.
		 *
		 * @param[in] id Channel's ID.
		 * @return Unread items count.
		 */
		virtual int GetUnreadItems (const IDType_t& id) const = 0;

		/** @brief Returns full information about an item.
		 *
		 * Returns full information about the item identified by
		 * its ID.
		 *
		 * @param[in] id The item's ID.
		 * @return Full information about the requested item.
		 */
		virtual boost::optional<Item> GetItem (const IDType_t& id) const = 0;

		/** @brief Finds first item with the given title, link and parent
		 * channel.
		 *
		 * Returns its ID or an empty optional object if no such item is
		 * found.
		 *
		 * This function requires both \em title and \em link to match.
		 * Sometimes this may be too strict, so functions FindItemByLink()
		 * and FindItemByTitle() may be useful instead.
		 *
		 * @param[in] title Title of the item to be found.
		 * @param[in] link Link of the item to be found.
		 * @param[in] channel ID of the parent channel.
		 * @return ID of the found item or an empty optional object if no
		 * such item exists.
		 *
		 * @sa FindItemByLink()
		 * @sa FindItemByTitle()
		 */
		virtual boost::optional<IDType_t> FindItem (const QString& title,
				const QString& link, const IDType_t& channel) const = 0;

		/** @brief Finds first item with the given title and parent
		 * channel.
		 *
		 * This function may be seen as a less strict one than FindItem().
		 *
		 * @param[in] title Title of the item to be found.
		 * @param[in] channel ID of the parent channel.
		 * @return ID of the found item or an empty optional object if no
		 * such item exists.
		 *
		 * @sa FindItem()
		 * @sa FindItemByLink()
		 */
		virtual boost::optional<IDType_t> FindItemByTitle (const QString& title, const IDType_t& channel) const = 0;

		/** @brief Finds first item with the given link and parent
		 * channel.
		 *
		 * If the link is an empty string, this function returns an empty
		 * optional object.
		 *
		 * This function may be seen as a less strict one than FindItem().
		 *
		 * @param[in] link Link of the item to be found.
		 * @param[in] channel ID of the parent channel.
		 * @return ID of the found item or an empty optional object if no
		 * such item exists, or if \em link is empty.
		 *
		 * @sa FindItem()
		 * @sa FindItemByTitle()
		 */
		virtual boost::optional<IDType_t> FindItemByLink (const QString& link, const IDType_t& channel) const = 0;

		/** @brief Returns all items in the channel.
		 *
		 * Returns full information about all the items in the
		 * channel identified by channel's ID. The returned
		 * information is appended to the passed container.
		 *
		 * Usually you will use this only inside handleJobFinished().
		 *
		 * @param[out] items The container with items.
		 * @param[in] id The channel's ID.
		 */
		virtual items_container_t GetFullItems (const IDType_t& id) const = 0;

		/** @brief Puts a feed and all its child channels and items into the
		 * storage.
		 *
		 * If the specified feed already exists in the storage, this function
		 * should do nothing.
		 *
		 * @param[in] feed Pointer to the feed that should be added.
		 */
		virtual void AddFeed (const Feed& feed) = 0;

		/** @brief Adds a new channel to an already existing feed.
		 *
		 * Channels are identified by parent feed's URL.
		 *
		 * If the specified channel already exists in the storage, this
		 * function should do nothing.
		 *
		 * @param[in] channel Pointer to the channel that should be added.
		 */
		virtual void AddChannel (const Channel& channel) = 0;

		/** @brief Adds a new item to an already existing channel.
		 *
		 * Items are compared by their ID.
		 *
		 * This function would emit channelDataUpdated() signal after it
		 * finishes.
		 *
		 * If the specified item already exists in the storage, this function
		 * should do nothing.
		 *
		 * @param[in] item Pointer to the item that should be added.
		 */
		virtual void AddItem (const Item& item) = 0;

		/** @brief Updates an already existing channel.
		 *
		 * If the specified channel doesn't exist in the storage, it should
		 * be inserted to the storage, so this function should behave like
		 * AddChannel() in this case.
		 *
		 * This function would emit channelDataUpdated() signal after it
		 * finishes.
		 *
		 * @param[in] channel Pointer to the new version of the channel
		 * that should be updated.
		 */
		virtual void UpdateChannel (const Channel& channel) = 0;

		/** @brief Updates an already existing channel.
		 *
		 * This is an overloaded function provided for convenience.
		 *
		 * @param[in] channel Short information about channel.
		 */
		virtual void UpdateChannel (const ChannelShort& channel) = 0;

		/** @brief Updates an already existing item.
		 *
		 * If the specified item doesn't exist in the storage, it should be
		 * inserted to the storage, so this function should behave like
		 * AddItem() in this case.
		 *
		 * This function would emit itemDataUpdated() signal after it
		 * finishes.
		 *
		 * @param[in] item Pointer to the new version of the item that
		 * should be updated.
		 */
		virtual void UpdateItem (const Item& item) = 0;

		/** @brief Updates an already existing item.
		 *
		 * This is an overloaded function provided for convenience.
		 *
		 * @param[in] item Short new version of the item.
		 */
		virtual void UpdateItem (const ItemShort& item) = 0;

		/** @brief Removes an already existing item.
		 *
		 * This function emits channelDataUpdated() and itemsRemoved()
		 * signals if it removes all the items successfully.
		 *
		 * If some of the specified items don't exist, this function
		 * merely ignores them..
		 *
		 * @param[in] ids IDs of the items that should be removed.
		 */
		virtual void RemoveItems (const QSet<IDType_t>& ids) = 0;

		/** @brief Removes an already existing channel.
		 *
		 * This function should remove the given channel, leaving other
		 * channels from the corresponding feed intact.
		 *
		 * If the specified channel doesn't exist, this function should
		 * do nothing.
		 *
		 * @param[in] id Channel ID.
		 */
		virtual void RemoveChannel (const IDType_t& id) = 0;

		/** @brief Removes an already existing feed.
		 *
		 * If the specified feed doesn't exist, this function should do
		 * nothing.
		 *
		 * @param[in] feedId The ID of the feed that should be removed.
		 */
		virtual void RemoveFeed (const IDType_t& feedId) = 0;

		/** @brief Update feeds storage section.
		 *
		 * This function is called if feeds storage section format version
		 * stored in application settings is lower than newer one.
		 *
		 * @param[in] oldV Old storage version.
		 * @param[in] newV New storage version.
		 *
		 * @return true if update successful, else false.
		 */
		virtual bool UpdateFeedsStorage (int oldV, int newV) = 0;

		/** @brief Update channels storage section.
		 *
		 * This function is called if channels storage section format version
		 * stored in application settings is lower than newer one.
		 *
		 * @param[in] oldV Old storage version.
		 * @param[in] newV New storage version.
		 *
		 * @return true if update successful, else false.
		 */
		virtual bool UpdateChannelsStorage (int oldV, int newV) = 0;

		/** @brief Update items storage section.
		 *
		 * This function is called if items storage section format version
		 * stored in application settings is lower than newer one.
		 *
		 * @param[in] oldV Old storage version.
		 * @param[in] newV New storage version.
		 *
		 * @return true if update successful, else false.
		 */
		virtual bool UpdateItemsStorage (int oldV, int newV) = 0;

		/** @brief Toggle channel state.
		 *
		 * Changes state of all the items matching the passed ID.
		 *
		 * @param[in] id Channel's ID.
		 * @param[in] state New state of the items.
		 */
		virtual void ToggleChannelUnread (const IDType_t& id, bool state) = 0;

		virtual QList<ITagsManager::tag_id> GetItemTags (const IDType_t& id) = 0;
		virtual void SetItemTags (const IDType_t& id, const QList<ITagsManager::tag_id>& tags) = 0;
		virtual QList<IDType_t> GetItemsForTag (const ITagsManager::tag_id& tag) = 0;

		/** @brief Searches for highest id of given type in the database
		 *
		 * @param[in] type of id to find
		 * @return highest channels id in the database or 0 if empty
		 */
		virtual IDType_t GetHighestID (const PoolType& type) const = 0;
	signals:
		void channelAdded (const Channel& channel) const;

		/** @brief Notifies about updated channel information.
		 *
		 * This signal is emitted when a channel is updated.
		 *
		 * @warning StorageBackendManager::channelDataUpdated() should
		 * be used instead as it collects the signal from all
		 * instantiated storage managers.
		 *
		 * @param[out] channel Pointer to the updated channel.
		 *
		 * @sa StorageBackendManager
		 */
		void channelDataUpdated (const Channel& channel) const;

		/** @brief Notifies about updated item information.
		 *
		 * This signal is emitted when a single item is updated.
		 *
		 * @warning StorageBackendManager::itemDataUpdated() should
		 * be used instead as it collects the signal from all
		 * instantiated storage managers.
		 *
		 * @param[out] item Pointer to the updated item.
		 * @param[out] channel Pointer to the channel containing updated
		 * item.
		 *
		 * @sa StorageBackendManager
		 */
		void itemDataUpdated (const Item& item, const Channel& channel) const;

		/** @brief Notifies that a number of items was removed.
		 *
		 * @warning StorageBackendManager::itemsRemoved() should
		 * be used instead as it collects the signal from all
		 * instantiated storage managers.
		 *
		 * @param[out] items The set of IDs of items that have been
		 * removed.
		 *
		 * @sa StorageBackendManager
		 */
		void itemsRemoved (const QSet<IDType_t>& items) const;

		/** @brief Should be emitted whenever a full item is loaded.
		 *
		 * @param[out] proxy Standard proxy object.
		 * @param[out] item The pointer to the already loaded item.
		 */
		void hookItemLoad (LeechCraft::IHookProxy_ptr proxy, Item *item) const;

		void channelRemoved (IDType_t channelId);
		void feedRemoved (IDType_t feedId);
	};
}
}
