/* 
 * Copyright (C) 2001, 2002 Red Hat Inc.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "pkg.h"
#include "parse.h"
#include "rpmvercmp.h"

#ifdef HAVE_MALLOC_H
# include <malloc.h>
#endif

#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <ctype.h>

static void verify_package (Package *pkg);

static GHashTable *packages = NULL;
static GHashTable *globals = NULL;
static GList *search_dirs = NULL;

gboolean disable_uninstalled = FALSE;
gboolean ignore_requires = FALSE;
gboolean ignore_requires_private = TRUE;
gboolean ignore_private_libs = TRUE;

void
add_search_dir (const char *path)
{
  search_dirs = g_list_append (search_dirs, g_strdup (path));
}

void
add_search_dirs (const char *path, const char *separator)
{
      char **search_dirs;
      char **iter;

      search_dirs = g_strsplit (path, separator, -1);
    
      iter = search_dirs;
      while (*iter)
        {
          debug_spew ("Adding directory '%s' from PKG_CONFIG_PATH\n",
                      *iter);
          add_search_dir (*iter);
          
          ++iter;
        }
      
      g_strfreev (search_dirs);
}

#ifdef G_OS_WIN32
/* Guard against .pc file being installed with UPPER CASE name */
# define FOLD(x) tolower(x)
# define FOLDCMP(a, b) g_ascii_strcasecmp (a, b)
#else
# define FOLD(x) (x)
# define FOLDCMP(a, b) strcmp (a, b)
#endif

#define EXT_LEN 3

static gboolean
ends_in_dotpc (const char *str)
{
  int len = strlen (str);
  
  if (len > EXT_LEN &&
      str[len - 3] == '.' &&
      FOLD (str[len - 2]) == 'p' &&
      FOLD (str[len - 1]) == 'c')
    return TRUE;
  else
    return FALSE;
}

/* strlen ("-uninstalled") */
#define UNINSTALLED_LEN 12

gboolean
name_ends_in_uninstalled (const char *str)
{
  int len = strlen (str);
  
  if (len > UNINSTALLED_LEN &&
      FOLDCMP ((str + len - UNINSTALLED_LEN), "-uninstalled") == 0)
    return TRUE;
  else
    return FALSE;
}

static Package *
internal_get_package (const char *name, gboolean warn);

/* Look for .pc files in the given directory and add them into
 * locations, ignoring duplicates
 */
static void
scan_dir (char *dirname)
{
  GDir *dir;
  const gchar *filename;

  int dirnamelen = strlen (dirname);
  /* Use a copy of dirname cause Win32 opendir doesn't like
   * superfluous trailing (back)slashes in the directory name.
   */
  char *dirname_copy = g_strdup (dirname);

  if (dirnamelen > 1 && dirname[dirnamelen-1] == G_DIR_SEPARATOR)
    {
      dirnamelen--;
      dirname_copy[dirnamelen] = '\0';
    }
#ifdef G_OS_WIN32
    {
      gchar *p;
      /* Turn backslashes into slashes or
       * g_shell_parse_argv() will eat them when ${prefix}
       * has been expanded in parse_libs().
       */
      p = dirname;
      while (*p)
        {
          if (*p == '\\')
            *p = '/';
          p++;
        }
    }
#endif
  dir = g_dir_open (dirname_copy, 0 , NULL);
  g_free (dirname_copy);

  if (!dir)
    {
      debug_spew ("Cannot open directory '%s' in package search path: %s\n",
                  dirname, g_strerror (errno));
      return;
    }

  debug_spew ("Scanning directory '%s'\n", dirname);

  while ((filename = g_dir_read_name(dir)))
    {
      char *path = g_build_filename (dirname, filename, NULL);
      internal_get_package (path, FALSE);
      g_free (path);
    }
  g_dir_close (dir);
}

