// Copyright (c) 2013 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 <string.h>

#include "gn/input_file.h"
#include "gn/parse_tree.h"
#include "gn/source_file.h"

#include "base/logging.h"
#include "gn/filesystem_utils.h"
#include "gn/source_dir.h"
#include "util/build_config.h"

namespace {

void AssertValueSourceFileString(const std::string& s) {
#if defined(OS_WIN)
  DCHECK(s[0] == '/' ||
         (s.size() > 2 && s[0] != '/' && s[1] == ':' && IsSlash(s[2])));
#else
  DCHECK(s[0] == '/');
#endif
  DCHECK(!EndsWithSlash(s)) << s;
}

bool EndsWithExtension(std::string_view str, std::string_view ext) {
  return str.size() > ext.size() && str[str.size() - ext.size() - 1] == '.' &&
         !::memcmp(str.data() + str.size() - ext.size(), ext.data(),
                   ext.size());
}

SourceFile::Type GetSourceFileType(const std::string& file) {
  size_t size = file.size();
  const char* str = file.data();

  // First, single-char extensions.
  if (size > 2 && str[size - 2] == '.') {
    switch (str[size - 1]) {
      case 'c':
        return SourceFile::SOURCE_C;  // .c
      case 'h':
        return SourceFile::SOURCE_H;  // .h
      case 'm':
        return SourceFile::SOURCE_M;  // .m
      case 'o':
        return SourceFile::SOURCE_O;  // .o
      case 'S':
      case 's':
        return SourceFile::SOURCE_S;  // .S and .s
      default:
        return SourceFile::SOURCE_UNKNOWN;
    }
  }

  // Second, two-char extensions
  if (size > 3 && str[size - 3] == '.') {
#define TAG2(c1, c2) ((unsigned)(c1) | ((unsigned)(c2) << 8))
    switch (TAG2(str[size - 2], str[size - 1])) {
      case TAG2('c', 'c'):
        return SourceFile::SOURCE_CPP;  // .cc
      case TAG2('g', 'o'):
        return SourceFile::SOURCE_GO;  // .go
      case TAG2('h', 'h'):
        return SourceFile::SOURCE_H;  // .hh
      case TAG2('m', 'm'):
        return SourceFile::SOURCE_MM;  // .mm
      case TAG2('r', 'c'):
        return SourceFile::SOURCE_RC;  // .rc
      case TAG2('r', 's'):
        return SourceFile::SOURCE_RS;  // .rs
      default:
        return SourceFile::SOURCE_UNKNOWN;
    }
#undef TAG2
  }

  if (size > 4 && str[size - 4] == '.') {
#define TAG3(c1, c2, c3) \
  ((unsigned)(c1) | ((unsigned)(c2) << 8) | ((unsigned)(c3) << 16))
    switch (TAG3(str[size - 3], str[size - 2], str[size - 1])) {
      case TAG3('c', 'p', 'p'):
      case TAG3('c', 'x', 'x'):
      case TAG3('c', '+', '+'):
        return SourceFile::SOURCE_CPP;
      case TAG3('h', 'p', 'p'):
      case TAG3('h', 'x', 'x'):
      case TAG3('i', 'n', 'c'):
      case TAG3('i', 'p', 'p'):
      case TAG3('i', 'n', 'l'):
        return SourceFile::SOURCE_H;
      case TAG3('a', 's', 'm'):
        return SourceFile::SOURCE_S;
      case TAG3('d', 'e', 'f'):
        return SourceFile::SOURCE_DEF;
      case TAG3('o', 'b', 'j'):
        return SourceFile::SOURCE_O;
      default:
        return SourceFile::SOURCE_UNKNOWN;
    }
#undef TAG3
  }

  // Other cases
  if (EndsWithExtension(file, "hpp11"))
    return SourceFile::SOURCE_H;

  if (EndsWithExtension(file, "swift"))
    return SourceFile::SOURCE_SWIFT;

  if (EndsWithExtension(file, "swiftmodule"))
    return SourceFile::SOURCE_SWIFTMODULE;

  if (EndsWithExtension(file, "modulemap"))
    return SourceFile::SOURCE_MODULEMAP;

  return SourceFile::SOURCE_UNKNOWN;
}

std::string Normalized(std::string value) {
  DCHECK(!value.empty());
  AssertValueSourceFileString(value);
  NormalizePath(&value);
  return value;
}

}  // namespace

