// 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 "Phase1Sema.h"
#include "ClangTransformInfo.h"
#include "ACBase/StringUtils.h"
using namespace ACBase;
using namespace std;

void Phase1Sema::init_sema (ACModel &model) {
  _model = &model;
  _root_scope._kind         = Scope::SCOPE_NAMESPACE;
  _root_scope._name         = "::";
  _root_scope._is_slice     = false;
  _root_scope._parent_scope = 0;
  _root_scope._jpm_link     = _model->get_root();
  _anon_class_no            = 0; // global count for anonymous classes
}

// create new scope object
Phase1Sema::Scope *Phase1Sema::create_new_scope(string name, Node::Kind kind, Scope *qual_scope,
  const std::vector<std::string> &names, bool in_model, bool in_slice) {
  Scope *inserted_scope = 0;
  Scope &scope = curr_scope();
  Scope new_scope;
  new_scope._name          = name;
  new_scope._is_slice      = (in_slice || scope._is_slice);
  new_scope._kind          = kind;
  new_scope._advice_no     = 0;
  new_scope._order_no      = 0;
  new_scope._intro_no      = 0;
  new_scope._anon_slice_no = 0;

  if (kind == Scope::SCOPE_NAMESPACE) {
    // register parent scopes in which the new namespace is nested (C++17)
    // TODO: check version of the C++ standard
    Scope *parent = &scope;
    for (int i = 0; i < (int)names.size() - 1; i++) {
      Scope parent_scope;
      parent_scope._name = names[i];
      parent_scope._is_slice = false;
      parent_scope._parent_scope = parent;
      parent_scope._kind = Scope::SCOPE_NAMESPACE;
      parent_scope._jpm_link = (in_model ? _model->register_namespace1 (parent->_jpm_link, names[i]) : 0);
      pair<set<Scope>::const_iterator, bool> res =
        parent->_sub_scopes.insert (parent_scope);
      parent = &(Scope&)*res.first;
      if (!parent->_jpm_link && parent_scope._jpm_link)
        parent->_jpm_link = parent_scope._jpm_link;
    }
    new_scope._parent_scope = (parent);
    new_scope._jpm_link     = (in_model ? _model->register_namespace1 (parent->_jpm_link, name) : 0);
  }
  else if (kind == Scope::SCOPE_ASPECT) {
    new_scope._parent_scope = (qual_scope ? qual_scope : &scope);
    new_scope._jpm_link     = (in_model ? _model->register_aspect1 (scope._jpm_link, name) : 0);
  }
  else if (kind == Scope::SCOPE_CLASS || kind == Scope::SCOPE_STRUCT) {
    new_scope._parent_scope = (qual_scope ? qual_scope : &scope);
    if (in_slice)
      new_scope._jpm_link = _model->register_class_slice(
          qual_scope ? qual_scope->_jpm_link : scope._jpm_link, name, (kind == Scope::SCOPE_STRUCT));
    else
      new_scope._jpm_link     = ((in_model && (!qual_scope || qual_scope->_jpm_link))?
          _model->register_class1 (qual_scope ? qual_scope->_jpm_link : scope._jpm_link, name) : 0);
  }
  else if (kind == Scope::SCOPE_UNION) {
    new_scope._parent_scope = (qual_scope ? qual_scope : &scope);
    new_scope._jpm_link     = 0;
  }
  pair<set<Scope>::const_iterator, bool> res =
      new_scope._parent_scope->_sub_scopes.insert (new_scope);
  inserted_scope = &(Scope&)*res.first;
  // A namespace first introduced as external, might now become part of the project.
  // Therefore, add the project model link here.
  if (!inserted_scope->_jpm_link && new_scope._jpm_link)
    inserted_scope->_jpm_link = new_scope._jpm_link;
  // if the inserted new sub-scope is an unnamed namespace, it has to be added as a search scope to the current scope
  if (kind == Scope::SCOPE_NAMESPACE && scope.is_namespace() && name == "<unnamed>" && res.second) {
    scope._search_scopes.push_back(inserted_scope);
  }
  return inserted_scope;
}

void Phase1Sema::create_class_alias(const string &name, Scope *target) {
  Element alias;
  alias._kind = Element::ELEMENT_CLASS_ALIAS;
  alias._jpm_link = 0;
  alias._name = name;
  alias._referred = target;
  _curr_scope->_elements.insert(alias);
}

