/* $Id: query.c,v 1.30 2002/07/21 18:23:45 richdawe Exp $ */

/*
 *  query.c - Query routines for pakke
 *  Copyright (C) 1999-2002 by Richard Dawe
 *      
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

/* libpakke includes */
#include <libpakke/package.h>
#include <libpakke/packlist.h>
#include <libpakke/packdep.h>
#include <libpakke/dsm.h>
#include <libpakke/mft.h>
#include <libpakke/util.h>
#include <libpakke/archive.h>

#include "pakke.h"
#include "query.h"
#include "md5hash.h"
#include "misc.h"

typedef struct {
  int                err;     /* 0 on success, otherwise an error code. */
  int                multi;   /* 0 => one or no packages; > 1 => multiple. */
  int                count;   /* Count of packages processed */
  int                matched; /* Number of packages matching query. */
  const PAKKE_QUERY *req;     /* pakke query request structure */
  const char        *db_path; /* pakke database path */
} q_display_context_t;

/* -------------------------
 * - query_display_browser -
 * ------------------------- */

/* TODO: Error handling */

static int
q_display_browser (PACKAGE_INFO *p, void *context_in)
{
  q_display_context_t *context = (q_display_context_t *) context_in;
  int                  status  = 0;
  int                  matched = 0;
  int                  ret     = QUERY_OK;

  if (context->req->mod == QM_LISTFILES) {
    /* List files for package */
    status = query_show_files((const char **) context->req->mft_path,
			      p, context->db_path, NULL);
    if (status > 0) {
      matched = 1;
      context->matched++;
    }
  } else if (context->req->mod == QM_HASFILE) {
    /* Find & display matching files in the specied MD5 hashes or manifest. */
    /* NB: It is not an error if none are found. */
    status = query_show_files((const char **) context->req->mft_path,
			      p, context->db_path, context->req->qname);
    if (status > 0) {
      matched = 1;
      context->matched++;
    }
  } else {
    int installed = 0;

    /* Is this the installed database or an available package? */
    if ((context->req->op == OP_QUERY) || (context->req->op == OP_QUERY_ALL))
      installed = 1;

    /* Normal info query */
    status = query_show_info(context->req->mod, context->req->verbosity,
			     p, installed,
			     context->req->prefix, context->req->name);
    if (!status) {
      /* Why has this failed? It should not. */
      ret = QUERY_FAILED;
    } else {
      matched = 1;
      context->matched++;
    }
  }

  /* If we're display more than one line of information and there's more
   * than one package, separate packages by a newline. */
  if ((context->req->mod != QM_NONE) && (context->multi) && matched)
    printf("\n");

  if (ret == QUERY_OK)
    context->count++;

  return( (ret == QUERY_OK) ? 0 /* continue browsing */ : 1 /* stop */ );
}

/* -----------------
 * - perform_query -
 * ----------------- */

int
perform_query (const PAKKE_QUERY *req)
{
  q_display_context_t   qd;
  DSM_FILE_ERROR       *dfe      = NULL;
  PACKAGE_INFO         *packages = NULL;  
  PACKAGE_INFO        **matched  = NULL;
  char db_path[PATH_MAX];
  int ret = 0;
  int i;

  memset(&qd, 0, sizeof(qd));

  /* Build the package list */
  packages = dsm_load_all((const char **) req->dsm_path, NULL, &dfe);
  packages = ver_load_all((const char **) req->mft_path, packages);

  packages = packlist_dedupe(packages);
  packlist_xref(packages);
	
  if (packages == NULL)
    die("Unable to obtain package list!");

  /* Display DSM errors. */
  if (dsm_file_errors_fatal(dfe))
    warn("Parsing failed for some DSM(s) in installed database");
	
  show_dsm_file_errors(dfe, req->verbosity);
  dsm_free_file_error_list(dfe);
  dfe = NULL;

  /* Build the DSM database path */
  strcpy(db_path, req->root);
  addforwardslash(db_path);
  strcat(db_path, PAKKE_DB_PREFIX);
  addforwardslash(db_path);

  /* Try matching on simple strings and user specifiers, etc. */
  /* TODO: Have option to enable regexp matches */
  matched = packlist_find(packages, req->name,
			  PACKLIST_FIND_SIMPLE|PACKLIST_FIND_USER);

  /* Packages found? */
  if (matched == NULL) {
    /* No */
    ret = QUERY_UNMATCHED;
  } else {
    /* Set up the context */
    qd.err     = 0;
    qd.req     = req;
    qd.db_path = db_path;
    qd.matched = qd.count = 0;

    if ((matched[0] != NULL) && (matched[1] != NULL))
      qd.multi = 1;
    else
      qd.multi = 0;

    /* Yes => display all matches */
    ret = QUERY_OK;

    for (i = 0; matched[i] != NULL; i++) {
      /* TODO: Return code? */
      q_display_browser(matched[i], (void *) &qd);
    }
  }  

  printf("%d package(s) matched\n\n", qd.count);

  /* Free memory */
  free(matched);
  packlist_free(packages);

  return(ret);
}

