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

#pragma once

#include <ArticleEnumClass-v2/EnumClass.h>

#include <string>
#include <utility>

#include <hilti/ast/ctor.h>
#include <hilti/ast/expression.h>
#include <hilti/ast/operator.h>
#include <hilti/ast/type.h>
#include <hilti/base/util.h>

namespace hilti {

/** Tunes the specifics of a type coercion operation. */
enum class CoercionStyle {
    /**
     * Specifies that coercion is taking place in the context of a assignment
     * of the source expression to a variable of the destination type.
     */
    Assignment = (1U << 0U),

    /**
     * Specifies that coercion is taking place in the context of passing the
     * source expression to a function parameter of the target type.
     */
    FunctionCall = (1U << 2U),

    /**
     * Let coercion succeed if the types fully match. (You probably always
     * want this).
     */
    TryExactMatch = (1U << 3U),

    /**
     * Let coercion succeed if the source type can be converted into the
     * destination type by a legal constness change.
     */
    TryConstPromotion = (1U << 4U),

    /**
     * Let coercion succeed if the source type can be converted into the
     * destination type by any of the plugins' provided type coercions. (This
     * is the main path to performing actual coercions that change types.)
     */
    TryCoercion = (1U << 5U),

    /**
     * Same as `TryCoercion` but limits the search to coercions where the base
     * type doesn't change. In other words, limits coercions to changing the
     * type's parameters. This is usually tried before general coercions across
     * types.
     */
    TryCoercionWithinSameType = (1U << 6U),

    /** Never allow any substantial type changes. */
    DisallowTypeChanges = (1U << 7U),

    /**
     * Signal that the coercion takes place in a semantic language context
     * expecting the given destination type. This can be used to support
     * coercions at locations where normally it wouldn't take place, such as
     * conversion to bool in conditional statements.
     */
    ContextualConversion = (1U << 8U),

    /** Deref any references to try getting coercion to succeed. */
    TryDeref = (1U << 9U),

    /**
     * Shortcut style activating all possible coercions in the context of an
     * assignment.
     */
    TryAllForAssignment = Assignment | TryExactMatch | TryConstPromotion | TryCoercion | TryDeref,

    /**
     * Shortcut style allowing for direct matches only in the context of
     * operator resolution.
     */
    TryDirectForMatching = TryExactMatch | TryConstPromotion,

    /**
     * Shortcut style activating all possible coercions in the context of
     * operator resolution.
     */
    TryAllForMatching = TryDirectForMatching | TryCoercion,

    /**
     * Shortcut style activating possible coercions in the context of
     * function parameter passing, however without allowing any type changes.
     */
    TryDirectMatchForFunctionCall = FunctionCall | TryExactMatch | TryConstPromotion,

    /**
     * Shortcut style activating all possible coercions in the context of
     * function parameter passing.
     */
    TryAllForFunctionCall = TryDirectMatchForFunctionCall | TryCoercion | TryDeref,
};

/**
 * Returns a readable representation of a coercion style setting for debugging
 * purposes.
 */
extern std::string to_string(bitmask<CoercionStyle> style);

} // namespace hilti

enableEnumClassBitmask(hilti::CoercionStyle); // Must be in global scope

namespace hilti {

/** Return type for the functions doing expression coercion. */
struct CoercedExpression {
    /** Returns true if coercion was successful. */
    operator bool() const { return coerced.hasValue(); }

    /**
     * Coerced expression if successful, an error if not. This will be set
     * even if the coerced expression ends up being identical to the source
     * expression.
     */
    Result<Expression*> coerced;

    /**
     * Coerced expression if successful and the coerced expression is not
     * identical to original one; unset otherwise.
     */
    Expression* nexpr = {};

    /**
     * If coerced is set, true if type of new expression's type is to be
     * considered changed compared to source expression's type for overload
     * resolution
     */
    bool consider_type_changed = false;

    /**
     *
     * Represents a successful coercion that led the source expression not
     * changing, which will be assigned to the `coerced` field.
     *
     * @note The expression not changing doesn't necessarily mean that
     * the expression's type is *exactly* matching the coercion's destination
     * type. However, even if not, the caller should proceed by using the
     * `coerced` field value for anywhere where the coerced expression is
     * expected.
     *
     * @param src the original source expression
     */
    CoercedExpression(Expression* src) : coerced(src) {}

    /**
     * Represents a successful coercion that led to a new expression
     * different from the source expression.
     *
     * @param src the original source expression's type
     * @param coerced the resulting expression that *src* was coerced to
     */
    CoercedExpression(QualifiedType* src, Expression* coerced)
        : coerced(coerced),
          nexpr(coerced),
          consider_type_changed(src->type()->typeClass() != coerced->type()->type()->typeClass()) {}

    /** Represents an unsuccessful coercion. */
    CoercedExpression() = default;

