
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. 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.
//
// 3. Neither the name of the copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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 "src/tint/cmd/common/helper.h"
#include "src/tint/lang/core/type/struct.h"
#include "src/tint/lang/wgsl/inspector/entry_point.h"
#include "src/tint/utils/command/args.h"
#include "src/tint/utils/text/string.h"
#include "src/tint/utils/text/styled_text_printer.h"

namespace {

struct Options {
    bool show_help = false;

#if TINT_BUILD_SPV_READER
    tint::spirv::reader::Options spirv_reader_options;
#endif

    std::string input_filename;
    bool emit_json = false;
};

const char kUsage[] = R"(Usage: tint [options] <input-file>

 options:
   --json                    -- Emit JSON
   -h                        -- This help text

)";

bool ParseArgs(tint::VectorRef<std::string_view> args, Options* opts) {
    for (auto arg : args) {
        if (arg == "-h" || arg == "--help") {
            opts->show_help = true;
        } else if (arg == "--json") {
            opts->emit_json = true;
        } else if (!arg.empty()) {
            if (arg[0] == '-') {
                std::cerr << "Unrecognized option: " << arg << "\n";
                return false;
            }
            if (!opts->input_filename.empty()) {
                std::cerr << "More than one input file specified: '" << opts->input_filename
                          << "' and '" << arg << "'\n";
                return false;
            }
            opts->input_filename = arg;
        }
    }
    return true;
}

void EmitJson(const tint::Program& program) {
    tint::inspector::Inspector inspector(program);

    std::cout << "{\n\"extensions\": [\n";

    if (!inspector.GetUsedExtensionNames().empty()) {
        bool first = true;
        for (const auto& name : inspector.GetUsedExtensionNames()) {
            if (!first) {
                std::cout << ",";
            }
            first = false;
            std::cout << "\"" << name << "\"\n";
        }
    }
    std::cout << "],\n";
    std::cout << "\"entry_points\": [";

    auto stage_var = [&](const tint::inspector::StageVariable& var) {
        std::cout << "\n{\n\"name\": \"" << var.name << "\",\n";
        if (auto location = var.attributes.location) {
            std::cout << "\"location\": " << location.value() << ",\n"
                      << "\"interpolation\": {\n"
                      << "\"type\": \""
                      << tint::cmd::InterpolationTypeToString(var.interpolation_type) << "\",\n"
                      << "\"sampling\": \""
                      << tint::cmd::InterpolationSamplingToString(var.interpolation_sampling)
                      << "\"\n"
                      << "},\n";
        }
        if (auto color = var.attributes.color) {
            std::cout << "\"color\": " << color.value() << ",\n";
        }
        std::cout << "\"component_type\": \""
                  << tint::cmd::ComponentTypeToString(var.component_type) << "\",\n"
                  << "\"composition_type\": \""
                  << tint::cmd::CompositionTypeToString(var.composition_type) << "\"\n\n"
                  << "}";
    };

    auto entry_points = inspector.GetEntryPoints();
    bool first = true;
    for (auto& entry_point : entry_points) {
        if (!first) {
            std::cout << ",";
        }
        first = false;

        std::cout << "\n{\n"
                  << "\"name\": \"" << entry_point.name << "\",\n"
                  << "\"stage\": \"" << tint::cmd::EntryPointStageToString(entry_point.stage)
                  << "\",\n";

        if (entry_point.workgroup_size) {
            std::cout << "\"workgroup_size\": [";
            std::cout << entry_point.workgroup_size->x << ", " << entry_point.workgroup_size->y
                      << ", " << entry_point.workgroup_size->z << "],\n";
        }

        std::cout << "\"input_variables\": [";
        bool input_first = true;
        for (const auto& var : entry_point.input_variables) {
            if (!input_first) {
                std::cout << ",";
            }
            input_first = false;
            stage_var(var);
        }
        std::cout << "\n],\n";
        std::cout << "\"output_variables\": [";
        bool output_first = true;
        for (const auto& var : entry_point.output_variables) {
            if (!output_first) {
                std::cout << ",";
            }
            output_first = false;
            stage_var(var);
        }
        std::cout << "\n],\n";
        std::cout << "\"overrides\": [";

        bool override_first = true;
        for (const auto& var : entry_point.overrides) {
            if (!override_first) {
                std::cout << ",";
            }
            override_first = false;

            std::cout << "\n{\n"
                      << "\"name\": \"" << var.name << "\",\n"
                      << "\"id\": " << var.id.value << ",\n"
                      << "\"type\": \"" << tint::cmd::OverrideTypeToString(var.type) << "\",\n"
                      << "\"is_initialized\": " << (var.is_initialized ? "true" : "false") << ",\n"
                      << "\"is_id_specified\": " << (var.is_id_specified ? "true" : "false")
                      << "\n}";
        }
        std::cout << "\n],\n";

        std::cout << "\"bindings\": [";
        auto bindings = inspector.GetResourceBindings(entry_point.name);
        bool ep_first = true;
        for (auto& binding : bindings) {
            if (!ep_first) {
                std::cout << ",";
            }
            ep_first = false;

            std::cout << "\n{\n"
                      << "\"binding\": " << binding.binding << ",\n"
                      << "\"group\": " << binding.bind_group << ",\n"
                      << "\"size\": " << binding.size << ",\n"
                      << "\"resource_type\": \""
                      << tint::cmd::ResourceTypeToString(binding.resource_type) << "\",\n"
                      << "\"dimensions\": \"" << tint::cmd::TextureDimensionToString(binding.dim)
                      << "\",\n"
                      << "\"sampled_kind\": \""
                      << tint::cmd::SampledKindToString(binding.sampled_kind) << "\",\n"
                      << "\"image_format\": \""
                      << tint::cmd::TexelFormatToString(binding.image_format) << "\"\n"
                      << "}";
        }
        std::cout << "\n]\n";
        std::cout << "}";
    }
    std::cout << "\n],\n";
    std::cout << "\"structures\": [";

    bool struct_first = true;
    for (const auto* ty : program.Types()) {
        if (!ty->Is<tint::core::type::Struct>()) {
            continue;
        }
        const auto* s = ty->As<tint::core::type::Struct>();

        if (!struct_first) {
            std::cout << ",";
        }
        struct_first = false;

        std::cout << "\n{\n"
                  << "\"name\": \"" << s->FriendlyName() << "\",\n"
                  << "\"align\": " << s->Align() << ",\n"
                  << "\"size\": " << s->Size() << ",\n"
                  << "\"members\": [";
        for (size_t i = 0; i < s->Members().Length(); ++i) {
            auto* const m = s->Members()[i];

            if (i != 0) {
                std::cout << ",";
            }
            std::cout << "\n";

            // Output field alignment padding, if any
            auto* const prev_member = (i == 0) ? nullptr : s->Members()[i - 1];
            if (prev_member) {
                uint32_t padding = m->Offset() - (prev_member->Offset() + prev_member->Size());
                if (padding > 0) {
                    size_t padding_offset = m->Offset() - padding;
                    std::cout << "{\n"
                              << "\"name\": \"implicit_padding\",\n"
                              << "\"offset\": " << padding_offset << ",\n"
                              << "\"align\": 1,\n"
                              << "\"size\": " << padding << "\n},\n";
                }
            }

            std::cout << "{\n"
                      << "\"name\": \"" << m->Name().Name() << "\",\n"
                      << "\"offset\": " << m->Offset() << ",\n"
                      << "\"align\": " << m->Align() << ",\n"
                      << "\"size\": " << m->Size() << "\n}";
        }
        std::cout << "\n]\n}";
    }
    std::cout << "\n]\n}\n";
}