/* ----------------------
 * - perform_query_file -
 * ---------------------- */

int
perform_query_file (const PAKKE_QUERY *req)
{
  q_display_context_t   qd;
  /* Installed packages */
  PACKAGE_INFO         *packages = NULL;
  DSM_FILE_ERROR       *dfe      = NULL;
  /* Package file */
  PACKAGE_INFO          package;
  DSM_ERROR            *de  = NULL; /* List of errors in DSM parsing */
  char                  db_path[PATH_MAX];
  int                   ret;

  packages = dsm_load_all((const char **) req->dsm_path, NULL, &dfe);
  packages = ver_load_all((const char **) req->mft_path, packages);

  packages = packlist_dedupe(packages);
  packlist_xref(packages);
	
  if (packages == NULL) die("Unable to obtain package list!");

  /* Display DSM errors. */
  if (dsm_file_errors_fatal(dfe))
    warn("Parsing failed for some DSM(s) in installed database");
	
  show_dsm_file_errors(dfe, req->verbosity);
  dsm_free_file_error_list(dfe);
  dfe = NULL;

  /* Build the DSM database path */
  strcpy(db_path, req->root);
  addforwardslash(db_path);
  strcat(db_path, PAKKE_DB_PREFIX);
  addforwardslash(db_path);

  if (dsm_get_and_parse(req->name, &package, NULL, &de) != DSM_OK) {
    /* If there were any parsing errors, display them. */
    show_dsm_errors(req->name, de, req->verbosity);

    /* Tidy up */
    dsm_free_error_list(de);
    de = NULL;

    /* Can't continue without a valid DSM. */
    die("Unable to parse DSM!");
  }

  if (isarchive(req->name))
    strcpy(package.path, req->name);

  /* Resolve dependencies, so user can see which packages need to be
   * installed. */
  package_xref(packages, &package);
	
  /* Set up the context */
  qd.err     = 0;
  qd.req     = req;
  qd.db_path = db_path;
  qd.matched = qd.count = 0;
  qd.multi   = 0;

  ret = q_display_browser(&package, (void *) &qd);

  return(ret);
}

/* ---------------------
 * - perform_query_all -
 * --------------------- */

int
perform_query_all (const PAKKE_QUERY *req)
{  
  DSM_FILE_ERROR       *dfe      = NULL;
  PACKAGE_INFO         *packages = NULL;
  PACKAGE_INFO        **matched  = NULL;
  PACKAGE_INFO         *package  = NULL;
  q_display_context_t   qd;
  char                  db_path[PATH_MAX];
  int                   count;
  int                   ret;
  int                   i;

  memset(&qd, 0, sizeof(qd));

  /* Build the DSM database path */
  strcpy(db_path, req->root);
  addforwardslash(db_path);
  strcat(db_path, PAKKE_DB_PREFIX);
  addforwardslash(db_path);

  /* Parse all packages */
  packages = dsm_load_all((const char **) req->dsm_path, NULL, &dfe);
  packages = ver_load_all((const char **) req->mft_path, packages);

  packages = packlist_dedupe(packages);

  /* Resolve dependencies for installed packages */
  packlist_xref(packages);
	
  if (packages == NULL) die("Unable to obtain package list!");

  /* Display DSM errors. */
  if (dsm_file_errors_fatal(dfe))
    warn("Parsing failed for some DSM(s) in installed database");

  show_dsm_file_errors(dfe, req->verbosity);
  dsm_free_file_error_list(dfe);
  dfe = NULL;

  /* Query all or matched packages */
  if (req->name == NULL) {
    /* Set up the context */
    qd.err     = QUERY_OK;
    qd.req     = req;
    qd.db_path = db_path;
    qd.matched = qd.count = 0;

    if ((packages != NULL) && (packages->q_forw != NULL))
      qd.multi = 1;
    else
      qd.multi = 0;

    packlist_browse(packages, q_display_browser, (void *) &qd);

    if ((req->mod != QM_LISTFILES) && (req->mod != QM_HASFILE)) {
      printf("%d packages are installed\n\n", qd.count);
    }

    ret = qd.err;
  } else {
    /* Try matching on simple strings and user specifiers, etc. */
    /* TODO: Have option to enable regexp matches */
    matched = packlist_find(packages, req->name,
			    PACKLIST_FIND_SIMPLE|PACKLIST_FIND_USER);

    /* Packages found? */
    if (matched == NULL) {
      /* Set up the context */
      qd.err     = QUERY_UNMATCHED;
      qd.req     = req;
      qd.db_path = db_path;
      qd.matched = qd.count = 0;

      /* No => display nothing*/
    } else {
      /* Set up the context */
      qd.err     = QUERY_OK;
      qd.req     = req;
      qd.db_path = db_path;
      qd.matched = qd.count = 0;

      if ((matched[0] != NULL) && (matched[1] != NULL))
	qd.multi = 1;
      else
	qd.multi = 0;

      /* Yes => display all matches */
      for (i = 0; matched[i] != NULL; i++) {
	/* TODO: Return code? */
	q_display_browser(matched[i], (void *) &qd);
      }
    }

    /* Count number of installed packages */
    for (package = packages, count = 0;
	 package != NULL;
	 package = package->q_forw) {
      count++;
    }

    if ((req->mod != QM_LISTFILES) && (req->mod != QM_HASFILE)) {
      printf("%d packages matched; %d packages are installed\n\n",
	     qd.count, count);
    }

    ret = qd.err;
  }

  /* Free memory */
  packlist_free(packages);

  return(ret);
}

