#ifndef FLATBUFFERS_NAMER
#define FLATBUFFERS_NAMER

#include "flatbuffers/util.h"

namespace flatbuffers {

// Options for Namer::File.
enum class SkipFile {
  None = 0,
  Suffix = 1,
  Extension = 2,
  SuffixAndExtension = 3,
};
inline SkipFile operator&(SkipFile a, SkipFile b) {
  return static_cast<SkipFile>(static_cast<int>(a) & static_cast<int>(b));
}
// Options for Namer::Directories
enum class SkipDir {
  None = 0,
  // Skip prefixing the -o $output_path.
  OutputPath = 1,
  // Skip trailing path seperator.
  TrailingPathSeperator = 2,
  OutputPathAndTrailingPathSeparator = 3,
};
inline SkipDir operator&(SkipDir a, SkipDir b) {
  return static_cast<SkipDir>(static_cast<int>(a) & static_cast<int>(b));
}

// `Namer` applies style configuration to symbols in generated code. It manages
// casing, escapes keywords, and object API naming.
// TODO: Refactor all code generators to use this.
class Namer {
 public:
  struct Config {
    // Symbols in code.

    // Case style for flatbuffers-defined types.
    // e.g. `class TableA {}`
    Case types;
    // Case style for flatbuffers-defined constants.
    // e.g. `uint64_t ENUM_A_MAX`;
    Case constants;
    // Case style for flatbuffers-defined methods.
    // e.g. `class TableA { int field_a(); }`
    Case methods;
    // Case style for flatbuffers-defined functions.
    // e.g. `TableA* get_table_a_root()`;
    Case functions;
    // Case style for flatbuffers-defined fields.
    // e.g. `struct Struct { int my_field; }`
    Case fields;
    // Case style for flatbuffers-defined variables.
    // e.g. `int my_variable = 2`
    Case variables;
    // Case style for flatbuffers-defined variants.
    // e.g. `enum class Enum { MyVariant, }`
    Case variants;
    // Seperator for qualified enum names.
    // e.g. `Enum::MyVariant` uses `::`.
    std::string enum_variant_seperator;

    // Configures, when formatting code, whether symbols are checked against
    // keywords and escaped before or after case conversion. It does not make
    // sense to do so before, but its legacy behavior. :shrug:
    // TODO(caspern): Deprecate.
    enum class Escape {
      BeforeConvertingCase,
      AfterConvertingCase,
    };
    Escape escape_keywords;

    // Namespaces

    // e.g. `namespace my_namespace {}`
    Case namespaces;
    // The seperator between namespaces in a namespace path.
    std::string namespace_seperator;

    // Object API.
    // Native versions flatbuffers types have this prefix.
    // e.g. "" (it's usually empty string)
    std::string object_prefix;
    // Native versions flatbuffers types have this suffix.
    // e.g. "T"
    std::string object_suffix;

    // Keywords.
    // Prefix used to escape keywords. It is usually empty string.
    std::string keyword_prefix;
    // Suffix used to escape keywords. It is usually "_".
    std::string keyword_suffix;

    // Files.

    // Case style for filenames. e.g. `foo_bar_generated.rs`
    Case filenames;
    // Case style for directories, e.g. `output_files/foo_bar/baz/`
    Case directories;
    // The directory within which we will generate files.
    std::string output_path;
    // Suffix for generated file names, e.g. "_generated".
    std::string filename_suffix;
    // Extension for generated files, e.g. ".cpp" or ".rs".
    std::string filename_extension;
  };
  Namer(Config config, std::set<std::string> keywords)
      : config_(config), keywords_(std::move(keywords)) {}

  virtual ~Namer() {}

  template<typename T> std::string Method(const T &s) const {
    return Method(s.name);
  }

  virtual std::string Method(const std::string &pre,
                             const std::string &mid,
                             const std::string &suf) const {
    return Format(pre + "_" +  mid + "_" + suf, config_.methods);
  }
  virtual std::string Method(const std::string &pre,
                             const std::string &suf) const {
    return Format(pre + "_" + suf, config_.methods);
  }
  virtual std::string Method(const std::string &s) const {
    return Format(s, config_.methods);
  }

  virtual std::string Constant(const std::string &s) const {
    return Format(s, config_.constants);
  }

  virtual std::string Function(const std::string &s) const {
    return Format(s, config_.functions);
  }

  virtual std::string Variable(const std::string &s) const {
    return Format(s, config_.variables);
  }

