// 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 "GccDepFile.h"

//stdc++ includes
#include <iostream>
#include <fstream>
#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 string parse_filename(const string &line, size_t &pos) {
  size_t len = line.length();
  string result;
  while (pos < len) {
    if (line[pos] == ' ' || (line[pos] == ':' && (pos == len - 1 || line[pos + 1] == ' ')))
      break;
    if (line[pos] == '\\') {
      if (pos+1 < len && line[pos+1] == ' ')
        pos++;
    }
    result += line[pos];
    pos++;
  }
  return result;
}

bool GccDepFile::parse(Puma::ErrorStream &err, const std::string &content) {

  istringstream in(content);
  Rule rule;
  enum { IN_TARGETS, IN_SOURCE, IN_DEPS } state = IN_TARGETS;
  string line;
  int line_no = 1;
  while (getline(in, line)) {
    size_t len = line.length();
    // cout << "line " << line_no << ": |" << line << "|" << endl;
    size_t pos = 0;
    while (true) {
      skip_whitespace(line, pos);
      // check for a phony target: appears here if -MP is used => add to previous rule
      if (line[pos] == ':' && pos == len - 1) {
        if (!phony_target_mode_) {
          err << sev_fatal << Location(Filename("<gen-dep-file>"), line_no)
              << "phony target without '-MP': " << line.c_str() << endMessage;
        }
        else if (state != IN_TARGETS) {
          err << sev_fatal << Location(Filename("<gen-dep-file>"), line_no)
              << "syntax error in rule: " << line.c_str() << endMessage;
          state = IN_TARGETS;
        }
        else {
          // the collect targets are in fact 'phony' target (=> -MP) and must be attached to
          // the previous rule
          for (const auto &target : rule.targets_)
            rules_.back().phony_targets_.insert(target);
        }
        rule.clear();
        break;
      }
      if (pos == len) {
        if (state < IN_DEPS)
          err << sev_fatal << Location(Filename("<gen-dep-file>"), line_no)
              << "syntax error in rule: " << line.c_str() << endMessage;
        else {
          rules_.push_back(rule); // save the parsed rule
        }
        rule.clear();
        state = IN_TARGETS;
        break;
      }
      if (line[pos] == '\\' && pos == len - 1)
        break; // a line continuation
      if (state == IN_TARGETS && line.substr(pos, 2) == ": ") {
        state = IN_SOURCE;
        pos += 2;
        continue;
      }
      string filename = parse_filename(line, pos);
      // cout << "file |" << filename << "| " << state << endl;
      switch (state) {
        case IN_TARGETS:
          rule.targets_.push_back(filename);
          break;
        case IN_SOURCE:
          rule.source_ = filename;
          state = IN_DEPS;
          break;
        case IN_DEPS:
          rule.deps_.insert(filename);
          break;
      }
    }
    line_no++;
  }

  if (rules_.size() != (size_t)(translation_units_ + aspect_units_))
    err << sev_fatal << "Unexpected number of rules in generated dependency file" << endMessage;

  return (err.severity() < sev_error);
}

void GccDepFile::discard_overwritten_rules() {
  if (translation_units_ > 1)
    rules_.erase(rules_.begin(), rules_.begin() + (translation_units_ - 1));
  translation_units_ = 1;
}

void GccDepFile::merge_aspect_dependencies(const std::string &aspect_header, int into_rule) {
  for (int i = translation_units_; i < translation_units_ + aspect_units_; i++) {
    if (rules_[i].source_ == aspect_header) {
      merge_dependencies(into_rule, rules_[i].deps_, rules_[i].source_);
      break;
    }
  }
}

void GccDepFile::print(ostream &out, bool including_aspects) {
  
  for (int rule_no = 0; rule_no < (int)rules_.size(); rule_no++) {
    if (!including_aspects && rule_no >= translation_units_)
      break;
    const Rule &rule = rules_[rule_no];
    for (auto &target : rule.targets_)
      out << target << " ";
    out << ": " << rule.source_ << " ";
    int len = rule.source_.length();
    for (auto &dep : rule.deps_) {
      if (len > 40) {
        out << "\\" << endl << " ";
        len = 0;
      }
      out << dep << " ";
      len += dep.length();
    }
    out << endl;
    for (auto &phony : rule.phony_targets_)
      out << phony << ":" << endl;
  }
}

void GccDepFile::retrieve_dependencies(int rule, std::set<std::string> &deps) {
  deps.insert(rules_[rule].source_);
  for (const auto &dep : rules_[rule].deps_)
    deps.insert(dep);
}

void GccDepFile::retrieve_aspect_dependencies(std::string aspect_header, std::set<std::string> & deps) {
  for (int i = translation_units_; i < translation_units_ + aspect_units_; i++) {
    if (rules_[i].source_ == aspect_header) {
      deps.insert(aspect_header);
      for (auto &file : rules_[i].deps_)
        deps.insert(file);
      break;
    }
  }
}

void GccDepFile::merge_dependencies(int rule_no, const std::set<std::string> &deps, const std::string &source) {
  auto &rule = rules_[rule_no];
  if (!source.empty()) {
    rule.deps_.insert(source);
    if (phony_target_mode_)
      rule.phony_targets_.insert(source);
  }
  for (auto &dep : deps) {
    rule.deps_.insert(dep);
    if (phony_target_mode_)
      rule.phony_targets_.insert(dep);
  }
}
