//
// Copyright Contributors to the MaterialX Project
// SPDX-License-Identifier: Apache-2.0
//

#include <MaterialXGenShader/Syntax.h>
#include <MaterialXGenShader/TypeDesc.h>
#include <MaterialXGenShader/Exception.h>
#include <MaterialXGenShader/GenContext.h>

#include <MaterialXCore/Value.h>

MATERIALX_NAMESPACE_BEGIN

const string Syntax::NEWLINE = "\n";
const string Syntax::SEMICOLON = ";";
const string Syntax::COMMA = ",";
const string Syntax::INDENTATION = "    ";
const string Syntax::STRING_QUOTE = "\"";
const string Syntax::INCLUDE_STATEMENT = "#include";
const string Syntax::SINGLE_LINE_COMMENT = "// ";
const string Syntax::BEGIN_MULTI_LINE_COMMENT = "/* ";
const string Syntax::END_MULTI_LINE_COMMENT = " */";

const std::unordered_map<char, size_t> Syntax::CHANNELS_MAPPING =
{
    { 'r', 0 }, { 'x', 0 },
    { 'g', 1 }, { 'y', 1 },
    { 'b', 2 }, { 'z', 2 },
    { 'a', 3 }, { 'w', 3 }
};

//
// Syntax methods
//

Syntax::Syntax(TypeSystemPtr typeSystem) :
    _typeSystem(typeSystem)
{
}

void Syntax::registerTypeSyntax(TypeDesc type, TypeSyntaxPtr syntax)
{
    auto it = _typeSyntaxIndexByType.find(type);
    if (it != _typeSyntaxIndexByType.end())
    {
        _typeSyntaxes[it->second] = syntax;
    }
    else
    {
        _typeSyntaxes.push_back(syntax);
        _typeSyntaxIndexByType[type] = _typeSyntaxes.size() - 1;
    }

    // Make this type a restricted name
    registerReservedWords({ syntax->getName() });
}

void Syntax::registerReservedWords(const StringSet& names)
{
    _reservedWords.insert(names.begin(), names.end());
}

void Syntax::registerInvalidTokens(const StringMap& tokens)
{
    _invalidTokens.insert(tokens.begin(), tokens.end());
}

/// Returns the type syntax object for a named type.
/// Throws an exception if a type syntax is not defined for the given type.
const TypeSyntax& Syntax::getTypeSyntax(TypeDesc type) const
{
    auto it = _typeSyntaxIndexByType.find(type);
    if (it == _typeSyntaxIndexByType.end())
    {
        throw ExceptionShaderGenError("No syntax is defined for the given type '" + type.getName() + "'.");
    }
    return *_typeSyntaxes[it->second];
}

string Syntax::getValue(const ShaderPort* port, bool uniform) const
{
    const TypeSyntax& syntax = getTypeSyntax(port->getType());
    return syntax.getValue(port, uniform);
}

string Syntax::getValue(TypeDesc type, const Value& value, bool uniform) const
{
    const TypeSyntax& syntax = getTypeSyntax(type);
    return syntax.getValue(value, uniform);
}

const string& Syntax::getDefaultValue(TypeDesc type, bool uniform) const
{
    const TypeSyntax& syntax = getTypeSyntax(type);
    return syntax.getDefaultValue(uniform);
}

const string& Syntax::getTypeName(TypeDesc type) const
{
    const TypeSyntax& syntax = getTypeSyntax(type);
    return syntax.getName();
}

string Syntax::getOutputTypeName(TypeDesc type) const
{
    const TypeSyntax& syntax = getTypeSyntax(type);
    const string& outputModifier = getOutputQualifier();
    return outputModifier.size() ? outputModifier + " " + syntax.getName() : syntax.getName();
}

const string& Syntax::getTypeAlias(TypeDesc type) const
{
    const TypeSyntax& syntax = getTypeSyntax(type);
    return syntax.getTypeAlias();
}

const string& Syntax::getTypeDefinition(TypeDesc type) const
{
    const TypeSyntax& syntax = getTypeSyntax(type);
    return syntax.getTypeDefinition();
}

bool Syntax::typeSupported(const TypeDesc*) const
{
    return true;
}

string Syntax::getArrayVariableSuffix(TypeDesc type, const Value& value) const
{
    if (type.isArray())
    {
        if (value.isA<vector<float>>())
        {
            const size_t size = value.asA<vector<float>>().size();
            return "[" + std::to_string(size) + "]";
        }
        else if (value.isA<vector<int>>())
        {
            const size_t size = value.asA<vector<int>>().size();
            return "[" + std::to_string(size) + "]";
        }
    }
    return string();
}

