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

#include <libbuild2/types.hxx> // Note: not <libbuild2/name.hxx>

#include <string.h> // strchr()

#include <libbuild2/diagnostics.hxx>

namespace build2
{
  const name empty_name;
  const names empty_names;

  void name::
  canonicalize ()
  {
    // We cannot assume the name part is a valid filesystem name so we will
    // have to do the splitting manually.
    //
    size_t p (path_traits::rfind_separator (value));

    if (p != string::npos)
    {
      if (p + 1 == value.size ())
        throw invalid_argument ("empty value");

      dir /= dir_path (value, p != 0 ? p : 1); // Special case: "/".

      value.erase (0, p + 1);
    }
  }

  string
  to_string (const name& n)
  {
    assert (!n.pattern);

    string r;

    // Note: similar to to_stream() below.
    //
    if (n.empty ())
      return r;

    if (n.proj)
    {
      r += n.proj->string ();
      r += '%';
    }

    // If the value is empty, then we want to put the last component of the
    // directory inside {}, e.g., dir{bar/}, not bar/dir{}.
    //
    bool v (!n.value.empty ());
    bool t (!n.type.empty ());

    const dir_path& pd (v ? n.dir              :
                        t ? n.dir.directory () :
                        dir_path ());

    if (!pd.empty ())
      r += pd.representation ();

    if (t)
    {
      r += n.type;
      r += '{';
    }

    if (v)
      r += n.value;
    else
      r += (pd.empty () ? n.dir : n.dir.leaf ()).representation ();

    if (t)
      r += '}';

    return r;
  }