/* --------------
 * - avail_xref -
 * -------------- */

/* This is a support function for perform_query_all_avail(), which is used
 * to resolve dependencies between an available package and
 * the installed package list. */

static int
avail_xref (PACKAGE_INFO *package, void *context)
{
  PACKAGE_INFO *packages = (PACKAGE_INFO *) context;

  package_xref(packages, package);
  return(0);
}

/* ---------------------------
 * - perform_query_all_avail -
 * --------------------------- */

int
perform_query_all_avail (const PAKKE_QUERY *req)
{  
  DSM_FILE_ERROR       *dfe            = NULL;
  PACKAGE_INFO         *packages       = NULL;
  PACKAGE_INFO         *packages_avail = NULL;
  PACKAGE_INFO        **matched  = NULL;
  PACKAGE_INFO         *package        = NULL;
  q_display_context_t   qd;
  char                  db_path[PATH_MAX];
  int                   count;
  int                   ret;
  int                   i;

  memset(&qd, 0, sizeof(qd));

  /* Build the DSM database path */
  strcpy(db_path, req->root);
  addforwardslash(db_path);
  strcat(db_path, PAKKE_DB_PREFIX);
  addforwardslash(db_path);

  /* Get installed packages */
  packages = dsm_load_all((const char **) req->dsm_path, NULL, &dfe);
  packages = ver_load_all((const char **) req->mft_path, packages);

  packages = packlist_dedupe(packages);

  /* Resolve dependencies for installed packages */
  packlist_xref(packages);
	
  if (packages == NULL)
    die("Unable to obtain package list!");

  /* Display DSM errors for installed database. */
  if (dsm_file_errors_fatal(dfe))
    warn("Parsing failed for some DSM(s) in installed database");

  show_dsm_file_errors(dfe, req->verbosity);
  dsm_free_file_error_list(dfe);
  dfe = NULL;

  /* Get available packages */
  packages_avail = dsm_load_all((const char **) req->dsm_path_avail,
				NULL, &dfe);

  /* Resolve dependencies on available packages, so user can see which
   * packages need to be installed. */
  packlist_browse(packages_avail, avail_xref, (void *) packages);

  if (packages_avail == NULL)
    die("Unable to obtain available package list!");

  /* Display DSM errors for available database. */
  if (dsm_file_errors_fatal(dfe))
    warn("Parsing failed for some DSM(s) in available database");

  show_dsm_file_errors(dfe, req->verbosity);
  dsm_free_file_error_list(dfe);
  dfe = NULL;

  /* Query all or matched packages */
  if (req->name == NULL) {
    /* Set up the context */
    qd.err     = QUERY_OK;
    qd.req     = req;
    qd.db_path = db_path;
    qd.matched = qd.count = 0;

    if ((packages_avail != NULL) && (packages_avail->q_forw != NULL))
      qd.multi = 1;
    else
      qd.multi = 0;

    packlist_browse(packages_avail, q_display_browser, (void *) &qd);

    if ((req->mod != QM_LISTFILES) && (req->mod != QM_HASFILE)) {
      printf("%d packages are available\n\n", qd.count);
    }

    ret = qd.err;
  } else {
    /* Try matching on simple strings and user specifiers, etc. */
    /* TODO: Have option to enable regexp matches */
    matched = packlist_find(packages_avail, req->name,
			    PACKLIST_FIND_SIMPLE|PACKLIST_FIND_USER);

    /* Packages found? */
    if (matched == NULL) {
      /* Set up the context */
      qd.err     = QUERY_UNMATCHED;
      qd.req     = req;
      qd.db_path = db_path;
      qd.matched = qd.count = 0;

      /* No => display nothing*/
    } else {
      /* Set up the context */
      qd.err     = QUERY_OK;
      qd.req     = req;
      qd.db_path = db_path;
      qd.matched = qd.count = 0;

      if ((matched[0] != NULL) && (matched[1] != NULL))
	qd.multi = 1;
      else
	qd.multi = 0;

      /* Yes => display all matches */
      for (i = 0; matched[i] != NULL; i++) {
	/* TODO: Return code? */
	q_display_browser(matched[i], (void *) &qd);
      }
    }

    /* Count number of installed packages */
    for (package = packages_avail, count = 0;
	 package != NULL;
	 package = package->q_forw) {
      count++;
    }

    if ((req->mod != QM_LISTFILES) && (req->mod != QM_HASFILE)) {
      printf("%d packages matched; %d packages are available\n\n",
	     qd.count, count);
    }

    ret = qd.err;
  }

  /* Free memory */
  packlist_free(packages);
  packlist_free(packages_avail);

  return(ret);
}