void Phase1Sema::create_namespace_alias(const string &name, Scope *target) {
  Element alias;
  alias._kind = Element::ELEMENT_NAMESPACE_ALIAS;
  alias._jpm_link = 0;
  alias._name = name;
  alias._referred = target;
  _curr_scope->_elements.insert(alias);
}

ACM_Attribute *Phase1Sema::create_attr(const string &name) {

  ACM_Namespace *parent = (ACM_Namespace*) _curr_scope->_jpm_link;
  if (!parent)
    parent = (ACM_Namespace*) register_scope(*_curr_scope);

  ACM_Attribute *attr = _model->register_attrdecl1(parent, name);
  if(!attr)
    return nullptr;

  Element attr_elem;
  attr_elem._kind = Element::ELEMENT_CXX11Attr;
  attr_elem._name = name;
  attr_elem._jpm_link = attr;
  _curr_scope->_elements.insert(attr_elem);
  return attr;
}

ACM_Pointcut *Phase1Sema::create_pointcut(const string &name, bool is_virtual, const string &expr) {

  // register the scope of the pointcut; should normally not be necessary
  ACM_Name *parent = _curr_scope->_jpm_link;
  if (!parent)
    parent = register_scope(*_curr_scope);

  // register the named pointcut itself
  ACM_Pointcut *pct = _model->register_pointcut1 (parent, name, is_virtual, expr);
  if (!pct)
    return nullptr;

  Element pct_elem;
  pct_elem._kind = Element::ELEMENT_POINTCUT;
  pct_elem._name = name;
  pct_elem._jpm_link = pct;
  _curr_scope->_elements.insert(pct_elem);
  return pct;
}

ACM_Pointcut *Phase1Sema::create_anon_pointcut(const string &expr) {
  return _model->register_pointcut1(0, "%anon", false, expr);
}

// attach collected advice filter patterns to the next advice (code, order, or intro)
void Phase1Sema::attach_advice_filters(ACM_Advice *adv) {
  for (auto &expr : _advice_filters) {
    auto filter = _model->newFilePattern();
    filter->set_given(std::get<0>(expr));
    filter->set_regex(std::get<1>(expr));
    adv->get_filters().insert(filter);
  }
  _advice_filters.clear();
}

ACM_AdviceCode *Phase1Sema::create_advice_code(const string &type, ACM_Pointcut *pct) {
  assert(_curr_scope && _curr_scope->_jpm_link && _curr_scope->_kind == Node::SCOPE_ASPECT);
  ACM_AdviceCode *new_elem = _model->newAdviceCode();
  ((ACM_Aspect*)_curr_scope->_jpm_link)->get_advices().insert(new_elem);
  new_elem->set_kind(type == "before" ? ACT_BEFORE: type == "after" ? ACT_AFTER:ACT_AROUND);
  new_elem->set_pointcut(pct);
//    new_elem->set_context(context); // will be done in phase 2
  new_elem->set_lid(_curr_scope->_advice_no);
  _curr_scope->_advice_no++; // increment number of code advice block per aspect
  TI_AdviceCode::of(*new_elem)->set_advice_filters(_advice_filters);
  attach_advice_filters(new_elem);
  return new_elem;
}

ACM_Order *Phase1Sema::create_order(ACM_Pointcut *pct) {
  assert(_curr_scope && _curr_scope->_jpm_link && _curr_scope->_kind == Node::SCOPE_ASPECT);
  ACM_Order *order = _model->newOrder();
  order->set_pointcut(pct);
  order->set_lid(_curr_scope->_order_no);
  _curr_scope->_order_no++;
  ((ACM_Aspect*)_curr_scope->_jpm_link)->get_orders().insert(order);
  TI_Order::of(*order)->set_advice_filters(_advice_filters);
  attach_advice_filters(order);
  return order;
}

ACM_Introduction *Phase1Sema::create_introduction(ACM_Pointcut *pct) {
  assert(_curr_scope && _curr_scope->_jpm_link && _curr_scope->_kind == Node::SCOPE_ASPECT);
  ACM_Introduction *intro = _model->newIntroduction();
  intro->set_pointcut(pct);
  intro->set_lid(_curr_scope->_intro_no);
  ((ACM_Aspect*)_curr_scope->_jpm_link)->get_intros().insert(intro);
  _curr_scope->_intro_no++;
  TI_Introduction::of(*intro)->set_advice_filters(_advice_filters);
  attach_advice_filters(intro);
  return intro;
}

