/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <iostream>
#include <vector>

#include <codemaker/codemaker.hxx>
#include <o3tl/string_view.hxx>
#include <rtl/strbuf.hxx>
#include <unoidl/unoidl.hxx>

#include "netproduce.hxx"
#include "csharpfile.hxx"

namespace
{
const std::unordered_set<OString> s_reservedKeywords{
    "abstract"_ostr, "as"_ostr,       "base"_ostr,       "bool"_ostr,      "break"_ostr,
    "byte"_ostr,     "case"_ostr,     "catch"_ostr,      "char"_ostr,      "checked"_ostr,
    "class"_ostr,    "const"_ostr,    "continue"_ostr,   "decimal"_ostr,   "default"_ostr,
    "delegate"_ostr, "do"_ostr,       "double"_ostr,     "else"_ostr,      "enum"_ostr,
    "event"_ostr,    "explicit"_ostr, "extern"_ostr,     "false"_ostr,     "finally"_ostr,
    "fixed"_ostr,    "float"_ostr,    "for"_ostr,        "foreach"_ostr,   "goto"_ostr,
    "if"_ostr,       "implicit"_ostr, "in"_ostr,         "int"_ostr,       "interface"_ostr,
    "internal"_ostr, "is"_ostr,       "lock"_ostr,       "long"_ostr,      "namespace"_ostr,
    "new"_ostr,      "null"_ostr,     "object"_ostr,     "operator"_ostr,  "out"_ostr,
    "override"_ostr, "params"_ostr,   "private"_ostr,    "protected"_ostr, "public"_ostr,
    "readonly"_ostr, "ref"_ostr,      "return"_ostr,     "sbyte"_ostr,     "sealed"_ostr,
    "short"_ostr,    "sizeof"_ostr,   "stackalloc"_ostr, "static"_ostr,    "string"_ostr,
    "struct"_ostr,   "switch"_ostr,   "this"_ostr,       "throw"_ostr,     "true"_ostr,
    "try"_ostr,      "typeof"_ostr,   "uint"_ostr,       "ulong"_ostr,     "unchecked"_ostr,
    "unsafe"_ostr,   "ushort"_ostr,   "using"_ostr,      "virtual"_ostr,   "void"_ostr,
    "volatile"_ostr, "while"_ostr,
};

const std::unordered_map<OString, OString> s_baseTypes{
    { "boolean"_ostr, "bool"_ostr },
    { "char"_ostr, "char"_ostr },
    { "byte"_ostr, "sbyte"_ostr },
    { "short"_ostr, "short"_ostr },
    { "unsigned short"_ostr, "ushort"_ostr },
    { "long"_ostr, "int"_ostr },
    { "unsigned long"_ostr, "uint"_ostr },
    { "hyper"_ostr, "long"_ostr },
    { "unsigned hyper"_ostr, "ulong"_ostr },
    { "float"_ostr, "float"_ostr },
    { "double"_ostr, "double"_ostr },
    { "string"_ostr, "string"_ostr },
    { "void"_ostr, "void"_ostr },
    { "type"_ostr, "System.Type"_ostr },
    { "any"_ostr, "com.sun.star.uno.Any"_ostr },
    { "com.sun.star.uno.Exception"_ostr, "com.sun.star.uno.UnoException"_ostr },
    { "com.sun.star.uno.XInterface"_ostr, "com.sun.star.uno.IQueryInterface"_ostr },
};

std::tuple<bool, std::string_view, std::string_view> splitName(std::string_view name)
{
    size_t split = name.find_last_of('.');
    if (split != std::string_view::npos)
        return std::make_tuple(true, name.substr(0, split), name.substr(split + 1));
    else
        return std::make_tuple(false, "", name);
}

OString getBaseUnoName(std::string_view name)
{
    size_t start = name.find_first_not_of("[]");
    if (start == std::string_view::npos)
        start = 0;

    size_t end = name.find_first_of('<');
    if (end == std::string_view::npos)
        end = name.size();

    return OString(name.substr(start, end - start));
}
OString getBaseUnoName(std::u16string_view name) { return getBaseUnoName(u2b(name)); }

OString getSafeIdentifier(std::string_view name)
{
    OString temp(name);
    return s_reservedKeywords.contains(temp) ? "@"_ostr + temp : temp;
}
OString getSafeIdentifier(std::u16string_view name) { return getSafeIdentifier(u2b(name)); }

void separatedForeach(const auto& items, auto&& sepFunc, auto&& itemFunc)
{
    for (auto it = items.begin(); it != items.end(); ++it)
    {
        if (it != items.begin())
            sepFunc();
        itemFunc(*it);
    }
}
}