static bool isInvalidChar(char c)
{
    return !isalnum(c) && c != '_';
}

void Syntax::makeValidName(string& name) const
{
    std::replace_if(name.begin(), name.end(), isInvalidChar, '_');
    name = replaceSubstrings(name, _invalidTokens);
    if (std::find(_reservedWords.begin(), _reservedWords.end(), name) != _reservedWords.end())
    {
        // We append "1" here because thats the prior behavior from makeIdentifier() below when
        // the reservedWords were added to the identifiers list.
        name = name+"1";
    }
}

void Syntax::makeIdentifier(string& name, IdentifierMap& identifiers) const
{
    makeValidName(name);

    auto it = identifiers.find(name);
    if (it != identifiers.end())
    {
        // Name is not unique so append the counter and keep
        // incrementing until a unique name is found.
        string name2;
        do
        {
            name2 = name + std::to_string(it->second++);
        } while (identifiers.count(name2));

        name = name2;
    }

    // Save it among the known identifiers.
    identifiers[name] = 1;
}

string Syntax::getVariableName(const string& name, TypeDesc /*type*/, IdentifierMap& identifiers) const
{
    // Default implementation just makes an identifier, but derived
    // classes can override this for custom variable naming.
    string variable = name;
    makeIdentifier(variable, identifiers);
    return variable;
}

bool Syntax::remapEnumeration(const string&, TypeDesc, const string&, std::pair<TypeDesc, ValuePtr>&) const
{
    return false;
}

StructTypeSyntaxPtr Syntax::createStructSyntax(const string& structTypeName, const string& defaultValue,
                                               const string& uniformDefaultValue, const string& typeAlias,
                                               const string& typeDefinition) const
{
    return std::make_shared<StructTypeSyntax>(
        this,
        structTypeName,
        defaultValue,
        uniformDefaultValue,
        typeAlias,
        typeDefinition);
}

const StringVec TypeSyntax::EMPTY_MEMBERS;

TypeSyntax::TypeSyntax(const Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue,
                       const string& typeAlias, const string& typeDefinition, const StringVec& members) :
    _parent(parent),
    _name(name),
    _defaultValue(defaultValue),
    _uniformDefaultValue(uniformDefaultValue),
    _typeAlias(typeAlias),
    _typeDefinition(typeDefinition),
    _members(members)
{
}

string TypeSyntax::getValue(const ShaderPort* port, bool uniform) const
{
    if (!port || !port->getValue())
    {
        return getDefaultValue(uniform);
    }
    return getValue(*port->getValue(), uniform);
}

ScalarTypeSyntax::ScalarTypeSyntax(const Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue,
                                   const string& typeAlias, const string& typeDefinition) :
    TypeSyntax(parent, name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, EMPTY_MEMBERS)
{
}

string ScalarTypeSyntax::getValue(const Value& value, bool /*uniform*/) const
{
    return value.getValueString();
}

StringTypeSyntax::StringTypeSyntax(const Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue,
                                   const string& typeAlias, const string& typeDefinition) :
    ScalarTypeSyntax(parent, name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition)
{
}

string StringTypeSyntax::getValue(const Value& value, bool /*uniform*/) const
{
    return "\"" + value.getValueString() + "\"";
}

AggregateTypeSyntax::AggregateTypeSyntax(const Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue,
                                         const string& typeAlias, const string& typeDefinition, const StringVec& members) :
    TypeSyntax(parent, name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, members)
{
}

string AggregateTypeSyntax::getValue(const Value& value, bool /*uniform*/) const
{
    const string valueString = value.getValueString();
    return valueString.empty() ? valueString : getName() + "(" + valueString + ")";
}

StructTypeSyntax::StructTypeSyntax(const Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue,
                                   const string& typeAlias, const string& typeDefinition, const StringVec& members) :
    TypeSyntax(parent, name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, members)
{
}

string StructTypeSyntax::getValue(const Value& value, bool /*uniform*/) const
{
    const AggregateValue& aggValue = static_cast<const AggregateValue&>(value);

    string result = "{";

    string separator = "";
    for (const auto& memberValue : aggValue.getMembers())
    {
        result += separator;
        separator = ";";

        const string& memberTypeName = memberValue->getTypeString();
        TypeDesc memberTypeDesc = _parent->getType(memberTypeName);

        // Recursively use the syntax to generate the output, so we can support nested structs.
        const string valueStr = _parent->getValue(memberTypeDesc, *memberValue, true);

        result += valueStr;
    }

    result += "}";

    return result;
}

MATERIALX_NAMESPACE_END