ACM_ClassSlice *Phase1Sema::create_class_slice(const string &name, bool is_struct, Scope *qual_scope) {
  ACM_ClassSlice *slice_obj =
    _model->register_class_slice((qual_scope ? qual_scope->_jpm_link : _curr_scope->_jpm_link), name, is_struct);
  // create new scope object
  Scope new_scope;
  new_scope._name         = name;
  new_scope._is_slice     = true;
  new_scope._parent_scope = (qual_scope ? qual_scope : _curr_scope);
  new_scope._kind         = (is_struct ? Scope::SCOPE_STRUCT : Scope::SCOPE_CLASS);
  new_scope._jpm_link     = slice_obj;
  new_scope._parent_scope->_sub_scopes.insert (new_scope);
  return slice_obj;
}

ACM_Name *Phase1Sema::register_scope (Scope &scope) {
  if (!scope._jpm_link) {
    ACM_Name *parent_scope = register_scope (*scope._parent_scope);
    if (scope.is_namespace()) {
      scope._jpm_link = _model->register_namespace1(parent_scope, scope._name, false);
    }
    else if (scope.is_aspect()) {
      scope._jpm_link = _model->register_aspect1(parent_scope, scope._name, false);
      register_base_classes (scope);
    }
    else if (scope.is_class_or_struct()) {
      scope._jpm_link = _model->register_class1(parent_scope, scope._name, false);
      register_base_classes (scope);
    }
  }
  return scope._jpm_link;
}

void Phase1Sema::register_base_classes (Scope &cls) {
  ACM_Class *elem = (ACM_Class*)cls._jpm_link;
  for (list<Scope*>::iterator i = cls._search_scopes.begin ();
      i != cls._search_scopes.end (); ++i) {
    ACM_Class *base_class = (ACM_Class*)register_scope (*(*i));
    elem->get_bases().insert (base_class);
    base_class->get_derived().insert(elem);
  }
}

Phase1Sema::Node *Phase1Sema::lookup_name (Scope &scope, bool root_qualified, const vector<string> &names,
  bool follow_alias) {
  if (root_qualified)
    return lookup_name(_root_scope, false, names, follow_alias);
  Scope *search_scope = &scope;
  while (search_scope) {
    set<Scope*> new_visited_scopes;
    Node *result = lookup_name_in_scope (*search_scope, names, new_visited_scopes, follow_alias);
    if (result)
      return result;
    search_scope = search_scope->_parent_scope;
  }
  return 0;
}

Phase1Sema::Node *Phase1Sema::lookup_name_in_scope (Scope &scope, const vector<string> &names,
    std::set<Scope*> &visited_scopes, bool follow_alias, int depth) {

  Node *result = lookup_unqualified_name(scope, names[depth]);
  if (follow_alias && result &&
      (result->_kind == Element::ELEMENT_CLASS_ALIAS ||
      result->_kind == Element::ELEMENT_NAMESPACE_ALIAS))
    result = ((Element*)result)->_referred;

  // for classes and aspects also lookup the name in base classes/aspects
  // for namespaces search in other namespaces mentioned in using directives
  if (!result) {
    visited_scopes.insert (&scope);
    for (std::list<Scope*>::const_iterator i = scope._search_scopes.begin ();
        i != scope._search_scopes.end (); ++i) {
      // if a search scope was searched alread, skip it silently
      if (visited_scopes.find (*i) != visited_scopes.end()) {
        continue;
      }
      // search the search scope
      if ((result = lookup_name_in_scope(**i, names, visited_scopes, follow_alias, depth)) != 0)
        return result;
    }
  }
  if (result && result->_kind == Node::ELEMENT_POINTCUT)
    return (depth == (int)(names.size() - 1)) ? result : 0;
  // if we found the scope, check the remaining parts of the qualified name
  if (result && (depth + 1 < (int)names.size ())) {
    set<Scope*> new_visited_scopes;
    return lookup_name_in_scope(*(Scope*)result, names, new_visited_scopes, follow_alias, depth + 1);
  }
  return result;
}

Phase1Sema::Scope *Phase1Sema::lookup_scope (Scope &scope, bool root_qualified, vector<string> &names) {
  if (names.size () <= 1)
    return 0;
  vector<string> scope_names;
  for (unsigned i = 0; i < names.size () - 1; i++)
    scope_names.push_back (names[i]);
  Node *result = lookup_name (scope, root_qualified, scope_names);
  return ((result && result->is_scope()) ? (Scope*)result : 0);
}

