/*
 * Copyright (c) 2015 PLUMgrid, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>

#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/Frontend/FrontendAction.h>
#include <clang/Rewrite/Core/Rewriter.h>

#include "table_storage.h"

namespace clang {
class ASTConsumer;
class ASTContext;
class CompilerInstance;
}

namespace llvm {
class raw_ostream;
class StringRef;
}

namespace ebpf {

class BFrontendAction;
class FuncSource;

// Traces maps with external pointers as values.
class MapVisitor : public clang::RecursiveASTVisitor<MapVisitor> {
 public:
  explicit MapVisitor(std::set<clang::Decl *> &m);
  bool VisitCallExpr(clang::CallExpr *Call);
  void set_ptreg(std::tuple<clang::Decl *, int> &pt) { ptregs_.insert(pt); }
 private:
  std::set<clang::Decl *> &m_;
  std::set<std::tuple<clang::Decl *, int>> ptregs_;
};

// Type visitor and rewriter for B programs.
// It will look for B-specific features and rewrite them into a valid
// C program. As part of the processing, open the necessary BPF tables
// and store the open handles in a map of table-to-fd's.
class BTypeVisitor : public clang::RecursiveASTVisitor<BTypeVisitor> {
 public:
  explicit BTypeVisitor(clang::ASTContext &C, BFrontendAction &fe);
  bool TraverseCallExpr(clang::CallExpr *Call);
  bool VisitFunctionDecl(clang::FunctionDecl *D);
  bool VisitCallExpr(clang::CallExpr *Call);
  bool VisitVarDecl(clang::VarDecl *Decl);
  bool VisitBinaryOperator(clang::BinaryOperator *E);
  bool VisitImplicitCastExpr(clang::ImplicitCastExpr *E);

 private:
  clang::SourceRange expansionRange(clang::SourceRange range);
  bool checkFormatSpecifiers(const std::string& fmt, clang::SourceLocation loc);
  void genParamDirectAssign(clang::FunctionDecl *D, std::string& preamble,
                            const char **calling_conv_regs);
  void genParamIndirectAssign(clang::FunctionDecl *D, std::string& preamble,
                              const char **calling_conv_regs);
  void rewriteFuncParam(clang::FunctionDecl *D);
  int64_t getFieldValue(clang::VarDecl *Decl, clang::FieldDecl *FDecl,
                        int64_t OrigFValue);
  template <unsigned N>
  clang::DiagnosticBuilder error(clang::SourceLocation loc, const char (&fmt)[N]);
  template <unsigned N>
  clang::DiagnosticBuilder warning(clang::SourceLocation loc, const char (&fmt)[N]);

  clang::ASTContext &C;
  clang::DiagnosticsEngine &diag_;
  BFrontendAction &fe_;
  clang::Rewriter &rewriter_;  /// modifications to the source go into this class
  llvm::raw_ostream &out_;  /// for debugging
  std::vector<clang::ParmVarDecl *> fn_args_;
  std::set<clang::Expr *> visited_;
  std::string current_fn_;
};

// Do a depth-first search to rewrite all pointers that need to be probed
class ProbeVisitor : public clang::RecursiveASTVisitor<ProbeVisitor> {
 public:
  explicit ProbeVisitor(clang::ASTContext &C, clang::Rewriter &rewriter,
                        std::set<clang::Decl *> &m, bool track_helpers);
  bool VisitVarDecl(clang::VarDecl *Decl);
  bool TraverseStmt(clang::Stmt *S);
  bool VisitCallExpr(clang::CallExpr *Call);
  bool VisitReturnStmt(clang::ReturnStmt *R);
  bool VisitBinaryOperator(clang::BinaryOperator *E);
  bool VisitUnaryOperator(clang::UnaryOperator *E);
  bool VisitMemberExpr(clang::MemberExpr *E);
  bool VisitArraySubscriptExpr(clang::ArraySubscriptExpr *E);
  void set_ptreg(std::tuple<clang::Decl *, int> &pt) { ptregs_.insert(pt); }
  void set_ctx(clang::Decl *D) { ctx_ = D; }
  std::set<std::tuple<clang::Decl *, int>> get_ptregs() { return ptregs_; }
 private:
  bool assignsExtPtr(clang::Expr *E, int *nbAddrOf);
  bool isMemberDereference(clang::Expr *E);
  bool IsContextMemberExpr(clang::Expr *E);
  clang::SourceRange expansionRange(clang::SourceRange range);
  clang::SourceLocation expansionLoc(clang::SourceLocation loc);
  template <unsigned N>
  clang::DiagnosticBuilder error(clang::SourceLocation loc, const char (&fmt)[N]);

  clang::ASTContext &C;
  clang::Rewriter &rewriter_;
  std::set<clang::Decl *> fn_visited_;
  std::set<clang::Expr *> memb_visited_;
  std::set<const clang::Stmt *> whitelist_;
  std::set<std::tuple<clang::Decl *, int>> ptregs_;
  std::set<clang::Decl *> &m_;
  clang::Decl *ctx_;
  bool track_helpers_;
  std::list<int> ptregs_returned_;
  const clang::Stmt *addrof_stmt_;
  bool is_addrof_;
};

// A helper class to the frontend action, walks the decls
class BTypeConsumer : public clang::ASTConsumer {
 public:
  explicit BTypeConsumer(clang::ASTContext &C, BFrontendAction &fe,
                         clang::Rewriter &rewriter, std::set<clang::Decl *> &m);
  void HandleTranslationUnit(clang::ASTContext &Context) override;
 private:
  BFrontendAction &fe_;
  MapVisitor map_visitor_;
  BTypeVisitor btype_visitor_;
  ProbeVisitor probe_visitor1_;
  ProbeVisitor probe_visitor2_;
};

// Create a B program in 2 phases (everything else is normal C frontend):
// 1. Catch the map declarations and open the fd's
// 2. Capture the IR
class BFrontendAction : public clang::ASTFrontendAction {
 public:
  // Initialize with the output stream where the new source file contents
  // should be written.
  BFrontendAction(llvm::raw_ostream &os, unsigned flags, TableStorage &ts,
                  const std::string &id, const std::string &main_path,
                  FuncSource &func_src, std::string &mod_src,
                  const std::string &maps_ns);

  // Called by clang when the AST has been completed, here the output stream
  // will be flushed.
  void EndSourceFileAction() override;

  std::unique_ptr<clang::ASTConsumer>
      CreateASTConsumer(clang::CompilerInstance &Compiler, llvm::StringRef InFile) override;

  clang::Rewriter &rewriter() const { return *rewriter_; }
  TableStorage &table_storage() const { return ts_; }
  std::string id() const { return id_; }
  std::string maps_ns() const { return maps_ns_; }
  bool is_rewritable_ext_func(clang::FunctionDecl *D);
  void DoMiscWorkAround();

 private:
  llvm::raw_ostream &os_;
  unsigned flags_;
  TableStorage &ts_;
  std::string id_;
  std::string maps_ns_;
  std::unique_ptr<clang::Rewriter> rewriter_;
  friend class BTypeVisitor;
  std::map<std::string, clang::SourceRange> func_range_;
  const std::string &main_path_;
  FuncSource &func_src_;
  std::string &mod_src_;
  std::set<clang::Decl *> m_;
};

}  // namespace visitor