static Package *
add_virtual_pkgconfig_package (void)
{
  Package *pkg = NULL;

  pkg = g_new0 (Package, 1);

  pkg->key = g_strdup ("pkg-config");
  pkg->version = g_strdup (VERSION);
  pkg->name = g_strdup ("pkg-config");
  pkg->description = g_strdup ("pkg-config is a system for managing "
			       "compile/link flags for libraries");
  pkg->url = g_strdup ("http://pkg-config.freedesktop.org/");

  if (pkg->vars == NULL)
    pkg->vars = g_hash_table_new (g_str_hash, g_str_equal);
  g_hash_table_insert (pkg->vars, "pc_path", pkg_config_pc_path);

  debug_spew ("Adding virtual 'pkg-config' package to list of known packages\n");
  g_hash_table_insert (packages, pkg->key, pkg);

  return pkg;
}

void
package_init (gboolean want_list)
{
  if (packages)
    return;
      
  packages = g_hash_table_new (g_str_hash, g_str_equal);

  if (want_list)
    g_list_foreach (search_dirs, (GFunc)scan_dir, NULL);
  else
    /* Should not add virtual pkgconfig package when listing to be
     * compatible with old code that only listed packages from real
     * files */
    add_virtual_pkgconfig_package ();
}

static Package *
internal_get_package (const char *name, gboolean warn)
{
  Package *pkg = NULL;
  char *key = NULL;
  char *location = NULL;
  unsigned int path_position = 0;
  GList *iter;
  GList *dir_iter;
  
  pkg = g_hash_table_lookup (packages, name);

  if (pkg)
    return pkg;

  debug_spew ("Looking for package '%s'\n", name);
  
  /* treat "name" as a filename if it ends in .pc and exists */
  if ( ends_in_dotpc (name) )
    {
      debug_spew ("Considering '%s' to be a filename rather than a package name\n", name);
      location = g_strdup (name);
      key = g_strdup (name);
    }
  else
    {
      /* See if we should auto-prefer the uninstalled version */
      if (!disable_uninstalled &&
          !name_ends_in_uninstalled (name))
        {
          char *un;

          un = g_strconcat (name, "-uninstalled", NULL);

          pkg = internal_get_package (un, FALSE);

          g_free (un);
          
          if (pkg)
            {
              debug_spew ("Preferring uninstalled version of package '%s'\n", name);
              return pkg;
            }
        }
      
      for (dir_iter = search_dirs; dir_iter != NULL;
           dir_iter = g_list_next (dir_iter))
        {
          path_position++;
          location = g_strdup_printf ("%s%c%s.pc", (char*)dir_iter->data,
                                      G_DIR_SEPARATOR, name);
          if (g_file_test (location, G_FILE_TEST_IS_REGULAR))
            break;
          g_free (location);
          location = NULL;
        }

    }
  
  if (location == NULL)
    {
      if (warn)
        verbose_error ("Package %s was not found in the pkg-config search path.\n"
                       "Perhaps you should add the directory containing `%s.pc'\n"
                       "to the PKG_CONFIG_PATH environment variable\n",
                       name, name);

      return NULL;
    }

  if (key == NULL)
    key = g_strdup (name);
  else
    {
      /* need to strip package name out of the filename */
      key = g_path_get_basename (name);
      key[strlen (key) - EXT_LEN] = '\0';
    }

  debug_spew ("Reading '%s' from file '%s'\n", name, location);
  pkg = parse_package_file (key, location, ignore_requires,
                            ignore_private_libs, ignore_requires_private);
  g_free (key);

  if (pkg != NULL && strstr (location, "uninstalled.pc"))
    pkg->uninstalled = TRUE;

  g_free (location);

  if (pkg == NULL)
    {
      debug_spew ("Failed to parse '%s'\n", location);
      return NULL;
    }

  pkg->path_position = path_position;

  debug_spew ("Path position of '%s' is %d\n",
              pkg->key, pkg->path_position);
  
  debug_spew ("Adding '%s' to list of known packages\n", pkg->key);
  g_hash_table_insert (packages, pkg->key, pkg);

  /* pull in Requires packages */
  for (iter = pkg->requires_entries; iter != NULL; iter = g_list_next (iter))
    {
      Package *req;
      RequiredVersion *ver = iter->data;

      debug_spew ("Searching for '%s' requirement '%s'\n",
                  pkg->key, ver->name);
      req = internal_get_package (ver->name, warn);
      if (req == NULL)
        {
          verbose_error ("Package '%s', required by '%s', not found\n",
                         ver->name, pkg->key);
          exit (1);
        }

      if (pkg->required_versions == NULL)
        pkg->required_versions = g_hash_table_new (g_str_hash, g_str_equal);

      g_hash_table_insert (pkg->required_versions, ver->name, ver);
      pkg->requires = g_list_prepend (pkg->requires, req);
    }

  /* pull in Requires.private packages */
  for (iter = pkg->requires_private_entries; iter != NULL;
       iter = g_list_next (iter))
    {
      Package *req;
      RequiredVersion *ver = iter->data;

      debug_spew ("Searching for '%s' private requirement '%s'\n",
                  pkg->key, ver->name);
      req = internal_get_package (ver->name, warn);
      if (req == NULL)
        {
          verbose_error ("Package '%s', required by '%s', not found\n",
			 ver->name, pkg->key);
          exit (1);
        }

      if (pkg->required_versions == NULL)
        pkg->required_versions = g_hash_table_new (g_str_hash, g_str_equal);

      g_hash_table_insert (pkg->required_versions, ver->name, ver);
      pkg->requires_private = g_list_prepend (pkg->requires_private, req);
    }

  /* make requires_private include a copy of the public requires too */
  pkg->requires_private = g_list_concat (g_list_copy (pkg->requires),
                                         pkg->requires_private);

  pkg->requires = g_list_reverse (pkg->requires);
  pkg->requires_private = g_list_reverse (pkg->requires_private);

  verify_package (pkg);

  return pkg;
}