Phase1Sema::Node *Phase1Sema::lookup_unqualified_name (Scope &scope, const std::string &name) {
  Node *result = 0;
  Scope search;
  search._name = name;
  set<Scope>::iterator i = scope._sub_scopes.find (search);
  if (i != scope._sub_scopes.end ())
    result = (Node*)&(*i);

  if (!result) {
    Element search_elem;
    search_elem._name = name;
    set<Element>::iterator i = scope._elements.find (search_elem);
    if (i != scope._elements.end ())
      result = (Node*)&(*i);
  }
  return result;
}

bool Phase1Sema::is_visible_scope (Scope &scope, const string &name) {
  if (scope._name == "::")
    return (name != "AC" && name != "JoinPoint");
  return is_visible_scope (*scope._parent_scope, scope._name);
}

string Phase1Sema::full_anon_class_name (Scope &scope) const {
  if (scope._parent_scope && starts_with(scope._parent_scope->_name, "__ac_anon"))
    return full_anon_class_name (*scope._parent_scope) + "::" + scope._name;
  else
    return scope._name;
}

// functions needed for PointcutSearcher interface

ACM_Pointcut *Phase1Sema::lookup_pct_func (bool root_qualified, vector<string> &qual_name) {
  Node *lookup_result = lookup_name (*_curr_scope, root_qualified, qual_name);
  if (lookup_result && lookup_result->_kind == Element::ELEMENT_POINTCUT &&
      lookup_result->_jpm_link &&
      lookup_result->_jpm_link->type_val() == JPT_Pointcut) {
    _lookup_record.push_back(lookup_result->_jpm_link);
    return (ACM_Pointcut*)lookup_result->_jpm_link;
  }
  return 0;
}

ACM_Attribute *Phase1Sema::lookup_pct_attr(bool root_qualified, std::vector<string> &qual_name)
{
  Node *lookup_result = lookup_name (*_curr_scope, root_qualified, qual_name);
  if (lookup_result && lookup_result->_kind == Element::ELEMENT_CXX11Attr &&
    lookup_result->_jpm_link &&
      lookup_result->_jpm_link->type_val() == JPT_Attribute) {
    _lookup_record.push_back(lookup_result->_jpm_link);
    return (ACM_Attribute*)lookup_result->_jpm_link;
  }
  return 0;
}

std::string Phase1Sema::anon_slice_name() {
  ostringstream name_str;
  name_str << "<anon-slice-" << _curr_scope->_anon_slice_no << ">";
  _curr_scope->_anon_slice_no++;
  return name_str.str();
}

std::string Phase1Sema::anon_class_name() {
  ostringstream name_str;
  string scope_name = curr_scope_name();
  if (scope_name == "::")
    name_str << "__ac_anon" << _anon_class_no;
  else {
    if (!starts_with(scope_name, "__ac_anon"))
      name_str << "__ac_anon";
    // "<unnamed" is not allowed in an identifier because of "<" and ">"
    name_str << (scope_name == "<unnamed>" ? "_unnamed_" : scope_name)
              << "_" << _anon_class_no;
  }
  _anon_class_no++;
  return name_str.str();
}

// debug function
void Phase1Sema::Scope::dump (int indent) const {
  for (int i = indent; i >= 0; i--) { cout << "  "; }
  if (_is_slice)
    cout << "slice ";
  switch (_kind) {
  case SCOPE_NAMESPACE: cout << "namespace"; break;
  case SCOPE_CLASS:     cout << "class"; break;
  case SCOPE_ASPECT:    cout << "aspect"; break;
  case SCOPE_UNION:     cout << "union"; break;
  default:              cout << "unknown"; break;
  }
  cout << " " << _name << ": ";
  for (std::list<Scope*>::const_iterator i = _search_scopes.begin ();
      i != _search_scopes.end (); ++i)
    cout << " search " << (*i)->_name;
  for (std::set<Element>::const_iterator i = _elements.begin ();
      i != _elements.end (); ++i)
    cout << " elem " << (*i)._name;
  cout << endl;
  for (std::set<Scope>::const_iterator i = _sub_scopes.begin ();
      i != _sub_scopes.end (); ++i)
    (*i).dump (indent + 1);
}

// annotate a file in the project model with an "affect" pragma
bool Phase1Sema::register_file_affects(const string &filename, const string &pattern_as_given, const string &pattern_as_regex) {
  ACM_File *ah = _model->register_file(filename, 0, 0, true, true);
  assert(ah);
  auto fp = _model->newFilePattern();
  fp->set_given(pattern_as_given);
  fp->set_regex(pattern_as_regex);
  ah->get_affects().push_back(fp);
  return true;
}
