// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.

#include <string>

#include <hilti/ast/builder/builder.h>
#include <hilti/ast/expressions/member.h>
#include <hilti/ast/node.h>
#include <hilti/ast/types/any.h>
#include <hilti/ast/types/struct.h>
#include <hilti/ast/types/unknown.h>

using namespace hilti;
using namespace hilti::operator_;

hilti::struct_::MemberCall::MemberCall(declaration::Field* fdecl) : Operator(fdecl->meta(), false), _fdecl(fdecl) {}

hilti::struct_::MemberCall::~MemberCall() {}

operator_::Signature hilti::struct_::MemberCall::signature(Builder* builder) const {
    auto ftype = _fdecl->type()->type()->as<type::Function>();
    auto stype = _fdecl->parent(1)->as<type::Struct>();
    auto params = type::OperandList::fromParameters(builder->context(), ftype->parameters());
    auto result = ftype->result();

    return Signature{
        .kind = Kind::MemberCall,
        .self = {parameter::Kind::InOut, nullptr, "", stype},
        .op1 = {parameter::Kind::In, builder->typeMember(ID(_fdecl->id()))},
        .op2 = {parameter::Kind::In, params},
        .result = {result->constness(), result->type()},
        .skip_doc = true,
    };
}

Result<expression::ResolvedOperator*> hilti::struct_::MemberCall::instantiate(Builder* builder, Expressions operands,
                                                                              Meta meta) const {
    auto callee = operands[0];
    auto member = operands[1];
    auto args = operands[2];
    auto result = _fdecl->type()->type()->as<type::Function>()->result();

    return {operator_::struct_::MemberCall::create(builder->context(), this, result, {callee, member, args},
                                                   std::move(meta))};
}

namespace {
namespace struct_ {

QualifiedType* _itemType(Builder* builder, const Expressions& operands) {
    auto stype = operands[0]->type()->type()->tryAs<type::Struct>();
    if ( ! stype )
        return builder->qualifiedType(builder->typeUnknown(), Constness::Const);

    if ( auto field = stype->field(operands[1]->as<expression::Member>()->id()) )
        return field->type();
    else
        return builder->qualifiedType(builder->typeUnknown(), Constness::Const);
}

void _checkName(expression::ResolvedOperator* op, bool check_optional = false) {
    const auto& id = op->op1()->as<expression::Member>()->id();
    auto t = op->op0()->type()->type();

    if ( auto x = t->tryAs<type::ValueReference>() )
        t = x->dereferencedType()->type();

    auto stype = t->tryAs<type::Struct>();
    if ( ! stype ) {
        op->addError("type is not a struct");
        return;
    }

    auto f = stype->field(id);
    if ( ! f ) {
        op->addError(util::fmt("type does not have field '%s'", id));
        return;
    }

    if ( check_optional && ! f->isOptional() )
        op->addError(util::fmt("field '%s' is not &optional", id));

    if ( f->isNoEmit() )
        op->addError(util::fmt("field '%s' cannot be accessed", id));
}

class Unset : public Operator {
public:
    Signature signature(Builder* builder) const final {
        return Signature{
            .kind = Kind::Unset,
            .op0 = {parameter::Kind::InOut, builder->typeStruct(type::Wildcard()), "<struct>"},
            .op1 = {parameter::Kind::In, builder->typeMember(type::Wildcard()), "<field>"},
            .result = {Constness::Const, builder->typeVoid()},
            .ns = "struct",
            .doc = R"(
Clears an optional field.
)",
        };
    }

    void validate(expression::ResolvedOperator* n) const final { _checkName(n, true); }

    HILTI_OPERATOR(hilti, struct_::Unset)
};
HILTI_OPERATOR_IMPLEMENTATION(Unset);

class MemberNonConst : public Operator {
public:
    Signature signature(Builder* builder) const final {
        return Signature{
            .kind = Kind::Member,
            .op0 = {parameter::Kind::InOut, builder->typeStruct(type::Wildcard()), "<struct>"},
            .op1 = {parameter::Kind::In, builder->typeMember(type::Wildcard()), "<field>"},
            .result_doc = "<field type>",
            .ns = "struct",
            .doc = R"(
Retrieves the value of a struct's field. If the field does not have a value assigned,
it returns its ``&default`` expression if that has been defined; otherwise it
triggers an exception.
)",
        };
    }

    QualifiedType* result(Builder* builder, const Expressions& operands, const Meta& meta) const final {
        return _itemType(builder, operands)->recreateAsLhs(builder->context());
    }

    void validate(expression::ResolvedOperator* n) const final { _checkName(n); }

    HILTI_OPERATOR(hilti, struct_::MemberNonConst)
};
HILTI_OPERATOR_IMPLEMENTATION(MemberNonConst);

class MemberConst : public Operator {
public:
    Signature signature(Builder* builder) const final {
        return Signature{
            .kind = Kind::Member,
            .priority = Priority::Low, // prefer the non-const version
            .op0 = {parameter::Kind::In, builder->typeStruct(type::Wildcard()), "<struct>"},
            .op1 = {parameter::Kind::In, builder->typeMember(type::Wildcard()), "<field>"},
            .result_doc = "<field type>",
            .ns = "struct",
            .doc = R"(
Retrieves the value of a struct's field. If the field does not have a value assigned,
it returns its ``&default`` expression if that has been defined; otherwise it
triggers an exception.
)",
        };
    }

    QualifiedType* result(Builder* builder, const Expressions& operands, const Meta& meta) const final {
        return _itemType(builder, operands)->recreateAsConst(builder->context());
    }

    void validate(expression::ResolvedOperator* n) const final { _checkName(n); }

    HILTI_OPERATOR(hilti, struct_::MemberConst)
};
HILTI_OPERATOR_IMPLEMENTATION(MemberConst);

class TryMember : public Operator {
public:
    Signature signature(Builder* builder) const final {
        return Signature{
            .kind = Kind::TryMember,
            .op0 = {parameter::Kind::InOut, builder->typeStruct(type::Wildcard()), "<struct>"},
            .op1 = {parameter::Kind::In, builder->typeMember(type::Wildcard()), "<field>"},
            .result_doc = "<field type>",
            .ns = "struct",
            .doc = R"(
Retrieves the value of a struct's field. If the field does not have a value
assigned, it returns its ``&default`` expression if that has been defined;
otherwise it signals a special non-error exception to the host application
(which will normally still lead to aborting execution, similar to the standard
dereference operator, unless the host application specifically handles this
exception differently).
)",
        };
    }

    QualifiedType* result(Builder* builder, const Expressions& operands, const Meta& meta) const final {
        return _itemType(builder, operands);
    }

    void validate(expression::ResolvedOperator* n) const final { _checkName(n); }

    HILTI_OPERATOR(hilti, struct_::TryMember)
};
HILTI_OPERATOR_IMPLEMENTATION(TryMember);

class HasMember : public Operator {
public:
    Signature signature(Builder* builder) const final {
        return Signature{
            .kind = Kind::HasMember,
            .op0 = {parameter::Kind::In, builder->typeStruct(type::Wildcard()), "<struct>"},
            .op1 = {parameter::Kind::In, builder->typeMember(type::Wildcard()), "<field>"},
            .result = {Constness::Const, builder->typeBool()},
            .ns = "struct",
            .doc = "Returns true if the struct's field has a value assigned (not counting any ``&default``).",
        };
    }

    void validate(expression::ResolvedOperator* n) const final { _checkName(n); }

    HILTI_OPERATOR(hilti, struct_::HasMember)
};
HILTI_OPERATOR_IMPLEMENTATION(HasMember);

} // namespace struct_
} // namespace