Package *
get_package (const char *name)
{
  return internal_get_package (name, TRUE);
}

Package *
get_package_quiet (const char *name)
{
  return internal_get_package (name, FALSE);
}

/* Strip consecutive duplicate arguments in the flag list. */
static GList *
flag_list_strip_duplicates (GList *list)
{
  GList *tmp;

  /* Start at the 2nd element of the list so we don't have to check for an
   * existing previous element. */
  for (tmp = g_list_next (list); tmp != NULL; tmp = g_list_next (tmp))
    {
      Flag *cur = tmp->data;
      Flag *prev = tmp->prev->data;

      if (cur->type == prev->type && g_strcmp0 (cur->arg, prev->arg) == 0)
        {
          /* Remove the duplicate flag from the list and move to the last
           * element to prepare for the next iteration. */
          GList *dup = tmp;

          debug_spew (" removing duplicate \"%s\"\n", cur->arg);
          tmp = g_list_previous (tmp);
          list = g_list_remove_link (list, dup);
        }
    }

  return list;
}

static char *
flag_list_to_string (GList *list)
{
  GList *tmp;
  GString *str = g_string_new ("");
  char *retval;
  
  tmp = list;
  while (tmp != NULL) {
    Flag *flag = tmp->data;
    char *tmpstr = flag->arg;

    if (pcsysrootdir != NULL && flag->type & (CFLAGS_I | LIBS_L)) {
      /* Handle non-I Cflags like -isystem */
      if (flag->type & CFLAGS_I && strncmp (tmpstr, "-I", 2) != 0) {
        char *space = strchr (tmpstr, ' ');

        /* Ensure this has a separate arg */
        g_assert (space != NULL && space[1] != '\0');
        g_string_append_len (str, tmpstr, space - tmpstr + 1);
        g_string_append (str, pcsysrootdir);
        g_string_append (str, space + 1);
      } else {
        g_string_append_c (str, '-');
        g_string_append_c (str, tmpstr[1]);
        g_string_append (str, pcsysrootdir);
        g_string_append (str, tmpstr+2);
      }
    } else {
      g_string_append (str, tmpstr);
    }
    g_string_append_c (str, ' ');
    tmp = g_list_next (tmp);
  }

  retval = str->str;
  g_string_free (str, FALSE);

  return retval;
}

