/*MT*

    MediaTomb - http://www.mediatomb.cc/

    database.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>

    Copyright (C) 2016-2026 Gerbera Contributors

    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 database/database.h

#ifndef __GRB_DATABASE_H__
#define __GRB_DATABASE_H__

#include "util/grb_fs.h"

#include <map>
#include <unordered_set>
#include <vector>

// forward declarations
class AutoscanDirectory;
class AutoscanList;
enum class AutoscanScanMode;
class BrowseParam;
class CdsContainer;
class CdsObject;
struct ClientObservation;
class ClientStatusDetail;
class Config;
class ConfigValue;
class ConverterManager;
class Mime;
class SearchParam;
class StatsParam;
class Timer;

#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

#define DB_DRIVER_SQLITE "sqlite"
#define DB_DRIVER_MYSQL "mysql"
#define DB_DRIVER_POSTGRES "postgres"

#define SL_DROP_FILE "sqlite3-drop.sql"
#define SL_UPGR_FILE "sqlite3-upgrade.xml"
#define SL_INIT_FILE "sqlite3.sql"
#define MY_DROP_FILE "mysql-drop.sql"
#define MY_UPGR_FILE "mysql-upgrade.xml"
#define MY_INIT_FILE "mysql.sql"
#define PG_DROP_FILE "postgres-drop.sql"
#define PG_UPGR_FILE "postgres-upgrade.xml"
#define PG_INIT_FILE "postgres.sql"

#define UNUSED_CLIENT_GROUP ""

enum class DbFileType {
    Auto,
    Directory,
    File,
    Virtual,
    Any,
};

class Database {
public:
    explicit Database(std::shared_ptr<Config> config);
    virtual ~Database();

    Database(const Database&) = delete;
    Database& operator=(const Database&) = delete;

    /// @brief run the database
    /// open connection to database
    virtual void run() { }

    /// @brief initialise the database
    /// create tables and upgrade to current version
    virtual void init() = 0;

    /// @brief shutdown the Database with its possible threads
    virtual void shutdown() = 0;

    /// @brief run drop-table script to clear the database
    virtual void dropTables() = 0;

    virtual void addObject(const std::shared_ptr<CdsObject>& object, int* changedContainer) = 0;

    /// @brief Adds a virtual container chain specified by path.
    /// @param parentContainerId the id of the parent container
    /// @param virtualPath container path separated by '/'
    /// @param cont reference of the object to copy data from. Slashes in container
    /// title must be escaped.
    /// @param containerID will be filled in by the function
    ///
    /// 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 bool addContainer(int parentContainerId, std::string virtualPath, const std::shared_ptr<CdsContainer>& cont, int* containerID) = 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 fs::path buildContainerPath(int parentID, const std::string& title) = 0;

    virtual void updateObject(const std::shared_ptr<CdsObject>& object, int* changedContainer) = 0;

    /// @brief Perform upnp browse on the database
    /// @param param browse parameters as received via request
    /// @return list of CdsObject matching the browse criteria
    virtual std::vector<std::shared_ptr<CdsObject>> browse(BrowseParam& param) = 0;

    /// @brief Perform upnp search on the database
    /// @param param search parameters as received via request
    /// @return list of CdsObject matching the search criteria
    virtual std::vector<std::shared_ptr<CdsObject>> search(SearchParam& param) = 0;

    /// @brief find container matching the content class
    /// @param contentClass to search for
    /// @param startIndex of search results
    /// @param count number of search results
    /// @param group user group name
    /// @return list of CdsObject matching the content class
    virtual std::vector<std::shared_ptr<CdsObject>> findObjectByContentClass(
        const std::string& contentClass,
        int startIndex,
        int count,
        const std::string& group)
        = 0;

    virtual std::vector<std::string> getMimeTypes() = 0;
    virtual std::map<std::string, std::shared_ptr<CdsContainer>> getShortcuts() = 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
    /// @param group user group name
    /// @param fileType type of the database entry to search
    /// @return the CdsObject
    virtual std::shared_ptr<CdsObject> findObjectByPath(
        const fs::path& path,
        const std::string& group,
        DbFileType fileType = DbFileType::Auto)
        = 0;

    /// @brief increments the updateIDs for the given objectIDs
    /// @param ids pointer to the array of ids
    /// @return a String for UPnP: a CSV list; for every existing object:
    ///  "id,update_id"
    virtual std::string incrementUpdateIDs(const std::unordered_set<int>& ids) = 0;

    /* utility methods */
    virtual std::shared_ptr<CdsObject> loadObject(
        int objectID,
        const std::string& group = UNUSED_CLIENT_GROUP)
        = 0;
    /// @brief get number of items in container by id
    virtual std::map<int, int> getChildCounts(
        const std::vector<int>& contId,
        bool containers = true,
        bool items = true,
        bool hideFsRoot = false)
        = 0;

    struct ChangedContainers {
        // Signed because IDs start at -1.
        std::vector<std::int32_t> upnp;
        std::vector<std::int32_t> 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 path delete resource references to this path
    /// @param all if true and the object to be removed is a reference
    /// @return changed container ids
    virtual std::unique_ptr<ChangedContainers> removeObject(
        int objectID,
        const fs::path& path,
        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
    /// @param ret list of matching objects
    /// @param full do full hierarchy search
    /// @param refID check for references of ID
    /// @return number of objects found
    virtual std::size_t getObjects(
        int parentID,
        bool withoutContainer,
        std::unordered_set<int>& ret,
        bool full,
        int refID)
        = 0;
    virtual std::vector<int> getRefObjects(int objectId) = 0;
    virtual std::unordered_set<int> getUnreferencedObjects() = 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 std::unique_ptr<ChangedContainers> removeObjects(const std::unordered_set<int>& list, bool all = false) = 0;

    /// @brief Loads an object given by the online service ID.
    virtual std::shared_ptr<CdsObject> loadObjectByServiceID(const std::string& serviceID, const std::string& group) = 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 std::vector<int> getServiceObjectIDs(char servicePrefix) = 0;

    /* accounting methods */
    virtual long long getFileStats(const StatsParam& stats) = 0;
    virtual std::map<std::string, long long> getGroupStats(const StatsParam& stats) = 0;

    /* internal setting methods */
    virtual std::string getInternalSetting(const std::string& key) = 0;
    virtual void storeInternalSetting(const std::string& key, const std::string& value) = 0;

    /* autoscan methods */
    virtual std::shared_ptr<AutoscanList> getAutoscanList(AutoscanScanMode scanode) = 0;
    virtual void updateAutoscanList(AutoscanScanMode scanmode, const std::shared_ptr<AutoscanList>& list) = 0;

    /* config methods */
    virtual std::vector<ConfigValue> getConfigValues() = 0;
    virtual void removeConfigValue(const std::string& item) = 0;
    virtual void updateConfigValue(const std::string& key, const std::string& item, const std::string& value, const std::string& status = "unchanged") = 0;

    /* clients methods */
    virtual std::vector<ClientObservation> getClients() = 0;
    virtual void saveClients(const std::vector<ClientObservation>& cache) = 0;
    virtual std::shared_ptr<ClientStatusDetail> getPlayStatus(const std::string& group, int objectId) = 0;
    virtual void savePlayStatus(const std::shared_ptr<ClientStatusDetail>& detail) = 0;
    virtual std::vector<std::shared_ptr<ClientStatusDetail>> getPlayStatusList(int objectId) = 0;
    virtual std::vector<std::map<std::string, std::string>> getClientGroupStats() = 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 std::shared_ptr<AutoscanDirectory> getAutoscanDirectory(int objectID) = 0;
    virtual void addAutoscanDirectory(const std::shared_ptr<AutoscanDirectory>& adir) = 0;
    virtual void updateAutoscanDirectory(const std::shared_ptr<AutoscanDirectory>& adir) = 0;
    virtual void removeAutoscanDirectory(const std::shared_ptr<AutoscanDirectory>& adir) = 0;
    virtual void checkOverlappingAutoscans(const std::shared_ptr<AutoscanDirectory>& adir) = 0;

    virtual std::vector<int> getPathIDs(int objectID) = 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 path location of the container to handle
    /// @param changedContainer returns the ID for the UpdateManager
    /// @return objectID of the container given by path
    virtual int ensurePathExistence(const fs::path& path, int* changedContainer) = 0;

    virtual void threadCleanup() = 0;
    virtual bool threadCleanupRequired() const = 0;
    virtual int getFirstVersion() const { return 1; };

protected:
    static std::shared_ptr<Database> createInstance(const std::shared_ptr<Config>& config,
        const std::shared_ptr<Mime>& mime,
        const std::shared_ptr<ConverterManager>& converterManager,
        const std::shared_ptr<Timer>& timer);
    friend class Server;

    virtual std::shared_ptr<Database> getSelf() = 0;

    std::shared_ptr<Config> config;
};

#endif // __GRB_DATABASE_H__