    /**
     * Represents an unsuccessful coercion, carrying an error message along
     * explaining why it failed.
     */
    CoercedExpression(const result::Error& error) : coerced(error) {}
};

/**
 * Coerces an expression to a given target type. This returns a struct with
 * fields that provide result of the coercion, along with additional meta
 * information. Depending on the coercion style, a coerced expression may be
 * the exact same expression as passed in if types match sufficiently.
 *
 * @note This function does not actually *perform* the coercion, it just
 * returns an AST of the specified target type that will let the compiler
 * later carry out the coercion (usually that'll be a `expression::Coerced``
 * node).
 *
 * @param builder builder to use
 * @param e expression to coerce
 * @param dst target type
 * @param style coercion style to use, given as a bitmask of any style
 * specifiers that apply
 * @return the *result* will evaluate to true if coercion was successful; if
 * so, the contained fields will provide more information
 */
CoercedExpression coerceExpression(Builder* builder,
                                   Expression* e,
                                   QualifiedType* dst,
                                   bitmask<CoercionStyle> style = CoercionStyle::TryAllForAssignment,
                                   bool lhs = false);

/**
 * Coerces an expression to a given target type. This returns a struct with
 * fields that provide result of the coercion, along with additional meta
 * information. Depending on the coercion style, a coerced expression may be
 * the exact same expression as passed in if types match sufficiently.
 *
 * @note This function does not actually *perform* the coercion, it just
 * returns an AST of the specified target type that will let the compiler
 * later carry out the coercion (usually that'll be a `expression::Coerced``
 * node).
 *
 * @param builder builder to use
 * @param e expression to coerce
 * @param src explicitly specified source type; this can be different from
 * the type of *e* and will be used instead of that
 * @param dst target type
 * @param style coercion style to use, given as a bitmask of any style
 * specifiers that apply
 * @return the *result* will evaluate to true if coercion was successful; if
 * so, the contained fields will provide more information
 */
CoercedExpression coerceExpression(Builder* builder,
                                   Expression* e,
                                   QualifiedType* src_,
                                   QualifiedType* dst_,
                                   bitmask<CoercionStyle> style = CoercionStyle::TryAllForAssignment,
                                   bool lhs = false);

/**
 * Matches a set of expressions against a set of operands, coercing them as
 * needed. This takes into account specifics of the operands, such as them
 * being optional or having defaults.
 *
 * @param builder builder to use
 * @param kind kind of operator the operands are for
 * @param exprs source expressions to match against the operands
 * @param operands operands to match against
 * @param style coercion style to use for each expression's coercion to its
 * operand, given as a bitmask of any style
 * specifiers that apply
 * @return If successful, a pair with a boolean as its 1st argument that
 * indicates whether any of the expressions was changed; and as its 2nd
 * element, the coerced expressions now matching the operands. The returned
 * vector will have defaults filled in for missing expressions where
 * available (missing expressions for optional operands without defaults will
 * remain left out). If unsuccessful, an error.
 */
Result<std::pair<bool, Expressions>> coerceOperands(Builder* builder,
                                                    operator_::Kind kind,
                                                    const Expressions& exprs,
                                                    const operator_::Operands& operands,
                                                    bitmask<CoercionStyle> style);

/**
 * Coerces a constructor to a given target type. This returns the coerced
 * constructor, now of the new type. If the constructor is already of the
 * right type, it will just be returned back.
 *
 * @param builder builder to use
 * @param c ctor to coerce
 * @param dst target type
 * @param style coercion style to use
 * @return if the coercion was successful, the returned new value (which may be the same as the old)
 */
Result<Ctor*> coerceCtor(Builder* builder,
                         Ctor* c,
                         QualifiedType* dst,
                         bitmask<CoercionStyle> style = CoercionStyle::TryAllForAssignment);

/**
 * Coerces a source type to a given target type. This returns the coerced
 * type. If the type is already of the right type, it will just be returned
 *  back.
 *
 * @param builder builder to use
 * @param c ctor to coerce
 * @param dst target type
 * @param style coercion style to use
 * @return if the coercion was successful, the returned new value (which may be the same as the old)
 */
Result<QualifiedType*> coerceType(Builder* builder,
                                  QualifiedType* src_,
                                  QualifiedType* dst_,
                                  bitmask<CoercionStyle> style = CoercionStyle::TryAllForAssignment);

namespace coercer::detail {

/** Implements the corresponding functionality for the default HILTI compiler plugin. */
Ctor* coerceCtor(Builder* builder, Ctor* c, QualifiedType* dst, bitmask<CoercionStyle> style);

/** Implements the corresponding functionality for the default HILTI compiler plugin. */
QualifiedType* coerceType(Builder* builder, QualifiedType* t, QualifiedType* dst, bitmask<CoercionStyle> style);

} // namespace coercer::detail

} // namespace hilti
