/*
 * Copyright (c) 2020-2026 Valve Corporation
 * Copyright (c) 2020-2026 LunarG, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Authors:
 * - Christophe Riccio <christophe@lunarg.com>
 */

#include "generate_layers_settings_env.h"

bool GenerateSettingsEnv(Configurator& configurator, ExportEnvMode mode, const Path& export_path) {
    const char* COMMENT = mode == EXPORT_ENV_BASH ? "#! " : ":: ";
    const char* EXPORT = mode == EXPORT_ENV_BASH ? "export " : "set ";

    QFile file(export_path.AbsolutePath().c_str());

    const bool result_layers_file = file.open(QIODevice::WriteOnly | QIODevice::Text);
    if (!result_layers_file) {
        return false;
    }

    const Configuration* configuration = configurator.GetActiveConfiguration();

    QTextStream stream(&file);

    stream << COMMENT << "Copyright (c) 2020-2026 Valve Corporation\n";
    stream << COMMENT << "Copyright (c) 2020-2026 LunarG, Inc.\n";
    stream << COMMENT << "\n";
    stream << COMMENT << "Licensed under the Apache License, Version 2.0 (the \"License\");\n";
    stream << COMMENT << "you may not use this file except in compliance with the License.\n";
    stream << COMMENT << "You may obtain a copy of the License at\n";
    stream << COMMENT << "\n";
    stream << COMMENT << "    http://www.apache.org/licenses/LICENSE-2.0\n";
    stream << COMMENT << "\n";
    stream << COMMENT << "Unless required by applicable law or agreed to in writing, software\n";
    stream << COMMENT << "distributed under the License is distributed on an \"AS IS\" BASIS,\n";
    stream << COMMENT << "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n";
    stream << COMMENT << "See the License for the specific language governing permissions and\n";
    stream << COMMENT << "limitations under the License.\n";
    stream << COMMENT << "\n";
    stream << COMMENT << format("This code was generated by Vulkan Configurator %s\n\n", Version::VKCONFIG.str().c_str()).c_str();

    stream << COMMENT << "Loader Settings:\n";

    const std::vector<std::string>& stderr_log = ::GetLogTokens(configurator.loader_log_messages_flags);
    const std::string stderr_logs = Merge(stderr_log, ",");

    /*
        if (configurator.loader_log_enabled) {
            stream << COMMENT << "Vulkan Loader enabled and ordered:\n";
            stream << EXPORT << "VK_LOADER_DEBUG=" << stderr_logs.c_str() << "\n";
        }
    */
    // if (configurator.layers_override_enabled)
    {
        stream << COMMENT << "Vulkan Layers enabled and ordered:\n";
        stream << EXPORT << "VK_INSTANCE_LAYERS=";
        std::vector<std::string> layer_list;
        for (std::size_t i = 0, n = configuration->parameters.size(); i < n; ++i) {
            const Parameter& parameter = configuration->parameters[i];
            if (parameter.builtin == LAYER_BUILTIN_UNORDERED) {
                continue;
            }
            if (parameter.control != LAYER_CONTROL_ON) {
                continue;
            }
            layer_list.push_back(parameter.key);
        }
        stream << Merge(layer_list, ",").c_str();
        stream << "\n";
    }

    stream << "\n";

    // Loop through all the layers
    for (std::size_t j = 0, n = configuration->parameters.size(); j < n; ++j) {
        const Parameter& parameter = configuration->parameters[j];
        if (!parameter.override_settings) {
            continue;
        }

        if (!(parameter.platform_flags & (1 << VKC_PLATFORM))) {
            continue;
        }

        if (parameter.builtin == LAYER_BUILTIN_UNORDERED) {
            continue;
        }

        if (parameter.control == LAYER_CONTROL_DISCARD || parameter.control == LAYER_CONTROL_OFF) {
            continue;
        }

        const Layer* layer = configurator.layers.Find(parameter.key.c_str(), parameter.api_version);
        if (layer == nullptr) {
            if (parameter.control == LAYER_CONTROL_ON) {
                configurator.Log(LOG_ERROR,
                                 format("`%s` layer is set to `%s` in `%s` layers configuration but missing and being ignored\n",
                                        parameter.key.c_str(), ::GetLabel(parameter.control), configuration->key.c_str()));
            } else {
                configurator.Log(LOG_WARN,
                                 format("`%s` layer is set to `%s` in `%s` layers configuration but missing and being ignored\n",
                                        parameter.key.c_str(), ::GetLabel(parameter.control), configuration->key.c_str()));
            }
            continue;
        }

        if (layer->settings.empty()) {
            continue;
        }

        std::string status;
        if (layer->status != STATUS_STABLE) {
            status = format(" (%s)", GetToken(layer->status));
        }

        std::string platforms = " (" + Merge(::GetPlatformTokens(layer->platforms), ", ") + ")";

        stream << "\n";
        stream << COMMENT << layer->description.c_str() << "\n";
        stream << COMMENT << "==========================================\n";
        stream << COMMENT
               << format("%s - %s%s%s\n", layer->key.c_str(), layer->api_version.str().c_str(), status.c_str(), platforms.c_str())
                      .c_str();

        if (!layer->introduction.empty()) {
            // Break up description into smaller words
            std::string description = layer->introduction;
            std::vector<std::string> paragraphs;
            std::size_t pos;
            while ((pos = description.find("\n")) != std::string::npos) {
                paragraphs.push_back(description.substr(0, pos));
                description.erase(0, pos + 1);
            }

            for (std::size_t i = 0, n = paragraphs.size(); i < n; ++i) {
                description = paragraphs[i];

                std::vector<std::string> words;
                while ((pos = description.find(" ")) != std::string::npos) {
                    words.push_back(description.substr(0, pos));
                    description.erase(0, pos + 1);
                }
                if (description.size() > 0) {
                    words.push_back(description);
                }
                if (words.size() > 0) {
                    stream << COMMENT;
                    std::size_t nchars = 2;
                    for (auto word : words) {
                        if (word.size() + nchars > 80) {
                            stream << "\n" << COMMENT;
                            nchars = 2;
                        }
                        stream << " " << word.c_str();
                        nchars += (word.size() + 1);
                    }
                }

                stream << "\n";
            }
        }

        if (!layer->url.Empty()) {
            stream << COMMENT
                   << format("For more information about the layer: %s\n",
                             ConvertStandardSeparators(layer->url.AbsolutePath()).c_str())
                          .c_str();
        }
        stream << "\n";

        std::string lc_layer_name = GetLayerSettingPrefix(layer->key);

        for (std::size_t i = 0, m = parameter.settings.size(); i < m; ++i) {
            const SettingData* setting_data = parameter.settings[i];

            // Skip groups - they aren't settings, so not relevant in this output
            if (setting_data->type == SETTING_GROUP) {
                continue;
            }

            // Skip missing settings
            const SettingMeta* meta = FindSetting(layer->settings, setting_data->key.c_str());
            if (meta == nullptr) {
                continue;
            }

            if (meta->view == SETTING_VIEW_HIDDEN) {
                continue;
            }

            // Skip overriden settings
            if (::CheckSettingOverridden(*meta)) {
                continue;
            }

            std::string platforms = " (" + Merge(::GetPlatformTokens(layer->platforms), ", ") + ")";

            stream << COMMENT << meta->label.c_str() << "\n";
            stream << COMMENT << "------------------------------------------\n";
            stream << COMMENT << meta->key.c_str();
            if (meta->status != STATUS_STABLE) {
                stream << format(" (%s)", GetToken(meta->status)).c_str();
            }
            stream << platforms.c_str();
            stream << "\n";

            // Break up description into smaller words
            std::string description = meta->description;
            std::vector<std::string> words;
            std::size_t pos;
            while ((pos = description.find(" ")) != std::string::npos) {
                words.push_back(description.substr(0, pos));
                description.erase(0, pos + 1);
            }
            if (description.size() > 0) {
                words.push_back(description);
            }
            if (words.size() > 0) {
                stream << COMMENT;
                std::size_t nchars = std::strlen(COMMENT);
                for (auto word : words) {
                    if (word.size() + nchars > 80) {
                        stream << "\n";
                        stream << COMMENT;
                        nchars = std::strlen(COMMENT);
                    }
                    stream << " " << word.c_str();
                    nchars += (word.size() + 1);
                }
            }
            stream << "\n";

            if (!meta->detailed.empty()) {
                stream << COMMENT << format("%s\n", meta->detailed.c_str()).c_str();
            }

            if (IsArray(meta->type)) {
                stream << COMMENT << "This setting can list multiple values using the \",\" (comma) separator.\n";
            }

            if (!meta->url.Empty()) {
                stream << COMMENT
                       << format("For more information about the feature: %s\n",
                                 ConvertStandardSeparators(meta->url.AbsolutePath()).c_str())
                              .c_str();
            }

            if (meta->status == STATUS_DEPRECATED && !meta->deprecated_by_key.empty()) {
                const SettingMeta* replaced_setting = ::FindSetting(layer->settings, meta->deprecated_by_key.c_str());
                stream << COMMENT
                       << format("This setting was deprecated and replaced by '%s' (%s) setting.\n",
                                 replaced_setting->label.c_str(), replaced_setting->key.c_str())
                              .c_str();
            }

            if (!meta->children.empty()) {
                stream << COMMENT << "This setting has sub-settings:\n";
                for (std::size_t i = 0, n = meta->children.size(); i < n; ++i) {
                    std::string setting_key = meta->children[i]->key;
                    const SettingData* setting_data = ::FindSetting(parameter.settings, setting_key.c_str());
                    std::vector<std::string> data = ::BuildEnvVariablesList(layer->key.c_str(), setting_key.c_str(), false);

                    if (meta->children[i]->type == SETTING_GROUP) {
                        stream << COMMENT << "- " << lc_layer_name.c_str() << setting_data->key.c_str() << " (Setting Group)\n";
                    } else {
                        stream << COMMENT << "- " << EXPORT << data[0].c_str() << "=";
                        stream << setting_data->Export(EXPORT_MODE_OVERRIDE).c_str() << "\n";
                    }
                }
            }

            if (!meta->dependence.empty()) {
                stream << COMMENT << "This setting requires " << ::GetToken(meta->dependence_mode) << " of the following values:\n";
                for (std::size_t i = 0, n = meta->dependence.size(); i < n; ++i) {
                    const SettingData* setting_data = meta->dependence[i];
                    std::vector<std::string> data = ::BuildEnvVariablesList(layer->key.c_str(), setting_data->key.c_str(), false);
                    stream << COMMENT << "- " << EXPORT << data[0].c_str() << "=";
                    stream << setting_data->Export(EXPORT_MODE_OVERRIDE).c_str() << "\n";
                }
            }

            // If feature has unmet dependency, output it but comment it out
            bool dependence_not_satisfied = false;
            if (::CheckDependence(*meta, parameter.settings) != SETTING_DEPENDENCE_ENABLE) {
                dependence_not_satisfied = true;
                stream << COMMENT;
            }

            std::vector<std::string> data = ::BuildEnvVariablesList(layer->key.c_str(), setting_data->key.c_str(), false);
            const bool need_wordaround = mode == EXPORT_ENV_BASH && setting_data->key == "force_device_name";

            stream << EXPORT << data[0].c_str() << "=";

            if (need_wordaround) {
                stream << "\"";
            }

            stream << setting_data->Export(EXPORT_MODE_OVERRIDE).c_str();

            if (need_wordaround) {
                stream << "\"";
            }

            if (dependence_not_satisfied) {
                stream << " (Commented out as the set of dependences is not satisfied)";
            }

            stream << "\n\n";
        }
    }

    file.close();

    return true;
}
