/*******************************************************************************
 * Copyright (c) 2023, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#include <memory>
#include <map>
#include <chrono>

#include <httppp/exception.h>
#include <httppp/http.h>

#include <htmlpp/html.h>

#include <dbpp/database.h>

#include <json-c/json.h>
#include <tinyxml2.h>

#include <uuidp.h>

#include "conf.h"
#include "session.h"
#include "auth.h"
#include "theme.h"
#include "mail.h"

#pragma once

#ifndef EXPORT
  #ifdef Windows
  #define EXPORT extern "C" __declspec(dllexport)
  #else
  #define EXPORT extern "C" __attribute__((visibility("default")))
  #endif
#endif

namespace blogi {

    class PluginApi;

    typedef void destroy_t(PluginApi*);

    struct PluginApiDeleter {
    private:
        destroy_t* destroyer = nullptr;
        void* pldata = nullptr;
    public:
        PluginApiDeleter() = default;
        PluginApiDeleter(destroy_t* fn, void* handle) : destroyer(fn), pldata(handle) {};
        PluginApiDeleter(const PluginApiDeleter&) = default;
        PluginApiDeleter& operator=(const PluginApiDeleter&) = default;

        void operator()(PluginApi* p) const {
            if (p && destroyer) {
                destroyer(p);
            }

            if (pldata) {
#ifdef  Windows
                FreeLibrary(static_cast<HMODULE>(pldata));
#else
                dlclose(pldata);
#endif
            }
        };
    };

    struct PluginArgs{
        std::vector<std::unique_ptr<dbpp::Database>> database;
        std::unique_ptr<Auth>                                         auth;
        std::unique_ptr<Theme>                                      theme;
        std::unique_ptr<SmtpSettings>                           smtp;
        Config       *config;
        int              maxthreads;
        bool            debug;
    };

    enum RenderingType {All=0,Desktop=1,Mobile=2};

    enum SharePostType { ShareText=0, SharePicture=1, ShareVideo=2, ShareAudio=3 };

    class SearchRet {
    public:
        SearchRet();
        ~SearchRet();

        class Data{
        public:
            Data();
            ~Data();
            std::string       title;
            std::string       anchor;
            std::vector<char> text;
            Data             *nextData();
            Data(const Data &dat);
        private:
            std::unique_ptr<Data> _next;
            friend class SearchRet;
        };

        void         push_back(const Data &dat);
        void         clear();
        Data        *getfirstData();
    private:
        std::unique_ptr<Data> _firstData;
        Data                             *_lastData;
    };

    class PluginApi {
    public:
        virtual ~PluginApi()=0;
        virtual const std::string getName()=0;
        virtual const std::string getVersion()=0;
        virtual const std::string getAuthor()=0;
        virtual bool        haveSettings();

        virtual json_object* getSettingsTabs();

        void setArgs(PluginArgs *args);

        virtual void initPlugin();

        virtual bool Controller(const int tid,libhttppp::HttpRequest &req,libhtmlpp::HtmlElement *page,const std::string &sessionid);

        virtual bool Response(const int tid,libhttppp::HttpRequest &req);

        virtual json_object* Settings(const int tid,libhttppp::HttpRequest &req,const std::string &sessionid);

        virtual void Rendering(const int tid,libhttppp::HttpRequest &req,libhtmlpp::HtmlElement *curpage,int type=0,const std::string &sessionid="");

        virtual void Search(const int tid,const char *word,SearchRet &result,const std::string &sessionid);

        virtual void GetSiteMap(const int tid,std::map<libhttppp::HttpUrl,std::chrono::year_month_day> &sitemap);

        virtual void JsonApi(const int tid,json_object *request,json_object *response,const uuid::uuid &authid);

        /// Returns true if this plugin can accept published content from the editor
        virtual bool canPublish();
        /// Returns JSON array describing the form fields needed for publishing
        virtual json_object* getPublishForm(const int tid);
        /// Publish rendered HTML content; params contains plugin-specific metadata
        virtual void Publish(const int tid, const std::string &html, json_object *params, json_object *response);

        /// Render published content into a page element, wrapping it as it appears on the published page.
        /// Used by the editor preview so WYSIWYG matches the final output.
        virtual void RenderPublished(const int tid, const std::string &html,
                                     json_object *params, libhtmlpp::HtmlElement &page);

        /// Share a post to social networks; url is the public link, title/description for text
        virtual void sharePost(const int tid, SharePostType type, const std::string &url,
                               const std::string &title, const std::string &description);
    protected:
        void registerGPO(const char *gpoid, const char *name, const char *desc, bool gpodefault);
        PluginArgs *Args;
    };

    typedef PluginApi* create_t();
    typedef void destroy_t(PluginApi*);

    class Plugin {
    public:
        Plugin();
        ~Plugin();

        void loadPlugins(const std::string &path,PluginArgs *args);

        class PluginData{
        private:
#ifdef Windows
			HMODULE pldata;
#else
            void* pldata;
#endif // Windows
            std::unique_ptr<PluginApi,PluginApiDeleter> ins=nullptr;
            std::unique_ptr<PluginData> next = nullptr;
        public:
            PluginData();
            virtual ~PluginData();
            PluginApi  *getInstace() const;
            const PluginData  *getNextPlg() const;
            friend class Plugin;
       };

      const PluginData* getFirstPlugin() const;

    private:
       std::unique_ptr<PluginData> _firstPlugin = nullptr;
       PluginData *_lastPlugin = nullptr;
   };

   namespace webedit {
     class EditPlugin;

        typedef EditPlugin* create_t();
        typedef void destroy_t(EditPlugin*);

        struct EditPluginDeleter {
            private:
                destroy_t* destroyer = nullptr;
                void* pldata = nullptr;
            public:
                EditPluginDeleter() = default;
                EditPluginDeleter(destroy_t* fn, void* handle) : destroyer(fn), pldata(handle) {};
                EditPluginDeleter(const EditPluginDeleter&) = default;
                EditPluginDeleter& operator=(const EditPluginDeleter&) = default;

                void operator()(EditPlugin* p) const {
                    if (p && destroyer) {
                        destroyer(p);
                    }

                    if (pldata) {
        #ifdef  Windows
                        FreeLibrary(static_cast<HMODULE>(pldata));
        #else
                        dlclose(pldata);
        #endif
                    }
                };
            };

        class EditPlugin {
        public:
            EditPlugin(){
                Id.generate();
            };

            static void setPrefix(const std::string &prefix) {
                _Prefix = prefix;
            }

            static const std::string& getPrefix() {
                return _Prefix;
            }

            virtual ~EditPlugin()=default;

            virtual const std::string getName() const =0;
            virtual const std::string getAuthor() const =0;
            virtual const std::string getVersion() const =0;

            const uuid::uuid &getInstanceId() const {
                return Id;
            }

            const uuid::uuid &getTypeId() const {
                return Type;
            }

            virtual void logError(const std::string& message) = 0;

            virtual void JsonApi(json_object *request,json_object *response)=0;

            virtual tinyxml2::XMLElement* toXmlObject(tinyxml2::XMLDocument* doc) = 0;
            virtual void fromXmlObject(tinyxml2::XMLElement* xml_data) = 0;

            virtual bool canHaveChildren() const = 0;
            virtual bool haveChilds()=0;

            virtual void Settings(libhttppp::HttpRequest &curreq,libhtmlpp::HtmlElement &settings)=0;
            virtual void Render(libhtmlpp::HtmlElement &el) const =0;

            virtual void setChildElement(const EditPlugin &el)=0;
            virtual void unsetChildElement(const EditPlugin &el)=0;

            virtual void addNextElement(const EditPlugin &el)=0;
            virtual void remNextElement(const EditPlugin &el)=0;

            const EditPlugin *getChildElement() const{
                return child;
            }

            const EditPlugin *nextElement() const{
                return nextel;
            }

            void setNextEl(EditPlugin *el) {
                nextel = el;
            }

            void setChildEl(EditPlugin *el) {
                child = el;
            }

            void regenerateId() {
                Id.generate();
            }

        protected:
            uuid::uuid                              Type;
            uuid::uuid                              Id;
            EditPlugin* nextel=nullptr;
            EditPlugin* child=nullptr;
            static inline std::string               _Prefix;
            friend class EditPluginLoad;
        };

        class EditPluginLoad {
        public:
            EditPluginLoad(const std::vector<std::string> &paths);
            ~EditPluginLoad();

            class PluginData{
            private:
    #ifdef Windows
                HMODULE pldata;
    #else
                void* pldata;
    #endif // Windows
                std::unique_ptr<EditPlugin,EditPluginDeleter> ins=nullptr;
                std::unique_ptr<PluginData> next = nullptr;
                create_t* create_func = nullptr;
                destroy_t* destroy_func = nullptr;
            public:
                PluginData() = default;
                virtual ~PluginData() = default;
                const uuid::uuid& getTypeId() const { return ins->getTypeId(); }
                EditPlugin  *getInstace() const;
                const PluginData  *getNextPlg() const;
                // Create a new instance of this plugin type (caller owns the raw pointer)
                EditPlugin* createNewInstance() const;
                friend class EditPluginLoad;
            };

            const PluginData* getFirstPlugin() const;
            const EditPlugin*  getPluginPrototype(const uuid::uuid& typeId) const;
        private:
#ifdef Windows
            HMODULE pldata;
#else
            void* pldata;
#endif // Windows
            std::unique_ptr<PluginData>  _firstPlugin = nullptr;
            PluginData                              *_lastPlugin = nullptr;

            std::map<uuid::uuid, const EditPlugin*> _pluginIndex;
            void buildPluginIndex();
        };
   };
};