/* -------------------
 * - query_show_info -
 * ------------------- */

int
query_show_info (const int qm, const int verbosity, PACKAGE_INFO *package,
		 const int installed, const char *prefix, const char *rname)
{
  int ret   = 0;
  int multi = 0;
  int i;

  if (package == NULL) return(0);

  switch(qm) {
    /* Just the name and version */
  default:
  case QM_NONE:
    printf("%s %s\n",
	   package->name, package_version_string(&package->version));
    ret = 1;
    break;
 
    /* Brief => just show package name & short description */
  case QM_SHORTINFO:
    /* Header */
    ret = query_show_info_header(package);
    break;

    /* Long => all descriptive fields */
  case QM_LONGINFO:
    /* Header */
    ret = query_show_info_header(package);

    if (package->long_description != NULL)
      printf("\nLong description:\n%s\n", package->long_description);

    printf("\n");

    /* organisation */
    if (package->organisation != NULL)
      printf("Organisation: %s\n", package->organisation);

    /* author* */
    for (i = 0; package->author[i] != NULL; i++) {
      printf("Author: %s", package->author[i]);
      if (package->author_email[i] != NULL)
	printf(" <%s>", package->author_email[i]);
      if (package->author_im[i] != NULL)
	printf(" IM: %s", package->author_im[i]);
      printf("\n");
    }

    /* web-site, ftp-site */
    for (i = 0; package->web_site[i] != NULL; i++) {
      printf("Web site: %s\n", package->web_site[i]);
    }

    for (i = 0; package->ftp_site[i] != NULL; i++) {
      printf("FTP site: %s\n", package->ftp_site[i]);
    }

    /* maintainer*, porter* */
    for (i = 0; package->maintainer[i] != NULL; i++) {
      printf("Maintainer: %s", package->maintainer[i]);
      if (package->maintainer_email[i] != NULL)
	printf(" <%s>", package->maintainer_email[i]);
      if (package->maintainer_im[i] != NULL)
	printf(" IM: %s", package->maintainer_im[i]);
      printf("\n");
    }

    for (i = 0; package->porter[i] != NULL; i++) {
      printf("Porter: %s", package->porter[i]);
      if (package->porter_email[i] != NULL)
	printf(" <%s>", package->porter_email[i]);
      if (package->porter_im[i] != NULL)
	printf(" IM: %s", package->porter_im[i]);
      printf("\n");
    }

    /* mailing-list* */
    for (i = 0; package->mailing_list[i] != NULL; i++) {
      printf("Mailing list: %s\n", package->mailing_list[i]);
      if (package->mailing_list_description[i] != NULL) {
	printf("Mailing list description: %s\n",
	       package->mailing_list_description[i]);
      }
      if (package->mailing_list_request[i] != NULL) {
	printf("Mailing list request: %s\n",
	       package->mailing_list_request[i]);
      }
      if (package->mailing_list_administrator[i] != NULL) {
	printf("Mailing list administrator: %s",
	       package->mailing_list_administrator[i]);
	if (package->mailing_list_administrator_email[i] != NULL)
	  printf(" <%s>", package->mailing_list_administrator_email[i]);
	if (package->mailing_list_administrator_im[i] != NULL)
	  printf(" IM: %s", package->mailing_list_administrator_im[i]);
	printf("\n");
      }
      if (package->mailing_list_web_site[i] != NULL) {
	printf("Mailing list web site: %s\n",
	       package->mailing_list_web_site[i]);
      }
      if (package->mailing_list_ftp_site[i] != NULL) {
	printf("Mailing list FTP site: %s\n",
	       package->mailing_list_ftp_site[i]);
      }
    }

    /* newsgroup* */
    for (i = 0; package->newsgroup[i] != NULL; i++) {
      printf("Newsgroup: %s", package->newsgroup[i]);
      if (package->newsgroup_email_gateway[i] != NULL)
	printf(" <%s>", package->newsgroup_email_gateway[i]);
      printf("\n");

      if (package->newsgroup_description[i] != NULL) {
	printf("Newsgroup description: %s\n",
	       package->newsgroup_description[i]);
      }
      if (package->newsgroup_administrator[i] != NULL) {
	printf("Newsgroup administrator: %s",
	       package->newsgroup_administrator[i]);
	if (package->newsgroup_administrator_email[i] != NULL)
	  printf(" <%s>", package->newsgroup_administrator_email[i]);
	if (package->newsgroup_administrator_im[i] != NULL)
	  printf(" IM: %s", package->newsgroup_administrator_im[i]);
	printf("\n");
      }
      if (package->newsgroup_web_site[i] != NULL) {
	printf("Newsgroup web site: %s\n",
	       package->newsgroup_web_site[i]);
      }
      if (package->newsgroup_ftp_site[i] != NULL) {
	printf("Newsgroup FTP site: %s\n",
	       package->newsgroup_ftp_site[i]);
      }
    }

    /* Package file information */
    printf("\n");

    if (strlen(package->simtelnet_path) > 0)
      printf("Simtel.net path: %s\n", package->simtelnet_path);

    if (package->zip[0] != NULL) {
      printf("ZIP package(s):");
      for (i = 0; package->zip[i] != NULL; i++) {
	printf(" %s", package->zip[i]);
      }
      printf("\n");
    }

    if (package->tar_gzip[0] != NULL) {
      printf(".tar.gz package(s):");
      for (i = 0; package->tar_gzip[i] != NULL; i++) {
	printf(" %s", package->tar_gzip[i]);
      }
      printf("\n");
    }

    printf("\n");

    /* Informational text files */
    if (package->changelog != NULL)
      printf("Changelog available\n");

    if (   (package->pre_install_readme != NULL)
	|| (package->post_install_readme != NULL)
	|| (package->pre_uninstall_readme != NULL)
	|| (package->post_uninstall_readme != NULL)) {
      printf("Readmes available: ");
      multi = 0;

      if (package->pre_install_readme != NULL) {
	printf("pre-install");
	multi++;
      }
      if (package->post_install_readme != NULL) {
	if (multi)
	  printf(", ");
	printf("post-install");
	multi++;
      }
      if (package->pre_uninstall_readme != NULL) {
	if (multi)
	  printf(", ");
	printf("pre-uninstall");
	multi++;
      }
      if (package->post_uninstall_readme != NULL) {
	if (multi)
	  printf(", ");
	printf("post-uninstall");
	multi++;
      }
      printf("\n");
    }

    /* *-script */
    if (   (package->builtin_pre_install_script != NULL)
        || (package->builtin_post_install_script != NULL)
        || (package->builtin_pre_uninstall_script != NULL)
        || (package->builtin_post_uninstall_script != NULL)	  
        || (package->pre_install_script != NULL)
        || (package->post_install_script != NULL)
        || (package->pre_uninstall_script != NULL)
	|| (package->post_uninstall_script != NULL)) {
      printf("Scripts: ");
      multi = 0;

      if (package->builtin_pre_install_script != NULL) {
	printf("built-in pre-install");
	multi++;
      }
      if (package->builtin_post_install_script != NULL) {
	if (multi)
	  printf(", ");
	printf("built-in post-install");
	multi++;
      }
      if (package->builtin_pre_uninstall_script != NULL) {
	if (multi)
	  printf(", ");
	printf("built-in pre-uninstall");
	multi++;
      }
      if (package->builtin_post_uninstall_script != NULL) {
	if (multi)
	  printf(", ");
	printf("built-in post-uninstall");
	multi++;
      }

      if (package->pre_install_script != NULL) {
	if (multi)
	  printf(", ");
	printf("pre-install");
	multi++;
      }
      if (package->post_install_script != NULL) {
	if (multi)
	  printf(", ");
	printf("post-install");
	multi++;
      }
      if (package->pre_uninstall_script != NULL) {
	if (multi)
	  printf(", ");
	printf("pre-uninstall");
	multi++;
      }
      if (package->post_uninstall_script != NULL) {
	if (multi)
	  printf(", ");
	printf("post-uninstall");
	multi++;
      }

      printf("\n");
    }    

    /* TODO: keep-file */

    /* duplicate-action */
    printf("Duplicate action: ");

    switch(package->duplicate_action) {
    case DUP_REPLACE: printf("replace\n"); break;
    case DUP_BACKUP:  printf("backup\n");  break;
    case DUP_KEEP:    printf("keep\n");    break;
    case DUP_SKIP:    printf("skip\n");    break;
    case DUP_QUERY:   printf("query\n");   break;
    default:
      dief("Internal error handling duplicate_action - %s:%d",
	   __FILE__, __LINE__);
    }

    /* install-warning */
    if (package->install_warning != NULL)
      printf("Install warning:\n%s\n", package->install_warning);

    /* Dependencies */
    /* TODO: We need to tidy up dep output, because it's a bit ugly. */
    printf("\n");
    query_show_all_deps(verbosity, package);

    break;

  case QM_REQUIRES:
  case QM_DEPENDS_ON:
  case QM_CONFLICTS_WITH:
  case QM_REPLACES:
  case QM_PROVIDES:
  case QM_INSTALL_BEFORE:
  case QM_INSTALL_AFTER:
    printf("--- %s %s ---\n",
	   package->name, package_version_string(&package->version));
    ret = query_show_deps(qm, verbosity, package);
    break;

  case QM_ALLDEPS:
    printf("--- %s %s ---\n",
	   package->name, package_version_string(&package->version));

    ret = query_show_all_deps(verbosity, package);
    break;

  case QM_CHANGELOG:
  case QM_PRE_INSTALL_README:
  case QM_POST_INSTALL_README:
  case QM_PRE_UNINSTALL_README:
  case QM_POST_UNINSTALL_README:
    ret = query_show_file(qm, verbosity, package, installed, prefix, rname);
    break;

  case QM_INSTALL_WARNING:
    printf("Install warning:\n%s\n", package->install_warning);
    break;
  }

  /* OK */
  return(ret);
}

