/*
 * dpkg - main program for package management
 * enquiry.c - status enquiry and listing options
 *
 * Copyright (C) 1995 Ian Jackson <iwj10@cus.cam.ac.uk>
 *
 * This 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,
 * or (at your option) any later version.
 *
 * This 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 dpkg; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* fixme: per-package audit */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fnmatch.h>
#include <assert.h>

#include "config.h"
#include "dpkg.h"
#include "dpkg-db.h"
#include "myopt.h"

#include "filesdb.h"
#include "main.h"

static int listqcmp(const void *a, const void *b) {
  struct pkginfo *pa= *(struct pkginfo**)a;
  struct pkginfo *pb= *(struct pkginfo**)b;
  return strcmp(pa->name,pb->name);
}

static void limiteddescription(struct pkginfo *pkg, int maxl,
                               const char **pdesc_r, int *l_r) {
  const char *pdesc, *p;
  int l;
  
  pdesc= pkg->installed.valid ? pkg->installed.description : 0;
  if (!pdesc) pdesc= "(no description available)";
  p= strchr(pdesc,'\n');
  if (!p) p= pdesc+strlen(pdesc);
  l= (p - pdesc > maxl) ? maxl : (int)(p - pdesc);
  *pdesc_r=pdesc; *l_r=l;
}

static void list1package(struct pkginfo *pkg, int *head) {
  int l;
  const char *pdesc;
    
  if (!*head) {
    fputs("\
Desired=Unknown/Install/Remove/Purge\n\
| Status=Not/Installed/Config-files/Unpacked/Failed-config/Half-installed\n\
|/ Err?=(none)/Hold/Reinst-required/X=both-problems (Status,Err: uppercase=bad)\n\
||/ Name         Version   Rev  Description\n\
+++-============-=========-====-===============================================\n",
          stdout);
    *head= 1;
  }
  
  if (!pkg->installed.valid) blankpackageperfile(&pkg->installed);
  limiteddescription(pkg,47,&pdesc,&l);
  printf("%c%c%c %-12.12s %9.9s %-4.4s %.*s\n",
         "uirp"[pkg->want],
         "nUFiHc"[pkg->status],
         " hRX"[pkg->eflag],
         pkg->name,
         pkg->installed.version ? pkg->installed.version : "",
         pkg->installed.revision ? pkg->installed.revision : "",
         l, pdesc);
}         

void listpackages(const char *const *argv) {
  struct pkgiterator *it;
  struct pkginfo *pkg;
  struct pkginfo **pkgl;
  const char *thisarg;
  int np, i, head, found;

  modstatdb_init(admindir,msdbrw_readonly);

  np= countpackages();
  pkgl= m_malloc(sizeof(struct pkginfo*)*np);
  it= iterpkgstart(); i=0;
  while ((pkg= iterpkgnext(it))) {
    assert(i<np);
    pkgl[i++]= pkg;
  }
  iterpkgend(it);
  assert(i==np);

  qsort(pkgl,np,sizeof(struct pkginfo*),listqcmp);
  head=0;
  
  if (!*argv) {
    for (i=0; i<np; i++) {
      pkg= pkgl[i];
      if (pkg->status == stat_notinstalled) continue;
      list1package(pkg,&head);
    }
  } else {
    while ((thisarg= *argv++)) {
      found= 0;
      for (i=0; i<np; i++) {
        pkg= pkgl[i];
        if (fnmatch(thisarg,pkg->name,0)) continue;
        list1package(pkg,&head); found++;
      }
      if (!found)
        fprintf(stderr,"No packages found matching %s.\n",thisarg);
    }
  }
  if (ferror(stdout)) werr("stdout");
  if (ferror(stderr)) werr("stderr");  
}

struct badstatinfo {
  int (*yesno)(struct pkginfo*, const struct badstatinfo *bsi);
  int val;
  const char *explanation;
};

static int bsyn_reinstreq(struct pkginfo *pkg, const struct badstatinfo *bsi) {
  return pkg->eflag &= eflagf_reinstreq;
}

static int bsyn_status(struct pkginfo *pkg, const struct badstatinfo *bsi) {
  if (pkg->eflag &= eflagf_reinstreq) return 0;
  return pkg->status == bsi->val;
}

