/* $Id: packdep.c,v 1.11 2001/12/27 17:43:45 richdawe Exp $ */

/*
 *  packdep.c - Package dependency functions for zippo
 *  Copyright (C) 2000, 2001 by Richard Dawe
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "common.h"

#include <string.h>

#include <libzippo/package.h>
#include <libzippo/packlist.h>
#include <libzippo/packdep.h>

/* Browser context for dep_exists_browser() */
typedef struct {
  const PACKAGE_INFO  *package;
  int                  dep_type;
  PACKAGE_INFO       **matched_package;
  PACKAGE_DEP        **matched_dep;
  int                  matched_dep_idx;
  int                  matched_dep_max;
} dep_exists_context_t;

/* ------------
 * - find_dep -
 * ------------ */

/* This package browser adds cross-references to a package list. It does this
 * by matching dependencies with package list entries that satisfy them. */

static int
find_dep(PACKAGE_INFO *p, void *context)
{
  PACKAGE_INFO *  pack_list = (PACKAGE_INFO *)context;
  PACKAGE_INFO *  pack_ptr;
  PACKAGE_DEP  ** deps;
  PACKAGE_DEP  *  dep;
  int             matched_provision = 0;
  int             ret;

  /* When searching for provides, these contain pointers to the dependencies
   * of the current package from the package list. */
  PACKAGE_DEP  ** deps_p;
  PACKAGE_DEP  *  dep_p;

  for (deps = p->deps; (deps != NULL) && (*deps != NULL); deps++) {
    dep = *deps;

    for (pack_ptr = pack_list;
	 pack_ptr != NULL;
	 pack_ptr = pack_ptr->q_forw) {
      /* Check that we're not trying to match the package with itself. */
      if (pack_ptr == p)
	continue;     

      /*
       * The following describes how the dependencies are matched to
       * the packages that satisfy them. The dependencies fall into
       * the following categories:
       *
       * - 'provides' dependencies are not really dependencies, so they are
       *   always considered to be satisfied.
       *
       * - 'requires' dependencies can depend on a real package or a virtual
       *   package. A virtual package contains 'provides'. This means checking
       *   'requires' dependencies is carried out in two steps - virtual
       *   packages (by looking at 'provides' dependencies) and then real
       *   packages (by looking at the name, version of the package).
       *
       * When looking for a match on a package or 'provides' dependency:
       *
       * - the names are compared;
       * - if no version information is specified in the dependency, then
       *   a name match is enough, otherwise the versions are also compared.
       *
       * For 'provides' dependencies, the qualifier is also checked. A package
       * may require a particular subset of functionality provided. 
       */

      /* - Requires dependencies - */

      /* Requires dependencies may require something that is provided using
       * 'provides'. So, scan the 'provides' list of the package. */
      matched_provision = 0;

      for (deps_p = pack_ptr->deps;
	   (deps_p != NULL) && (*deps_p != NULL);
	   deps_p++) {
	dep_p = *deps_p;

	/* Must be a provide */
	if (dep_p->dep_type != PACKAGE_DEP_PROVIDES)
	  continue;

	/* Names must match */
	if (strcasecmp(dep_p->name, dep->name) != 0)
	  continue;

	/* Version info? */
	if ((dep->op == DEP_OP_NONE) && !dep->version.has_version) {
	  /* No => match */
	  dep->dep = pack_ptr;
	  matched_provision = 1;
	  break;
	}

	/* Compare versions */
	ret = package_vercmp(&dep_p->version, &dep->version);

	/* Check whether the dependency is satisfied by the version
	 * comparison & operator combination. */
	switch(dep->op) {
	case DEP_OP_EQUAL:
	  ret = (ret == 0);
	  break;

	case DEP_OP_LESSEQUAL:
	  ret = (ret <= 0);
	  break;

	case DEP_OP_GREATEREQUAL:
	  ret = (ret >= 0);
	  break;
	
	case DEP_OP_NOTEQUAL:
	  ret = (ret != 0);
	  break;

	case DEP_OP_LESS:
	  ret = (ret <  0);
	  break;

	case DEP_OP_GREATER:
	  ret = (ret >  0);
	  break;

	  /* Fail by default */
	default:
	  ret = 0;
	  break;
	}

	if (!ret)
	  continue;

	/* Qualifier? */
	if (dep_p->qualifier == NULL) {
	  /* None required, full feature */
	  dep->dep = pack_ptr;
	  matched_provision = 1;
	  break;
	} else {
	  if (strcmp(dep_p->qualifier, dep->qualifier) == 0) {
	    dep->dep = pack_ptr;
	    matched_provision = 1;
	    break;
	  }
	}
      }

      if (matched_provision) {
	/* A provision satisfied this dependency, so move onto next dep. */
	continue;
      }

      /* - Normal dependencies, aka "the rest" ;) - */

      /* Skip provides */
      if (dep->dep_type == PACKAGE_DEP_PROVIDES)
	continue;

      /* Names must match */
      if (strcasecmp(pack_ptr->name, dep->name) != 0)
	continue;

      /* Version info? */
      if ((dep->op == DEP_OP_NONE) && !dep->version.has_version) {
	/* No => match */
	dep->dep = pack_ptr;
	break;
      }

      /* Compare versions */
      ret = package_vercmp(&pack_ptr->version, &dep->version);

      /* Check whether the dependency is satisfied by the version
       * comparison & operator combination. */
      switch(dep->op) {
      case DEP_OP_EQUAL:
	ret = (ret == 0);
	break;

      case DEP_OP_LESSEQUAL:
	ret = (ret <= 0);
	break;

      case DEP_OP_GREATEREQUAL:
	ret = (ret >= 0);
	break;
	
      case DEP_OP_NOTEQUAL:
	ret = (ret != 0);
	break;

      case DEP_OP_LESS:
	ret = (ret <  0);
	break;

      case DEP_OP_GREATER:
	ret = (ret >  0);
	break;

	/* Fail by default */
      default:
	ret = 0;
	break;
      }

      if (!ret)
	continue;

      /* Match found */
      dep->dep = pack_ptr;
      break;
    }
  }

  /* Done */
  return 0;
}