  template<typename T>
  std::string Variable(const std::string &p, const T &s) const {
    return Format(p + "_" + s.name, config_.variables);
  }
  virtual std::string Variable(const std::string &p,
                               const std::string &s) const {
    return Format(p + "_" + s, config_.variables);
  }

  virtual std::string Namespace(const std::string &s) const {
    return Format(s, config_.namespaces);
  }

  virtual std::string Namespace(const std::vector<std::string> &ns) const {
    std::string result;
    for (auto it = ns.begin(); it != ns.end(); it++) {
      if (it != ns.begin()) result += config_.namespace_seperator;
      result += Namespace(*it);
    }
    return result;
  }

  virtual std::string NamespacedType(const std::vector<std::string> &ns,
                                     const std::string &s) const {
    return (ns.empty() ? "" : (Namespace(ns) + config_.namespace_seperator)) +
           Type(s);
  }

  // Returns `filename` with the right casing, suffix, and extension.
  virtual std::string File(const std::string &filename,
                           SkipFile skips = SkipFile::None) const {
    const bool skip_suffix = (skips & SkipFile::Suffix) != SkipFile::None;
    const bool skip_ext = (skips & SkipFile::Extension) != SkipFile::None;
    return ConvertCase(filename, config_.filenames, Case::kUpperCamel) +
           (skip_suffix ? "" : config_.filename_suffix) +
           (skip_ext ? "" : config_.filename_extension);
  }
  template<typename T>
  std::string File(const T &f, SkipFile skips = SkipFile::None) const {
    return File(f.name, skips);
  }

  // Formats `directories` prefixed with the output_path and joined with the
  // right seperator. Output path prefixing and the trailing separator may be
  // skiped using `skips`.
  // Callers may want to use `EnsureDirExists` with the result.
  virtual std::string Directories(const std::vector<std::string> &directories,
                                  SkipDir skips = SkipDir::None) const {
    const bool skip_output_path =
        (skips & SkipDir::OutputPath) != SkipDir::None;
    const bool skip_trailing_seperator =
        (skips & SkipDir::TrailingPathSeperator) != SkipDir::None;
    std::string result = skip_output_path ? "" : config_.output_path;
    for (auto d = directories.begin(); d != directories.end(); d++) {
      result += ConvertCase(*d, config_.directories, Case::kUpperCamel);
      result.push_back(kPathSeparator);
    }
    if (skip_trailing_seperator && !result.empty()) result.pop_back();
    return result;
  }

  virtual std::string EscapeKeyword(const std::string &name) const {
    if (keywords_.find(name) == keywords_.end()) {
      return name;
    } else {
      return config_.keyword_prefix + name + config_.keyword_suffix;
    }
  }

  virtual std::string Type(const std::string &s) const {
    return Format(s, config_.types);
  }
  virtual std::string Type(const std::string &t, const std::string &s) const {
    return Format(t + "_" + s, config_.types);
  }

  virtual std::string ObjectType(const std::string &s) const {
    return config_.object_prefix + Type(s) + config_.object_suffix;
  }

  virtual std::string Field(const std::string &s) const {
    return Format(s, config_.fields);
  }

  virtual std::string Variant(const std::string &s) const {
    return Format(s, config_.variants);
  }

  virtual std::string Format(const std::string &s, Case casing) const {
    if (config_.escape_keywords == Config::Escape::BeforeConvertingCase) {
      return ConvertCase(EscapeKeyword(s), casing, Case::kLowerCamel);
    } else {
      return EscapeKeyword(ConvertCase(s, casing, Case::kLowerCamel));
    }
  }

  // Denamespaces a string (e.g. The.Quick.Brown.Fox) by returning the last part
  // after the `delimiter` (Fox) and placing the rest in `namespace_prefix`
  // (The.Quick.Brown).
  virtual std::string Denamespace(const std::string &s,
                                  std::string &namespace_prefix,
                                  const char delimiter = '.') const {
    const size_t pos = s.find_last_of(delimiter);
    if (pos == std::string::npos) {
      namespace_prefix = "";
      return s;
    }
    namespace_prefix = s.substr(0, pos);
    return s.substr(pos + 1);
  }

  // Same as above, but disregards the prefix.
  virtual std::string Denamespace(const std::string &s,
                                  const char delimiter = '.') const {
    std::string prefix;
    return Denamespace(s, prefix, delimiter);
  }

  const Config config_;
  const std::set<std::string> keywords_;
};

}  // namespace flatbuffers

#endif  // FLATBUFFERS_NAMER
