//===--- SourceCodeBuilder.cpp ----------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "clang/Tooling/Transformer/SourceCodeBuilders.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/Transformer/SourceCode.h"
#include "llvm/ADT/Twine.h"
#include <string>

using namespace clang;
using namespace tooling;

const Expr *tooling::reallyIgnoreImplicit(const Expr &E) {
  const Expr *Expr = E.IgnoreImplicit();
  if (const auto *CE = dyn_cast<CXXConstructExpr>(Expr)) {
    if (CE->getNumArgs() > 0 &&
        CE->getArg(0)->getSourceRange() == Expr->getSourceRange())
      return CE->getArg(0)->IgnoreImplicit();
  }
  return Expr;
}

bool tooling::mayEverNeedParens(const Expr &E) {
  const Expr *Expr = reallyIgnoreImplicit(E);
  // We always want parens around unary, binary, and ternary operators, because
  // they are lower precedence.
  if (isa<UnaryOperator>(Expr) || isa<BinaryOperator>(Expr) ||
      isa<AbstractConditionalOperator>(Expr))
    return true;

  // We need parens around calls to all overloaded operators except: function
  // calls, subscripts, and expressions that are already part of an (implicit)
  // call to operator->. These latter are all in the same precedence level as
  // dot/arrow and that level is left associative, so they don't need parens
  // when appearing on the left.
  if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Expr))
    return Op->getOperator() != OO_Call && Op->getOperator() != OO_Subscript &&
           Op->getOperator() != OO_Arrow;

  return false;
}

bool tooling::needParensAfterUnaryOperator(const Expr &E) {
  const Expr *Expr = reallyIgnoreImplicit(E);
  if (isa<BinaryOperator>(Expr) || isa<AbstractConditionalOperator>(Expr))
    return true;

  if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Expr))
    return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus &&
           Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call &&
           Op->getOperator() != OO_Subscript;

  return false;
}

bool tooling::isKnownPointerLikeType(QualType Ty, ASTContext &Context) {
  using namespace ast_matchers;
  const auto PointerLikeTy = type(hasUnqualifiedDesugaredType(
      recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
          "::std::unique_ptr", "::std::shared_ptr", "::std::weak_ptr",
          "::std::optional", "::absl::optional", "::llvm::Optional",
          "absl::StatusOr", "::llvm::Expected"))))));
  return match(PointerLikeTy, Ty, Context).size() > 0;
}

llvm::Optional<std::string> tooling::buildParens(const Expr &E,
                                                 const ASTContext &Context) {
  StringRef Text = getText(E, Context);
  if (Text.empty())
    return llvm::None;
  if (mayEverNeedParens(E))
    return ("(" + Text + ")").str();
  return Text.str();
}

llvm::Optional<std::string>
tooling::buildDereference(const Expr &E, const ASTContext &Context) {
  if (const auto *Op = dyn_cast<UnaryOperator>(&E))
    if (Op->getOpcode() == UO_AddrOf) {
      // Strip leading '&'.
      StringRef Text =
          getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context);
      if (Text.empty())
        return llvm::None;
      return Text.str();
    }

  StringRef Text = getText(E, Context);
  if (Text.empty())
    return llvm::None;
  // Add leading '*'.
  if (needParensAfterUnaryOperator(E))
    return ("*(" + Text + ")").str();
  return ("*" + Text).str();
}

llvm::Optional<std::string> tooling::buildAddressOf(const Expr &E,
                                                    const ASTContext &Context) {
  if (E.isImplicitCXXThis())
    return std::string("this");
  if (const auto *Op = dyn_cast<UnaryOperator>(&E))
    if (Op->getOpcode() == UO_Deref) {
      // Strip leading '*'.
      StringRef Text =
          getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context);
      if (Text.empty())
        return llvm::None;
      return Text.str();
    }
  // Add leading '&'.
  StringRef Text = getText(E, Context);
  if (Text.empty())
    return llvm::None;
  if (needParensAfterUnaryOperator(E)) {
    return ("&(" + Text + ")").str();
  }
  return ("&" + Text).str();
}