/* -------------------
 * - query_show_deps -
 * ------------------- */

int
query_show_deps (const int qm, const int verbosity, PACKAGE_INFO *package)
{
  PACKAGE_DEP **deps = NULL;
  int dep_type, found;

  switch(qm) {
  case QM_REQUIRES:       
    dep_type = PACKAGE_DEP_REQUIRES;  
    break;
  case QM_DEPENDS_ON:     
    dep_type = PACKAGE_DEP_DEPENDS_ON;
    break;
  case QM_CONFLICTS_WITH: 
    dep_type = PACKAGE_DEP_CONFLICTS_WITH; 
    break;
  case QM_REPLACES:       
    dep_type = PACKAGE_DEP_REPLACES;       
    break;
  case QM_PROVIDES:       
    dep_type = PACKAGE_DEP_PROVIDES;       
    break;
  case QM_INSTALL_BEFORE: 
    dep_type = PACKAGE_DEP_INSTALL_BEFORE; 
    break;
  case QM_INSTALL_AFTER:  
    dep_type = PACKAGE_DEP_INSTALL_AFTER;  
    break;
  default: return(0);
  }

  found = 0;
  deps  = package->deps;

  if (deps != NULL) {
    while (*deps != NULL) {
      if ((*deps)->dep_type == dep_type) {
	printf("%s ", package_dep_string(*deps));

	switch(dep_type) {
	case PACKAGE_DEP_REQUIRES:
	case PACKAGE_DEP_DEPENDS_ON:
	  if ((*deps)->dep == NULL)
	    printf("(MISSING)\n");
	  else
	    printf("\n");
	  break;

	default:
	  printf("\n");
	  break;
	}

	found = 1;				
      }

      deps++;
    }
  }

  if (!found)
    printf("No dependencies of this type found.\n");

  /* OK */
  return(1);
}