static const struct badstatinfo badstatinfos[]= {
  {
    bsyn_reinstreq, 0,
    "The following packages are in a mess due to serious problems during\n"
    "installation.  They must be reinstalled for them (and any packages\n"
    "that depend on them) to function properly:\n"
  }, {
    bsyn_status, stat_unpacked,
    "The following packages have been unpacked but not yet configured.\n"
    "They must be configured using " DPKG " --configure or the configure\n"
    "menu option in " DSELECT " for them to work:\n"
  }, {
    bsyn_status, stat_halfconfigured,
    "The following packages are only half configured, probably due to problems\n"
    "configuring them the first time.  The configuration should be retried using\n"
    DPKG " --configure <package> or the configure menu option in " DSELECT ":\n"
  }, {
    bsyn_status, stat_halfinstalled,
    "The following packages are only half installed, due to problems during\n"
    "installation.  The installation can probably be completed by retrying it;\n"
    "the packages can be removed using " DSELECT " or " DPKG " --remove:\n"
  }, {
    0
  }
};

static void describebriefly(struct pkginfo *pkg) {
  int maxl, l;
  const char *pdesc;

  maxl= 57;
  l= strlen(pkg->name);
  if (l>20) maxl -= (l-20);
  limiteddescription(pkg,maxl,&pdesc,&l);
  printf(" %-20s %.*s\n",pkg->name,l,pdesc);
}

void audit(const char *const *argv) {
  struct pkgiterator *it;
  struct pkginfo *pkg;
  const struct badstatinfo *bsi;
  int head;

  if (*argv) badusage("--audit does not take any arguments");

  modstatdb_init(admindir,msdbrw_readonly);

  for (bsi= badstatinfos; bsi->yesno; bsi++) {
    head= 0;
    it= iterpkgstart(); 
    while ((pkg= iterpkgnext(it))) {
      if (!bsi->yesno(pkg,bsi)) continue;
      if (!head) {
        fputs(bsi->explanation,stdout);
        head= 1;
      }
      describebriefly(pkg);
    }
    iterpkgend(it);
    if (head) putchar('\n');
  }
  if (ferror(stderr)) werr("stderr");  
}

struct sectionentry {
  struct sectionentry *next;
  const char *name;
  int count;
};

static int yettobeunpacked(struct pkginfo *pkg, const char **thissect) {
  if (pkg->want != want_install) return 0;

  switch (pkg->status) {
  case stat_unpacked: case stat_installed: case stat_halfconfigured:
    return 0;
  case stat_notinstalled: case stat_halfinstalled: case stat_configfiles:
    if (thissect)
      *thissect= pkg->section && *pkg->section ? pkg->section : "<unknown>";
    return 1;
  default:
    internerr("unknown status checking for unpackedness");
  }
}

void unpackchk(const char *const *argv) {
  int totalcount, sects;
  struct sectionentry *sectionentries, *se, **sep;
  struct pkgiterator *it;
  struct pkginfo *pkg;
  const char *thissect;
  char buf[20];
  int width;
  
  if (*argv) badusage("--yet-to-unpack does not take any arguments");

  modstatdb_init(admindir,msdbrw_readonly);

  totalcount= 0;
  sectionentries= 0;
  sects= 0;
  it= iterpkgstart(); 
  while ((pkg= iterpkgnext(it))) {
    if (!yettobeunpacked(pkg, &thissect)) continue;
    for (se= sectionentries; se && strcasecmp(thissect,se->name); se= se->next);
    if (!se) {
      se= nfmalloc(sizeof(struct sectionentry));
      for (sep= &sectionentries;
           *sep && strcasecmp(thissect,(*sep)->name) > 0;
           sep= &(*sep)->next);
      se->name= thissect;
      se->count= 0;
      se->next= *sep;
      *sep= se;
      sects++;
    }
    se->count++; totalcount++;
  }
  iterpkgend(it);

  if (totalcount == 0) exit(0);
  
  if (totalcount <= 12) {
    it= iterpkgstart(); 
    while ((pkg= iterpkgnext(it))) {
      if (!yettobeunpacked(pkg,0)) continue;
      describebriefly(pkg);
    }
    iterpkgend(it);
  } else if (sects <= 12) {
    for (se= sectionentries; se; se= se->next) {
      sprintf(buf,"%d",se->count);
      printf(" %d in %s: ",se->count,se->name);
      width= 70-strlen(se->name)-strlen(buf);
      while (width > 59) { putchar(' '); width--; }
      it= iterpkgstart(); 
      while ((pkg= iterpkgnext(it))) {
        if (!yettobeunpacked(pkg,&thissect)) continue;
        if (strcasecmp(thissect,se->name)) continue;
        width -= strlen(pkg->name); width--;
        if (width < 4) { printf(" ..."); break; }
        printf(" %s",pkg->name);
      }
      iterpkgend(it);
      putchar('\n');
    }
  } else {
    printf(" %d packages, from the following sections:",totalcount);
    width= 0;
    for (se= sectionentries; se; se= se->next) {
      sprintf(buf,"%d",se->count);
      width -= (6 + strlen(se->name) + strlen(buf));
      if (width < 0) { putchar('\n'); width= 73 - strlen(se->name) - strlen(buf); }
      printf("   %s (%d)",se->name,se->count);
    }
    putchar('\n');
  }
  fflush(stdout);
  if (ferror(stdout)) werr("stdout");
}