// Append the appropriate access operation (syntactically) to `E`, assuming `E`
// is a non-pointer value.
static llvm::Optional<std::string>
buildAccessForValue(const Expr &E, const ASTContext &Context) {
  if (const auto *Op = llvm::dyn_cast<UnaryOperator>(&E))
    if (Op->getOpcode() == UO_Deref) {
      // Strip leading '*', add following '->'.
      const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts();
      StringRef DerefText = getText(*SubExpr, Context);
      if (DerefText.empty())
        return llvm::None;
      if (needParensBeforeDotOrArrow(*SubExpr))
        return ("(" + DerefText + ")->").str();
      return (DerefText + "->").str();
    }

  // Add following '.'.
  StringRef Text = getText(E, Context);
  if (Text.empty())
    return llvm::None;
  if (needParensBeforeDotOrArrow(E)) {
    return ("(" + Text + ").").str();
  }
  return (Text + ".").str();
}

// Append the appropriate access operation (syntactically) to `E`, assuming `E`
// is a pointer value.
static llvm::Optional<std::string>
buildAccessForPointer(const Expr &E, const ASTContext &Context) {
  if (const auto *Op = llvm::dyn_cast<UnaryOperator>(&E))
    if (Op->getOpcode() == UO_AddrOf) {
      // Strip leading '&', add following '.'.
      const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts();
      StringRef DerefText = getText(*SubExpr, Context);
      if (DerefText.empty())
        return llvm::None;
      if (needParensBeforeDotOrArrow(*SubExpr))
        return ("(" + DerefText + ").").str();
      return (DerefText + ".").str();
    }

  // Add following '->'.
  StringRef Text = getText(E, Context);
  if (Text.empty())
    return llvm::None;
  if (needParensBeforeDotOrArrow(E))
    return ("(" + Text + ")->").str();
  return (Text + "->").str();
}

llvm::Optional<std::string> tooling::buildDot(const Expr &E,
                                              const ASTContext &Context) {
  return buildAccessForValue(E, Context);
}

llvm::Optional<std::string> tooling::buildArrow(const Expr &E,
                                                const ASTContext &Context) {
  return buildAccessForPointer(E, Context);
}

// If `E` is an overloaded-operator call of kind `K` on an object `O`, returns
// `O`. Otherwise, returns `nullptr`.
static const Expr *maybeGetOperatorObjectArg(const Expr &E,
                                             OverloadedOperatorKind K) {
  if (const auto *OpCall = dyn_cast<clang::CXXOperatorCallExpr>(&E)) {
    if (OpCall->getOperator() == K && OpCall->getNumArgs() == 1)
      return OpCall->getArg(0);
  }
  return nullptr;
}

static bool treatLikePointer(QualType Ty, PLTClass C, ASTContext &Context) {
  switch (C) {
  case PLTClass::Value:
    return false;
  case PLTClass::Pointer:
    return isKnownPointerLikeType(Ty, Context);
  }
  llvm_unreachable("Unknown PLTClass enum");
}

// FIXME: move over the other `maybe` functionality from Stencil. Should all be
// in one place.
llvm::Optional<std::string> tooling::buildAccess(const Expr &RawExpression,
                                                 ASTContext &Context,
                                                 PLTClass Classification) {
  if (RawExpression.isImplicitCXXThis())
    // Return the empty string, because `None` signifies some sort of failure.
    return std::string();

  const Expr *E = RawExpression.IgnoreImplicitAsWritten();

  if (E->getType()->isAnyPointerType() ||
      treatLikePointer(E->getType(), Classification, Context)) {
    // Strip off operator-> calls. They can only occur inside an actual arrow
    // member access, so we treat them as equivalent to an actual object
    // expression.
    if (const auto *Obj = maybeGetOperatorObjectArg(*E, clang::OO_Arrow))
      E = Obj;
    return buildAccessForPointer(*E, Context);
  }

  if (const auto *Obj = maybeGetOperatorObjectArg(*E, clang::OO_Star)) {
    if (treatLikePointer(Obj->getType(), Classification, Context))
      return buildAccessForPointer(*Obj, Context);
  };

  return buildAccessForValue(*E, Context);
}
