/*******************************************************************************
 * 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 <fstream>
#include <cstring>
#include <string>
#include <vector>
#ifdef Windows
#include <windows.h>
#else
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/types.h>
#endif // Windows

#ifdef FreeBSD
    #include <sys/sysctl.h>
#endif

#ifdef __APPLE__
    #include <sys/sysctl.h>
    #include <mach/mach.h>
#endif


#include <dbpp/database.h>

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

#ifdef Linux
#include "icons/tux.h"
#endif
#ifdef Windows
#include "icons/win_ico.h"
#endif
#ifdef FreeBSD
#include "icons/freebsd.h"
#endif

#if defined(__APPLE__) && !defined(FreeBSD)
#include "icons/macos.h"
#endif

#define RELEASEFILE "/etc/os-release"

namespace blogi {
    class NodeInfo : public PluginApi {
    public:
        NodeInfo(){
#ifndef Windows
            std::ifstream osr(RELEASEFILE);
            std::string osline;

            if(osr.is_open()){
                while( getline (osr,osline) ){
                    auto deli=osline.find('=');
                    if(osline.substr(0,deli)=="NAME"){
                        ++deli;
                        _DistName=osline.substr(deli,osline.size()-deli);
                        continue;
                    }
                    size_t sts=osline.find('\"',deli);
                    size_t ets=osline.find('\"',++sts);
                    if(osline.substr(0,deli)=="VERSION_ID"){
                        ++deli;
                        _DistEntry=osline.substr(deli,osline.size()-deli);
                        continue;
                    }
                }
            }
#endif
        }

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

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

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

        void initPlugin(){
        }

        const char* getArchText(int arch) {
            switch (arch) {
            case 6:
                return "x64";
            case 9:
                return "x64";
            case 0:
                return "x32";
            default:
                return "Warning: architecture detected, but not supported in this example";
            }
        }

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

            if(req.getRequestURL().compare(0,Args->config->buildurl("nodeinfo/sysico.svg").length(),Args->config->buildurl("nodeinfo/sysico.svg"))==0){
                libhttppp::HttpResponse resp;
                resp.setState(HTTP200);
                resp.setContentType("image/svg+xml");
                resp.send(req,sysicon);
                return true;
            }



            std::string _lang = getLang(*Args->database[tid]);
            libhtmlpp::HtmlString condat,sysicohtml;

            sysicohtml << sysicon;
            
            libhtmlpp::HtmlTable htmltable;

#if !defined(Windows)
            struct utsname usysinfo;
            uname(&usysinfo);
            /*create htmltable widget*/
            
            char hostname[255];

            if(gethostname(hostname,255)==0){
                htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Hostname:").c_str() << hostname);
            }

            /*create table rows*/
            htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Operating system:").c_str() << (sysicohtml << usysinfo.sysname));
            htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Release Version:").c_str() << usysinfo.release);
            htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Hardware:").c_str() << usysinfo.machine);
            htmltable << (libhtmlpp::HtmlTable::Row() <<  _DistName << _DistEntry);