static int
pathposcmp (gconstpointer a, gconstpointer b)
{
  const Package *pa = a;
  const Package *pb = b;
  
  if (pa->path_position < pb->path_position)
    return -1;
  else if (pa->path_position > pb->path_position)
    return 1;
  else
    return 0;
}

static void
spew_package_list (const char *name,
                   GList     *list)
{
  GList *tmp;

  debug_spew (" %s:", name);

  tmp = list;
  while (tmp != NULL)
    {
      Package *pkg = tmp->data;
      debug_spew (" %s", pkg->key);
      tmp = tmp->next;
    }
  debug_spew ("\n");
}


static GList *
packages_sort_by_path_position (GList *list)
{
  return g_list_sort (list, pathposcmp);
}

/* Construct a topological sort of all required packages.
 *
 * This is a depth first search starting from the right.  The output 'listp' is
 * in reverse order, with the first node reached in the depth first search at
 * the end of the list.  Previously visited nodes are skipped.  The result is
 * a list of packages such that each packages is listed once and comes before
 * any package that it depends on.
 */
static void
recursive_fill_list (Package *pkg, gboolean include_private,
                     GHashTable *visited, GList **listp)
{
  GList *tmp;

  /*
   * If the package has already been visited, then it is already in 'listp' and
   * we can skip it. Additionally, this allows circular requires loops to be
   * broken.
   */
  if (g_hash_table_lookup_extended (visited, pkg->key, NULL, NULL))
    {
      debug_spew ("Package %s already in requires chain, skipping\n",
                  pkg->key);
      return;
    }
  /* record this package in the dependency chain */
  else
    {
      g_hash_table_replace (visited, pkg->key, pkg->key);
    }

  /* Start from the end of the required package list to maintain order since
   * the recursive list is built by prepending. */
  tmp = include_private ? pkg->requires_private : pkg->requires;
  for (tmp = g_list_last (tmp); tmp != NULL; tmp = g_list_previous (tmp))
    recursive_fill_list (tmp->data, include_private, visited, listp);

  *listp = g_list_prepend (*listp, pkg);
}

/* merge the flags from the individual packages */
static GList *
merge_flag_lists (GList *packages, FlagType type)
{
  GList *last = NULL;
  GList *merged = NULL;

  /* keep track of the last element to avoid traversing the whole list */
  for (; packages != NULL; packages = g_list_next (packages))
    {
      Package *pkg = packages->data;
      GList *flags = (type & LIBS_ANY) ? pkg->libs : pkg->cflags;

      /* manually copy the elements so we can keep track of the end */
      for (; flags != NULL; flags = g_list_next (flags))
        {
          Flag *flag = flags->data;

          if (flag->type & type)
            {
              if (last == NULL)
                {
                  merged = g_list_prepend (NULL, flags->data);
                  last = merged;
                }
              else
                last = g_list_next (g_list_append (last, flags->data));
            }
        }
    }

  return merged;
}

static GList *
fill_list (GList *packages, FlagType type,
           gboolean in_path_order, gboolean include_private)
{
  GList *tmp;
  GList *expanded = NULL;
  GList *flags;
  GHashTable *visited;

  /* Start from the end of the requested package list to maintain order since
   * the recursive list is built by prepending. */
  visited = g_hash_table_new (g_str_hash, g_str_equal);
  for (tmp = g_list_last (packages); tmp != NULL; tmp = g_list_previous (tmp))
    recursive_fill_list (tmp->data, include_private, visited, &expanded);
  g_hash_table_destroy (visited);
  spew_package_list ("post-recurse", expanded);

  if (in_path_order)
    {
      spew_package_list ("original", expanded);
      expanded = packages_sort_by_path_position (expanded);
      spew_package_list ("  sorted", expanded);
    }

  flags = merge_flag_lists (expanded, type);
  g_list_free (expanded);

  return flags;
}

static GList *
add_env_variable_to_list (GList *list, const gchar *env)
{
  gchar **values;
  gint i;

  values = g_strsplit (env, G_SEARCHPATH_SEPARATOR_S, 0);
  for (i = 0; values[i] != NULL; i++)
    {
      list = g_list_append (list, g_strdup (values[i]));
    }
  g_strfreev (values);

  return list;
}