/* -----------------------
 * - query_show_all_deps -
 * ----------------------- */

int
query_show_all_deps (const int verbosity, PACKAGE_INFO *package)
{
  int ret;

  printf("- Requirements\n");
  ret =        query_show_deps(QM_REQUIRES, verbosity, package);
  printf("- Depends on\n");
  ret = ret && query_show_deps(QM_DEPENDS_ON, verbosity, package);
  printf("- Conflicts with\n");
  ret = ret && query_show_deps(QM_CONFLICTS_WITH, verbosity, package);
  printf("- Replaces\n");
  ret = ret && query_show_deps(QM_REPLACES, verbosity, package);
  printf("- Provides\n");
  ret = ret && query_show_deps(QM_PROVIDES, verbosity, package);
  printf("- Install before\n");
  ret = ret && query_show_deps(QM_INSTALL_BEFORE, verbosity, package);
  printf("- Install after\n");
  ret = ret && query_show_deps(QM_INSTALL_AFTER, verbosity, package);

  return(ret);
}

/* --------------------------
 * - query_show_info_header -
 * -------------------------- */

int
query_show_info_header (PACKAGE_INFO *package)
{
  printf("--- %s %s ---\n\n",
	 package->name, package_version_string(&package->version));

  printf("Package:  %s %s\n",
	 package->name, package_version_string(&package->version));

  printf("Manifest: ");

  if (strlen(package->manifest) > 0)
    printf("%s.mft\n", package->manifest);
  else
    printf("(none)\n");

  printf("DSM:      ");
  if (strlen(package->dsm_name) > 0)
    printf("%s.dsm\n", package->dsm_name);
  else
    printf("(none)\n");

  printf("Summary:  %s\n", package->short_description);

  if (package->license != NULL)
    printf("License:  %s\n", package->license);
  else
    printf("License:  Unknown\n");

  return(1);
}