void EmitText(const tint::Program& program) {
    auto printer = tint::StyledTextPrinter::Create(stdout);
    tint::inspector::Inspector inspector(program);
    if (!inspector.GetUsedExtensionNames().empty()) {
        std::cout << "Extensions:\n";
        for (const auto& name : inspector.GetUsedExtensionNames()) {
            std::cout << "\t" << name << "\n";
        }
    }
    std::cout << "\n";

    tint::cmd::PrintInspectorData(inspector);

    bool has_struct = false;
    for (const auto* ty : program.Types()) {
        if (!ty->Is<tint::core::type::Struct>()) {
            continue;
        }
        has_struct = true;
        break;
    }

    if (has_struct) {
        std::cout << "Structures\n";
        for (const auto* ty : program.Types()) {
            if (!ty->Is<tint::core::type::Struct>()) {
                continue;
            }
            const auto* s = ty->As<tint::core::type::Struct>();
            printer->Print(s->Layout() << "\n\n");
        }
    }
}

}  // namespace

int main(int argc, const char** argv) {
    tint::Vector<std::string_view, 8> args = tint::args::Vectorize(argc, argv);
    Options options;

    if (!ParseArgs(args, &options)) {
        std::cerr << "Failed to parse arguments.\n";
        return 1;
    }

    if (options.show_help) {
        std::cout << kUsage << "\n";
        return 0;
    }

    tint::cmd::LoadProgramOptions opts;
    opts.filename = options.input_filename;
#if TINT_BUILD_SPV_READER
    opts.spirv_reader_options = options.spirv_reader_options;
#endif

    auto info = tint::cmd::LoadProgramInfo(opts);
    if (!info.program.IsValid()) {
        return 1;
    }

    if (options.emit_json) {
        EmitJson(info.program);
    } else {
        EmitText(info.program);
    }

    return 0;
}
