// 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;

  string
  to_string (const name& n)
  {
    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, bool quote, char pair, bool escape)
  {
    auto write_string = [quote, pair, escape, &os](const string& v)
    {
      char sc[] = {
        '{', '}', '[', ']', '$', '(', ')', // Token endings.
        ' ', '\t', '\n', '#',              // Spaces.
        '\\', '"',                         // Escaping and quoting.
        '%',                               // Project name separator.
        '*', '?',                          // Wildcard characters.
        pair,                              // Pair separator, if any.
        '\0'};

      if (quote && 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 << '"';
      }
      else if (quote && v.find_first_of (sc) != string::npos)
      {
        if (escape) os << '\\';
        os << '\'';

        os << v;

        if (escape) os << '\\';
        os << '\'';
      }
      else
        os << v;
    };

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

    auto write_dir = [dv, quote, &os, &write_string] (const dir_path& d)
    {
      if (quote)
        write_string (dv < 1 ? diag_relative (d) : d.representation ());
      else
        os << d;
    };

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

    // If quoted then print empty name as '' rather than {}.
    //
    if (quote && 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 ());

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

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

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

      os << '{';
    }

    if (v)
      write_string (n.value);
    else if (d)
    {
      if (rd.empty ())
        write_string (dir_path (".").representation ());
      else if (!pd.empty ())
        write_string (rd.leaf ().representation ());
      else
        write_dir (rd);
    }

    if (t || (!d && !v))
      os << '}';

    return os;
  }

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

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

    return os;
  }
}