/* --------------------
 * - query_show_files -
 * -------------------- */

int
query_show_files (const char **mft_path, PACKAGE_INFO *package,
		  const char *db_path, const char *qfilename)
{
  typedef enum {
    MATCH_TYPE_PATH =  1, /* Whole name including path */
    MATCH_TYPE_FILE =  0, /* Just file component of name */
    MATCH_TYPE_ANY  = -1  /* Match anything */
  } match_type_t;

  match_type_t    match_type = MATCH_TYPE_FILE;
  md5hash_entry **md5_list   = NULL;
  char          **mft_list   = NULL;
  char           *filename   = NULL;
  char           *p          = NULL;
  int             i          = 0;
  int             found      = 0;

  /*
   * TODO: Improve the way this handles matching. Should be able to do:
   *
   * -qa -f ls.exe
   * -qa -f bin/ls.exe
   * -qa -f c:/djgpp/ls.exe
   * -qa -f 'ls*'
   * -qa -f 'ls.*'
   *
   * to name just a few.
   */

  /* Can this package have a manifest? */
  switch(package->version.type) {
  case TYPE_BINARIES:
  case TYPE_SOURCES:
  case TYPE_DOCUMENTATION:
    /* Yep => carry on */
    break;

  default:
  case TYPE_NONE:
  case TYPE_VIRTUAL:
  case TYPE_GROUP:
    /* Nope => doesn't match */
    return(0);
  }

  /*
   * If the package has a DSM and is installed, try to read its MD5 hashes.
   * If it does not have MD5 hashes, fall back on its manifest. If it has
   * no DSM, just use the manifest.
   */
  if (package->has_dsm && isdsm(package->path)) {
    md5_list = md5hash_get(package, db_path);
    if (md5_list == NULL) {
      warnf("Unable to read MD5 hashes for %s %s - using manifest",
	    package->name, package_version_string(&package->version));
    }
  }

  if (md5_list == NULL) {
    /* Error? */
    if (strlen(package->manifest) == 0) {
      warnf("Package %s %s has no manifest",
	    package->name, package_version_string(&package->version));
      return(-1);
    }

    mft_list = mft_get_list(mft_path, package);
    if (mft_list == NULL) {
      warnf("Unable to get list of files for package %s %s",
	    package->name,
	    package_version_string(&package->version));
      return(-1);
    }
  }
	
  /* TODO: Special matches ignoring file extension? */

  /*
   * Work out the match type. If there's no filename, match anything.
   * If the filename contains slashes, assume it's a fully qualified path.
   * Otherwise, just assume it's the filename, which will be matched
   * completely with case sensitivity.
   */

  if (qfilename == NULL) {
    /* Match anything */
    match_type = MATCH_TYPE_ANY;
  } else {
    /* Guess match type */
    if (strchr(qfilename, '/'))
      match_type = MATCH_TYPE_PATH; /* Full match */
    else
      match_type = MATCH_TYPE_FILE; 
  }

  /* Find matching files */
  for (found = i = 0; ; i++) {
    /* Get filename from MD5 or manifest list. */
    if (md5_list != NULL) {
      if (md5_list[i] == NULL) {
	break;
      } else {
	filename = md5_list[i]->filename;
      }
    } else {
      if (mft_list[i] == NULL) {
	break;
      } else {
	filename = mft_list[i];
      }
    }

    /* Fully matches? */
    switch(match_type) {
    case MATCH_TYPE_PATH:
      if (strcmp(filename, qfilename) != 0)
	continue;
      break;

    case MATCH_TYPE_FILE:
      p = strrchr(filename, '/');
      if (p == NULL)
	p = filename;
      while (*p == '/') { p++; }
      if (strcmp(p, qfilename) != 0)
	continue;
      break;

    case MATCH_TYPE_ANY:
      /* Nothing to do - pass through */
      break;

    default:
      /* Skip it */
      continue;
    }

    found++;

    /* Print header on first match */
    if (found == 1) {
      if (md5_list != NULL) {
	/* Listing from database */	
	printf("--- %s %s ---\n",
	       package->name, package_version_string(&package->version));
      } else {
	/* Listing from manifest => include manifest filename */
	printf("--- %s %s [%s.mft] ---\n",
	       package->name,
	       package_version_string(&package->version),
	       package->manifest);
      }
    }

    printf("%s\n", filename);
  }

  if (found)
    printf("\n%d match(es) found\n", found);

  /* Tidy up */
  if (md5_list != NULL) {
    md5hash_entries_free(md5_list);
    free(md5_list);
    md5_list = NULL;
  }

  if (mft_list != NULL) {
    for (i = 0; mft_list[i] != NULL; i++) { free(mft_list[i]); }
    free(mft_list[i]);
    mft_list[i] = NULL;
  }

  return(found);
}

