/*MT*
    
    MediaTomb - http://www.mediatomb.cc/
    
    storage.h - this file is part of MediaTomb.
    
    Copyright (C) 2005 Gena Batyan <bgeradz@mediatomb.cc>,
                       Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>
    
    Copyright (C) 2006-2010 Gena Batyan <bgeradz@mediatomb.cc>,
                            Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>,
                            Leonhard Wimmer <leo@mediatomb.cc>
    
    MediaTomb is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2
    as published by the Free Software Foundation.
    
    MediaTomb 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
    version 2 along with MediaTomb; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
    
    $Id$
*/

/// \file storage.h

#ifndef __STORAGE_H__
#define __STORAGE_H__

#include <memory>
#include <unordered_set>

#include "zmm/zmmf.h"
#include "singleton.h"
#include "cds_objects.h"
#include "dictionary.h"
#include "autoscan.h"

#define BROWSE_DIRECT_CHILDREN      0x00000001
#define BROWSE_ITEMS                0x00000002
#define BROWSE_CONTAINERS           0x00000004
#define BROWSE_EXACT_CHILDCOUNT     0x00000008
#define BROWSE_TRACK_SORT           0x00000010
#define BROWSE_HIDE_FS_ROOT         0x00000020

class BrowseParam : public zmm::Object
{
protected:
    unsigned int flags;
    int objectID;
    
    int startingIndex;
    int requestedCount;
    
    // output parameters
    int totalMatches;
    
public:
    inline BrowseParam(int objectID, unsigned int flags)
    {
        this->objectID = objectID;
        this->flags = flags;
        startingIndex = 0;
        requestedCount = 0;
    }
    
    inline int getFlags() { return flags; }
    inline unsigned int getFlag(unsigned int mask) { return flags & mask; }
    inline void setFlags(unsigned int flags) { this->flags = flags; }
    inline void setFlag(unsigned int mask) { flags |= mask; }
    inline void changeFlag(unsigned int mask, bool value) { if (value) setFlag(mask); else clearFlag(mask); }
    inline void clearFlag(unsigned int mask) { flags &= !mask; }
    
    inline int getObjectID() { return objectID; }
    
    inline void setRange(int startingIndex, int requestedCount)
    {
        this->startingIndex = startingIndex;
        this->requestedCount = requestedCount;
    }
    inline void setStartingIndex(int startingIndex)
    { this->startingIndex = startingIndex; }
    
    inline void setRequestedCount(int requestedCount)
    { this->requestedCount = requestedCount; }
    
    inline int getStartingIndex() { return startingIndex; }
    inline int getRequestedCount() { return requestedCount; }
    
    inline int getTotalMatches() { return totalMatches; }
    
    inline void setTotalMatches(int totalMatches)
    { this->totalMatches = totalMatches; }
    
};

class Storage : public Singleton<Storage, std::mutex>
{
public:
    static zmm::Ref<Storage> getInstance();

    zmm::String getName() override { return _("Storage"); }
    
    virtual void init() override = 0;
    virtual void addObject(zmm::Ref<CdsObject> object, int *changedContainer) = 0;

    /// \brief Adds a virtual container chain specified by path.
    /// \param path container path separated by '/'. Slashes in container
    /// titles must be escaped.
    /// \param lastClass upnp:class of the last container in the chain, it
    /// is only set when the container is created for the first time.
    /// \param lastRefID reference id of the last container in the chain,
    /// INVALID_OBJECT_ID indicates that the id will not be set.
    /// \param containerID will be filled in by the function
    /// \param updateID will be filled in by the function only if it is set to INVALID_OBJECT_ID
    /// and it is necessary to update a container. Otherwise it will be left unchanged.
    ///
    /// The function gets a path (i.e. "/Audio/All Music/") and will create
    /// the container path if needed. The container ID will be filled in with
    /// the object ID of the container that is last in the path. The
    /// updateID will hold the objectID of the container that was changed,
    /// in case new containers were created during the operation.
    virtual void addContainerChain(zmm::String path, zmm::String lastClass, int lastRefID, int *containerID,
                                   int *updateID, zmm::Ref<Dictionary> lastMetadata) = 0;
    