#else // Windows implementation
            WSADATA wsaData;
            int iResult;

            iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
            if (iResult == 0) {
                char hostName[256];
                DWORD hostNameLen = sizeof(hostName);

                if (gethostname(hostName, hostNameLen) == SOCKET_ERROR) {
                    std::cerr << "gethostname failed with error: " << WSAGetLastError() << std::endl;
                    WSACleanup();
                    return false;
                }

                htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Hostname:").c_str() << hostName);

                WSACleanup();
            }

            auto GetTrueOSVersion = [](OSVERSIONINFOW* info) {
                typedef LONG NTSTATUS;
                typedef NTSTATUS (WINAPI *RTLGETVERSION)(POSVERSIONINFOW lpVersionInformation);
                
                // RtlGetVersion resides in ntdll.dll
                HMODULE hNtdll = GetModuleHandle(TEXT("ntdll.dll"));
                if (!hNtdll) 
                    return static_cast<NTSTATUS>(-1); 
                
                // Dynamically load RtlGetVersion
                RTLGETVERSION pRtlGetVersion = (RTLGETVERSION)GetProcAddress(hNtdll, "RtlGetVersion");
                if (!pRtlGetVersion) 
                    return static_cast<NTSTATUS>(-1);

                info->dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
                return pRtlGetVersion(info);
            };

            OSVERSIONINFOW info;
            memset(&info, 0, sizeof(OSVERSIONINFOW));

            if (GetTrueOSVersion(&info) != 0) {
               // Fallback if RtlGetVersion fails (e.g., truly ancient OS)
                info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
                GetVersionExW(&info); 
            }

            SYSTEM_INFO usysinfo;
            memset(&usysinfo, 0, sizeof(SYSTEM_INFO));
            GetSystemInfo(&usysinfo);

            libhtmlpp::HtmlString rver;

            rver << (size_t)info.dwMajorVersion
                << "." << (size_t)info.dwMinorVersion << "("
                << (size_t)info.dwBuildNumber << ")";

            htmltable << libhtmlpp::HtmlTable::Row() << tr(_lang,"Operating system:").c_str() << "Windows";
            htmltable << libhtmlpp::HtmlTable::Row() << tr(_lang,"Release Version:").c_str() << rver;
            htmltable << libhtmlpp::HtmlTable::Row() << tr(_lang,"Hardware:").c_str() << getArchText(usysinfo.wProcessorArchitecture);

           // --- Build Year Mapping ---
            std::map<DWORD, std::pair<std::string, std::string>> buildToVersionMap = {
                // Build:            {Client Version,      Server Version}
                {9200,               {"Win 8",             "2012"}}, 
                {9600,               {"Win 8.1",           "2012 R2"}},
                {14393,              {"Win 10 1607",       "2016"}}, 
                {17763,              {"Win 10 1809",       "2019"}},
                {19041,              {"Win 10 2004",       "N/A"}}, // Client-only builds
                {19045,              {"Win 10 22H2",       "N/A"}}, 
                {20348,              {"N/A",               "2022"}}, // Server-only build
                {22000,              {"Win 11 21H2",       "N/A"}}, 
                {22621,              {"Win 11 22H2",       "N/A"}},
                {22631,              {"Win 11 23H2",       "N/A"}},
                {26100,              {"Win 11 24H2",       "2025"}}, // Shared build (future)
                {26200,              {"Win 11 25H2",       "N/A"}}
            };
            
            // --------------------------------------------------------------------
            // --- Windows Edition Retrieval Implemented as a Lambda ---
            // --------------------------------------------------------------------
            auto getEditionLambda = [](DWORD dwMajorVersion, DWORD dwMinorVersion, DWORD& productType) -> std::string {

                typedef BOOL (WINAPI *PGETPRODUCTINFO)(
                    DWORD dwOSMajorVersion,
                    DWORD dwOSMinorVersion,
                    DWORD dwSpMajorVersion,
                    DWORD dwSpMinorVersion,
                    PDWORD pdwReturnedProductType
                );
                
                productType = 0;
                PGETPRODUCTINFO pGetProductInfo = (PGETPRODUCTINFO)GetProcAddress(
                    GetModuleHandle(TEXT("kernel32.dll")), 
                    "GetProductInfo"
                );

                if (!pGetProductInfo || !pGetProductInfo(dwMajorVersion, dwMinorVersion, 0, 0, &productType)) {
                    return "Edition Check Failed";
                }

                // Map the product ID to a string AND determine if it's a Server OS
                switch (productType) {
                    case PRODUCT_STANDARD_SERVER:   return "Server Standard";
                    case PRODUCT_DATACENTER_SERVER: return "Server Datacenter";
                    case PRODUCT_WEB_SERVER:        return "Web Server";
                    case PRODUCT_ENTERPRISE_SERVER: return "Server Enterprise";
                    case PRODUCT_CLUSTER_SERVER:    return "Cluster Server";
                    
                    // Client Editions
                    case PRODUCT_CORE:              return "Core / Home";
                    case PRODUCT_HOME_PREMIUM:      return "Home Premium";
                    case PRODUCT_PROFESSIONAL:      return "Professional";
                    case PRODUCT_ENTERPRISE:        return "Enterprise";
                    case PRODUCT_EDUCATION:         return "Education";
                    case PRODUCT_IOTENTERPRISE:     return "IoT Enterprise";
                    default:                        return "Unknown type";
                }
            };
            // --------------------------------------------------------------------

            // Call the lambda to get the edition
            DWORD dwProductTypeID = 0;
            std::string edition_name = getEditionLambda(info.dwMajorVersion, info.dwMinorVersion, dwProductTypeID);

            std::string versionString = "Unknown Version";

            auto it = buildToVersionMap.find(info.dwBuildNumber);
            if (it != buildToVersionMap.end()) {
                const auto& versions = it->second;
                
                bool isServerEdition = (dwProductTypeID == PRODUCT_STANDARD_SERVER ||
                                        dwProductTypeID == PRODUCT_DATACENTER_SERVER ||
                                        dwProductTypeID == PRODUCT_ENTERPRISE_SERVER ||
                                        dwProductTypeID == PRODUCT_WEB_SERVER);

                if (isServerEdition && versions.second != "N/A") {
                    versionString = versions.second; // Use the Server Version string
                } else if (!isServerEdition && versions.first != "N/A") {
                    versionString = versions.first;  // Use the Client Version string
                } else {
                    // Fallback for an unexpected Server/Client match
                    versionString = "Build " + std::to_string(info.dwBuildNumber) + " (Type Mismatch)";
                }
            } else {
                versionString = "Build " + std::to_string(info.dwBuildNumber);
            }

            std::string finalDisplay = edition_name + " (" + versionString + ")";
            
            htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Edition:").c_str() << finalDisplay);