/* -------------------
 * - query_show_file -
 * ------------------- */

/*
 * Display a particular information file from the package. 'installed'
 * indicates whether the package is installed (1) or not (0). On success
 * returns 1, else 0.
 */

int
query_show_file (const int qm, const int verbosity, PACKAGE_INFO *package,
		 const int installed, const char *prefix, const char *rname)
{
  char *filename      = NULL;
  char  doc_filename[PATH_MAX];
  char *buf           = NULL;
  int   use_text_mode = 1; /* Assume a text file by default */

  switch(qm) {
  case QM_CHANGELOG:
    filename = package->changelog;
    break;

  case QM_PRE_INSTALL_README:
    filename = package->pre_install_readme;
    break;

  case QM_POST_INSTALL_README:
    filename = package->post_install_readme;
    break;

  case QM_PRE_UNINSTALL_README:
    filename = package->pre_uninstall_readme;
    break;

  case QM_POST_UNINSTALL_README:
    filename = package->post_uninstall_readme;
    break;

  default:
    return(0);
  }

  /* Do we have a valid filename? */
  if (filename == NULL)
    return(0);

  if (!installed) {
    if (isarchive(rname)) {
      /* TODO: I guess we should check that 'rname' is a valid filename. */

      /* TODO: Should this support the '-q -p somezip.zip --changelog', etc.
       * format when somezip.zip is in one of the ZIP directories that pakke
       * uses? */

      /* Dump the file from the archive. We get the file into a buffer in
       * text/binary mode, as appropriate. */
      /* TODO: How do we detect failure vs. a bad filename? */
      if (use_text_mode)
	buf = archive_extract_text_file_to_memory(rname, filename);
      else
	buf = archive_extract_file_to_memory(rname, filename);
    } else {
      warnf("'%s' is not a package file", rname);
    }
  } else {
    /* Look for the text file in the installation */
    strcpy(doc_filename, prefix);
    addforwardslash(doc_filename);
    strcat(doc_filename, filename);

    /* TODO: How do we detect failure vs. a bad filename? */
    buf = read_text_file_to_memory(doc_filename);
  }
  
  if (buf == NULL)
    return(0);

  /* TODO: Something more sophisticated? */
  printf("%s", buf);

  /* Tidy up */
  free(buf);

  return(1);
}