    /// \brief Builds the container path. Fetches the path of the
    /// parent and adds the title
    /// \param parentID the parent id of the parent container
    /// \param title the title of the container to add to the path.
    /// It will be escaped.
    virtual zmm::String buildContainerPath(int parentID, zmm::String title) = 0;
    
    virtual void updateObject(zmm::Ref<CdsObject> object, int *changedContainer) = 0;
    
    virtual zmm::Ref<zmm::Array<CdsObject> > browse(zmm::Ref<BrowseParam> param) = 0;
    virtual zmm::Ref<zmm::Array<zmm::StringBase> > getMimeTypes() = 0;
    
    //virtual zmm::Ref<zmm::Array<CdsObject> > selectObjects(zmm::Ref<SelectParam> param) = 0;
    
    /// \brief Loads a given (pc directory) object, identified by the given path
    /// from the database
    /// \param path the path of the object; object is interpreted as directory
    /// if the path ends with DIR_SEPERATOR, as file otherwise
    /// multiple DIR_SEPERATOR are irgnored
    /// \return the CdsObject
    virtual zmm::Ref<CdsObject> findObjectByPath(zmm::String path) = 0;
    
    /// \brief checks for a given (pc directory) object, identified by the given path
    /// from the database
    /// \param path the path of the object; object is interpreted as directory
    /// if the path ends with DIR_SEPERATOR, as file otherwise
    /// multiple DIR_SEPERATOR are irgnored
    /// \return the obejectID
    virtual int findObjectIDByPath(zmm::String fullpath) = 0;
    
    /// \brief increments the updateIDs for the given objectIDs
    /// \param ids pointer to the array of ids
    /// \param size number of entries in the given array
    /// \return a String for UPnP: a CSV list; for every existing object:
    ///  "id,update_id"
    virtual zmm::String incrementUpdateIDs(std::shared_ptr<std::unordered_set<int> > ids) = 0;
    
    /* utility methods */
    virtual zmm::Ref<CdsObject> loadObject(int objectID) = 0;
    virtual int getChildCount(int contId, bool containers = true, bool items = true, bool hideFsRoot = false) = 0;

    virtual zmm::String findFolderImage(int id, zmm::String trackArtBase) = 0;

    
    class ChangedContainers : public Object
    {
    public:
        ChangedContainers()
        {
            upnp = zmm::Ref<zmm::IntArray>(new zmm::IntArray());
            ui = zmm::Ref<zmm::IntArray>(new zmm::IntArray());
        }
        zmm::Ref<zmm::IntArray> upnp;
        zmm::Ref<zmm::IntArray> ui;
    };
    
    /// \brief Removes the object identified by the objectID from the database.
    /// all references will be automatically removed. If the object is
    /// a container, all children will be also removed automatically. If
    /// the object is a reference to another object, the "all" flag
    /// determines, if the main object will be removed too.
    /// \param objectID the object id of the object to remove
    /// \param all if true and the object to be removed is a reference
    /// \param objectType pointer to an int; will be filled with the objectType of
    /// the removed object, if not NULL
    /// \return changed container ids
    virtual zmm::Ref<ChangedContainers> removeObject(int objectID, bool all) = 0;
    
    /// \brief Get all objects under the given parentID.
    /// \param parentID parent container
    /// \param withoutContainer if false: all children are returned; if true: only items are returned
    /// \return DBHash containing the objectID's - nullptr if there are none!
    virtual std::shared_ptr<std::unordered_set<int> > getObjects(int parentID, bool withoutContainer) = 0;
    
    /// \brief Remove all objects found in list
    /// \param list a DBHash containing objectIDs that have to be removed
    /// \param all if true and the object to be removed is a reference
    /// \return changed container ids
    virtual zmm::Ref<ChangedContainers> removeObjects(std::shared_ptr<std::unordered_set<int> > list, bool all = false) = 0;
    
