/*
 * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include <cstddef>
#include <cstring>
#include "tstrings.h"
#include "JvmLauncher.h"
#include "Log.h"
#include "Dll.h"
#include "CfgFile.h"
#include "FileUtils.h"
#include "Toolbox.h"
#include "ErrorHandling.h"

Jvm::Jvm() {
    LOG_TRACE(tstrings::any() << "Jvm(" << this << ")::Jvm()");
}


Jvm::~Jvm() {
    LOG_TRACE(tstrings::any() << "Jvm(" << this << ")::~Jvm()");
}


Jvm& Jvm::initFromConfigFile(const CfgFile& cfgFile) {
    const CfgFile::Properties& appOptions = cfgFile.getProperties(
            SectionName::Application);

    {
        const CfgFile::Properties::const_iterator modulepath = appOptions.find(
                PropertyName::modulepath);
        if (modulepath != appOptions.end()) {
            tstring_array::const_iterator it = modulepath->second.begin();
            const tstring_array::const_iterator end = modulepath->second.end();
            for (; it != end; ++it) {
                addArgument(_T("--module-path"));
                addArgument(*it);
            };
        }
    }

    {
        const CfgFile::Properties::const_iterator classpath = appOptions.find(
                PropertyName::classpath);
        if (classpath != appOptions.end()) {
            addArgument(_T("-classpath"));
            addArgument(CfgFile::asPathList(*classpath));
        }
    }

    {
        const CfgFile::Properties::const_iterator splash = appOptions.find(
                PropertyName::splash);
        if (splash != appOptions.end()) {
            const tstring splashPath = CfgFile::asString(*splash);
            if (FileUtils::isFileExists(splashPath)) {
                addArgument(_T("-splash:") + splashPath);
            } else {
                LOG_WARNING(tstrings::any()
                        << "Splash property ignored. File \""
                        << splashPath << "\" not found");
            }
        }
    }

    {
        const CfgFile::Properties& section = cfgFile.getProperties(
                SectionName::JavaOptions);
        const CfgFile::Properties::const_iterator javaOptions = section.find(
                PropertyName::javaOptions);
        if (javaOptions != section.end()) {
            tstring_array::const_iterator it = javaOptions->second.begin();
            const tstring_array::const_iterator end = javaOptions->second.end();
            for (; it != end; ++it) {
                addArgument(*it);
            };
        }
    }

    {
        addArgument(_T("-Djpackage.app-path=")
                + SysInfo::getProcessModulePath());
    }

    // No validation of data in config file related to how Java app should be
    // launched intentionally.
    // Just read what is in config file and put on jvm's command line as is.

    { // Run modular app
        const CfgFile::Properties::const_iterator mainmodule = appOptions.find(
                PropertyName::mainmodule);
        if (mainmodule != appOptions.end()) {
            addArgument(_T("-m"));
            addArgument(CfgFile::asString(*mainmodule));
        }
    }

    { // Run main class
        const CfgFile::Properties::const_iterator mainclass = appOptions.find(
                PropertyName::mainclass);
        if (mainclass != appOptions.end()) {
            addArgument(CfgFile::asString(*mainclass));
        }
    }

    { // Run jar
        const CfgFile::Properties::const_iterator mainjar = appOptions.find(
                PropertyName::mainjar);
        if (mainjar != appOptions.end()) {
            addArgument(_T("-jar"));
            addArgument(CfgFile::asString(*mainjar));
        }
    }

    {
        const CfgFile::Properties& section = cfgFile.getProperties(
                SectionName::ArgOptions);
        const CfgFile::Properties::const_iterator arguments = section.find(
                PropertyName::arguments);
        if (arguments != section.end()) {
            tstring_array::const_iterator it = arguments->second.begin();
            const tstring_array::const_iterator end = arguments->second.end();
            for (; it != end; ++it) {
                addArgument(*it);
            };
        }
    }

    return *this;
}


bool Jvm::isWithSplash() const {
    tstring_array::const_iterator it = args.begin();
    const tstring_array::const_iterator end = args.end();
    for (; it != end; ++it) {
        if (tstrings::startsWith(*it, _T("-splash:"))) {
            return true;
        }
    }
    return false;
}


namespace {

struct JvmlLauncherHandleCloser {
    typedef JvmlLauncherHandle pointer;

    void operator()(JvmlLauncherHandle h) {
        jvmLauncherCloseHandle(jvmLauncherGetAPI(), h);
    }
};

struct JvmlLauncherDataDeleter {
    typedef JvmlLauncherData* pointer;

    void operator()(JvmlLauncherData* ptr) {
        free(ptr);
    }
};

} // namespace

void Jvm::launch() {
    typedef std::unique_ptr<
        JvmlLauncherHandle, JvmlLauncherHandleCloser> AutoJvmlLauncherHandle;

    typedef std::unique_ptr<
        JvmlLauncherData, JvmlLauncherDataDeleter> AutoJvmlLauncherData;

    AutoJvmlLauncherHandle jlh(exportLauncher());

    JvmlLauncherAPI* api = jvmLauncherGetAPI();

    AutoJvmlLauncherData jld(jvmLauncherCreateJvmlLauncherData(api,
                                                        jlh.release(), 0));

    LOG_TRACE(tstrings::any() << "JVM library: \"" << jvmPath << "\"");

    DllFunction<void*> func(Dll(jvmPath), "JLI_Launch");

    int exitStatus = jvmLauncherStartJvm(jld.get(), func.operator void*());

    if (exitStatus != 0) {
        JP_THROW("Failed to launch JVM");
    }
}


void Jvm::setEnvVariables() {
    for (size_t i = 0; i != envVarNames.size(); i++) {
        SysInfo::setEnvVariable(envVarNames.at(i), envVarValues.at(i));
    }
}


namespace {

struct JliLaunchData {
    std::string jliLibPath;
    std::vector<std::string> args;
    tstring_array envVarNames;
    tstring_array envVarValues;

    int initJvmlLauncherData(JvmlLauncherData* ptr, int bufferSize) const {
        int minimalBufferSize = initJvmlLauncherData(0);
        if (minimalBufferSize <= bufferSize) {
            initJvmlLauncherData(ptr);
        }
        return minimalBufferSize;
    }

private:
    template <class T>
    static char* copyStrings(const std::vector<T>& src,
                JvmlLauncherData* ptr, const size_t offset, char* curPtr) {
        char** strArray = 0;
        if (ptr) {
            strArray = *reinterpret_cast<char***>(
                                    reinterpret_cast<char*>(ptr) + offset);
        }

        for (size_t i = 0; i != src.size(); i++) {
            const size_t count = (src[i].size() + 1 /* trailing zero */)
                                            * sizeof(typename T::value_type);
            if (ptr) {
                std::memcpy(curPtr, src[i].c_str(), count);
                strArray[i] = curPtr;
            }

            curPtr += count;
        };

        return curPtr;
    }

    int initJvmlLauncherData(JvmlLauncherData* ptr) const {
        // Store path to JLI library just behind JvmlLauncherData header.
        JvmlLauncherData dummy;
        char* curPtr = reinterpret_cast<char*>((ptr ? ptr : &dummy) + 1);
        {
            const size_t count = sizeof(char)
                    * (jliLibPath.size() + 1 /* trailing zero */);
            if (ptr) {
                std::memcpy(curPtr, jliLibPath.c_str(), count);
                ptr->jliLibPath = curPtr;
            }
            curPtr += count;
        }

        // Write array of char* pointing to JLI lib arg strings.
        if (ptr) {
            ptr->jliLaunchArgv = reinterpret_cast<char**>(curPtr);
            ptr->jliLaunchArgc = (int)args.size();
            // Add terminal '0' arg.
            ptr->jliLaunchArgv[ptr->jliLaunchArgc] = 0;
        }
        // Skip memory occupied by JvmlLauncherData::jliLaunchArgv array.
        curPtr += sizeof(char*) * (args.size() + 1 /* terminal '0' arg */);
        // Store JLI lib arg strings.
        curPtr = copyStrings(args, ptr,
                            offsetof(JvmlLauncherData, jliLaunchArgv), curPtr);

        // Write array of char* pointing to env variable name strings.
        if (ptr) {
            ptr->envVarNames = reinterpret_cast<TCHAR**>(curPtr);
            ptr->envVarCount = (int)envVarNames.size();
        }
        // Skip memory occupied by JvmlLauncherData::envVarNames array.
        curPtr += sizeof(TCHAR*) * envVarNames.size();
        // Store env variable names.
        curPtr = copyStrings(envVarNames, ptr,
                            offsetof(JvmlLauncherData, envVarNames), curPtr);

        // Write array of char* pointing to env variable value strings.
        if (ptr) {
            ptr->envVarValues = reinterpret_cast<TCHAR**>(curPtr);
        }
        // Skip memory occupied by JvmlLauncherData::envVarValues array.
        curPtr += sizeof(TCHAR*) * envVarValues.size();
        // Store env variable values.
        curPtr = copyStrings(envVarValues, ptr,
                            offsetof(JvmlLauncherData, envVarValues), curPtr);

        const size_t bufferSize = curPtr - reinterpret_cast<char*>(ptr ? ptr : &dummy);
        if (ptr) {
            LOG_TRACE(tstrings::any() << "Initialized " << bufferSize
                                        << " bytes at " << ptr << " address");
        } else {
            LOG_TRACE(tstrings::any() << "Need " << bufferSize
                                    << " bytes for JvmlLauncherData buffer");
        }
        return static_cast<int>(bufferSize);
    }
};