/* -----------------
 * - packlist_xref -
 * ----------------- */

/* Generate cross-references between packages in the package list. */

int
packlist_xref (PACKAGE_INFO *packages)
{
  packlist_browse(packages, find_dep, packages);
  return(1);
}

/* ----------------
 * - package_xref -
 * ---------------- */

/* Generate cross-references between package and packages in the package list
 * 'packages'. */

int
package_xref (PACKAGE_INFO *packages, PACKAGE_INFO *package)
{
  /* Pass the package list in as the context. 'package' is the package list
   * to cross-reference, a single entry list. */
  packlist_browse(package, find_dep, packages);
  return(1);
}

/* ----------------------
 * - package_check_deps -
 * ---------------------- */

/*
 * This returns 1 when the spec'd dependencies are satisfied, 0 otherwise.
 * Up to failed_dep_max failures are returned in the (user supplied) array
 * failed. These dependency structures have pointers pointing to the packages
 * in 'packages' that failed, for later diagnosis/analysis.
 *
 * This function should only be called after packlist_xref() has been called
 * on the package list.
 */

int
package_check_deps (const PACKAGE_INFO *package,
		    const int dep_type,
		    PACKAGE_DEP **failed_dep,
		    const int failed_dep_max)
{
  PACKAGE_DEP **deps           = NULL;
  int           failed_dep_idx = 0;
  int           ret            = 1; /* OK by default */
  int           j;

  /* pflag = whether the packages in the dep should be present or not:
   * 1 = yes, 0 = no. */
  int pflag;

  /* Clear the failed arrays */
  for (j = 0; j < failed_dep_max; j++) { failed_dep[j] = NULL; }

  /* Check what dep_type we have. If the dep_type requires no dependency
   * checking, return appropriate status. Set the presence flag, based on
   * the dep_type.  */
  switch(dep_type) {
  case PACKAGE_DEP_REQUIRES:
  case PACKAGE_DEP_DEPENDS_ON:
    pflag = 1;
    break;

  case PACKAGE_DEP_CONFLICTS_WITH:
  case PACKAGE_DEP_REPLACES:
  case PACKAGE_DEP_INSTALL_BEFORE:
    pflag = 0;
    break;

  case PACKAGE_DEP_NONE:
  case PACKAGE_DEP_INSTALL_AFTER:
  case PACKAGE_DEP_PROVIDES:
    /* Handle no-op dep_types immediately. */
    return(1);
    break;

  default:
    /* Don't understand dep_type => fail. */
    return(0);
    break;
  }

  deps = ((PACKAGE_INFO *) package)->deps;

  /* No deps => success */
  if (deps == NULL)
    return(1);

  /* Check the dependencies. */
  for (j = 0; deps[j] != NULL; j++) {
    /* Right dep type? */
    if (deps[j]->dep_type != dep_type)
      continue;

    if (   ((deps[j]->dep != NULL) && !pflag) /* present & shouldn't be */
	|| ((deps[j]->dep == NULL) && pflag)  /* not present & should be */) {
      /* Add to the list of failed deps, if there's room. */
      if (failed_dep_idx < failed_dep_max) {
	ret = 0;
	failed_dep[failed_dep_idx] = deps[j];
	failed_dep_idx++;
      } else {
	/* No more room, so abort */
	break;
      }
    }
  }

  /* Done */  
  return(ret);
}