    /// \brief Loads an object given by the online service ID.
    virtual zmm::Ref<CdsObject> loadObjectByServiceID(zmm::String serviceID) = 0;
    
    /// \brief Return an array of object ID's for a particular service.
    ///
    /// In the database, the service is identified by a service id prefix.
    virtual zmm::Ref<zmm::IntArray> getServiceObjectIDs(char servicePrefix) = 0;
    
    /* accounting methods */
    virtual int getTotalFiles() = 0;
    
    /* internal setting methods */
    virtual zmm::String getInternalSetting(zmm::String key) = 0;
    virtual void storeInternalSetting(zmm::String key, zmm::String value) = 0;
    
    /* autoscan methods */
    virtual void updateAutoscanPersistentList(ScanMode scanmode, zmm::Ref<AutoscanList> list) = 0;
    virtual zmm::Ref<AutoscanList> getAutoscanList(ScanMode scanode) = 0;
    virtual void addAutoscanDirectory(zmm::Ref<AutoscanDirectory> adir) = 0;
    virtual void updateAutoscanDirectory(zmm::Ref<AutoscanDirectory> adir) = 0;
    virtual void removeAutoscanDirectoryByObjectID(int objectID) = 0;
    virtual void removeAutoscanDirectory(int autoscanID) = 0;
    /// \brief checks if the given object is a direct or indirect child of
    /// a recursive autoscan start point
    /// \param objectID the object id of the object to check
    /// \return the objectID of the nearest matching autoscan start point or
    /// INVALID_OBJECT_ID if it is not a child.
    virtual int isAutoscanChild(int objectID) = 0;
    
    /// \brief returns wheather the given id is an autoscan start point and if yes, if it is persistent
    /// \param objectId the object id to check
    /// \return 0 if the given id is no autoscan start point, 1 if it is a non-persistent one, 2 if it is a persistent one
    virtual int getAutoscanDirectoryType(int objectId) = 0;
    
    /// \brief returns wheather the given id is an autoscan start point and if yes, if it is recursive
    /// \param objectId the object id to check
    /// \return 0 if the given id is no autoscan start point, 1 if it is a non-recursive one, 2 if it is a recursive on
    virtual int isAutoscanDirectoryRecursive(int objectId) = 0;
    
    /// \brief returns the AutoscanDirectory for the given objectID or nullptr if
    /// it's not an autoscan start point - scan id will be invalid
    /// \param objectID the object id to get the AutoscanDirectory for
    /// \return nullptr if the given id is no autoscan start point,
    /// or the matching AutoscanDirectory
    virtual zmm::Ref<AutoscanDirectory> getAutoscanDirectory(int objectID) = 0;
    
    /// \brief updates the last modified info for the given AutoscanDirectory
    /// in the database
    /// \param adir the AutoscanDirectory to be updated
    virtual void autoscanUpdateLM(zmm::Ref<AutoscanDirectory> adir) = 0;
    
    virtual void checkOverlappingAutoscans(zmm::Ref<AutoscanDirectory> adir) = 0;
    
    virtual zmm::Ref<zmm::IntArray> getPathIDs(int objectID) = 0;
    
    /// \brief shutdown the Storage with its possible threads
    virtual void shutdown() override = 0;
    
    /// \brief Ensures that a container given by it's location on disk is
    /// present in the database. If it does not exist it will be created, but
    /// it's content will not be added.
    /// 
    /// \param *changedContainer returns the ID for the UpdateManager
    /// \return objectID of the container given by path
    virtual int ensurePathExistence(zmm::String path, int *changedContainer) = 0;
    
    /// \brief clears the given flag in all objects in the DB
    virtual void clearFlagInDB(int flag) = 0;
    
    virtual zmm::String getFsRootName() = 0;
    
    virtual void threadCleanup() = 0;
    virtual bool threadCleanupRequired() = 0;
    
protected:
    /* helper for addContainerChain */
    static void stripAndUnescapeVirtualContainerFromPath(zmm::String path, zmm::String &first, zmm::String &last);
    static zmm::Ref<Storage> createInstance();
};

#endif // __STORAGE_H__