/* Well known compiler include path environment variables. These are
 * used to find additional system include paths to remove. See
 * https://gcc.gnu.org/onlinedocs/gcc/Environment-Variables.html. */
static const gchar *gcc_include_envvars[] = {
  "CPATH",
  "C_INCLUDE_PATH",
  "CPP_INCLUDE_PATH",
  NULL
};

#ifdef G_OS_WIN32
/* MSVC include path environment variables. See
 * https://msdn.microsoft.com/en-us/library/73f9s62w.aspx. */
static const gchar *msvc_include_envvars[] = {
  "INCLUDE",
  NULL
};
#endif

static void
verify_package (Package *pkg)
{
  GList *requires = NULL;
  GList *conflicts = NULL;
  GList *system_directories = NULL;
  GList *iter;
  GList *requires_iter;
  GList *conflicts_iter;
  GList *system_dir_iter = NULL;
  GHashTable *visited;
  int count;
  const gchar *search_path;
  const gchar **include_envvars;
  const gchar **var;

  /* Be sure we have the required fields */

  if (pkg->key == NULL)
    {
      fprintf (stderr,
               "Internal pkg-config error, package with no key, please file a bug report\n");
      exit (1);
    }
  
  if (pkg->name == NULL)
    {
      verbose_error ("Package '%s' has no Name: field\n",
                     pkg->key);
      exit (1);
    }

  if (pkg->version == NULL)
    {
      verbose_error ("Package '%s' has no Version: field\n",
                     pkg->key);
      exit (1);
    }

  if (pkg->description == NULL)
    {
      verbose_error ("Package '%s' has no Description: field\n",
                     pkg->key);
      exit (1);
    }
  
  /* Make sure we have the right version for all requirements */

  iter = pkg->requires_private;

  while (iter != NULL)
    {
      Package *req = iter->data;
      RequiredVersion *ver = NULL;

      if (pkg->required_versions)
        ver = g_hash_table_lookup (pkg->required_versions,
                                   req->key);

      if (ver)
        {
          if (!version_test (ver->comparison, req->version, ver->version))
            {
              verbose_error ("Package '%s' requires '%s %s %s' but version of %s is %s\n",
                             pkg->key, req->key,
                             comparison_to_str (ver->comparison),
                             ver->version,
                             req->key,
                             req->version);
              if (req->url)
                verbose_error ("You may find new versions of %s at %s\n",
                               req->name, req->url);

              exit (1);
            }
        }
                                   
      iter = g_list_next (iter);
    }

  /* Make sure we didn't drag in any conflicts via Requires
   * (inefficient algorithm, who cares)
   */
  visited = g_hash_table_new (g_str_hash, g_str_equal);
  recursive_fill_list (pkg, TRUE, visited, &requires);
  g_hash_table_destroy (visited);
  conflicts = pkg->conflicts;

  requires_iter = requires;
  while (requires_iter != NULL)
    {
      Package *req = requires_iter->data;
      
      conflicts_iter = conflicts;

      while (conflicts_iter != NULL)
        {
          RequiredVersion *ver = conflicts_iter->data;

	  if (strcmp (ver->name, req->key) == 0 &&
	      version_test (ver->comparison,
			    req->version,
			    ver->version))
            {
              verbose_error ("Version %s of %s creates a conflict.\n"
                             "(%s %s %s conflicts with %s %s)\n",
                             req->version, req->key,
                             ver->name,
                             comparison_to_str (ver->comparison),
                             ver->version ? ver->version : "(any)",
                             ver->owner->key,
                             ver->owner->version);

              exit (1);
            }

          conflicts_iter = g_list_next (conflicts_iter);
        }
      
      requires_iter = g_list_next (requires_iter);
    }
  
  g_list_free (requires);

  /* We make a list of system directories that compilers expect so we
   * can remove them.
   */

  search_path = g_getenv ("PKG_CONFIG_SYSTEM_INCLUDE_PATH");

  if (search_path == NULL)
    {
      search_path = PKG_CONFIG_SYSTEM_INCLUDE_PATH;
    }

  system_directories = add_env_variable_to_list (system_directories, search_path);

#ifdef G_OS_WIN32
  include_envvars = msvc_syntax ? msvc_include_envvars : gcc_include_envvars;
#else
  include_envvars = gcc_include_envvars;
#endif
  for (var = include_envvars; *var != NULL; var++)
    {
      search_path = g_getenv (*var);
      if (search_path != NULL)
        system_directories = add_env_variable_to_list (system_directories, search_path);
    }

  count = 0;
  for (iter = pkg->cflags; iter != NULL; iter = g_list_next (iter))
    {
      gint offset = 0;
      Flag *flag = iter->data;

      if (!(flag->type & CFLAGS_I))
        continue;

      /* Handle the system cflags. We put things in canonical
       * -I/usr/include (vs. -I /usr/include) format, but if someone
       * changes it later we may as well be robust.
       *
       * Note that the -i* flags are left out of this handling since
       * they're intended to adjust the system cflags behavior.
       */
      if (((strncmp (flag->arg, "-I", 2) == 0) && (offset = 2))||
          ((strncmp (flag->arg, "-I ", 3) == 0) && (offset = 3)))
        {
	  if (offset == 0)
	    {
	      iter = iter->next;
	      continue;
	    }

	  system_dir_iter = system_directories;
	  while (system_dir_iter != NULL)
	    {
	      if (strcmp (system_dir_iter->data,
                          ((char*)flag->arg) + offset) == 0)
		{
                  debug_spew ("Package %s has %s in Cflags\n",
			      pkg->key, (gchar *)flag->arg);
		  if (g_getenv ("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS") == NULL)
		    {
                      debug_spew ("Removing %s from cflags for %s\n",
                                  flag->arg, pkg->key);
		      ++count;
		      iter->data = NULL;

		      break;
		    }
		}
	      system_dir_iter = system_dir_iter->next;
	    }
        }
    }

  while (count)
    {
      pkg->cflags = g_list_remove (pkg->cflags, NULL);
      --count;
    }

  g_list_foreach (system_directories, (GFunc) g_free, NULL);
  g_list_free (system_directories);

  system_directories = NULL;

  search_path = g_getenv ("PKG_CONFIG_SYSTEM_LIBRARY_PATH");

  if (search_path == NULL)
    {
      search_path = PKG_CONFIG_SYSTEM_LIBRARY_PATH;
    }

  system_directories = add_env_variable_to_list (system_directories, search_path);

  count = 0;
  for (iter = pkg->libs; iter != NULL; iter = g_list_next (iter))
    {
      GList *system_dir_iter = system_directories;
      Flag *flag = iter->data;

      if (!(flag->type & LIBS_L))
        continue;

      while (system_dir_iter != NULL)
        {
          gboolean is_system = FALSE;
          const char *linker_arg = flag->arg;
          const char *system_libpath = system_dir_iter->data;

          if (strncmp (linker_arg, "-L ", 3) == 0 &&
              strcmp (linker_arg + 3, system_libpath) == 0)
            is_system = TRUE;
          else if (strncmp (linker_arg, "-L", 2) == 0 &&
              strcmp (linker_arg + 2, system_libpath) == 0)
            is_system = TRUE;
          if (is_system)
            {
              debug_spew ("Package %s has -L %s in Libs\n",
                          pkg->key, system_libpath);
              if (g_getenv ("PKG_CONFIG_ALLOW_SYSTEM_LIBS") == NULL)
                {
                  iter->data = NULL;
                  ++count;
                  debug_spew ("Removing -L %s from libs for %s\n",
                              system_libpath, pkg->key);
                  break;
                }
            }
          system_dir_iter = system_dir_iter->next;
        }
    }
  g_list_free (system_directories);

  while (count)
    {
      pkg->libs = g_list_remove (pkg->libs, NULL);
      --count;
    }
}

