
// 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 "ACModel.h"
#include "Utils.h"
#include "XmlModelReader.h"
#include "XmlModelWriter.h"
#include <filesystem>

ACModel::ACModel() {
  clear();
}

// reset the model to a state as if it were just created
void ACModel::clear() {
  ProjectModel::clear(); // clear the underlying data container
  get_project_paths().clear(); // TODO: this should be done in ProjectModel::clear()
  get_project_paths().set_xml_node(0);
  get_project_paths().set_xml_dirty();
  get_files().clear();
  get_files().set_xml_node(0);
  get_files().set_xml_dirty();
  file_map_.clear();
  canonical_map_.clear();
  _tunit_file = nullptr;
  register_namespace1(0, "::");
}

// clear the model and load new contents from a file
bool ACModel::load(ACModelFile &model_file) {

  // first clear the model to avoid memory leaks
  clear();

  // set the file position to the start to make sure that consecutive 'loads' work
  if (!model_file.seek_start())
    return false;

  XmlModelReader reader;
  if (!reader.read (*this, model_file))
    return false;

  // For a freshly loaded model there is no file map -> build it
  for (auto file : get_files()) {
    string filename = file->get_filename();
    file_map_.insert({ filename, file});
    if (file->has_path())
      filename = (std::filesystem::path(file->get_path()->get_canonical()) / filename).string();
    canonical_map_.insert({ filename, file});
  }

  return true;
}

// save the model into a file
bool ACModel::save(ACModelFile &model_file) {
  XmlModelWriter writer;
  return writer.write (*this, model_file);
}

// merge the elements of another model into this one
void ACModel::merge(ProjectModel &that) {
  ProjectModel::merge(that);
  // For a freshly merged model there is an incomplete file map -> fix it
  for (auto file : get_files()) {
    string filename = file->get_filename();
    file_map_.insert({ filename, file});
    if (file->has_path())
      filename = (std::filesystem::path(file->get_path()->get_canonical()) / filename).string();
    canonical_map_.insert({ filename, file});
  }
}

string ACModel::model_filename(const std::string &filename, ACM_ProjectPath *&projectpath) {
  // determine the absolute (canonical) path of the file with default delimiters ('/')
  string filepath = std::filesystem::weakly_canonical(filename).generic_string();
  for (auto pp : get_project_paths()) {
    if (filepath.find(pp->get_canonical() + "/") == 0) {
      // return the relative path
      projectpath = pp;
      return filepath.substr(pp->get_canonical().length() + 1);
    }
  }
  projectpath = nullptr; // outside project tree
  return filepath;       // return the canonical path
}

ACM_TUnit *ACModel::setup_tunit(std::string &filename, int len, int modi_time) {
  _tunit_file = (ACM_TUnit*)register_file(filename, len, modi_time, false);
  return _tunit_file;
}

// a header file shall be found or registered if it is still unknown
ACM_File *ACModel::register_file(const string &filename, int len, int modi_time, bool is_header, bool is_crosscutting) {

  // check whether the file is already registered under the provided filename
  auto file_find = file_map_.find(filename);
  if (file_find != file_map_.end ()) {
    auto file = file_find->second;
    if (file->type_val() == JPT_Header) {
      auto &sorted = ((ACM_Header*)file)->get_in().get_sorted();
      if (sorted.find(_tunit_file) == sorted.end())
        ((ACM_Header*)file)->get_in().insert(_tunit_file);
    }
    return file;
  }

  // determine the filename to be used in the project model
  ACM_ProjectPath *pp;
  string mfn = ACModel::model_filename(filename, pp);
  string canonical = mfn;
  if (pp) canonical = (std::filesystem::path(pp->get_canonical()) / mfn).string();

  auto canonical_find = canonical_map_.find(canonical);
  if (canonical_find != canonical_map_.end()) {
    auto file = canonical_find->second;
    if (file->type_val() == JPT_Header) {
      auto &sorted = ((ACM_Header*)file)->get_in().get_sorted();
      if (sorted.find(_tunit_file) == sorted.end())
        ((ACM_Header*)file)->get_in().insert(_tunit_file);
    }
    return file;
  }

  ACM_File *file;
  if (is_header) {
    ACM_Header *new_header = newHeader();
    new_header->get_in().insert(_tunit_file);
    file = new_header;
  }
  else {
    ACM_TUnit *new_tunit = newTUnit();
    new_tunit->set_dirty_flag(false); // not used
    file = new_tunit;
  }
  get_files().insert(file);
  file->set_filename(mfn);
  file->set_len(len);
  file->set_time(modi_time);
  file->set_crosscutting(is_crosscutting);
  if (pp)
    file->set_path(pp);

  file_map_.insert(FileMapPair(filename, file));
  canonical_map_.insert(FileMapPair(canonical, file));
  return file;
}

ACM_Class * ACModel::register_class1 (ACM_Name *scope, string name, bool in_project) {
  ACM_Name *found = map_lookup(*scope, name);
  if (found && found->type_val() == JPT_Class)
    return (ACM_Class*)found;
  ACM_Class *new_elem = newClass();
  new_elem->set_name(name);
  new_elem->set_builtin(false);
  if (!in_project) new_elem->get_tunits().insert (_tunit_file);
  new_elem->set_intro_target(in_project);
  scope->get_children().insert(new_elem);
  map_insert(*scope, *new_elem, name);
  return new_elem;
}

