/*
 * 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_hpp.h"

#include <QTextStream>

bool GenerateSettingsCode(Configurator& configurator, ExportHppMode mode, const Path& export_path) {
    QFile file(export_path.AbsolutePath().c_str());

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

    QTextStream stream(&file);

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

    stream << "\n";
    stream << "#pragma once\n";
    stream << "\n";
    stream << "#include <vector>\n";
    stream << "#include <string>\n";
    stream << "\n";
    if (mode == EXPORT_HPP_VULKAN_HPP) {
        stream << "#include <vulkan/vulkan.hpp>\n";
    } else {
        stream << "#include <vulkan/vulkan.h>\n";
    }
    stream << "\n";
    stream << "struct LayerSettings;\n";
    stream << "\n";

    const Configuration* configuration = configurator.GetActiveConfiguration();
    for (std::size_t parameter_index = 0, parameter_count = configuration->parameters.size(); parameter_index < parameter_count;
         ++parameter_index) {
        const Parameter& parameter = configuration->parameters[parameter_index];

        if (!parameter.override_settings) {
            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.FindFromManifest(parameter.manifest);
        if (layer == nullptr) {
            continue;
        }

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

        for (std::size_t setting_index = 0, setting_count = parameter.settings.size(); setting_index < setting_count;
             ++setting_index) {
            SettingData* setting = parameter.settings[setting_index];
            if (setting->type != SETTING_FLAGS && setting->type != SETTING_ENUM) {
                continue;
            }

            stream << setting->Export(EXPORT_MODE_CPP_DECLARATION_VALUES).c_str();
        }

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

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

        stream << format("//%s\n", layer->description.c_str()).c_str();
        stream << "// ==========================================\n";
        stream << 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()) {
            stream << format("/*\n%s\n*/\n", layer->introduction.c_str()).c_str();
        }

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

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

        stream << format("struct %s {\n", ::GetCodeType(parameter.key).c_str()).c_str();
        stream << format("\tstatic const uint32_t VERSION = VK_MAKE_API_VERSION(%d, %d, %d, %d);\n\n",
                         layer->api_version.GetMajor(), layer->api_version.GetMinor(), layer->api_version.GetPatch(),
                         layer->api_version.GetRevision())
                      .c_str();

        for (std::size_t setting_index = 0, setting_count = parameter.settings.size(); setting_index < setting_count;
             ++setting_index) {
            SettingData* setting = parameter.settings[setting_index];
            if (setting->type == SETTING_GROUP) {
                continue;
            }

            const SettingMeta* meta = ::FindSetting(layer->settings, setting->key.c_str());
            if (meta == nullptr) {
                continue;
            }

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

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

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

            stream << format("\t// %s\n", meta->label.c_str()).c_str();
            stream << "\t// ------------------------------------------\n";

            stream << "\t// " << 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 << "\t//";
                std::size_t nchars = 2;
                for (auto word : words) {
                    if (word.size() + nchars > 80) {
                        stream << "\n\t//";
                        nchars = 2;
                    }
                    stream << " " << word.c_str();
                    nchars += (word.size() + 1);
                }
            }
            stream << "\n";

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

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

            if (!meta->url.Empty()) {
                stream << format("\t// 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 << format("\t// 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 << "\t// 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());

                    if (meta->children[i]->type == SETTING_GROUP) {
                        stream << "\t // - " << lc_layer_name.c_str() << setting_data->key.c_str() << " (Setting Group)\n";
                    } else {
                        stream << "\t// - " << setting_data->Export(EXPORT_MODE_CPP_DECLARATION_AND_INIT).c_str();
                    }
                }
            }

            if (!meta->dependence.empty()) {
                stream << "\t// 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* data = meta->dependence[i];
                    stream << "\t// - " << data->Export(EXPORT_MODE_CPP_DECLARATION_AND_INIT).c_str();
                }
            }
            stream << "\t" << setting->Export(EXPORT_MODE_CPP_DECLARATION_AND_INIT).c_str();
            stream << "\n";
        }

        stream << "private:\n";
        stream << "\tfriend struct LayerSettings;\n";
        stream << "\n";

        for (std::size_t setting_index = 0, setting_count = parameter.settings.size(); setting_index < setting_count;
             ++setting_index) {
            SettingData* setting = parameter.settings[setting_index];
            if (IsInfoStringArrayRequired(setting->type)) {
                stream << format("\tstd::vector<const char*> %s_info;\n", setting->key.c_str()).c_str();
                continue;
            } else if (IsInfoStringRequired(setting->type)) {
                stream << format("\tconst char* %s_info = nullptr;\n", setting->key.c_str()).c_str();
                continue;
            }
        }

        stream << "\n";
        stream << "\tvoid init() {\n";
        for (std::size_t setting_index = 0, setting_count = parameter.settings.size(); setting_index < setting_count;
             ++setting_index) {
            SettingData* setting = parameter.settings[setting_index];
            if (IsInfoStringArrayRequired(setting->type)) {
                stream
                    << format("\t\tthis->%s_info.resize(this->%s.size());\n", setting->key.c_str(), setting->key.c_str()).c_str();
                stream << format("\t\tfor (std::size_t i = 0, n = %s_info.size(); i < n; ++i) {\n", setting->key.c_str()).c_str();
                stream << format("\t\t\tthis->%s_info[i] = this->%s[i].c_str();\n", setting->key.c_str(), setting->key.c_str())
                              .c_str();
                stream << "\t\t}\n";
                continue;
            } else if (IsInfoStringRequired(setting->type)) {
                stream << format("\t\tthis->%s_info = this->%s.c_str();\n", setting->key.c_str(), setting->key.c_str()).c_str();
            }
        }
        stream << "\t}\n";

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

    stream << "// `LayerSettings` allows initializing layer settings from Vulkan application code.\n";
    stream << "struct LayerSettings {\n";
    stream << "\tstd::vector<std::string> layer_list {\n";
    for (std::size_t parameter_index = 0, parameter_count = configuration->parameters.size(); parameter_index < parameter_count;
         ++parameter_index) {
        const Parameter& parameter = configuration->parameters[parameter_index];
        if (parameter.control == LAYER_CONTROL_ON && parameter.builtin == LAYER_BUILTIN_NONE) {
            stream << format("\t\t\"%s\",\n", parameter.key.c_str()).c_str();
        }
    }
    stream << "\t};\n";

    for (std::size_t parameter_index = 0, parameter_count = configuration->parameters.size(); parameter_index < parameter_count;
         ++parameter_index) {
        const Parameter& parameter = configuration->parameters[parameter_index];
        if (parameter.settings.empty()) {
            continue;
        }

        if (!parameter.override_settings) {
            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.FindFromManifest(parameter.manifest);
        if (layer == nullptr) {
            continue;
        }

        stream << format("\t%s %s;\n", GetCodeType(parameter.key).c_str(), GetCodeData(parameter.key).c_str()).c_str();
    }

    stream << "\n";
    stream << "\t// Use for VkLayerSettingsCreateInfoEXT `settingCount` and `pSettings` argument\n";
    stream << "\t// Provided by VK_EXT_layer_settings\n";
    stream << "\t// typedef struct VkLayerSettingsCreateInfoEXT {\n";
    stream << "\t// \tVkStructureType             sType;\n";
    stream << "\t// \tconst void*                 pNext;\n";
    stream << "\t// \tuint32_t                    settingCount;\n";
    stream << "\t// \tconst VkLayerSettingEXT*    pSettings;\n";
    stream << "\t// } VkLayerSettingsCreateInfoEXT;\n\n";

    stream << "\tstd::vector<const char*> layers() {\n";
    stream << "\t\tstd::vector<const char*> results;\n";
    stream << "\t\tfor (std::size_t i = 0, n = layer_list.size(); i < n; ++i) {\n";
    stream << "\t\t\tresults.push_back(layer_list[i].c_str());\n";
    stream << "\t\t};\n";
    stream << "\t\treturn results;\n";
    stream << "\t};\n\n";

    if (mode == EXPORT_HPP_VULKAN_HPP) {
        stream << "\tstd::vector<vk::LayerSettingEXT> settings() {\n";
    } else {
        stream << "\tstd::vector<VkLayerSettingEXT> settings() {\n";
    }

    for (std::size_t parameter_index = 0, parameter_count = configuration->parameters.size(); parameter_index < parameter_count;
         ++parameter_index) {
        const Parameter& parameter = configuration->parameters[parameter_index];
        if (parameter.settings.empty()) {
            continue;
        }

        if (!parameter.override_settings) {
            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.FindFromManifest(parameter.manifest);
        if (layer == nullptr) {
            continue;
        }

        stream << format("\t\tthis->%s.init();\n", GetCodeData(parameter.key).c_str()).c_str();
    }

    stream << "\n";
    if (mode == EXPORT_HPP_VULKAN_HPP) {
        stream << "\t\tstd::vector<vk::LayerSettingEXT> init{\n";
    } else {
        stream << "\t\tstd::vector<VkLayerSettingEXT> init{\n";
    }

    for (std::size_t parameter_index = 0, parameter_count = configuration->parameters.size(); parameter_index < parameter_count;
         ++parameter_index) {
        const Parameter& parameter = configuration->parameters[parameter_index];
        if (parameter.settings.empty()) {
            continue;
        }

        if (!parameter.override_settings) {
            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.FindFromManifest(parameter.manifest);
        if (layer == nullptr) {
            continue;
        }

        for (std::size_t setting_index = 0, setting_count = parameter.settings.size(); setting_index < setting_count;
             ++setting_index) {
            SettingData* setting = parameter.settings[setting_index];
            if (setting->type == SETTING_GROUP) {
                continue;
            }

            std::string layer_settings_type = mode == EXPORT_HPP_VULKAN_HPP ? ::GetLayerSettingTypeHPPString(setting->type)
                                                                            : ::GetLayerSettingTypeString(setting->type);
            if (IsArray(setting->type)) {
                stream << format(
                              "\t\t\t{\"%s\", \"%s\", %s, static_cast<uint32_t>(this->%s.%s_info.size()), this->%s.%s_info.empty() "
                              "? nullptr : &this->%s.%s_info[0]},\n",
                              parameter.key.c_str(), setting->key.c_str(), layer_settings_type.c_str(),
                              ::GetCodeData(parameter.key).c_str(), setting->key.c_str(), ::GetCodeData(parameter.key).c_str(),
                              setting->key.c_str(), ::GetCodeData(parameter.key).c_str(), setting->key.c_str())
                              .c_str();
            } else if (IsString(setting->type)) {
                stream << format("\t\t\t{\"%s\", \"%s\", %s, 1, this->%s.%s.c_str()},\n", parameter.key.c_str(),
                                 setting->key.c_str(), layer_settings_type.c_str(), ::GetCodeData(parameter.key).c_str(),
                                 setting->key.c_str())
                              .c_str();
            } else {
                stream << format("\t\t\t{\"%s\", \"%s\", %s, 1, &this->%s.%s},\n", parameter.key.c_str(), setting->key.c_str(),
                                 layer_settings_type.c_str(), ::GetCodeData(parameter.key).c_str(), setting->key.c_str())
                              .c_str();
            }
        }
    }
    stream << "\t\t};\n";
    stream << "\t\treturn init;\n";
    stream << "\t};\n";
    stream << "};\n\n";

    file.close();

    return true;
}