/* Create a merged list of required packages and retrieve the flags from them.
 * Strip the duplicates from the flags list. The sorting and stripping can be
 * done in one of two ways: packages sorted by position in the pkg-config path
 * and stripping done from the beginning of the list, or packages sorted from
 * most dependent to least dependent and stripping from the end of the list.
 * The former is done for -I/-L flags, and the latter for all others.
 */
static char *
get_multi_merged (GList *pkgs, FlagType type, gboolean in_path_order,
                  gboolean include_private)
{
  GList *list;
  char *retval;

  list = fill_list (pkgs, type, in_path_order, include_private);
  list = flag_list_strip_duplicates (list);
  retval = flag_list_to_string (list);
  g_list_free (list);

  return retval;
}

char *
packages_get_flags (GList *pkgs, FlagType flags)
{
  GString *str;
  char *cur;

  str = g_string_new (NULL);

  /* sort packages in path order for -L/-I, dependency order otherwise */
  if (flags & CFLAGS_OTHER)
    {
      cur = get_multi_merged (pkgs, CFLAGS_OTHER, FALSE, TRUE);
      debug_spew ("adding CFLAGS_OTHER string \"%s\"\n", cur);
      g_string_append (str, cur);
      g_free (cur);
    }
  if (flags & CFLAGS_I)
    {
      cur = get_multi_merged (pkgs, CFLAGS_I, TRUE, TRUE);
      debug_spew ("adding CFLAGS_I string \"%s\"\n", cur);
      g_string_append (str, cur);
      g_free (cur);
    }
  if (flags & LIBS_L)
    {
      cur = get_multi_merged (pkgs, LIBS_L, TRUE, !ignore_private_libs);
      debug_spew ("adding LIBS_L string \"%s\"\n", cur);
      g_string_append (str, cur);
      g_free (cur);
    }
  if (flags & (LIBS_OTHER | LIBS_l))
    {
      cur = get_multi_merged (pkgs, flags & (LIBS_OTHER | LIBS_l), FALSE,
                              !ignore_private_libs);
      debug_spew ("adding LIBS_OTHER | LIBS_l string \"%s\"\n", cur);
      g_string_append (str, cur);
      g_free (cur);
    }

  /* Strip trailing space. */
  if (str->len > 0 && str->str[str->len - 1] == ' ')
    g_string_truncate (str, str->len - 1);

  debug_spew ("returning flags string \"%s\"\n", str->str);
  return g_string_free (str, FALSE);
}