void copyStringArray(const tstring_array& src, std::vector<std::string>& dst) {
#ifdef TSTRINGS_WITH_WCHAR
    {
        tstring_array::const_iterator it = src.begin();
        const tstring_array::const_iterator end = src.end();
        for (; it != end; ++it) {
            dst.push_back(tstrings::toACP(*it));
        }
    }
#else
    dst = src;
#endif
}

} // namespace

JvmlLauncherHandle Jvm::exportLauncher() const {
    std::unique_ptr<JliLaunchData> result(new JliLaunchData());

    result->jliLibPath = tstrings::toUtf8(jvmPath);

    copyStringArray(args, result->args);
    result->envVarNames = envVarNames;
    result->envVarValues = envVarValues;

    return result.release();
}


namespace {

void closeHandle(JvmlLauncherHandle h) {
    JP_TRY;

    JliLaunchData* data = static_cast<JliLaunchData*>(h);
    const std::unique_ptr<JliLaunchData> deleter(data);

    JP_CATCH_ALL;
}


int getJvmlLauncherDataSize(JvmlLauncherHandle h) {
    JP_TRY;

    const JliLaunchData* data = static_cast<const JliLaunchData*>(h);
    return data->initJvmlLauncherData(0, 0);

    JP_CATCH_ALL;

    return -1;
}


JvmlLauncherData* initJvmlLauncherData(JvmlLauncherHandle h,
                                                void* ptr, int bufferSize) {
    JP_TRY;

    const JliLaunchData* data = static_cast<const JliLaunchData*>(h);
    const int usedBufferSize = data->initJvmlLauncherData(
                            static_cast<JvmlLauncherData*>(ptr), bufferSize);
    if (bufferSize <= usedBufferSize) {
        return static_cast<JvmlLauncherData*>(ptr);
    }

    JP_CATCH_ALL;

    return 0;
}

class Impl : public JvmlLauncherAPI {
public:
    Impl() {
        this->closeHandle = ::closeHandle;
        this->getJvmlLauncherDataSize = ::getJvmlLauncherDataSize;
        this->initJvmlLauncherData = ::initJvmlLauncherData;
    }
} api;

} // namespace


extern "C" {

JNIEXPORT JvmlLauncherAPI* jvmLauncherGetAPI(void) {
    return &api;
}

} // extern "C"