  ostream&
  to_stream (ostream& os, const name& n, quote_mode q, char pair, bool escape)
  {
    using pattern_type = name::pattern_type;

    auto write_string = [&os, q, pair, escape] (
      const string& v,
      optional<pattern_type> pat = nullopt,
      bool curly = false)
    {
      // We don't expect the effective quoting mode to be specified for the
      // name patterns.
      //
      assert (q != quote_mode::effective || !pat);

      // Special characters, path pattern characters, and regex pattern
      // characters. The latter only need to be quoted in the first position
      // and if followed by a non-alphanumeric delimiter. If that's the only
      // special character, then we handle it with escaping rather than
      // quoting (see the parsing logic for rationale). Additionally, we
      // escape leading `+` in the curly braces which is also recognized as a
      // path pattern.
      //
      char nsc[] = {
        '{', '}', '[', ']', '$', '(', ')', // Token endings.
        ' ', '\t', '\n', '#',              // Spaces.
        '\\', '"',                         // Escaping and quoting.
        '%',                               // Project name separator.
        pair,                              // Pair separator, if any.
        '\0'};

      char pc[] = {
        '*', '?',                          // Path wildcard characters.
        '\0'};

      auto rc = [] (const string& v)
      {
        return (v[0] == '~' || v[0] == '^') && v[1] != '\0' && !alnum (v[1]);
      };

      char esc[] = {
        '{', '}', '$', '(',   // Token endings.
        ' ', '\t', '\n', '#', // Spaces.
        '"',                  // Quoting.
        pair,                 // Pair separator, if any.
        '\0'};

      auto ec = [&esc] (const string& v)
      {
        for (size_t i (0); i < v.size (); ++i)
        {
          char c (v[i]);

          if (strchr (esc, c) != nullptr || (c == '\\' && v[i + 1] == '\\'))
            return true;
        }

        return false;
      };

      if (pat)
      {
        switch (*pat)
        {
        case pattern_type::path:                          break;
        case pattern_type::regex_pattern:      os << '~'; break;
        case pattern_type::regex_substitution: os << '^'; break;
        }
      }

      if (q != quote_mode::none && v.find ('\'') != string::npos)
      {
        // Quote the string with the double quotes rather than with the single
        // one. Escape some of the special characters.
        //
        if (escape) os << '\\';
        os << '"';

        for (auto c: v)
        {
          if (strchr ("\\$(\"", c) != nullptr) // Special inside double quotes.
            os << '\\';

          os << c;
        }

        if (escape) os << '\\';
        os << '"';
      }
      //
      // Note that a regex pattern does not need to worry about special path
      // pattern character but not vice-verse. See the parsing logic for
      // details.
      //
      else if ((q == quote_mode::normal &&
                (v.find_first_of (nsc) != string::npos ||
                 (!pat && v.find_first_of (pc) != string::npos))) ||
               (q == quote_mode::effective && ec (v)))
      {
        if (escape) os << '\\';
        os << '\'';

        os << v;

        if (escape) os << '\\';
        os << '\'';
      }
      // Note that currently we do not preserve a leading `+` as a pattern
      // unless it has other wildcard characters (see the parsing code for
      // details). So we escape it both if it's not a pattern or is a path
      // pattern.
      //
      else if (q == quote_mode::normal              &&
               (!pat || *pat == pattern_type::path) &&
               ((v[0] == '+' && curly) || rc (v)))
      {
        if (escape) os << '\\';
        os << '\\' << v;
      }
      else
        os << v;
    };

    uint16_t dv (stream_verb (os).path); // Directory verbosity.

    auto write_dir = [&os, q, &write_string, dv] (
      const dir_path& d,
      optional<pattern_type> pat = nullopt,
      bool curly = false)
    {
      if (q != quote_mode::none)
      {
        // Note that diag_relative() just returns a directory representation
        // for a relative path.
        //
        write_string (dv < 1 ? diag_relative (d) : d.representation (),
                      pat,
                      curly);
      }
      else
        os << d;
    };

    // Note: similar to to_string() above.
    //

    // If quoted then print empty name as '' rather than {}.
    //
    if (q != quote_mode::none && n.empty ())
      return os << (escape ? "\\'\\'" : "''");

    if (n.proj)
    {
      write_string (n.proj->string ());
      os << '%';
    }

    // If the value is empty, then we want to print the last component of the
    // directory inside {}, e.g., dir{bar/}, not bar/dir{}. We also want to
    // print {} for an empty name (unless quoted, which is handled above).
    //
    bool d (!n.dir.empty ());
    bool v (!n.value.empty ());
    bool t (!n.type.empty ());

    // If both the name directory and value are not empty but the type is
    // empty, then the directory and value are written sequentially. If any of
    // them needs to be quoted, then we will end up with a valid but strange-
    // looking construct, for example:
    //
    // 'f oo/'bar
    // foo/'b ar'
    // 'f oo'/'b ar'
    //
    // Let's handle such cases specially to quote the whole name
    // representation. Note that this approach doesn't work well for name
    // patterns, since in this case the special character sets will differ for
    // the directory and value parts.
    //
    if (q != quote_mode::none && d && v && !t && !n.pattern)
    {
      // Note that we request diag_relative() to return an empty path rather
      // than `./` if the name directory is a relative base.
      //
      string s (dv < 1
                ? diag_relative (n.dir, false /* current */)
                : n.dir.representation ());

      s += n.value;
      write_string (s);
      return os;
    }

    // Note: relative() may potentially return empty or absolute path.
    //
    const dir_path& rd (dv < 1 ? relative (n.dir) : n.dir);
    const dir_path& pd (v ? rd              :
                        t ? rd.directory () :
                        dir_path ());

    if (!pd.empty ())
      write_dir (pd);

    bool curly;
    if ((curly = t || (!d && !v)))
    {
      if (t)
        write_string (n.type);

      os << '{';
    }

    if (v)
      write_string (n.value, n.pattern, curly);
    else if (d)
    {
      // A directory pattern cannot be regex.
      //
      assert (!n.pattern || *n.pattern == pattern_type::path);

      if (rd.empty ())
        write_string (dir_path (".").representation (), nullopt, curly);
      else if (!pd.empty ())
        write_string (rd.leaf ().representation (), n.pattern, curly);
      else
        write_dir (rd, n.pattern, curly);
    }

    if (curly)
      os << '}';

    return os;
  }

  ostream&
  to_stream (ostream& os,
             const names_view& ns,
             quote_mode q,
             char pair,
             bool escape)
  {
    for (auto i (ns.begin ()), e (ns.end ()); i != e; )
    {
      const name& n (*i);
      ++i;
      to_stream (os, n, q, pair, escape);

      if (n.pair)
        os << n.pair;
      else if (i != e)
        os << ' ';
    }

    return os;
  }
}
