/*******************************************************************************
 * 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 <iostream>
#include <sstream>
#include <chrono>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>

#include "plugin.h"
#include "theme.h"
#include "conf.h"
#include <i18n.h>
#include <compress.h>

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

#include <dbpp/database.h>

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

#include <htmlpp/html.h>
#include <htmlpp/exception.h>

#include <netplus/exception.h>

#include <confplus/exception.h>


namespace blogi {

    class NginxFiler : public PluginApi {
    public:
        NginxFiler() {
        }

        const std::string getName(){
            return "nginxfiler";
        }

        const std::string getVersion(){
            return "0.1";
        }

        const std::string getAuthor(){
            return "Jan Koester";
        }

        void initPlugin(){
            registerGPO("3bc7c168-0a57-46a7-9727-d85d3acae610",
                        "Blogi: NginxFiler Settings",
                        "Controls access to Nginx filer settings",
                        true);

            try{
                getConfig(*Args->database[0],"NGINXFILERHOST",_NHost);
                getConfig(*Args->database[0],"NGINXFILERPORT",_NPort);
                getConfig(*Args->database[0],"NGINXFILERPREFIX",_NPrefix);
                getConfig(*Args->database[0],"NGINXFILERSSL",_NSSL);
                getConfig(*Args->database[0],"NGINXFILERTIMEOUT",_Timeout);
            }catch(...){
            }
        }

        bool haveSettings(){
            return true;
        }

        json_object* Settings(const int tid,libhttppp::HttpRequest &req,const std::string &sessionid){
            std::string _lang = getLang(*Args->database[tid]);
            bool saved = false;

            libhttppp::HttpForm form;
            form.parse(req);

            for(const auto &cur : form.urlData()){
                if(cur.key=="nginxfilerhost"){
                    setConfig(*Args->database[tid],"NGINXFILERHOST",cur.value);
                    saved = true;
                }else if(cur.key=="nginxfilerport"){
                    setConfig(*Args->database[tid],"NGINXFILERPORT",cur.value);
                    saved = true;
                }else if(cur.key=="nginxfilerprefix"){
                    setConfig(*Args->database[tid],"NGINXFILERPREFIX",cur.value);
                    saved = true;
                }else if(cur.key=="nginxfilerssl"){
                    setConfig(*Args->database[tid],"NGINXFILERSSL",cur.value);
                    saved = true;
                }else if(cur.key=="nginxfilertimeout"){
                    setConfig(*Args->database[tid],"NGINXFILERTIMEOUT",cur.value);
                    saved = true;
                }
            }

            if(saved){
                try{ getConfig(*Args->database[tid],"NGINXFILERHOST",_NHost); }catch(...){}
                try{ getConfig(*Args->database[tid],"NGINXFILERPORT",_NPort); }catch(...){}
                try{ getConfig(*Args->database[tid],"NGINXFILERPREFIX",_NPrefix); }catch(...){}
                try{ getConfig(*Args->database[tid],"NGINXFILERSSL",_NSSL); }catch(...){}
                try{ getConfig(*Args->database[tid],"NGINXFILERTIMEOUT",_Timeout); }catch(...){}
            }

            json_object *jroot = json_object_new_object();
            json_object_object_add(jroot, "title", json_object_new_string("Nginx Filer"));

            if(saved){
                json_object_object_add(jroot, "message", json_object_new_string(tr(_lang,"Saved!").c_str()));
                json_object_object_add(jroot, "message_type", json_object_new_string("success"));
            }

            json_object *jform = json_object_new_object();
            json_object_object_add(jform, "method", json_object_new_string("POST"));
            json_object_object_add(jform, "action", json_object_new_string(
                Args->config->buildurl("settings/nginxfiler").c_str()));

            json_object *jfields = json_object_new_array();

            auto addField = [&](const char *name, const char *label, const char *type, const std::string &value){
                json_object *jf = json_object_new_object();
                json_object_object_add(jf, "name", json_object_new_string(name));
                json_object_object_add(jf, "label", json_object_new_string(label));
                json_object_object_add(jf, "type", json_object_new_string(type));
                json_object_object_add(jf, "value", json_object_new_string(value.c_str()));
                json_object_array_add(jfields, jf);
            };

            addField("nginxfilerhost", "Host", "text", _NHost);
            addField("nginxfilerport", "Port", "text", std::to_string(_NPort));
            addField("nginxfilerprefix", "Prefix", "text", _NPrefix);
            addField("nginxfilerssl", "SSL (true/false)", "text", _NSSL ? "true" : "false");
            addField("nginxfilertimeout", "Timeout (s)", "text", std::to_string(_Timeout));

            json_object_object_add(jform, "fields", jfields);
            json_object_object_add(jform, "submit", json_object_new_string(tr(_lang,"save").c_str()));
            json_object_object_add(jroot, "form", jform);

            return jroot;
        }

        void RenderUI(std::string path,struct json_object *jobj,libhtmlpp::HtmlString &out){
            if (json_object_get_type(jobj) != json_type_array) {
                    libhttppp::HTTPException he;
                    he[libhttppp::HTTPException::Error] << "<p>Error: Invalid data format for directory listing.</p>";
                    throw he;
            }

            int fcount = json_object_array_length(jobj);
            out<<"<div id=\"nginxfiler\"><ul>";
            for(int i =0; i<fcount; ++i) {
                int ntype=-1;
                std::string name,date;
                json_object_object_foreach(json_object_array_get_idx(jobj,i), key, val) {
                    enum json_type type = json_object_get_type(val);
                    switch (type) {
                        case json_type_string: {
                            if(strcmp(key,"type")==0){
                                if(strcmp(json_object_get_string(val),"file")==0){
                                    ntype=0;
                                }else if(strcmp(json_object_get_string(val),"directory")==0){
                                    ntype=1;
                                }
                            }else if(strcmp(key,"name")==0){
                                name=json_object_get_string(val);
                            }else if(strcmp(key,"mtime")==0){
                                date=json_object_get_string(val);
                            }
                            break;
                        }
                        default: {
                            break;
                        }
                    }
                }
                if(ntype==0)
                    out << "<li class=\"file\" >"
                        << "<a href=\"http://" << _NHost << ":" << _NPort << "/"
                        << _NPrefix << path << name <<"\" >"
                        << "<img alt=\"file\" src=\""<< Args->config->buildurl("theme/public/file.webp") << "\" ><p>" << name << "</p></a></li>";
                else if(ntype==1)
                    out << "<li class=\"dir\" >"
                        << "<a href=\""<< Args->config->buildurl("nginxfiler")
                        << path << name << "\" >"
                        << "<img alt=\"folder\" src=\""<< Args->config->buildurl("theme/public/folder.webp") << "\" ><p>" << name << "</p></a></li>";
            }
            if(path!=_NPrefix){
                size_t nend=path.length();
                bool nstart=false;

                while(nend>0){
                    --nend;
                    if(path[nend]!='/' && !nstart){
                        nstart=true;
                    }
                    if(path[nend]=='/' && nstart){
                        break;
                    }
                }

                if(nend>0){
                    std::string back=path.substr(0,nend);
                    out << "<li><a href=\"" << Args->config->buildurl("nginxfiler")
                        << "/" << back.c_str() << "\"><img alt=\"folder\" src=\""
                        << Args->config->buildurl("theme/public/back_icon.webp") << "\" ><p>...</p></a></li>";
                }
            }
            out<<"</ul></div>";
        }

        bool Controller(const int tid, libhttppp::HttpRequest& req, libhtmlpp::HtmlElement* page, const std::string& sessionid) {
            if(req.getRequestURL().compare(0,Args->config->buildurl("nginxfiler").length(),Args->config->buildurl("nginxfiler"))!=0){
                return false;
            }

            std::string path;

            size_t len =Args->config->buildurl("nginxfiler").length();

            if(len < req.getRequestURL().length())
                path=req.getRequestURL().substr(Args->config->buildurl("nginxfiler").length(),
                                                                          req.getRequestURL().length()-Args->config->buildurl("nginxfiler").length());

            path+="/";

            try{
                std::stringstream ss;
                if(_NSSL)
                    ss << "https://" << _NHost << ":" << _NPort << _NPrefix << path;
                else
                    ss << "http://" << _NHost << ":" << _NPort << _NPrefix << path;

                libhttppp::HttpUrl url(ss.str());

                libhttppp::HttpRequest nreq;

                libhttppp::HttpClient client(url);

                std::vector<char> json = client.Get(nreq);

                json.push_back('\0');

                struct json_object *ndir;

                enum json_tokener_error jerrno;

                ndir = json_tokener_parse_verbose(json.data(),&jerrno);

                if(jerrno != json_tokener_success){
                    libhttppp::HTTPException e;
                    e[libhttppp::HTTPException::Error] << "nginxfiler: counld't read json !";
                    std::cerr << json_tokener_error_desc(jerrno) <<std::endl;
                    throw e;
                }

                libhtmlpp::HtmlString fileHtml,out;


                RenderUI(path,ndir,fileHtml);

                libhtmlpp::HtmlElement fel=(libhtmlpp::HtmlElement*)&fileHtml.parse();

                if(page->getElementbyID("main"))
                    page->getElementbyID("main")->appendChild(fel);

                Args->theme->printSite(tid,out,page,req.getRequestURL(),Args->auth->isLoggedIn(tid,req,sessionid));

                libhttppp::HttpResponse curres;
                curres.setContentType("text/html");
                curres.setState(HTTP200);
                sendCompressed(req, curres, out.c_str(),out.length());
            }catch(libhttppp::HTTPException &e){
                libhttppp::HttpResponse curres;
                libhtmlpp::HtmlString error,out;

                std::string enc;
                libhtmlpp::HtmlEncode(e.what(),enc);

                error << "<div class=\"errormsg\" >" << enc << "</div>";

                libhtmlpp::HtmlElement *index=page,*main=nullptr;

                if(index)
                    main=index->getElementbyID("main");

                if(!index || ! main){
                    curres.setContentType("text/html");
                    curres.setState(HTTP500);
                    curres.send(req, e.what(),e.what().length());
                    return true;
                }

                main->insertChild(error.parse());

                Args->theme->printSite(tid, out, page, req.getRequestURL(), Args->auth->isLoggedIn(tid, req, sessionid));

                curres.send(req, out.c_str(),out.length());
            }
            return true;
        }
    private:

        std::string _NHost;
        int         _NPort;
        std::string _NPrefix;
        bool        _NSSL;
        int         _Timeout;
    };
};

EXPORT blogi::PluginApi* create() {
    return new blogi::NginxFiler();
}

EXPORT void destroy(blogi::PluginApi* p) {
    delete p;
}