void
define_global_variable (const char *varname,
                        const char *varval)
{
  if (globals == NULL)
    globals = g_hash_table_new (g_str_hash, g_str_equal);

  if (g_hash_table_lookup (globals, varname))
    {
      verbose_error ("Variable '%s' defined twice globally\n", varname);
      exit (1);
    }
  
  g_hash_table_insert (globals, g_strdup (varname), g_strdup (varval));
      
  debug_spew ("Global variable definition '%s' = '%s'\n",
              varname, varval);
}

char *
var_to_env_var (const char *pkg, const char *var)
{
  char *new = g_strconcat ("PKG_CONFIG_", pkg, "_", var, NULL);
  char *p;
  for (p = new; *p != 0; p++)
    {
      char c = g_ascii_toupper (*p);

      if (!g_ascii_isalnum (c))
        c = '_';

      *p = c;
    }

  return new;
}

char *
package_get_var (Package *pkg,
                 const char *var)
{
  char *varval = NULL;

  if (globals)
    varval = g_strdup (g_hash_table_lookup (globals, var));

  /* Allow overriding specific variables using an environment variable of the
   * form PKG_CONFIG_$PACKAGENAME_$VARIABLE
   */
  if (pkg->key)
    {
      char *env_var = var_to_env_var (pkg->key, var);
      const char *env_var_content = g_getenv (env_var);
      g_free (env_var);
      if (env_var_content)
        {
          debug_spew ("Overriding variable '%s' from environment\n", var);
          return g_strdup (env_var_content);
        }
    }


  if (varval == NULL && pkg->vars)
    varval = g_strdup (g_hash_table_lookup (pkg->vars, var));

  return varval;
}