ACM_Aspect * ACModel::register_aspect1 (ACM_Name *scope, string name, bool in_project) {
  ACM_Name *found = map_lookup(*scope, name);
  if (found && found->type_val() == JPT_Aspect)
    return (ACM_Aspect*)found;
  ACM_Aspect *new_elem = newAspect();
  new_elem->set_name(name);
  new_elem->set_builtin(false);
  if (!in_project) new_elem->get_tunits().insert (_tunit_file);
  new_elem->set_intro_target(in_project);
  scope->get_children().insert(new_elem);
  map_insert(*scope, *new_elem, name);
  return new_elem;
}

ACM_Attribute *ACModel::register_attrdecl1 (ACM_Namespace *parent, const std::string &name)
{
  ACM_Attribute *elem = 0;
  if (parent)
    elem = (ACM_Attribute*)map_lookup(*parent, name);
  if (!elem || elem->type_val() != JPT_Attribute) {
    elem = newAttribute();
    elem->set_name(name);
    // set the 'builtin' flag in phase 2
    if (parent) {
      map_insert(*parent, *elem, name);
      parent->get_children().insert(elem);
    }
  }
  else {
    //for already registered attributes?
    elem = 0;
  }

  return elem;
}

// find an attribute declaration
ACM_Attribute *ACModel::find_attrdecl (const std::vector<std::string> &qual_name) {
  ACM_Name *scope = get_root();
  ACM_Name *found = nullptr;
  for (string name : qual_name) {
    found = map_lookup(*scope, name);
    if (!found)
      return nullptr;
    scope = found;
  }
  if (found->type_val() != JPT_Attribute)
    return nullptr;
  return (ACM_Attribute*)found;
}

ACM_Pointcut *ACModel::register_pointcut1 (ACM_Name *parent, const string &name,
    bool is_virtual, const string& expr) {

  ACM_Pointcut *elem = 0;
  if (parent)
    elem = (ACM_Pointcut*)map_lookup(*parent, name);
  if (!elem || elem->type_val() != JPT_Pointcut) {
    elem = newPointcut();
    elem->set_name(name);
    elem->set_expr (expr);
    elem->set_builtin(false);
    elem->set_kind(PT_NORMAL);
    if (parent) {
      if (is_virtual && expr == "0")
        elem->set_kind(PT_PURE_VIRTUAL);
      else if (is_virtual || overrides_virtual_pointcut(parent, name))
        elem->set_kind(PT_VIRTUAL);
      map_insert(*parent, *elem, name);
      parent->get_children().insert(elem);
    }
  }
  else {
    elem = 0;
  }

  return elem;
}

bool ACModel::overrides_virtual_pointcut (ACM_Name *parent, const string &name) {
  if (parent->type_val() != JPT_Class && parent->type_val() != JPT_Aspect)
    return false;
  ACM_Class *cls = (ACM_Class*)parent;
  typedef ACM_Container<ACM_Class, false> BList;
  const BList &blist = cls->get_bases();
  for (BList::const_iterator i = blist.begin (); i != blist.end(); ++i) {
    ACM_Class *base = (ACM_Class*)*i;
    ACM_Name *elem = map_lookup(*base, name);
    if (!elem || elem->type_val() != JPT_Pointcut)
      continue;
    ACM_Pointcut *base_pct = (ACM_Pointcut*)elem;
    if (base_pct->get_kind() == PT_PURE_VIRTUAL ||
        base_pct->get_kind() == PT_VIRTUAL ||
        overrides_virtual_pointcut (base, name))
      return true;
  }
  return false;
}

ACM_Namespace * ACModel::register_namespace1 (ACM_Name *scope, string name, bool in_project) {
  ACM_Namespace *result = 0;
  assert(scope || name == "::");
  ACM_Name *found = (scope ? map_lookup(*scope, name) : get_root());
  if (!found || found->type_val() != JPT_Namespace) {
    result = newNamespace();
    result->set_name(name);
    result->set_builtin(false);
    if (!in_project) result->get_tunits().insert (_tunit_file);
    if (scope) {
      scope->get_children().insert(result);
      map_insert(*scope, *result, name);
    }
    else {
      set_root(result);
    }
  }
  else
    result = (ACM_Namespace*)found;
  return result;
}

ACM_ClassSlice *ACModel::register_class_slice (ACM_Name *scope, string name, bool is_struct) {
  ACM_Name *found = map_lookup(*scope, name);
  if (found && found->type_val() == JPT_ClassSlice)
    return (ACM_ClassSlice*)found;

  ACM_ClassSlice *new_elem = (ACM_ClassSlice*)newClassSlice();
  new_elem->set_name(name);
  new_elem->set_builtin(false);
  new_elem->set_is_struct(is_struct);
  map_insert(*scope, *new_elem, name);
  scope->get_children().insert(new_elem);
  return new_elem;
}


