/*MT*

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

    script.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-2025 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 content/scripting/script.h

#ifndef __SCRIPTING_SCRIPT_H__
#define __SCRIPTING_SCRIPT_H__

#include "util/grb_fs.h"

#include <duktape.h>
#include <map>
#include <memory>
#include <mutex>
#include <vector>

// forward declaration
enum class AutoscanMediaMode;
class CdsContainer;
class CdsItem;
class CdsObject;
class Config;
class ConfigDefinition;
class Content;
class Database;
class ScriptingRuntime;
class StringConverter;
class ConverterManager;

// perform garbage collection after script has been run for x times
#define JS_CALL_GC_AFTER_NUM (1000)

enum class CharsetConversion {
    M2I,
    F2I,
    J2I,
    P2I,
    I2I,
};

class Script : public std::enable_shared_from_this<Script> {
public:
    virtual ~Script();

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

    /// @brief initialise script variables and library
    void init();

    /// @brief Write property with string value to object
    /// @param name of property
    /// @param value of property
    /// @param doEmpty write if property value is empty
    void setProperty(
        const std::string& name,
        const std::string& value,
        bool doEmpty = true);
    /// @brief Write property with number value to object
    /// @param name of property
    /// @param value of property
    void setIntProperty(const std::string& name, int value);
    /// @brief Write property with string value to object
    /// @param name of property
    /// @param value of property
    /// @param checkValue write if property value is not equal checkValue
    void setIntProperty(const std::string& name, int value, int checkValue);
    /// @brief Write property with boolean value to object
    /// @param name of property
    /// @param value of property: write 1 if true 0 else
    void setBoolProperty(const std::string& name, bool value);

    /// @brief add C function to js
    void defineFunction(
        const std::string& name,
        duk_c_function function,
        std::uint32_t numParams);
    /// @brief add list of C functions to js
    void defineFunctions(const duk_function_list_entry* functions);
    /// @brief load script content as conffigured
    void loadContent();
    /// @brief load all js files from folder
    void loadFolder(const fs::path& scriptFolder);

    /// @brief Convert javascript object back to CdsObject
    /// @param pcd Parent object that was added to the script context
    std::shared_ptr<CdsObject> dukObject2cdsObject(const std::shared_ptr<CdsObject>& pcd);

    /// @brief Convert CdsObject to javascript object
    /// @param obj CdsObject to convert
    void cdsObject2dukObject(const std::shared_ptr<CdsObject>& obj);

    /// @brief get hidden file setting from content manager
    bool isHiddenFile(const std::shared_ptr<CdsObject>& obj, const std::string& rootPath);

    std::string convertToCharset(const std::string& str, CharsetConversion chr);
    /// @brief create cds object from duktape stack
    virtual std::pair<std::shared_ptr<CdsObject>, int> createObject2cdsObject(
        const std::shared_ptr<CdsObject>& origObject,
        const std::string& rootPath)
        = 0;
    virtual bool setRefId(
        const std::shared_ptr<CdsObject>& cdsObj,
        const std::shared_ptr<CdsObject>& origObject,
        int pcdId)
        = 0;
    /// @brief get script belonging to duktape context
    static Script* getContextScript(duk_context* ctx);

    /// @brief the database target
    std::shared_ptr<Database> getDatabase() const { return database; }
    /// @brief the content manager
    std::shared_ptr<Content> getContent() const { return content; }
    /// @brief name of global js variable containing processed object
    std::string getOrigName() const { return objectName; }
    /// @brief CdsObject currently processed by script
    std::shared_ptr<CdsObject> getProcessedObject() const { return processed; }

protected:
    Script(
        const std::shared_ptr<Content>& content,
        const std::string& parent,
        const std::string& name,
        std::string objName,
        bool needResult,
        std::shared_ptr<StringConverter> sc);

    /// @brief call js function to generate layout for object
    std::vector<int> call(
        const std::shared_ptr<CdsObject>& obj,
        const std::shared_ptr<CdsContainer>& cont,
        const std::string& functionName,
        const fs::path& rootPath,
        const std::string& containerType);
    void setMetaData(
        const std::shared_ptr<CdsObject>& obj,
        const std::shared_ptr<CdsItem>& item,
        const std::string& sym,
        const std::string& val) const;

    /// @brief hook for converting script objects to items
    virtual void handleObject2cdsItem(
        duk_context* ctx,
        const std::shared_ptr<CdsObject>& pcd,
        const std::shared_ptr<CdsItem>& item)
    {
    }
    /// @brief hook for converting script objects to containers
    virtual void handleObject2cdsContainer(
        duk_context* ctx,
        const std::shared_ptr<CdsObject>& pcd,
        const std::shared_ptr<CdsContainer>& cont)
    {
    }
    virtual std::shared_ptr<CdsObject> createObject(const std::shared_ptr<CdsObject>& pcd);

    int gc_counter {};
    /// @brief object that is currently being processed by the script (set in import script)
    std::shared_ptr<CdsObject> processed;

    duk_context* ctx;

    std::shared_ptr<Config> config;
    std::shared_ptr<Database> database;
    std::shared_ptr<ConverterManager> converterManager;
    std::shared_ptr<ConfigDefinition> definition;
    std::shared_ptr<Content> content;
    std::shared_ptr<ScriptingRuntime> runtime;
    std::shared_ptr<StringConverter> sc;

private:
    bool hasCaseSensitiveNames;
    bool needResult;
    std::string entrySeparator;
    std::string contextName;
    std::string objectName;
    std::string scriptPath;
    void _load(const fs::path& scriptPath);
    void _execute();
    std::shared_ptr<StringConverter> _p2i;
    std::shared_ptr<StringConverter> _j2i;
    std::shared_ptr<StringConverter> _f2i;
    std::shared_ptr<StringConverter> _m2i;
    std::shared_ptr<StringConverter> _i2i;
};

#endif // __SCRIPTING_SCRIPT_H__
