// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2003  The 'ac++' developers (see aspectc.org)
//
// This program is free software;  you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
// MA  02111-1307  USA

#include "ClangIntroSema.h"
#include "ClangIntroducer.h"
#include "ClangBinding.h"
#include "ACProject.h"
#include "ModelBuilder.h"

using namespace clang;

// remove all attributes that have namespace "<NS>::" (with <NS> neither being "gnu" nor
// "clang") in its name from the attribute list 'in' and put them into 'hidden'
void hideACAttrs(ParsedAttributesView &in, ParsedAttributesView &hidden) {
  for (ParsedAttr &attr : in) {
    std::string full_attr_name = attr.getNormalizedFullName();
    // if (attr.getNormalizedFullName() == "ac::attr") {
    if (full_attr_name == "__ac_attr" || full_attr_name.find("::__ac_attr") == full_attr_name.length() - 11)  {
      hidden.addAtEnd(&attr);
    }
  }
  for (ParsedAttr &attr : hidden) {
    in.remove(&attr);
  }
}

// add all attributes from 'hidden' into 'out'. 'hidden' is empty afterwards
void restoreACAttrs(ParsedAttributesView &out, ParsedAttributesView &hidden) {
  for (ParsedAttr &attr : hidden) {
    out.addAtEnd(&attr);
  }
  hidden.clearListOnly();
}

/*
 * This function injects attributes (al) into an existing AST declaration (decl).
 */
void injectAttrs(Decl *decl, ASTContext &context, ParsedAttributesView &attrs) {
  for (ParsedAttr &attr : attrs) {
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
    AnnotateAttr *anno_attr = AnnotateAttr::Create(context, attr.getAttrName()->getName(),attr);
#else
    AnnotateAttr *anno_attr = AnnotateAttr::Create(context, attr.getAttrName()->getName(),attr.getRange());
#endif
    decl->addAttr(anno_attr);
  }
}

/*
 * This function injects attributes (attrList) into a statement (Stmt). If it is an AttributedStmt already, we
 * get the SubStmt and append the attributes to our own list. We then wrap the passed stmt (or its SubStmt) into an
 * AttributedStmt (Clang would to it this way).
 */
AttributedStmt* injectAttrs(SourceLocation AttrLoc, ParsedAttributesView &attrList, ASTContext &context, Stmt *stmt) {
  SmallVector<const Attr*, 8> Attrs;
  for (ParsedAttr &attr : attrList) {
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
    AnnotateAttr *anno_attr = AnnotateAttr::Create(context, attr.getAttrName()->getName(),attr);
#else
    AnnotateAttr *anno_attr = AnnotateAttr::Create(context, attr.getAttrName()->getName(),attr.getRange());
#endif
    Attrs.push_back(anno_attr);
  }

  Stmt *subStmt = stmt;
  if (AttributedStmt *attr_stmt = dyn_cast<AttributedStmt>(stmt)) {
    subStmt = attr_stmt->getSubStmt();
    ArrayRef< const Attr * > existing_attrs = attr_stmt->getAttrs();
    for (auto attr : existing_attrs) {
      Attrs.push_back(attr);
    }
  }

  AttributedStmt *LS = AttributedStmt::Create(context, AttrLoc, Attrs, subStmt);
  return LS;
}


ClangIntroSema::ClangIntroSema(Sema &sema, ClangIntroducer &introducer) :
        _sema(sema), _introducer (&introducer) {
}


bool ClangIntroSema::canSkipFunctionBody(Decl *FctDecl) {
  // FIXME: We would like to get rid of this wrapper function.
  //        The problem is that the clang parser is not fully compatible with newer gcc versions.
  //        Therefore, skipping external function bodies helps to get rid of parse errors.
  SourceManager &sm = _introducer->get_source_manager();
  PresumedLoc PL = sm.getPresumedLoc(FctDecl->getLocation());
  llvm::StringRef Name = PL.getFilename();
  llvm::StringRef BufferName = sm.getBufferName(FctDecl->getLocation());
  ACProject &project = _introducer->get_model_builder().get_project();
  bool in_project = (CLANG_STR_STARTS_WITH(BufferName, "<intro") ||
      (!Name.empty() && (Name == "<ac>" || CLANG_STR_ENDS_WITH(Name, "_virt") || project.is_below(Name.str()))));
//  cout << Name.str() << " " << BufferName.str() << " " << in_project << endl;
  return !in_project && RealcanSkipFunctionBody(_sema, FctDecl);
}

/*
 * This function is called on every statement. We hide own attributes and process the other ones with default Clang
 * functionality. If we get the same statement back, it means there were no other (valid) attributes. In this case
 * we have to call ActOnAttributedStmt ourselves, because Clang will not do it.
 * We then have to inject our hidden attributes into whatever statement we get back.
 * After that, we restore the attributes into the AttrList, although this may be unnecessary (but just to be safe).
 */
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_20_1_2

// (hopefully) it is no longer necessary to change this function
void ClangIntroSema::ProcessStmtAttributes(Stmt *S, const ParsedAttributes &InAttrs, SmallVectorImpl<const Attr *> &OutAttrs) {
  RealProcessStmtAttributes(_sema, S, InAttrs, OutAttrs);
}

#elif CLANG_VERSION_NUMBER >= VERSION_NUMBER_14_0_0

void ClangIntroSema::ProcessStmtAttributes(Stmt *S, const ParsedAttributes &InAttrs, SmallVectorImpl<const Attr *> &OutAttrs) {
  ParsedAttributesView stmt_attrs;
  hideACAttrs ((ParsedAttributesView &)InAttrs, stmt_attrs);
  RealProcessStmtAttributes(_sema, S, InAttrs, OutAttrs);
  for (ParsedAttr &attr : stmt_attrs) {
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
    AnnotateAttr *anno_attr = AnnotateAttr::Create(_sema.getASTContext(), attr.getAttrName()->getName(),attr);
#else
    AnnotateAttr *anno_attr = AnnotateAttr::Create(_sema.getASTContext(), attr.getAttrName()->getName(),attr.getRange());
#endif
    OutAttrs.push_back(anno_attr);
  }
  stmt_attrs.clearListOnly();
}

#else

StmtResult ClangIntroSema::ProcessStmtAttributes(Stmt *S, const ParsedAttributesView &AttrList,
							SourceRange Range) {
  ParsedAttributesView stmt_attrs;
  hideACAttrs ((ParsedAttributesView &)AttrList, stmt_attrs);
  StmtResult stmtResult = RealProcessStmtAttributes(_sema, S, AttrList, Range);
  Stmt* subStmt = stmtResult.get();
  AttributedStmt* attributedStmt = injectAttrs(Range.getBegin(), stmt_attrs, _sema.getASTContext(), subStmt);
  restoreACAttrs((ParsedAttributesView &)AttrList, stmt_attrs);
  return attributedStmt;
}

#endif