char *
packages_get_var (GList      *pkgs,
                  const char *varname)
{
  GList *tmp;
  GString *str;

  str = g_string_new (NULL);

  tmp = pkgs;
  while (tmp != NULL)
    {
      Package *pkg = tmp->data;
      char *var;

      var = parse_package_variable (pkg, varname);
      if (var)
        {
          if (str->len > 0)
            g_string_append_c (str, ' ');
          g_string_append (str, var);
          g_free (var);
        }

      tmp = g_list_next (tmp);
    }

  return g_string_free (str, FALSE);
}

int
compare_versions (const char * a, const char *b)
{
  return rpmvercmp (a, b);
}

gboolean
version_test (ComparisonType comparison,
              const char *a,
              const char *b)
{
  switch (comparison)
    {
    case LESS_THAN:
      return compare_versions (a, b) < 0;
      break;

    case GREATER_THAN:
      return compare_versions (a, b) > 0;
      break;

    case LESS_THAN_EQUAL:
      return compare_versions (a, b) <= 0;
      break;

    case GREATER_THAN_EQUAL:
      return compare_versions (a, b) >= 0;
      break;

    case EQUAL:
      return compare_versions (a, b) == 0;
      break;

    case NOT_EQUAL:
      return compare_versions (a, b) != 0;
      break;

    case ALWAYS_MATCH:
      return TRUE;
      break;
      
    default:
      g_assert_not_reached ();
      break;
    }

  return FALSE;
}

const char *
comparison_to_str (ComparisonType comparison)
{
  switch (comparison)
    {
    case LESS_THAN:
      return "<";
      break;

    case GREATER_THAN:
      return ">";
      break;

    case LESS_THAN_EQUAL:
      return "<=";
      break;

    case GREATER_THAN_EQUAL:
      return ">=";
      break;

    case EQUAL:
      return "=";
      break;

    case NOT_EQUAL:
      return "!=";
      break;

    case ALWAYS_MATCH:
      return "(any)";
      break;
      
    default:
      g_assert_not_reached ();
      break;
    }

  return "???";
}

static gint
packages_sort_cb (gconstpointer a,
                  gconstpointer b)
{
  const Package *package_a = *((Package **) a);
  const Package *package_b = *((Package **) b);

  return g_strcmp0 (package_a->key, package_b->key);
}

void
print_package_list (void)
{
  gsize mlen = 0;
  GPtrArray *packages_array = NULL;
  GHashTableIter iter;
  gpointer key, value;
  guint i;

  ignore_requires = TRUE;
  ignore_requires_private = TRUE;

  /* Add the packages to a pointer array and sort by pkg->key first, to give
   * deterministic output. While doing that, work out the maximum key length
   * so we can pad the output correctly. */
  packages_array = g_ptr_array_sized_new (g_hash_table_size (packages));
  g_hash_table_iter_init (&iter, packages);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      g_ptr_array_add (packages_array, value);
      mlen = MAX (mlen, strlen (key));
    }

  g_ptr_array_sort (packages_array, packages_sort_cb);

  for (i = 0; i < packages_array->len; i++)
    {
        Package *pkg = g_ptr_array_index (packages_array, i);
        char *pad;

        pad = g_strnfill (mlen + 1 - strlen (pkg->key), ' ');

        printf ("%s%s%s - %s\n",
                pkg->key, pad, pkg->name, pkg->description);

        g_free (pad);
    }

  g_ptr_array_free (packages_array, TRUE);
}

void
enable_private_libs(void)
{
  ignore_private_libs = FALSE;
}

void
disable_private_libs(void)
{
  ignore_private_libs = TRUE;
}

void
enable_requires(void)
{
  ignore_requires = FALSE;
}

void
disable_requires(void)
{
  ignore_requires = TRUE;
}

void
enable_requires_private(void)
{
  ignore_requires_private = FALSE;
}

void
disable_requires_private(void)
{
  ignore_requires_private = TRUE;
}
