// file      : libbuild2/name.hxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

// Note: include <libbuild2/types.hxx> instead of this file directly.
//

#ifndef LIBBUILD2_NAME_HXX
#define LIBBUILD2_NAME_HXX

// We cannot include <libbuild2/utility.hxx> since it includes
// <libbuild2/types.hxx>.
//
#include <utility> // move()

#include <libbuild2/export.hxx>

namespace build2
{
  using std::move;

  // A name is what we operate on by default. Depending on the context, it can
  // be interpreted as a target or prerequisite name. A name without a type
  // and directory can be used to represent any text. A name with directory
  // and empty value represents a directory.
  //
  // A name may also be qualified with a project. If the project name is
  // empty, then it means the name is in a project other than our own (e.g.,
  // it is installed).
  //
  // A type can only be specified if either directory or value are not empty.
  // We allow project-qualified empty names for reversibility.
  //
  // If pair is not '\0', then this name and the next in the list form a
  // pair. Can be used as a bool flag.
  //
  // If pattern is present then this is a name pattern (e.g., file{*.txt},
  // file{~'/(.+)\.txt/i'}, file{^'/\1/'}). A directory name cannot be a regex
  // pattern (since we would need to store it in dir_path and a regex is not
  // necessarily a valid path).
  //
  struct name
  {
    enum class pattern_type: uint8_t {path, regex_pattern, regex_substitution};

    optional<project_name> proj;
    dir_path dir;
    string type;
    string value;
    char pair = '\0';
    optional<pattern_type> pattern;

    name () {} // = default; Clang needs this to initialize const object.
    name (string v): value (move (v)) {}
    name (dir_path d): dir (move (d)) {}
    name (string t, string v): type (move (t)), value (move (v)) {}
    name (dir_path d, string v): dir (move (d)), value (move (v)) {}

    name (dir_path d, string t, string v)
        : dir (move (d)), type (move (t)), value (move (v)) {}

    name (string p, dir_path d, string t, string v)
        : proj (project_name (move (p))), dir (move (d)), type (move (t)),
          value (move (v)) {}

    name (optional<project_name> p, dir_path d, string t, string v)
        : proj (move (p)), dir (move (d)), type (move (t)), value (move (v)) {}

    name (optional<project_name> p,
          dir_path d,
          string t,
          string v,
          optional<pattern_type> pt)
        : proj (move (p)), dir (move (d)), type (move (t)), value (move (v)),
          pattern (pt) {}

    bool
    qualified () const {return proj.has_value ();}

    bool
    unqualified () const {return !qualified ();}

    bool
    typed () const {return !type.empty ();}

    bool
    untyped () const {return type.empty ();}

    // Note: if dir and value are empty then there should be no proj or type.
    //
    bool
    empty () const {return dir.empty () && value.empty ();}

    // Note that strictly speaking the following tests should be orthogonal
    // to qualification. However, the vast majority of cases where we expect
    // a simple or directory name, we also expect it to be unqualified.
    //
    // Note also that empty name is simple but not a directory.
    //
    bool
    simple (bool ignore_qual = false) const
    {
      return (ignore_qual || unqualified ()) && untyped () && dir.empty ();
    }

    bool
    directory (bool ignore_qual = false) const
    {
      return (ignore_qual || unqualified ()) &&
        untyped () && !dir.empty () && value.empty ();
    }

    // File path-like (only optional directory and non-empty value).
    //
    bool
    file (bool ignore_qual = false) const
    {
      return (ignore_qual || unqualified ()) && untyped () && !value.empty ();
    }

    bool
    absolute () const
    {
      return !dir.empty () && dir.absolute ();
    }

    bool
    relative () const
    {
      return dir.empty () || dir.relative ();
    }

    int
    compare (const name&) const;

    // Canonicalize the name by moving the directory component (if any) from
    // value to dir. Throw invalid_argument if value would become empty. May
    // also throw invalid_path.
    //
    void
    canonicalize ();
  };

  LIBBUILD2_SYMEXPORT extern const name empty_name;

  inline bool
  operator== (const name& x, const name& y) {return x.compare (y) == 0;}

  inline bool
  operator!= (const name& x, const name& y) {return !(x == y);}

  inline bool
  operator< (const name& x, const name& y) {return x.compare (y) < 0;}

  // Return string representation of a name.
  //
  // Note that this function does not quote special characters and you should
  // use the to_stream() function below if this is necessary. It also cannot
  // be used on a name pattern.
  //
  LIBBUILD2_SYMEXPORT string
  to_string (const name&);

  template <typename T>
  inline void
  to_checksum (T& cs, const name& n)
  {
    if (n.proj)
      cs.append (n.proj->string ());
    cs.append (n.dir.string ());
    cs.append (n.type);
    cs.append (n.value);
    cs.append (n.pair);
    if (n.pattern)
      cs.append (static_cast<uint8_t> (*n.pattern));
  }

  // Store a string in a name in a reversible way. If the string ends with a
  // trailing directory separator then it is stored as a directory, otherwise
  // as a simple name. Note that the returned name is never a pattern.
  //
  // NOTE: this function does not parse the full name syntax.
  //
  name
  to_name (string);

  // Serialize the name to the stream. If requested, the name components
  // containing special characters are quoted and/or escaped. In the normal
  // quoting mode the special characters are:
  //
  // {}[]$() \t\n#\"'%
  //
  // And additionally, unless name is a pattern:
  //
  // *?
  //
  // As well as leading and if followed by a non-alphanumeric delimiter:
  //
  // ~^
  //
  // As well as leading `+` if in the curly braces.
  //
  // In the effective quoting mode the special characters are:
  //
  // {}$( \t\n#"'
  //
  // As well as `\` if followed by any of the above characters or itself.
  //
  // If the pair argument is not '\0', then it is added to the above special
  // characters sets. If the quote character is present in the component then
  // it is double quoted rather than single quoted. In this case the following
  // characters are escaped:
  //
  // \$("
  //
  // If escape is true, then escape (with a backslash) the quote characters
  // being added (this is useful if the result will be re-parsed, for example
  // as a Testscript command line).
  //
  // Note that in the quoted mode empty unqualified name is printed as '',
  // not {}.
  //
  enum class quote_mode
  {
    none,
    normal,
    effective
  };

  LIBBUILD2_SYMEXPORT ostream&
  to_stream (ostream&,
             const name&,
             quote_mode,
             char pair = '\0',
             bool escape = false);

  inline ostream&
  operator<< (ostream& os, const name& n) {
    return to_stream (os, n, quote_mode::none);}

  // Vector of names.
  //
  // Quite often it will contain just one element so we use small_vector<1>.
  // Note also that it must be a separate type rather than an alias for
  // vector<name> in order to distinguish between untyped variable values
  // (names) and typed ones (vector<name>).
  //
  using names = small_vector<name, 1>;
  using names_view = vector_view<const name>;

  LIBBUILD2_SYMEXPORT extern const names empty_names;

  // The same semantics as to_stream(name).
  //
  LIBBUILD2_SYMEXPORT ostream&
  to_stream (ostream&,
             const names_view&,
             quote_mode,
             char pair = '\0',
             bool escape = false);

  inline ostream&
  operator<< (ostream& os, const names_view& ns) {
    return to_stream (os, ns, quote_mode::none);}

  inline ostream&
  operator<< (ostream& os, const names& ns) {return os << names_view (ns);}

  // Pair of names.
  //
  using name_pair = pair<name, name>;
}

#include <libbuild2/name.ixx>

#endif // LIBBUILD2_NAME_HXX