/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/sksl/ir/SkSLPrefixExpression.h"

#include "src/sksl/SkSLConstantFolder.h"
#include "src/sksl/ir/SkSLBoolLiteral.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLConstructorArray.h"
#include "src/sksl/ir/SkSLConstructorCompound.h"
#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
#include "src/sksl/ir/SkSLConstructorSplat.h"
#include "src/sksl/ir/SkSLFloatLiteral.h"
#include "src/sksl/ir/SkSLIntLiteral.h"

namespace SkSL {

static ExpressionArray negate_operands(const Context& context, ExpressionArray operands);

static std::unique_ptr<Expression> negate_operand(const Context& context,
                                                  std::unique_ptr<Expression> operand) {
    const Expression* value = ConstantFolder::GetConstantValueForVariable(*operand);
    switch (value->kind()) {
        case Expression::Kind::kFloatLiteral:
            // Convert -floatLiteral(1) to floatLiteral(-1).
            return FloatLiteral::Make(operand->fOffset,
                                      -value->as<FloatLiteral>().value(),
                                      &value->type());

        case Expression::Kind::kIntLiteral:
            // Convert -intLiteral(1) to intLiteral(-1).
            return IntLiteral::Make(operand->fOffset,
                                    -value->as<IntLiteral>().value(),
                                    &value->type());

        case Expression::Kind::kPrefix:
            if (context.fConfig->fSettings.fOptimize) {
                // Convert `-(-expression)` into `expression`.
                PrefixExpression& prefix = operand->as<PrefixExpression>();
                if (prefix.getOperator().kind() == Token::Kind::TK_MINUS) {
                    return std::move(prefix.operand());
                }
            }
            break;

        case Expression::Kind::kConstructorArray:
            // Convert `-array[N](literal, ...)` into `array[N](-literal, ...)`.
            if (context.fConfig->fSettings.fOptimize && value->isCompileTimeConstant()) {
                ConstructorArray& ctor = operand->as<ConstructorArray>();
                return ConstructorArray::Make(
                        context, ctor.fOffset, ctor.type(),
                        negate_operands(context, std::move(ctor.arguments())));
            }
            break;

        case Expression::Kind::kConstructorDiagonalMatrix:
            // Convert `-matrix(literal)` into `matrix(-literal)`.
            if (context.fConfig->fSettings.fOptimize && value->isCompileTimeConstant()) {
                ConstructorDiagonalMatrix& ctor = operand->as<ConstructorDiagonalMatrix>();
                return ConstructorDiagonalMatrix::Make(
                        context, ctor.fOffset, ctor.type(),
                        negate_operand(context, std::move(ctor.argument())));
            }
            break;

        case Expression::Kind::kConstructorSplat:
            // Convert `-vector(literal)` into `vector(-literal)`.
            if (context.fConfig->fSettings.fOptimize && value->isCompileTimeConstant()) {
                ConstructorSplat& ctor = operand->as<ConstructorSplat>();
                return ConstructorSplat::Make(context, ctor.fOffset, ctor.type(),
                                              negate_operand(context, std::move(ctor.argument())));
            }
            break;

        case Expression::Kind::kConstructorCompound:
            // Convert `-vecN(literal, ...)` into `vecN(-literal, ...)`.
            if (context.fConfig->fSettings.fOptimize && value->isCompileTimeConstant()) {
                ConstructorCompound& ctor = operand->as<ConstructorCompound>();
                return ConstructorCompound::Make(
                        context, ctor.fOffset, ctor.type(),
                        negate_operands(context, std::move(ctor.arguments())));
            }
            break;

        default:
            break;
    }

    // No simplified form; convert expression to Prefix(TK_MINUS, expression).
    return std::make_unique<PrefixExpression>(Token::Kind::TK_MINUS, std::move(operand));
}

static ExpressionArray negate_operands(const Context& context, ExpressionArray operands) {
    for (std::unique_ptr<Expression>& arg : operands) {
        arg = negate_operand(context, std::move(arg));
    }
    return operands;
}

static std::unique_ptr<Expression> logical_not_operand(const Context& context,
                                                       std::unique_ptr<Expression> operand) {
    const Expression* value = ConstantFolder::GetConstantValueForVariable(*operand);
    switch (value->kind()) {
        case Expression::Kind::kBoolLiteral: {
            // Convert !boolLiteral(true) to boolLiteral(false).
            const BoolLiteral& b = value->as<BoolLiteral>();
            return BoolLiteral::Make(operand->fOffset, !b.value(), &operand->type());
        }
        case Expression::Kind::kPrefix:
            if (context.fConfig->fSettings.fOptimize) {
                // Convert `!(!expression)` into `expression`.
                PrefixExpression& prefix = operand->as<PrefixExpression>();
                if (prefix.getOperator().kind() == Token::Kind::TK_LOGICALNOT) {
                    return std::move(prefix.operand());
                }
            }
            break;

        default:
            break;
    }

    // No simplified form; convert expression to Prefix(TK_LOGICALNOT, expression).
    return std::make_unique<PrefixExpression>(Token::Kind::TK_LOGICALNOT, std::move(operand));
}

std::unique_ptr<Expression> PrefixExpression::Convert(const Context& context,
                                                      Operator op,
                                                      std::unique_ptr<Expression> base) {
    const Type& baseType = base->type();
    switch (op.kind()) {
        case Token::Kind::TK_PLUS:
            if (!baseType.componentType().isNumber()) {
                context.fErrors.error(base->fOffset,
                                      "'+' cannot operate on '" + baseType.displayName() + "'");
                return nullptr;
            }
            break;

        case Token::Kind::TK_MINUS:
            if (!baseType.componentType().isNumber()) {
                context.fErrors.error(base->fOffset,
                                      "'-' cannot operate on '" + baseType.displayName() + "'");
                return nullptr;
            }
            break;

        case Token::Kind::TK_PLUSPLUS:
        case Token::Kind::TK_MINUSMINUS:
            if (!baseType.isNumber()) {
                context.fErrors.error(base->fOffset,
                                      String("'") + op.operatorName() + "' cannot operate on '" +
                                      baseType.displayName() + "'");
                return nullptr;
            }
            if (!Analysis::MakeAssignmentExpr(base.get(), VariableReference::RefKind::kReadWrite,
                                              &context.fErrors)) {
                return nullptr;
            }
            break;

        case Token::Kind::TK_LOGICALNOT:
            if (!baseType.isBoolean()) {
                context.fErrors.error(base->fOffset,
                                      String("'") + op.operatorName() + "' cannot operate on '" +
                                      baseType.displayName() + "'");
                return nullptr;
            }
            break;

        case Token::Kind::TK_BITWISENOT:
            if (context.fConfig->strictES2Mode()) {
                // GLSL ES 1.00, Section 5.1
                context.fErrors.error(
                        base->fOffset,
                        String("operator '") + op.operatorName() + "' is not allowed");
                return nullptr;
            }
            if (!baseType.isInteger()) {
                context.fErrors.error(base->fOffset,
                                      String("'") + op.operatorName() + "' cannot operate on '" +
                                      baseType.displayName() + "'");
                return nullptr;
            }
            if (baseType.isLiteral()) {
                // The expression `~123` is no longer a literal; coerce to the actual type.
                base = baseType.scalarTypeForLiteral().coerceExpression(std::move(base), context);
            }
            break;

        default:
            SK_ABORT("unsupported prefix operator");
    }

    return PrefixExpression::Make(context, op, std::move(base));
}

std::unique_ptr<Expression> PrefixExpression::Make(const Context& context, Operator op,
                                                   std::unique_ptr<Expression> base) {
    switch (op.kind()) {
        case Token::Kind::TK_PLUS:
            SkASSERT(base->type().componentType().isNumber());
            return base;

        case Token::Kind::TK_MINUS:
            SkASSERT(base->type().componentType().isNumber());
            return negate_operand(context, std::move(base));

        case Token::Kind::TK_LOGICALNOT:
            SkASSERT(base->type().isBoolean());
            return logical_not_operand(context, std::move(base));

        case Token::Kind::TK_PLUSPLUS:
        case Token::Kind::TK_MINUSMINUS:
            SkASSERT(base->type().isNumber());
            SkASSERT(Analysis::IsAssignable(*base));
            break;

        case Token::Kind::TK_BITWISENOT:
            SkASSERT(!context.fConfig->strictES2Mode());
            SkASSERT(base->type().isInteger());
            SkASSERT(!base->type().isLiteral());
            break;

        default:
            SkDEBUGFAILF("unsupported prefix operator: %s", op.operatorName());
    }

    return std::make_unique<PrefixExpression>(op, std::move(base));
}

}  // namespace SkSL
