// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gn/eclipse_writer.h"

#include <fstream>
#include <memory>

#include "base/files/file_path.h"
#include "gn/builder.h"
#include "gn/config_values_extractors.h"
#include "gn/filesystem_utils.h"
#include "gn/loader.h"
#include "gn/xml_element_writer.h"

namespace {

// Escapes |unescaped| for use in XML element content.
std::string EscapeForXML(const std::string& unescaped) {
  std::string result;
  result.reserve(unescaped.length());
  for (const char c : unescaped) {
    if (c == '<')
      result += "&lt;";
    else if (c == '>')
      result += "&gt;";
    else if (c == '&')
      result += "&amp;";
    else
      result.push_back(c);
  }
  return result;
}

}  // namespace

EclipseWriter::EclipseWriter(const BuildSettings* build_settings,
                             const Builder& builder,
                             std::ostream& out)
    : build_settings_(build_settings), builder_(builder), out_(out) {
  languages_.push_back("C++ Source File");
  languages_.push_back("C Source File");
  languages_.push_back("Assembly Source File");
  languages_.push_back("GNU C++");
  languages_.push_back("GNU C");
  languages_.push_back("Assembly");
}

EclipseWriter::~EclipseWriter() = default;

// static
bool EclipseWriter::RunAndWriteFile(const BuildSettings* build_settings,
                                    const Builder& builder,
                                    Err* err) {
  base::FilePath file = build_settings->GetFullPath(build_settings->build_dir())
                            .AppendASCII("eclipse-cdt-settings.xml");
  std::ofstream file_out;
  file_out.open(FilePathToUTF8(file).c_str(),
                std::ios_base::out | std::ios_base::binary);
  if (file_out.fail()) {
    *err =
        Err(Location(), "Couldn't open eclipse-cdt-settings.xml for writing");
    return false;
  }

  EclipseWriter gen(build_settings, builder, file_out);
  gen.Run();
  return true;
}

void EclipseWriter::Run() {
  GetAllIncludeDirs();
  GetAllDefines();
  WriteCDTSettings();
}

void EclipseWriter::GetAllIncludeDirs() {
  std::vector<const Target*> targets = builder_.GetAllResolvedTargets();
  for (const Target* target : targets) {
    if (!UsesDefaultToolchain(target))
      continue;

    for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
      for (const SourceDir& include_dir : it.cur().include_dirs()) {
        include_dirs_.insert(
            FilePathToUTF8(build_settings_->GetFullPath(include_dir)));
      }
    }
  }
}

void EclipseWriter::GetAllDefines() {
  std::vector<const Target*> targets = builder_.GetAllResolvedTargets();
  for (const Target* target : targets) {
    if (!UsesDefaultToolchain(target))
      continue;

    for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
      for (const std::string& define : it.cur().defines()) {
        size_t equal_pos = define.find('=');
        std::string define_key;
        std::string define_value;
        if (equal_pos == std::string::npos) {
          define_key = define;
        } else {
          define_key = define.substr(0, equal_pos);
          define_value = define.substr(equal_pos + 1);
        }
        defines_[define_key] = define_value;
      }
    }
  }
}

bool EclipseWriter::UsesDefaultToolchain(const Target* target) const {
  return target->toolchain()->label() ==
         builder_.loader()->GetDefaultToolchain();
}

void EclipseWriter::WriteCDTSettings() {
  out_ << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
  XmlElementWriter cdt_properties_element(out_, "cdtprojectproperties",
                                          XmlAttributes());

  {
    const char* kIncludesSectionName =
        "org.eclipse.cdt.internal.ui.wizards.settingswizards.IncludePaths";
    std::unique_ptr<XmlElementWriter> section_element =
        cdt_properties_element.SubElement(
            "section", XmlAttributes("name", kIncludesSectionName));

    section_element->SubElement(
        "language", XmlAttributes("name", "holder for library settings"));

    for (const std::string& language : languages_) {
      std::unique_ptr<XmlElementWriter> language_element =
          section_element->SubElement("language",
                                      XmlAttributes("name", language));
      for (const std::string& include_dir : include_dirs_) {
        language_element
            ->SubElement("includepath",
                         XmlAttributes("workspace_path", "false"))
            ->Text(EscapeForXML(include_dir));
      }
    }
  }

  {
    const char* kMacrosSectionName =
        "org.eclipse.cdt.internal.ui.wizards.settingswizards.Macros";
    std::unique_ptr<XmlElementWriter> section_element =
        cdt_properties_element.SubElement(
            "section", XmlAttributes("name", kMacrosSectionName));

    section_element->SubElement(
        "language", XmlAttributes("name", "holder for library settings"));

    for (const std::string& language : languages_) {
      std::unique_ptr<XmlElementWriter> language_element =
          section_element->SubElement("language",
                                      XmlAttributes("name", language));
      for (const auto& key_val : defines_) {
        std::unique_ptr<XmlElementWriter> macro_element =
            language_element->SubElement("macro");
        macro_element->SubElement("name")->Text(EscapeForXML(key_val.first));
        macro_element->SubElement("value")->Text(EscapeForXML(key_val.second));
      }
    }
  }
}