void NetProducer::initProducer(const NetOptions& options)
{
    m_outputDir = options.getOption("--output-dir"_ostr);
    m_dryRun = options.isValid("--dry-run"_ostr);
    m_verbose = options.isValid("--verbose"_ostr);

    if (options.isValid("--types"_ostr))
    {
        const OString& names(options.getOption("--types"_ostr));
        for (size_t i = 0; i != std::string_view::npos;)
        {
            std::string_view name(o3tl::getToken(names, ';', i));
            if (name == "*")
                m_startingTypes.insert(""_ostr);
            else if (name.ends_with(".*"))
                m_startingTypes.emplace(name.substr(0, name.size() - 2));
            else
                m_startingTypes.emplace(name);
        }
    }
    else
    {
        m_startingTypes.insert(""_ostr);
    }

    for (const OString& file : options.getInputFiles())
        m_manager->loadProvider(convertToFileUrl(file), true);
    for (const OString& file : options.getExtraInputFiles())
        m_manager->loadProvider(convertToFileUrl(file), false);
}

void NetProducer::produceAll()
{
    for (const OString& name : m_startingTypes)
        produceType(name);
}

void NetProducer::produceType(const OString& name)
{
    if (m_typesProduced.contains(name))
        return;

    m_typesProduced.insert(name);

    if (s_baseTypes.contains(name))
        return;

    OUString uname(b2u(name));

    rtl::Reference<unoidl::Entity> entity;
    rtl::Reference<unoidl::MapCursor> cursor;

    if (m_manager->foundAtPrimaryProvider(uname))
    {
        switch (m_manager->getSort(uname, &entity, &cursor))
        {
            case codemaker::UnoType::Sort::Module:
                produceModule(name, cursor);
                break;

            case codemaker::UnoType::Sort::Enum:
                produceEnum(name, dynamic_cast<unoidl::EnumTypeEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::PlainStruct:
                producePlainStruct(name,
                                   dynamic_cast<unoidl::PlainStructTypeEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::PolymorphicStructTemplate:
                producePolyStruct(
                    name, dynamic_cast<unoidl::PolymorphicStructTypeTemplateEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::Exception:
                produceException(name, dynamic_cast<unoidl::ExceptionTypeEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::Interface:
                produceInterface(name, dynamic_cast<unoidl::InterfaceTypeEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::Typedef:
                produceTypedef(name, dynamic_cast<unoidl::TypedefEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::ConstantGroup:
                produceConstantGroup(name,
                                     dynamic_cast<unoidl::ConstantGroupEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::SingleInterfaceBasedService:
                produceService(
                    name, dynamic_cast<unoidl::SingleInterfaceBasedServiceEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::InterfaceBasedSingleton:
                produceSingleton(
                    name, dynamic_cast<unoidl::InterfaceBasedSingletonEntity*>(entity.get()));
                break;

            case codemaker::UnoType::Sort::AccumulationBasedService:
            case codemaker::UnoType::Sort::ServiceBasedSingleton:
                // old-style services and singletons not supported
                break;

            default:
                throw CannotDumpException(u"entity '"_ustr + uname + u"' has unexpected type"_ustr);
        }
    }
    else
    {
        // type from --extra-types
        switch (m_manager->getSort(uname, &entity, &cursor))
        {
            case codemaker::UnoType::Sort::Typedef:
                produceTypedef(name, dynamic_cast<unoidl::TypedefEntity*>(entity.get()));
                break;

            default:
                break;
        }
    }
}

void NetProducer::produceModule(std::string_view name,
                                const rtl::Reference<unoidl::MapCursor>& cursor)
{
    OUString moduleName;
    while (cursor->getNext(&moduleName).is())
    {
        OString memberName = name.empty() ? u2b(moduleName) : name + "."_ostr + u2b(moduleName);
        produceType(memberName);
    }
}

void NetProducer::produceEnum(std::string_view name,
                              const rtl::Reference<unoidl::EnumTypeEntity>& entity)
{
    CSharpFile file(m_outputDir, name);

    if (m_verbose)
        std::cout << "[enum] " << name << " -> " << file.getPath() << '\n';
    if (m_dryRun)
        return;

    file.openFile();

    auto[hasNamespace, namespaceName, typeName] = splitName(name);

    if (hasNamespace)
        file.beginLine().append("namespace ").append(namespaceName).endLine().beginBlock();

    file.beginLine()
        .append("[com.sun.star.uno.UnoGenerated]")
        .endLine()
        .beginLine()
        .append("public enum ")
        .append(getSafeIdentifier(typeName))
        .endLine()
        .beginBlock();
    for (const auto& member : entity->getMembers())
    {
        for (const auto& anno : member.annotations)
            if (anno == "deprecated")
                file.beginLine().append("[System.Obsolete]").endLine();
        file.beginLine()
            .append(getSafeIdentifier(member.name))
            .append(" = ")
            .append(OString::number(member.value))
            .append(",")
            .endLine();
    }
    file.endBlock();

    if (hasNamespace)
        file.endBlock();

    file.closeFile();
}

void NetProducer::producePlainStruct(std::string_view name,
                                     const rtl::Reference<unoidl::PlainStructTypeEntity>& entity)
{
    // produce referenced types
    const auto& base = entity->getDirectBase();
    if (!base.isEmpty())
        produceType(getBaseUnoName(base));
    for (const auto& member : entity->getDirectMembers())
        produceType(getBaseUnoName(member.type));

    CSharpFile file(m_outputDir, name);

    // verbose and dry run checks
    if (m_verbose)
        std::cout << "[struct] " << name << " -> " << file.getPath() << '\n';
    if (m_dryRun)
        return;

    file.openFile();

    // output namespace block
    auto[hasNamespace, namespaceName, typeName] = splitName(name);

    if (hasNamespace)
        file.beginLine().append("namespace ").append(namespaceName).endLine().beginBlock();

    // generate struct and base structs list
    file.beginLine()
        .append("[com.sun.star.uno.UnoGenerated]")
        .endLine()
        .beginLine()
        .append("public class ")
        .append(getSafeIdentifier(typeName));
    if (!base.isEmpty())
        file.append(" : ").append(getNetName(base));
    file.endLine().beginBlock();

    // generate default constructor
    file.beginLine()
        .append("public ")
        .append(getSafeIdentifier(typeName))
        .append("()")
        .endLine()
        .beginBlock()
        .endBlock();
    file.endLine(); // extra blank line

    // generate full constructor
    std::vector<unoidl::PlainStructTypeEntity::Member> baseFields;
    {
        OUString baseTypeName = base;
        while (!baseTypeName.isEmpty())
        {
            rtl::Reference<unoidl::Entity> baseEntity;
            if (m_manager->getSort(baseTypeName, &baseEntity)
                != codemaker::UnoType::Sort::PlainStruct)
                throw CannotDumpException("'" + b2u(name) + "' base type '" + baseTypeName
                                          + "' is not a plain struct");

            rtl::Reference<unoidl::PlainStructTypeEntity> ref(
                dynamic_cast<unoidl::PlainStructTypeEntity*>(baseEntity.get()));
            baseFields.insert(baseFields.begin(), ref->getDirectMembers().begin(),
                              ref->getDirectMembers().end());

            baseTypeName = ref->getDirectBase();
        }
    }

    std::vector<unoidl::PlainStructTypeEntity::Member> allFields(entity->getDirectMembers());
    allFields.insert(allFields.begin(), baseFields.begin(), baseFields.end());

    file.beginLine().append("public ").append(getSafeIdentifier(typeName)).append("(");
    separatedForeach(
        allFields, [&file]() { file.append(", "); },
        [this, &file](const auto& member) {
            file.append(getNetName(member.type)).append(" ").append(getSafeIdentifier(member.name));
        });
    file.append(")").endLine();
    file.beginLine().extraIndent().append(": base(");
    separatedForeach(baseFields, [&file]() { file.append(", "); },
                     [&file](const auto& member) { file.append(getSafeIdentifier(member.name)); });
    file.append(")").endLine();
    file.beginBlock();
    for (const auto& member : entity->getDirectMembers())
    {
        file.beginLine()
            .append("this.")
            .append(getSafeIdentifier(member.name))
            .append(" = ")
            .append(getSafeIdentifier(member.name))
            .append(";")
            .endLine();
    }
    file.endBlock();
    file.endLine(); // extra blank line

    // generate deconstructor
    file.beginLine().append("public void Deconstruct(");
    separatedForeach(allFields, [&file]() { file.append(", "); },
                     [this, &file](const auto& member) {
                         file.append("out ")
                             .append(getNetName(member.type))
                             .append(" ")
                             .append(getSafeIdentifier(member.name));
                     });
    file.append(")").endLine();
    file.beginBlock();
    for (const auto& member : allFields)
    {
        file.beginLine()
            .append(getSafeIdentifier(member.name))
            .append(" = ")
            .append("this.")
            .append(getSafeIdentifier(member.name))
            .append(";")
            .endLine();
    }
    file.endBlock();
    file.endLine(); // extra blank line

    // generate struct fields
    for (const auto& member : entity->getDirectMembers())
    {
        for (const auto& anno : member.annotations)
            if (anno == "deprecated")
                file.beginLine().append("[System.Obsolete]").endLine();
        file.beginLine()
            .append("public ")
            .append(getNetName(member.type))
            .append(" ")
            .append(getSafeIdentifier(member.name))
            .append(";")
            .endLine();
    }

    file.endBlock();
    if (hasNamespace)
        file.endBlock();
    file.closeFile();
}

void NetProducer::producePolyStruct(
    std::string_view name,
    const rtl::Reference<unoidl::PolymorphicStructTypeTemplateEntity>& entity)
{
    // produce referenced types
    for (const auto& member : entity->getMembers())
        if (!member.parameterized)
            produceType(getBaseUnoName(member.type));

    CSharpFile file(m_outputDir, name);

    // verbose and dry run checks
    if (m_verbose)
        std::cout << "[polystruct] " << name << " -> " << file.getPath() << '\n';
    if (m_dryRun)
        return;

    file.openFile();

    // output namespace block
    auto[hasNamespace, namespaceName, typeName] = splitName(name);

    if (hasNamespace)
        file.beginLine().append("namespace ").append(namespaceName).endLine().beginBlock();

    // generate struct and type parameters list
    file.beginLine()
        .append("[com.sun.star.uno.UnoGenerated]")
        .endLine()
        .beginLine()
        .append("public class ")
        .append(getSafeIdentifier(typeName))
        .append("<");
    separatedForeach(entity->getTypeParameters(), [&file]() { file.append(", "); },
                     [&file](const auto& param) { file.append(param); });
    file.append(">").endLine().beginBlock();

    // generate default constructor
    file.beginLine()
        .append("public ")
        .append(getSafeIdentifier(typeName))
        .append("()")
        .endLine()
        .beginBlock()
        .endBlock();
    file.endLine(); // extra blank line

    // generate full constructor
    file.beginLine().append("public ").append(getSafeIdentifier(typeName)).append("(");
    separatedForeach(entity->getMembers(), [&file]() { file.append(", "); },
                     [this, &file](const auto& member) {
                         file.append(member.parameterized ? u2b(member.type)
                                                          : getNetName(member.type))
                             .append(" ")
                             .append(getSafeIdentifier(member.name));
                     });
    file.append(")").endLine();
    file.beginBlock();
    for (const auto& member : entity->getMembers())
    {
        file.beginLine()
            .append("this.")
            .append(getSafeIdentifier(member.name))
            .append(" = ")
            .append(getSafeIdentifier(member.name))
            .append(";")
            .endLine();
    }
    file.endBlock();
    file.endLine(); // extra blank line

    // generate struct fields
    for (const auto& member : entity->getMembers())
    {
        for (const auto& anno : member.annotations)
            if (anno == "deprecated")
                file.beginLine().append("[System.Obsolete]").endLine();
        file.beginLine()
            .append("public ")
            .append(member.parameterized ? u2b(member.type) : getNetName(member.type))
            .append(" ")
            .append(getSafeIdentifier(member.name))
            .append(";")
            .endLine();
    }

    file.endBlock();
    if (hasNamespace)
        file.endBlock();
    file.closeFile();
}

void NetProducer::produceException(std::string_view name,
                                   const rtl::Reference<unoidl::ExceptionTypeEntity>& entity)
{
    // produce referenced types
    const auto& base = entity->getDirectBase();
    if (!base.isEmpty())
        produceType(getBaseUnoName(base));
    for (const auto& member : entity->getDirectMembers())
        produceType(getBaseUnoName(member.type));

    CSharpFile file(m_outputDir, name);

    // verbose and dry run checks
    if (m_verbose)
        std::cout << "[exception] " << name << " -> " << file.getPath() << '\n';
    if (m_dryRun)
        return;

    file.openFile();

    // output namespace block
    auto[hasNamespace, namespaceName, typeName] = splitName(name);

    if (hasNamespace)
        file.beginLine().append("namespace ").append(namespaceName).endLine().beginBlock();

    // generate exception and base exceptions list
    file.beginLine()
        .append("[com.sun.star.uno.UnoGenerated]")
        .endLine()
        .beginLine()
        .append("public class ")
        .append(getSafeIdentifier(typeName));
    if (!base.isEmpty())
        file.append(" : ").append(getNetName(base));
    file.endLine().beginBlock();

    // generate default constructor
    file.beginLine()
        .append("public ")
        .append(getSafeIdentifier(typeName))
        .append("()")
        .endLine()
        .beginBlock()
        .endBlock();
    file.endLine(); // extra blank line

    // generate full constructor
    std::vector<unoidl::ExceptionTypeEntity::Member> baseFields;
    {
        OUString baseTypeName = base;
        while (!baseTypeName.isEmpty())
        {
            rtl::Reference<unoidl::Entity> baseEntity;
            if (m_manager->getSort(baseTypeName, &baseEntity)
                != codemaker::UnoType::Sort::Exception)
                throw CannotDumpException("'" + b2u(name) + "' base type '" + baseTypeName
                                          + "' is not an exception");

            rtl::Reference<unoidl::ExceptionTypeEntity> ref(
                dynamic_cast<unoidl::ExceptionTypeEntity*>(baseEntity.get()));
            baseFields.insert(baseFields.begin(), ref->getDirectMembers().begin(),
                              ref->getDirectMembers().end());

            baseTypeName = ref->getDirectBase();
        }
    }

    std::vector<unoidl::ExceptionTypeEntity::Member> allFields(entity->getDirectMembers());
    allFields.insert(allFields.begin(), baseFields.begin(), baseFields.end());

    file.beginLine().append("public ").append(getSafeIdentifier(typeName)).append("(");
    separatedForeach(
        allFields, [&file]() { file.append(", "); },
        [this, &file](const auto& member) {
            file.append(getNetName(member.type)).append(" ").append(getSafeIdentifier(member.name));
        });
    file.append(")").endLine();
    file.beginLine().extraIndent().append(": base(");
    separatedForeach(baseFields, [&file]() { file.append(", "); },
                     [&file](const auto& member) { file.append(getSafeIdentifier(member.name)); });
    file.append(")").endLine();
    file.beginBlock();
    for (const auto& member : entity->getDirectMembers())
    {
        file.beginLine()
            .append("this.")
            .append(getSafeIdentifier(member.name))
            .append(" = ")
            .append(getSafeIdentifier(member.name))
            .append(";")
            .endLine();
    }
    file.endBlock();
    file.endLine(); // extra blank line

    // generate deconstructor
    file.beginLine().append("public void Deconstruct(");
    separatedForeach(allFields, [&file]() { file.append(", "); },
                     [this, &file](const auto& member) {
                         file.append("out ")
                             .append(getNetName(member.type))
                             .append(" ")
                             .append(getSafeIdentifier(member.name));
                     });
    file.append(")").endLine();
    file.beginBlock();
    for (const auto& member : allFields)
    {
        file.beginLine()
            .append(getSafeIdentifier(member.name))
            .append(" = ")
            .append("this.")
            .append(getSafeIdentifier(member.name))
            .append(";")
            .endLine();
    }
    file.endBlock();
    file.endLine(); // extra blank line

    // generate exception fields
    for (const auto& member : entity->getDirectMembers())
    {
        for (const auto& anno : member.annotations)
            if (anno == "deprecated")
                file.beginLine().append("[System.Obsolete]").endLine();
        file.beginLine()
            .append("public ")
            .append(getNetName(member.type))
            .append(" ")
            .append(getSafeIdentifier(member.name))
            .append(";")
            .endLine();
    }

    file.endBlock();
    if (hasNamespace)
        file.endBlock();
    file.closeFile();
}

void NetProducer::produceInterface(std::string_view name,
                                   const rtl::Reference<unoidl::InterfaceTypeEntity>& entity)
{
    // produce referenced types
    for (const auto& base : entity->getDirectMandatoryBases())
        produceType(getBaseUnoName(base.name));
    for (const auto& member : entity->getDirectAttributes())
    {
        produceType(getBaseUnoName(member.type));
        for (const auto& e : member.getExceptions)
            produceType(getBaseUnoName(e));
        for (const auto& e : member.setExceptions)
            produceType(getBaseUnoName(e));
    }
    for (const auto& member : entity->getDirectMethods())
    {
        produceType(getBaseUnoName(member.returnType));
        for (const auto& e : member.exceptions)
            produceType(getBaseUnoName(e));
        for (const auto& p : member.parameters)
            produceType(getBaseUnoName(p.type));
    }

    CSharpFile file(m_outputDir, name);

    // verbose and dry run checks
    if (m_verbose)
        std::cout << "[interface] " << name << " -> " << file.getPath() << '\n';
    if (m_dryRun)
        return;

    file.openFile();

    // output namespace block
    auto[hasNamespace, namespaceName, typeName] = splitName(name);

    if (hasNamespace)
        file.beginLine().append("namespace ").append(namespaceName).endLine().beginBlock();

    // generate interface and base interfaces list
    file.beginLine()
        .append("[com.sun.star.uno.UnoGenerated]")
        .endLine()
        .beginLine()
        .append("public interface ")
        .append(getSafeIdentifier(typeName));
    const auto& bases = entity->getDirectMandatoryBases();
    if (!bases.empty())
    {
        file.append(" : ");
        separatedForeach(bases, [&file]() { file.append(", "); },
                         [this, &file](const auto& b) { file.append(getNetName(b.name)); });
    }
    file.endLine().beginBlock();

    // generate interface properties
    for (const auto& member : entity->getDirectAttributes())
    {
        for (const auto& anno : member.annotations)
            if (anno == "deprecated")
                file.beginLine().append("[System.Obsolete]").endLine();
        if (member.bound)
            file.beginLine().append("[com.sun.star.uno.Bound]").endLine();

        file.beginLine()
            .append(getNetName(member.type))
            .append(" ")
            .append(getSafeIdentifier(member.name))
            .endLine()
            .beginBlock();

        if (!member.getExceptions.empty())
        {
            file.beginLine().append("[com.sun.star.uno.Raises(");
            separatedForeach(member.getExceptions, [&file]() { file.append(", "); },
                             [this, &file](const auto& e) {
                                 file.append("typeof(").append(getNetName(e)).append(")");
                             });
            file.append(")]").endLine();
        }
        file.beginLine().append("get;").endLine();

        if (!member.readOnly)
        {
            if (!member.setExceptions.empty())
            {
                file.beginLine().append("[com.sun.star.uno.Raises(");
                separatedForeach(member.setExceptions, [&file]() { file.append(", "); },
                                 [this, &file](const auto& e) {
                                     file.append("typeof(").append(getNetName(e)).append(")");
                                 });
                file.append(")]").endLine();
            }

            file.beginLine().append("set;").endLine();
        }
        file.endBlock();
    }
    if (!entity->getDirectAttributes().empty())
        file.endLine(); // extra blank line

    // generate interface methods
    for (const auto& member : entity->getDirectMethods())
    {
        for (const auto& anno : member.annotations)
            if (anno == "deprecated")
                file.beginLine().append("[System.Obsolete]").endLine();
        if (!member.exceptions.empty())
        {
            file.beginLine().append("[com.sun.star.uno.Raises(");
            separatedForeach(member.exceptions, [&file]() { file.append(", "); },
                             [this, &file](const auto& e) {
                                 file.append("typeof(").append(getNetName(e)).append(")");
                             });
            file.append(")]").endLine();
        }
        file.beginLine()
            .append(getNetName(member.returnType))
            .append(" ")
            .append(getSafeIdentifier(member.name))
            .append("(");
        separatedForeach(
            member.parameters, [&file]() { file.append(", "); },
            [this, &file](const auto& p) {
                using Dir = unoidl::InterfaceTypeEntity::Method::Parameter::Direction;
                switch (p.direction)
                {
                    case Dir::DIRECTION_IN:
                        break;
                    case Dir::DIRECTION_OUT:
                        file.append("out ");
                        break;
                    case Dir::DIRECTION_IN_OUT:
                        file.append("ref ");
                        break;
                }
                file.append(getNetName(p.type)).append(" ").append(getSafeIdentifier(p.name));
            });
        file.append(");").endLine();
    }

    file.endBlock();
    if (hasNamespace)
        file.endBlock();
    file.closeFile();
}

void NetProducer::produceTypedef(std::string_view name,
                                 const rtl::Reference<unoidl::TypedefEntity>& entity)
{
    OString type = u2b(entity->getType());

    produceType(getBaseUnoName(type));
    m_typedefs.emplace(name, type);

    if (m_verbose)
        std::cout << "[typedef] " << name << " = " << type << '\n';
}

void NetProducer::produceConstantGroup(std::string_view name,
                                       const rtl::Reference<unoidl::ConstantGroupEntity>& entity)
{
    CSharpFile file(m_outputDir, name);

    if (m_verbose)
        std::cout << "[constants] " << name << " -> " << file.getPath() << '\n';
    if (m_dryRun)
        return;

    file.openFile();

    auto[hasNamespace, namespaceName, typeName] = splitName(name);

    if (hasNamespace)
        file.beginLine().append("namespace ").append(namespaceName).endLine().beginBlock();

    file.beginLine()
        .append("[com.sun.star.uno.UnoGenerated]")
        .endLine()
        .beginLine()
        .append("public static class ")
        .append(getSafeIdentifier(typeName))
        .endLine()
        .beginBlock();
    for (const auto& member : entity->getMembers())
    {
        for (const auto& anno : member.annotations)
            if (anno == "deprecated")
                file.beginLine().append("[System.Obsolete]").endLine();
        OString type, value;
        switch (member.value.type)
        {
            case unoidl::ConstantValue::TYPE_BOOLEAN:
                type = "bool"_ostr;
                value = member.value.booleanValue ? "true"_ostr : "false"_ostr;
                break;
            case unoidl::ConstantValue::TYPE_BYTE:
                type = "sbyte"_ostr;
                value = OString::number(member.value.byteValue);
                break;
            case unoidl::ConstantValue::TYPE_SHORT:
                type = "short"_ostr;
                value = OString::number(member.value.shortValue);
                break;
            case unoidl::ConstantValue::TYPE_UNSIGNED_SHORT:
                type = "ushort"_ostr;
                value = OString::number(member.value.unsignedShortValue);
                break;
            case unoidl::ConstantValue::TYPE_LONG:
                type = "int"_ostr;
                value = OString::number(member.value.longValue);
                break;
            case unoidl::ConstantValue::TYPE_UNSIGNED_LONG:
                type = "uint"_ostr;
                value = OString::number(member.value.unsignedLongValue);
                break;
            case unoidl::ConstantValue::TYPE_HYPER:
                type = "long"_ostr;
                value = OString::number(member.value.hyperValue);
                break;
            case unoidl::ConstantValue::TYPE_UNSIGNED_HYPER:
                type = "ulong"_ostr;
                value = OString::number(member.value.unsignedHyperValue);
                break;
            case unoidl::ConstantValue::TYPE_FLOAT:
                type = "float"_ostr;
                value = OString::number(member.value.floatValue) + "f";
                break;
            case unoidl::ConstantValue::TYPE_DOUBLE:
                type = "double"_ostr;
                value = OString::number(member.value.doubleValue);
                break;
        }
        file.beginLine()
            .append("public const ")
            .append(type)
            .append(" ")
            .append(getSafeIdentifier(member.name))
            .append(" = ")
            .append(value)
            .append(";")
            .endLine();
    }
    file.endBlock();
    if (hasNamespace)
        file.endBlock();
    file.closeFile();
}

void NetProducer::produceService(
    std::string_view name, const rtl::Reference<unoidl::SingleInterfaceBasedServiceEntity>& entity)
{
    CSharpFile file(m_outputDir, name);

    if (m_verbose)
        std::cout << "[service] " << name << " -> " << file.getPath() << '\n';
    if (m_dryRun)
        return;

    file.openFile();

    auto[hasNamespace, namespaceName, typeName] = splitName(name);

    if (hasNamespace)
        file.beginLine().append("namespace ").append(namespaceName).endLine().beginBlock();

    file.beginLine()
        .append("[com.sun.star.uno.UnoGenerated]")
        .endLine()
        .beginLine()
        .append("public static class ")
        .append(getSafeIdentifier(typeName))
        .endLine()
        .beginBlock();

    for (const auto& ctor : entity->getConstructors())
    {
        for (const auto& anno : ctor.annotations)
            if (anno == "deprecated")
                file.beginLine().append("[System.Obsolete]").endLine();

        std::vector<OUString> exceptions(ctor.exceptions);
        exceptions.emplace(exceptions.begin(), "com.sun.star.uno.DeploymentException");

        file.beginLine().append("[com.sun.star.uno.Raises(");
        separatedForeach(exceptions, [&file]() { file.append(", "); },
                         [this, &file](const auto& e) {
                             file.append("typeof(").append(getNetName(e)).append(")");
                         });
        file.append(")]").endLine();

        if (ctor.defaultConstructor)
        {
            const auto returnType(getNetName(entity->getBase()));

            file.beginLine()
                .append("public static ")
                .append(returnType)
                .append(" create(com.sun.star.uno.XComponentContext ctx)")
                .endLine()
                .beginBlock();

            file.beginLine()
                .append("try")
                .endLine()
                .beginBlock()
                .beginLine()
                .append("com.sun.star.lang.XMultiComponentFactory mcf = ")
                .append("ctx.getServiceManager();")
                .endLine()
                .beginLine()
                .append("com.sun.star.uno.Any srv = new com.sun.star.uno.Any(")
                .append("mcf.createInstanceWithContext(\"")
                .append(name)
                .append("\", ctx));")
                .endLine()
                .beginLine()
                .append("return srv.cast<")
                .append(returnType)
                .append(">();")
                .beginLine()
                .endLine()
                .endBlock();

            for (const auto& e : ctor.exceptions)
            {
                file.beginLine()
                    .append("catch (")
                    .append(e)
                    .append(")")
                    .endLine()
                    .beginBlock()
                    .beginLine()
                    .append("throw;")
                    .endLine()
                    .endBlock();
            }

            file.beginLine()
                .append("catch")
                .endLine()
                .beginBlock()
                .beginLine()
                .append("throw new com.sun.star.uno.DeploymentException(")
                .append("\"Could not create service ")
                .append(name)
                .append(" from given XComponentContext\", ctx);")
                .endLine()
                .endBlock();

            file.endBlock();
        }
        else
        {
            const auto returnType(getNetName(entity->getBase()));
            const auto* restParam = !ctor.parameters.empty() && ctor.parameters.front().rest
                                        ? &ctor.parameters.front()
                                        : nullptr;

            file.beginLine()
                .append("public static ")
                .append(returnType)
                .append(" ")
                .append(getSafeIdentifier(ctor.name))
                .append("(com.sun.star.uno.XComponentContext ctx");
            if (!ctor.parameters.empty())
                file.append(", ");
            separatedForeach(
                ctor.parameters, [&file]() { file.append(", "); },
                [this, &file](const auto& p) {
                    file.append(getNetName(p.type)).append(" ").append(getSafeIdentifier(p.name));
                });
            file.append(")").endLine().beginBlock();

            file.beginLine()
                .append("try")
                .endLine()
                .beginBlock()
                .beginLine()
                .append("com.sun.star.lang.XMultiComponentFactory mcf = ")
                .append("ctx.getServiceManager();")
                .endLine()
                .beginLine()
                .append("com.sun.star.uno.Any srv = new com.sun.star.uno.Any(")
                .append("mcf.createInstanceWithArgumentsAndContext(\"")
                .append(name)
                .append("\", ");
            if (restParam)
            {
                file.append(getSafeIdentifier(ctor.parameters.front().name));
            }
            else if (ctor.parameters.empty())
            {
                file.append("System.Array.Empty<com.sun.star.uno.Any>()");
            }
            else
            {
                file.append("new com.sun.star.uno.Any[] { ");
                separatedForeach(ctor.parameters, [&file]() { file.append(", "); },
                                 [&file](const auto& p) {
                                     file.append("new com.sun.star.uno.Any(")
                                         .append(getSafeIdentifier(p.name))
                                         .append(")");
                                 });
                file.append(" }");
            }
            file.append(", ctx));")
                .endLine()
                .beginLine()
                .append("return srv.cast<")
                .append(returnType)
                .append(">();")
                .endLine()
                .endBlock();

            for (const auto& e : ctor.exceptions)
            {
                file.beginLine()
                    .append("catch (")
                    .append(getNetName(e))
                    .append(")")
                    .endLine()
                    .beginBlock()
                    .beginLine()
                    .append("throw;")
                    .endLine()
                    .endBlock();
            }

            file.beginLine()
                .append("catch")
                .endLine()
                .beginBlock()
                .beginLine()
                .append("throw new com.sun.star.uno.DeploymentException(")
                .append("\"Could not create service ")
                .append(name)
                .append(" from given XComponentContext\", ctx);")
                .endLine()
                .endBlock();

            file.endBlock();
        }
    }

    file.endBlock();
    if (hasNamespace)
        file.endBlock();
    file.closeFile();
}

void NetProducer::produceSingleton(
    std::string_view name, const rtl::Reference<unoidl::InterfaceBasedSingletonEntity>& entity)
{
    CSharpFile file(m_outputDir, name);

    if (m_verbose)
        std::cout << "[singleton] " << name << " -> " << file.getPath() << '\n';
    if (m_dryRun)
        return;

    file.openFile();

    auto[hasNamespace, namespaceName, typeName] = splitName(name);

    if (hasNamespace)
        file.beginLine().append("namespace ").append(namespaceName).endLine().beginBlock();

    file.beginLine()
        .append("[com.sun.star.uno.UnoGenerated]")
        .endLine()
        .beginLine()
        .append("public static class ")
        .append(getSafeIdentifier(typeName))
        .endLine()
        .beginBlock();

    file.beginLine()
        .append("[com.sun.star.uno.Raises(typeof(com.sun.star.uno.DeploymentException))]")
        .endLine();
    file.beginLine()
        .append("public static ")
        .append(getNetName(entity->getBase()))
        .append(" get(com.sun.star.uno.XComponentContext ctx)")
        .endLine()
        .beginBlock();

    file.beginLine()
        .append("try")
        .endLine()
        .beginBlock()
        .beginLine()
        .append("com.sun.star.uno.Any sgtn = ctx.getValueByName(\"/singletons/")
        .append(name)
        .append("\");")
        .endLine()
        .beginLine()
        .append("return sgtn.cast<")
        .append(getNetName(entity->getBase()))
        .append(">();")
        .endLine()
        .endBlock();

    file.beginLine()
        .append("catch")
        .endLine()
        .beginBlock()
        .beginLine()
        .append("throw new com.sun.star.uno.DeploymentException(\"Could not get singleton ")
        .append(name)
        .append(" from given XComponentContext\", ctx);")
        .endLine()
        .endBlock();

    file.endBlock().endBlock();

    if (hasNamespace)
        file.endBlock();

    file.closeFile();
}

OString NetProducer::getNetName(std::string_view name)
{
    OString fullName(name);
    OStringBuffer buffer;

    while (true)
    {
        OString baseName = getBaseUnoName(fullName);
        if (m_typedefs.contains(baseName))
            fullName = fullName.replaceFirst(baseName, m_typedefs.at(baseName));
        else
            break;
    }

    std::string_view fullNameView(fullName);

    // if sequence, count dimensions
    int dimensions = 0;
    while (fullNameView.starts_with("[]"))
    {
        ++dimensions;
        fullNameView = fullNameView.substr(2);
    }

    // if polymorphic, process parameters too
    if (fullNameView.ends_with('>'))
    {
        const std::string_view::size_type nFirstAngle = fullNameView.find_first_of('<');
        size_t start = (nFirstAngle != std::string_view::npos) ? (nFirstAngle + 1) : 0;
        size_t end = fullNameView.size() - 1;
        buffer.append(fullNameView.substr(0, start));
        OString params(fullNameView.substr(start, end - start));

        bool first = true;
        for (start = 0; start != std::string_view::npos;)
        {
            std::string_view param(o3tl::getToken(params, ',', start));
            if (first)
                first = false;
            else
                buffer.append(", ");
            buffer.append(getNetName(param));
        }
        buffer.append(">");
    }
    else
    {
        // assumes basetypes are not polymorphic for a tiny optimization
        // if this is changed later, move this part out of the else block
        fullName = OString(fullNameView);
        OString baseName = getBaseUnoName(fullName);
        if (s_baseTypes.contains(baseName))
            buffer.append(fullName.replaceFirst(baseName, s_baseTypes.at(baseName)));
        else
            buffer.append(fullName);
    }

    // if sequence, add [] to make array
    while (dimensions--)
        buffer.append("[]");

    return buffer.makeStringAndClear();
}
OString NetProducer::getNetName(std::u16string_view name) { return getNetName(u2b(name)); }

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