/* ----------------------
 * - dep_exists_browser -
 * ---------------------- */

static int
dep_exists_browser (PACKAGE_INFO *p, void *context_in)
{
  dep_exists_context_t *context = (dep_exists_context_t *) context_in;
  int j;

  /* Skip package that we're checking for. */
  if (p == context->package)
    return(0);

  /* No room, so abort */
  if (context->matched_dep_idx > context->matched_dep_max)
    return(0);

  /* Check deps of the right type */
  for (j = 0; p->deps[j] != NULL; j++) {    
    if (p->deps[j]->dep_type != context->dep_type)
      continue;

    if (p->deps[j]->dep != context->package)
      continue;

    /* Found a match */
    if (context->matched_dep_idx > context->matched_dep_max) {
      /* No room, so abort */
      break;
    }

    context->matched_package[context->matched_dep_idx] = p;
    context->matched_dep[context->matched_dep_idx]     = p->deps[j];
    context->matched_dep_idx++;
  }

  return(0);
}

/* ----------------------
 * - package_dep_exists -
 * ---------------------- */

/*
 * This function checks whether any of the packages in 'packages' has
 * a dependency on 'package'. It does this by comparing pointers, so
 * 'packages' must be a cross-referenced package list. Any matches are
 * put in 'matched_dep'.
 *
 * If a dependency with type 'dep_type' exists, 1 is returned, else 0.
 */

int
package_dep_exists (PACKAGE_INFO *packages,
		    PACKAGE_INFO *package,
		    const int dep_type,
		    PACKAGE_INFO **matched_package,
		    PACKAGE_DEP **matched_dep,
		    const int matched_dep_max)
{
  dep_exists_context_t context;
  int i;

  /* Clear the matched arrays */
  for (i = 0; i < matched_dep_max; i++) {
    matched_package[i] = NULL;
    matched_dep[i]     = NULL;
  }

  /* Set up context */
  memset(&context, 0, sizeof(context));

  context.package         = package;
  context.dep_type        = dep_type;
  context.matched_package = matched_package;
  context.matched_dep     = matched_dep;
  context.matched_dep_idx = 0;
  context.matched_dep_max = matched_dep_max;

  /* Use a browser function to enumerate all packages. */
  packlist_browse(packages, dep_exists_browser, (void *) &context);

  /* Got any matches? */
  if (matched_dep[0] != NULL)
    return(1);

  return(0);
}
