/*******************************************************************************
 * 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 <cstring>
#include <string>
#include <vector>

#include <gameinfoplus/hldsview.h>
#include <gameinfoplus/exception.h>

#include <dbpp/database.h>

#include <json-c/json.h>

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

#ifdef Windows
#define HIDDEN 
#else
#define HIDDEN __attribute__ ((visibility ("hidden")))
#endif

namespace blogi {
    HIDDEN std::mutex rlock;

    class GameStatus : public PluginApi {
    public:
        GameStatus(){
        }
        const std::string getName(){
            return "gamestatus";
        }

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

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

        void initPlugin(){
            registerGPO("067a4685-47ea-4c64-8211-6822172bb9eb",
                        "Blogi: GameInfo Settings",
                        "Controls access to game server settings",
                        true);

            dbpp::SQL        sql;
            dbpp::DBResult   res;


            sql << "CREATE TABLE IF NOT EXISTS gameserver_protocols("
                <<   "id integer PRIMARY KEY " << Args->database[0]->autoincrement(sql) << ","
                <<   "pname character varying(255) NOT NULL"
                << "); "
                << "CREATE TABLE IF NOT EXISTS gameserver("
                <<   "id integer PRIMARY KEY " << Args->database[0]->autoincrement(sql) << ","
                <<   "protocol integer,"
                <<   "addr character varying(255) NOT NULL,"
                <<   "port integer,"
                <<   "FOREIGN KEY (protocol) REFERENCES gameserver_protocols (id)"
                << ");";

            Args->database[0]->exec(sql,res);

            sql="select protocol,addr,port from gameserver";

            int count = Args->database[0]->exec(sql,res);

            for (int i = 0; i < count; i++) {
                if(atoi(res[i][0])!=0)
                    continue;
                gameinfo::HldsView view(res[i][1],atoi(res[i][2]));
                HLDS.push_back(view);
            }
        }

        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);

            std::string formAddr, formPort, formProtocol;
            bool hasServerForm = false;
            std::string delServerId;
            std::string formProtoName;
            bool hasProtoForm = false;
            std::string delProtoId;

            for(const auto &cur : form.urlData()){
                if(cur.key=="server_addr"){
                    formAddr = cur.value;
                    hasServerForm = true;
                }else if(cur.key=="server_port"){
                    formPort = cur.value;
                    hasServerForm = true;
                }else if(cur.key=="server_protocol"){
                    formProtocol = cur.value;
                    hasServerForm = true;
                }else if(cur.key=="delete_server_id"){
                    delServerId = cur.value;
                }else if(cur.key=="protocol_name"){
                    formProtoName = cur.value;
                    hasProtoForm = true;
                }else if(cur.key=="delete_protocol_id"){
                    delProtoId = cur.value;
                }
            }

            // Delete protocol
            if(!delProtoId.empty()){
                dbpp::SQL sql;
                dbpp::DBResult res;
                std::vector<char> buf;
                sql << "DELETE FROM gameserver_protocols WHERE id='" << sql.escaped(buf, delProtoId.c_str()) << "'";
                Args->database[tid]->exec(sql,res);
                saved = true;
            }

            // Add protocol
            if(hasProtoForm && !formProtoName.empty()){
                dbpp::SQL sql;
                dbpp::DBResult res;
                std::vector<char> buf;
                sql << "INSERT INTO gameserver_protocols (pname) VALUES ('"
                    << sql.escaped(buf, formProtoName.c_str()) << "')";
                Args->database[tid]->exec(sql,res);
                saved = true;
            }

            // Delete server
            if(!delServerId.empty()){
                dbpp::SQL sql;
                dbpp::DBResult res;
                std::vector<char> buf;
                sql << "DELETE FROM gameserver WHERE id='" << sql.escaped(buf, delServerId.c_str()) << "'";
                Args->database[tid]->exec(sql,res);
                saved = true;
            }

            // Add server
            if(hasServerForm && !formAddr.empty() && !formPort.empty()){
                dbpp::SQL sql;
                dbpp::DBResult res;
                std::vector<char> buf;
                sql << "INSERT INTO gameserver (protocol,addr,port) VALUES ('"
                    << sql.escaped(buf, formProtocol.c_str()) << "','"
                    << sql.escaped(buf, formAddr.c_str()) << "','"
                    << sql.escaped(buf, formPort.c_str()) << "')";
                Args->database[tid]->exec(sql,res);
                saved = true;
            }

            json_object *jroot = json_object_new_object();
            json_object_object_add(jroot, "title", json_object_new_string(tr(_lang,"Game Server Settings").c_str()));

            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"));
            }

            // --- Protocols section ---
            json_object *jprotocols = json_object_new_array();
            {
                dbpp::SQL sql;
                dbpp::DBResult res;
                sql << "SELECT id,pname FROM gameserver_protocols";
                int count = Args->database[tid]->exec(sql,res);
                for(int i = 0; i < count; ++i){
                    json_object *jp = json_object_new_object();
                    json_object_object_add(jp, "id", json_object_new_string(res[i][0]));
                    json_object_object_add(jp, "pname", json_object_new_string(res[i][1]));
                    json_object_array_add(jprotocols, jp);
                }
            }
            json_object_object_add(jroot, "protocols", jprotocols);

            // Add protocol form
            json_object *jprotoform = json_object_new_object();
            json_object_object_add(jprotoform, "method", json_object_new_string("POST"));
            json_object_object_add(jprotoform, "action", json_object_new_string(
                Args->config->buildurl("settings/gamestatus").c_str()));
            {
                json_object *jfields = json_object_new_array();
                json_object *jf = json_object_new_object();
                json_object_object_add(jf, "name", json_object_new_string("protocol_name"));
                json_object_object_add(jf, "label", json_object_new_string("Protocol Name"));
                json_object_object_add(jf, "type", json_object_new_string("text"));
                json_object_object_add(jf, "value", json_object_new_string(""));
                json_object_array_add(jfields, jf);
                json_object_object_add(jprotoform, "fields", jfields);
            }
            json_object_object_add(jprotoform, "submit", json_object_new_string(tr(_lang,"Add Protocol").c_str()));
            json_object_object_add(jroot, "protocol_form", jprotoform);

            // Delete protocol form
            json_object *jdelprotoform = json_object_new_object();
            json_object_object_add(jdelprotoform, "method", json_object_new_string("POST"));
            json_object_object_add(jdelprotoform, "action", json_object_new_string(
                Args->config->buildurl("settings/gamestatus").c_str()));
            {
                json_object *jfields = json_object_new_array();
                json_object *jf = json_object_new_object();
                json_object_object_add(jf, "name", json_object_new_string("delete_protocol_id"));
                json_object_object_add(jf, "label", json_object_new_string("Protocol ID to delete"));
                json_object_object_add(jf, "type", json_object_new_string("text"));
                json_object_object_add(jf, "value", json_object_new_string(""));
                json_object_array_add(jfields, jf);
                json_object_object_add(jdelprotoform, "fields", jfields);
            }
            json_object_object_add(jdelprotoform, "submit", json_object_new_string(tr(_lang,"Delete Protocol").c_str()));
            json_object_object_add(jroot, "delete_protocol_form", jdelprotoform);

            // --- Servers section ---
            json_object *jservers = json_object_new_array();
            {
                dbpp::SQL sql;
                dbpp::DBResult res;
                sql << "SELECT g.id,COALESCE(p.pname,'unknown'),g.addr,g.port FROM gameserver g "
                    << "LEFT JOIN gameserver_protocols p ON g.protocol=p.id";
                int count = Args->database[tid]->exec(sql,res);
                for(int i = 0; i < count; ++i){
                    json_object *js = json_object_new_object();
                    json_object_object_add(js, "id", json_object_new_string(res[i][0]));
                    json_object_object_add(js, "protocol", json_object_new_string(res[i][1]));
                    json_object_object_add(js, "addr", json_object_new_string(res[i][2]));
                    json_object_object_add(js, "port", json_object_new_string(res[i][3]));
                    json_object_array_add(jservers, js);
                }
            }
            json_object_object_add(jroot, "items", jservers);

            // Add server form
            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/gamestatus").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("server_addr", "Server Address", "text", "");
            addField("server_port", "Server Port", "text", "");
            addField("server_protocol", "Protocol ID", "text", "");

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

            // Delete server form
            json_object *jdelform = json_object_new_object();
            json_object_object_add(jdelform, "method", json_object_new_string("POST"));
            json_object_object_add(jdelform, "action", json_object_new_string(
                Args->config->buildurl("settings/gamestatus").c_str()));
            json_object *jdelfields = json_object_new_array();
            {
                json_object *jf = json_object_new_object();
                json_object_object_add(jf, "name", json_object_new_string("delete_server_id"));
                json_object_object_add(jf, "label", json_object_new_string("Server ID to delete"));
                json_object_object_add(jf, "type", json_object_new_string("text"));
                json_object_object_add(jf, "value", json_object_new_string(""));
                json_object_array_add(jdelfields, jf);
            }
            json_object_object_add(jdelform, "fields", jdelfields);
            json_object_object_add(jdelform, "submit", json_object_new_string(tr(_lang,"Delete Server").c_str()));
            json_object_object_add(jroot, "delete_form", jdelform);

            return jroot;
        }

        bool Controller(const int tid,libhttppp::HttpRequest &req,libhtmlpp::HtmlElement *page,const std::string &sessionid){
            if(req.getRequestURL().compare(0,Args->config->buildurl("gamestatus").length(),Args->config->buildurl("gamestatus"))!=0){
                return false;
            }
            std::string _lang = getLang(*Args->database[tid]);
            libhtmlpp::HtmlString condat;
            libhttppp::HTTPException excep;

            condat << "<div id=\"content\"><div class=\"gamecard\">";

            for (auto i = HLDS.begin(); i!=HLDS.end(); ++i) {
                try{
                    gameinfo::HldsView::HldsData hldsdata;
                    rlock.lock();
                    i->refresh(hldsdata);
                    rlock.unlock();
                    condat << "<div class=\"gamestatus-card\">";
                    condat << "<h2 class=\"server-name\">" << hldsdata.ServerName << "</h2>";
                    const char* statusClass = (hldsdata.MaxPlayers > 0) ? "online" : "offline";
                    condat << "<div class=\"status-indicator " << statusClass << "\">"
                           << ((hldsdata.MaxPlayers > 0) ? tr(_lang,"ONLINE") : tr(_lang,"OFFLINE")) << "</div>";
                    // Start the details grid container
                    condat << "<div class=\"details-grid\">";
                    // Row 1: Gameport
                    condat << "<div class=\"detail-item\">"
                           << "<span class=\"label\">"+tr(_lang,"Gameport:")+"</span>"
                           << "<span class=\"value\">" << hldsdata.Port << "</span>"
                           << "</div>";
                    // Row 2: Gamename
                    condat << "<div class=\"detail-item\">"
                           << "<span class=\"label\">"+tr(_lang,"Gamename:")+"</span>"
                           << "<span class=\"value\">" << hldsdata.GameName << "</span>"
                           << "</div>";
                    // Row 3: Modname
                    condat << "<div class=\"detail-item\">"
                           << "<span class=\"label\">"+tr(_lang,"Modname:")+"</span>"
                           << "<span class=\"value\">" << hldsdata.ModName << "</span>"
                           << "</div>";
                    // Row 4: Mapname
                    condat << "<div class=\"detail-item\">"
                           << "<span class=\"label\">"+tr(_lang,"Mapname:")+"</span>"
                           << "<span class=\"value\">" << hldsdata.MapName << "</span>"
                           << "</div>";
                    // Row 5: Password Protected
                    condat << "<div class=\"detail-item\">"
                           << "<span class=\"label\">"+tr(_lang,"Password:")+"</span>"
                           << "<span class=\"value\">" << (hldsdata.pwProtected ? tr(_lang,"Yes") : tr(_lang,"No")) << "</span>"
                           << "</div>";
                    // Row 6: Players
                    condat << "<div class=\"detail-item\">"
                           << "<span class=\"label\">"+tr(_lang,"Players:")+"</span>"
                           << "<span class=\"value player-count\">"
                           << hldsdata.Players << " (" << hldsdata.BotsAmount << ") / "
                           << hldsdata.MaxPlayers
                           << "</span>"
                           << "</div>";
                    // Close details grid and card
                    condat << "</div></div>";
                }catch(gameinfo::GameInfoException &e){
                    std::cerr << e.what();
                    rlock.unlock();
                    continue;
                }
            }

            condat << "</div></div>";

            libhtmlpp::HtmlString out;

            page->getElementbyID("main")->insertChild(condat.parse());

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

            libhttppp::HttpResponse resp;
            resp.setState(HTTP200);
            resp.setContentType("text/html");
            sendCompressed(req,resp,out.c_str(),out.length());
            return true;

        }
    private:
        std::vector<gameinfo::HldsView> HLDS;
    };
};

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

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