// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2004  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 "ACDeps.h"

//stdc++ includes
#include <iostream>
#include <fstream>
#include <sstream>
#include <cctype>

using namespace std;
using namespace Puma;

static void skip_whitespace(const string &line, size_t &pos) {
  size_t len = line.length();
  while (pos < len && isspace(line[pos]))
    pos++;
}

static bool parse_pattern(const string &line, size_t &pos, string &out) {
  size_t len = line.length();
  if (pos < len && line[pos] != '\"')
    return false;
  pos++;
  string result;
  while (pos < len && line[pos] != '\"' ) {
    if (line[pos] == '\\') {
      if (pos+1 < len && (line[pos+1] == '\\' || line[pos+1] == '\"' || line[pos+1] == '*'))
        pos++;
    }
    result += line[pos];
    pos++;
  }
  if (line[pos] != '\"')
    return false;
  pos++;
  out = result;
  return true;
}

static string to_regex_syntax(const string &in, char prefix, char suffix) {
  ostringstream out;
  out << prefix;
  int len = in.length();
  // if the first character is not '/' (root path) and if the pattern does not
  // begin with "**/" already, inject "([^/]*/)*"
  if (!in.empty() && in.substr(0,1) != "/" && in.substr(0,3) != "**/") {
      out << "([^/]*/)*";
  }
  bool path_wildcard_allowed = true;
  bool sep_allowed = true;
  int pos = 0;
  string error_msg;
  while (pos < len) {
    if (in[pos] == '/') {
      if (!sep_allowed) {
        error_msg = "two path separators without path in between";
        break;
      }
      if (pos == len - 1) {
        error_msg = "path separator at end of filename pattern";
        break;
      }
    }
    if (in.substr(pos, 2) == "**") {
      if (!path_wildcard_allowed) {
        error_msg = "double path wildcard '**'";
        break;
      }
      if (in.substr(pos, 3) != "**/") {
        error_msg = "path wildcard '**' not follow by '/'";
        break;
      }
      out << "([^/]*/)*/"; // match path sequence: [^/]* => character sequence without '/'
      pos += 3;
      path_wildcard_allowed = false;
      sep_allowed = false;
      continue;
    }

    // escape character that have special meanings in regular expressions
    sep_allowed = (in[pos] != '/');
    switch (in[pos]) {
      case '*':
        out << "[^/]*"; // match a sequence of non-path-separator characters
        break;
      case '?':
        out << "[^/]";  // match a single non-path-separator character
        break;
      case '.':         // handle characters with special meaning in ECMA regular expressions
      case '+':
      case '[':
      case ']':
      case '(':
      case ')':
      case '^':
      case '$':
      case '\\':
        out << "\\";    // escape the character
      default:          // fallthrough
        out << in[pos]; // default: just copy the character
    }
    pos++;
    path_wildcard_allowed = true;
  }

  if (error_msg.empty()) { // no error detected
    out << suffix;
    // cout << "regex: |" << out.str() << "|" << endl;
  }
  else {
    out.clear(); out.str("");
    out << error_msg << " in column " << (pos + 1) << " of \"" << in << "\"";
  }
  return out.str();
}

bool ACDeps::merge_file(const std::filesystem::path &dep_file_path, Puma::ErrorStream &err) {

  ifstream in(dep_file_path);
  if (!in.is_open()) {
      err << sev_error << "Could not open aspect dependency file: "
          << dep_file_path.string().c_str() << endMessage;
      return false;
  }

  string line;
  int line_no = 1;
  while (getline(in, line)) {
    size_t pos = 0;
    bool line_correct = false;
    skip_whitespace(line, pos);
    if (line[pos] == '#') {
      line_no++;
      continue;
    }
    string pattern1, pattern2;
    if (parse_pattern(line, pos, pattern1)) {
      skip_whitespace(line, pos);
      if (line.substr(pos, 2) == "=>") {
        pos += 2;
        skip_whitespace(line, pos);
        if (parse_pattern(line, pos, pattern2)) {
          skip_whitespace(line, pos);
          if (pos == line.length() || line[pos] == '#')
            line_correct = true;
        }
      }
    }
    Location loc(dep_file_path.string().c_str(), line_no);
    if (line_correct) {
      string aspect_regex = to_regex_syntax(pattern1, '^', '$');
      if (aspect_regex[0] != '^') {
        err << sev_error << loc << aspect_regex.c_str() << endMessage;
        return false;
      }
      string file_regex = to_regex_syntax(pattern2, '(', ')');
      if (file_regex[0] != '(') {
        err << sev_error << loc << file_regex.c_str() << endMessage;
        return false;
      }
      rules_.emplace_back(regex(aspect_regex, regex::optimize), file_regex);
    }
    else
      err << sev_error << loc << "Syntax error in aspect dependency file: " << line.c_str() << endMessage;
    line_no++;
  }

  in.close();
  if (err.severity() >= sev_error)
    return false;

  return true;
}

bool ACDeps::register_aspect_header(const std::string &ah_file, Puma::ErrorStream &err) {
  error_code ec;
  filesystem::path ah_file_path = filesystem::canonical(ah_file, ec);
  if (ec) {
    err << sev_error << "Problem with aspect header '" << ah_file.c_str()
        << "': " << ec.message().c_str() << endMessage;
    return false;
  }
  auto res = ah_canonicals_.insert(ah_file_path);
  if (!res.second) {
    err << sev_error << "Aspect header '" << ah_file_path.string().c_str()
        << "' registered more than once" << endMessage;
    return false;
  }
  // cout << "canonical: " << ah_file_path.string().c_str() << endl;

  set<string> expr_set;
  find_ah_deps(ah_file_path.string(), expr_set);

  string combined_expr = "^";
  bool first = true;
  for (const auto &subexpr : expr_set) {
    if (!first)
      combined_expr += "|";
    combined_expr += subexpr;
    first = false;
  }
  combined_expr += "$";
  if (combined_expr != "^$") {
    // cout << " -> match: " << combined_expr << endl;
    ah_deps_.emplace(ah_file, std::regex(combined_expr, std::regex::optimize));
  }
  else {
    // cout << " -> no match!" << endl;
    ah_unknown_.insert(ah_file);
  }

  return true;
}

void ACDeps::get_aspect_headers(std::list<std::string> &ah_files, HeaderFilter hf) {
  ah_files.clear();
  if (hf & WITHOUT_RULE)
    for (const auto &ah : ah_unknown_)
      ah_files.push_back(ah);
  if (hf & WITH_RULE)
    for (const auto &ah : ah_deps_)
      ah_files.push_back(ah.first);
}

void ACDeps::find_ah_deps(const string &ah_file, set<string> &deps) {
  for (const auto& rule : rules_)
    if(std::regex_match(ah_file, get<0>(rule)))
      deps.insert(get<1>(rule));
}

bool ACDeps::affects_any(const std::string &ah_file, std::set<std::string> &deps) {
  for (const auto &dep : deps)
    if (std::regex_match(dep, ah_deps_[ah_file]))
      return true;
  return false;
}