#endif // Windows
#if defined(FreeBSD)
            long psize;
            if((psize=sysconf(_SC_PAGESIZE))>0){
                // BSD/sysctl implementation (Your previous update)
                long total_pages = 0;
                long free_pages = 0;
                size_t len;

                // Get Total Pages
                len = sizeof(total_pages);
                sysctlbyname("vm.stats.vm.v_page_count", &total_pages, &len, NULL, 0);

                // Get Free Pages
                len = sizeof(free_pages);
                sysctlbyname("vm.stats.vm.v_free_count", &free_pages, &len, NULL, 0);

                // Calculation and Formatting
                char ab[512];
                long total_mb = (total_pages * psize) / 1048576;
                long free_mb = (free_pages * psize) / 1048576;

                snprintf(ab, 512, "%ld/%ld MB", free_mb, total_mb);
                htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Memory:").c_str() << ab);
            }
#elif defined(Linux) // New Linux-specific implementation
            // Linux implementation: Read /proc/meminfo
            std::ifstream meminfo("/proc/meminfo");
            long total_mem_kb = 0;
            long free_mem_kb = 0;
            std::string line;

            if (meminfo.is_open()) {
                while (std::getline(meminfo, line)) {
                    // Check for MemTotal
                    if (line.compare(0, 9, "MemTotal:") == 0) {
                        try {
                            // Skip "MemTotal:" and " " and parse the number
                            total_mem_kb = std::stol(line.substr(9));
                        } catch (...) {
                            // Handle parsing error
                        }
                    }
                    // Check for MemFree (or MemAvailable for a more accurate free value)
                    if (line.compare(0, 8, "MemFree:") == 0) {
                        try {
                            free_mem_kb = std::stol(line.substr(8));
                        } catch (...) {
                            // Handle parsing error
                        }
                    }
                }
                meminfo.close();
            }

            if (total_mem_kb > 0) {
                char ab[512];
                // Convert kB to MB (1024 kB = 1 MB)
                long total_mb = total_mem_kb / 1024;
                long free_mb = free_mem_kb / 1024;

                snprintf(ab, 512, "%ld/%ld MB", free_mb, total_mb);
                htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Memory:").c_str() << ab);
            }
#elif defined(Windows)
            MEMORYSTATUSEX status;
            status.dwLength = sizeof(status);

            // Call the Windows API to get memory status
            if (GlobalMemoryStatusEx(&status)) {
                    char ab[512];

                    // Convert bytes to MB (1024 * 1024 = 1048576)
                    // dwTotalPhys is total physical RAM in bytes
                    long total_mb = status.ullTotalPhys / 1048576;
                    // ullAvailPhys is available physical RAM in bytes
                    long free_mb = status.ullAvailPhys / 1048576;

                    snprintf(ab, 512, "%ld/%ld MB", free_mb, total_mb);
                    htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Memory:").c_str() << ab);
            }
#elif defined(__APPLE__)
            {
                int64_t total_mem = 0;
                size_t len = sizeof(total_mem);
                sysctlbyname("hw.memsize", &total_mem, &len, NULL, 0);

                mach_port_t host = mach_host_self();
                vm_statistics64_data_t vmstat;
                mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
                host_statistics64(host, HOST_VM_INFO64, (host_info64_t)&vmstat, &count);

                long page_size = sysconf(_SC_PAGESIZE);
                long free_mem = (long)(vmstat.free_count + vmstat.inactive_count) * page_size;

                char ab[512];
                snprintf(ab, 512, "%ld/%ld MB", free_mem / 1048576, (long)(total_mem / 1048576));
                htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Memory:").c_str() << ab);
            }
#else // Fallback for generic Unix/Unknown
            // Use the generic POSIX sysconf if Linux/FreeBSD aren't defined,
            // though this might fail as seen previously.

            long psize;
            if((psize=sysconf(_SC_PAGESIZE))>0){
                char ab[512];
                // Note: _SC_AVPHYS_PAGES may still be undeclared on some systems
                snprintf(ab,512,"%ld/%ld MB",(sysconf(_SC_AVPHYS_PAGES)*psize)/(1048576),((sysconf(_SC_PHYS_PAGES)*psize)/1048576));
                htmltable << (libhtmlpp::HtmlTable::Row() << tr(_lang,"Memory:").c_str() <<  ab);
            }
#endif // Linux/FreeBSD/Windows/else

            libhtmlpp::HtmlElement table;

            htmltable.insert(&table);
            table.setAttribute("class","kinfo");

            libhtmlpp::HtmlElement html;
            html.setTagname("div");;
            html.setAttribute("id","content");
            html.insertChild(&table);

            libhtmlpp::HtmlString out;

            page->getElementbyID("main")->insertChild(html);

            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::string _DistName,_DistEntry;
    };
};

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

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