/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef StmtToBlockMap_h__
#define StmtToBlockMap_h__

#include "Utils.h"

// This method is copied from clang-tidy's ExprSequence.cpp.
//
// Returns the Stmt nodes that are parents of 'S', skipping any potential
// intermediate non-Stmt nodes.
//
// In almost all cases, this function returns a single parent or no parents at
// all.
inline SmallVector<const Stmt *, 1> getParentStmts(const Stmt *S,
                                                   ASTContext *Context) {
  SmallVector<const Stmt *, 1> Result;

  auto Parents = Context->getParents(*S);

  SmallVector<clang::DynTypedNode, 1> NodesToProcess(Parents.begin(),
                                                     Parents.end());

  while (!NodesToProcess.empty()) {
    clang::DynTypedNode Node = NodesToProcess.back();
    NodesToProcess.pop_back();

    if (const auto *S = Node.get<Stmt>()) {
      Result.push_back(S);
    } else {
      Parents = Context->getParents(Node);
      NodesToProcess.append(Parents.begin(), Parents.end());
    }
  }

  return Result;
}

// This class is a modified version of the class from clang-tidy's
// ExprSequence.cpp
//
// Maps `Stmt`s to the `CFGBlock` that contains them. Some `Stmt`s may be
// contained in more than one `CFGBlock`; in this case, they are mapped to the
// innermost block (i.e. the one that is furthest from the root of the tree).
// An optional outparameter provides the index into the block where the `Stmt`
// was found.
class StmtToBlockMap {
public:
  // Initializes the map for the given `CFG`.
  StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext)
      : Context(TheContext) {
    for (const auto *B : *TheCFG) {
      for (size_t I = 0; I < B->size(); ++I) {
        if (Optional<CFGStmt> S = (*B)[I].getAs<CFGStmt>()) {
          Map[S->getStmt()] = std::make_pair(B, I);
        }
      }
    }
  }

  // Returns the block that S is contained in. Some `Stmt`s may be contained
  // in more than one `CFGBlock`; in this case, this function returns the
  // innermost block (i.e. the one that is furthest from the root of the tree).
  //
  // The optional outparameter `Index` is set to the index into the block where
  // the `Stmt` was found.
  const CFGBlock *blockContainingStmt(const Stmt *S,
                                      size_t *Index = nullptr) const {
    while (!Map.count(S)) {
      SmallVector<const Stmt *, 1> Parents = getParentStmts(S, Context);
      if (Parents.empty())
        return nullptr;
      S = Parents[0];
    }

    const auto &E = Map.lookup(S);
    if (Index)
      *Index = E.second;
    return E.first;
  }

private:
  ASTContext *Context;

  llvm::DenseMap<const Stmt *, std::pair<const CFGBlock *, size_t>> Map;
};

#endif // StmtToBlockMap_h__