SourceFile::SourceFile(const std::string& value)
    : SourceFile(StringAtom(Normalized(value))) {}

SourceFile::SourceFile(std::string&& value)
    : SourceFile(StringAtom(Normalized(std::move(value)))) {}

SourceFile::SourceFile(StringAtom value) : value_(value) {}

SourceFile::Type SourceFile::GetType() const {
  return GetSourceFileType(value_.str());
}

bool SourceFile::IsDefType() const {
  std::string_view v = value_.str();
  return EndsWithExtension(v, "def");
}

bool SourceFile::IsObjectType() const {
  std::string_view v = value_.str();
  return EndsWithExtension(v, "o") || EndsWithExtension(v, "obj");
}

bool SourceFile::IsModuleMapType() const {
  std::string_view v = value_.str();
  return EndsWithExtension(v, "modulemap");
}

bool SourceFile::IsSwiftType() const {
  std::string_view v = value_.str();
  return EndsWithExtension(v, "swift");
}

bool SourceFile::IsSwiftModuleType() const {
  std::string_view v = value_.str();
  return EndsWithExtension(v, "swiftmodule");
}

std::string SourceFile::GetName() const {
  if (is_null())
    return std::string();

  const std::string& value = value_.str();
  DCHECK(value.find('/') != std::string::npos);
  size_t last_slash = value.rfind('/');
  return std::string(&value[last_slash + 1], value.size() - last_slash - 1);
}

SourceDir SourceFile::GetDir() const {
  if (is_null())
    return SourceDir();

  const std::string& value = value_.str();
  DCHECK(value.find('/') != std::string::npos);
  size_t last_slash = value.rfind('/');
  return SourceDir(value.substr(0, last_slash + 1));
}

base::FilePath SourceFile::Resolve(const base::FilePath& source_root) const {
  return ResolvePath(value_.str(), true, source_root);
}

void SourceFile::SetValue(const std::string& value) {
  value_ = StringAtom(value);
}

SourceFileTypeSet::SourceFileTypeSet() : empty_(true) {
  memset(flags_, 0,
         sizeof(bool) * static_cast<int>(SourceFile::SOURCE_NUMTYPES));
}

bool SourceFileTypeSet::CSourceUsed() const {
  return empty_ || Get(SourceFile::SOURCE_CPP) ||
         Get(SourceFile::SOURCE_MODULEMAP) || Get(SourceFile::SOURCE_H) ||
         Get(SourceFile::SOURCE_C) || Get(SourceFile::SOURCE_M) ||
         Get(SourceFile::SOURCE_MM) || Get(SourceFile::SOURCE_RC) ||
         Get(SourceFile::SOURCE_S) || Get(SourceFile::SOURCE_O) ||
         Get(SourceFile::SOURCE_DEF);
}

bool SourceFileTypeSet::RustSourceUsed() const {
  return Get(SourceFile::SOURCE_RS);
}

bool SourceFileTypeSet::GoSourceUsed() const {
  return Get(SourceFile::SOURCE_GO);
}

bool SourceFileTypeSet::SwiftSourceUsed() const {
  return Get(SourceFile::SOURCE_SWIFT);
}

bool SourceFileTypeSet::MixedSourceUsed() const {
  return (1 << static_cast<int>(CSourceUsed())
            << static_cast<int>(RustSourceUsed())
            << static_cast<int>(GoSourceUsed())
            << static_cast<int>(SwiftSourceUsed())) > 2;
}

bool InSourceAllowList(const ParseNode* node, const SourceFileSet* allowlist) {
  if (!allowlist)
    return true;

  LocationRange range = node->GetRange();
  if (!range.begin().file())
    return true;

  return allowlist->find(range.begin().file()->name()) != allowlist->end();
}