static int searchoutput(struct filenamenode *namenode) {
  int found, i;
  struct filepackages *packageslump;

  found= 0;
  for (packageslump= namenode->packages;
       packageslump;
       packageslump= packageslump->more) {
    for (i=0; i < PERFILEPACKAGESLUMP && packageslump->pkgs[i]; i++) {
      found++;
      printf("%s: %s\n", packageslump->pkgs[i]->name, namenode->name);
    }
  }
  return found;
}

void searchfiles(const char *const *argv) {
  struct filenamenode *namenode;
  struct fileiterator *it;
  const char *thisarg;
  int found;
  static struct varbuf vb;
  
  if (!*argv)
    badusage("--search needs at least one file name pattern argument");

  modstatdb_init(admindir,msdbrw_readonly);
  ensure_allinstfiles_available_quiet();

  while ((thisarg= *argv++) != 0) {
    found= 0;
    if (!strchr("*[?/",*thisarg)) {
      varbufreset(&vb);
      varbufaddc(&vb,'*');
      varbufaddstr(&vb,thisarg);
      varbufaddc(&vb,'*');
      varbufaddc(&vb,0);
      thisarg= vb.buf;
    }
    if (strcspn(thisarg,"*[?\\") == strlen(thisarg)) {
      namenode= findnamenode(thisarg);
      found += searchoutput(namenode);
    } else {
      it= iterfilestart();
      while ((namenode= iterfilenext(it)) != 0) {
        if (fnmatch(thisarg,namenode->name,0)) continue;
        found+= searchoutput(namenode);
      }
      iterfileend(it);
    }
    if (!found) {
      fprintf(stderr,DPKG ": %s not found.\n",thisarg);
      if (ferror(stderr)) werr("stderr");
    } else {
      if (ferror(stdout)) werr("stdout");
    }
  }
}

void enqperpackage(const char *const *argv) {
  int failures;
  const char *thisarg;
  struct fileinlist *file;
  struct pkginfo *pkg;
  
  if (!*argv)
    badusage("--%s needs at least one package name argument", cipaction->olong);

  failures= 0;
  modstatdb_init(admindir,msdbrw_readonly);
  
  while ((thisarg= *argv++) != 0) {
    pkg= findpackage(thisarg);

    switch (cipaction->arg) {
      
    case act_status:
      if (pkg->status == stat_notinstalled &&
          pkg->priority == pri_unknown &&
          !(pkg->section && *pkg->section) &&
          pkg->want == want_unknown &&
          !informativeperfile(&pkg->installed)) {
        printf("Package `%s' is not installed and no info is available.\n",pkg->name);
        failures++;
      } else {
        writerecord(stdout, "<stdout>", pkg, &pkg->installed);
      }
      break;

    case act_listfiles:
      switch (pkg->status) {
      case stat_notinstalled: 
        printf("Package `%s' is not installed.\n",pkg->name);
        failures++;
        break;
        
      default:
        ensure_packagefiles_available(pkg);
        file= pkg->clientdata->files;
        if (!file) {
          printf("Package `%s' does not contain any files (!)\n",pkg->name);
        } else {
          while (file) {
            puts(file->namenode->name);
            file= file->next;
          }
        }
        break;
      }
      break;

    default:
      internerr("unknown action");
    }
        
    putchar('\n');
    if (ferror(stdout)) werr("stdout");
  }

  if (failures) {
    puts("Use " DPKG " --info (= " BACKEND " --info) to examine archive files,\n"
         "and " DPKG " --contents (= " BACKEND " --contents) to list their contents.");
    if (ferror(stdout)) werr("stdout");
  }
}
