#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <libgen.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/stat.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>

#include "s1kd_tools.h"

/* Initial maximum number of CSDB objects of each type. */
#define OBJECT_MAX 1
static unsigned DM_MAX  = OBJECT_MAX;
static unsigned PM_MAX  = OBJECT_MAX;
static unsigned COM_MAX = OBJECT_MAX;
static unsigned IMF_MAX = OBJECT_MAX;
static unsigned DDN_MAX = OBJECT_MAX;
static unsigned DML_MAX = OBJECT_MAX;
static unsigned ICN_MAX = OBJECT_MAX;
static unsigned SMC_MAX = OBJECT_MAX;
static unsigned UPF_MAX = OBJECT_MAX;
static unsigned NON_MAX = OBJECT_MAX;

#define PROG_NAME "s1kd-ls"
#define VERSION "1.15.0"

#define ERR_PREFIX PROG_NAME ": ERROR: "

#define EXIT_OBJECT_MAX 1 /* Cannot allocate memory for more objects. */

#define E_MAX_OBJECT ERR_PREFIX "Maximum CSDB objects reached: %d\n"
#define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n"

/* Set of CSDB object types to list. */
#define SHOW_DM  0x001
#define SHOW_PM  0x002
#define SHOW_COM 0x004
#define SHOW_IMF 0x008
#define SHOW_DDN 0x010
#define SHOW_DML 0x020
#define SHOW_ICN 0x040
#define SHOW_SMC 0x080
#define SHOW_UPF 0x100
#define SHOW_NON 0x200

/* Lists of CSDB objects. */
static char (*dms)[PATH_MAX] = NULL;
static char (*pms)[PATH_MAX] = NULL;
static char (*smcs)[PATH_MAX] = NULL;
static char (*coms)[PATH_MAX] = NULL;
static char (*icns)[PATH_MAX] = NULL;
static char (*imfs)[PATH_MAX] = NULL;
static char (*ddns)[PATH_MAX] = NULL;
static char (*dmls)[PATH_MAX] = NULL;
static char (*upfs)[PATH_MAX] = NULL;
static char (*nons)[PATH_MAX] = NULL;
static int ndms = 0, npms = 0, ncoms = 0, nicns = 0, nimfs = 0, nddns = 0, ndmls = 0, nsmcs = 0, nupfs = 0, nnons = 0;

/* Separator between printed CSDB objects. */
static char sep = '\n';

/* Whether the CSDB objects were created with the -N option. */
static int no_issue = 0;

/* Command string to execute with the -e option. */
static char *execstr = NULL;

static void printfiles(char (*files)[PATH_MAX], int n)
{
       int i;
       if (execstr) {
               for (i = 0; i < n; ++i) execfile(execstr, files[i]);
       } else {
               for (i = 0; i < n; ++i) printf("%s%c", files[i], sep);
       }
}

/* Compare two ICN files, grouped by file extension. */
static int compare_icn(const void *a, const void *b)
{
       char *sa, *sb, *ba, *bb, *e, *ea, *eb;
       int d;

       sa = strdup((const char *) a);
       sb = strdup((const char *) b);
       ba = basename(sa);
       bb = basename(sb);

       /* Move the file extension to the front. */
       ea = malloc(strlen(ba) + 1);
       eb = malloc(strlen(bb) + 1);

       if ((e = strchr(ba, '.'))) {
               d = e - ba;
               sprintf(ea, "%s%.*s", e, d, ba);
       } else {
               strcpy(ea, ba);
       }

       if ((e = strchr(bb, '.'))) {
               d = e - bb;
               sprintf(eb, "%s%.*s", e, d, bb);
       } else {
               strcpy(eb, bb);
       }

       d = strcasecmp(ea, eb);

       free(ea);
       free(eb);
       free(sa);
       free(sb);

       return d;
}

/* Show usage message. */
static void show_help(void)
{
       puts("Usage: " PROG_NAME " [-0CDGIiLlMNnoPRrSUwX7] [<object>|<dir> ...]");
       puts("");
       puts("Options:");
       puts("  -0, --null        Output null-delimited list.");
       puts("  -C, --com         List comments.");
       puts("  -D, --dm          List data modules.");
       puts("  -e, --exec <cmd>  Execute <cmd> for each CSDB object.");
       puts("  -G, --icn         List ICN files.");
       puts("  -I, --inwork      Show only inwork issues.");
       puts("  -i, --official    Show only official issues.");
       puts("  -h, -?, --help    Show this help message.");
       puts("  -L, --dml         List DMLs.");
       puts("  -l, --latest      Show only latest official/inwork issue.");
       puts("  -M, --imf         List ICN metadata files.");
       puts("  -N, --omit-issue  Assume issue/inwork numbers are omitted.");
       puts("  -n, --other       List non-S1000D files.");
       puts("  -o, --old         Show only old official/inwork issues.");
       puts("  -P, --pm          List publication modules.");
       puts("  -R, --read-only   Show only non-writable object files.");
       puts("  -r, --recursive   Recursively search directories.");
       puts("  -S, --smc         List SCORM content packages.");
       puts("  -U, --upf         List data update files.");
       puts("  -w, --writable    Show only writable object files.");
       puts("  -X, --ddn         List DDNs.");
       puts("  -7, --list        Treat input as list of CSDB objects.");
       puts("  --version         Show version information.");
       LIBXML2_PARSE_LONGOPT_HELP
}

/* Show version information. */
static void show_version(void)
{
       printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION);
       printf("Using libxml %s\n", xmlParserVersion);
}

/* Resize CSDB object lists when it is full. */
static void resize(char (**list)[PATH_MAX], unsigned *max)
{
       if (!(*list = realloc(*list, (*max *= 2) * PATH_MAX))) {
               fprintf(stderr, E_MAX_OBJECT,
                       ndms + npms + ncoms + nimfs + nicns + nddns + ndmls + nsmcs);
               exit(EXIT_OBJECT_MAX);
       }
}

/* Determine if file is not a CSDB object. */
static int is_non(const char *base)
{
       return !(base[0] == '.' || is_com(base) || is_ddn(base) || is_dm(base) || is_dml(base) || is_icn(base) || is_imf(base) || is_pm(base) || is_smc(base) || is_upf(base));
}

/* Find CSDB objects in a given directory. */
static void list_dir(const char *path, int only_writable, int only_readonly, int recursive)
{
       DIR *dir;
       struct dirent *cur;

       int len = strlen(path);
       char fpath[PATH_MAX];
       char cpath[PATH_MAX];

       if (strcmp(path, ".") == 0) {
               strcpy(fpath, "");
       } else if (path[len - 1] != '/') {
               strcpy(fpath, path);
               strcat(fpath, "/");
       } else {
               strcpy(fpath, path);
       }

       dir = opendir(path);

       while ((cur = readdir(dir))) {
               strcpy(cpath, fpath);
               strcat(cpath, cur->d_name);

               if (access(cpath, R_OK) != 0) {
                       continue;
               } else if (only_writable && access(cpath, W_OK) != 0) {
                       continue;
               } else if (only_readonly && access(cpath, W_OK) == 0) {
                       continue;
               } else if (dms && is_dm(cur->d_name)) {
                       if (ndms == DM_MAX) {
                               resize(&dms, &DM_MAX);
                       }
                       strcpy(dms[(ndms)++], cpath);
               } else if (pms && is_pm(cur->d_name)) {
                       if (npms == PM_MAX) {
                               resize(&pms, &PM_MAX);
                       }
                       strcpy(pms[(npms)++], cpath);
               } else if (coms && is_com(cur->d_name)) {
                       if (ncoms == COM_MAX) {
                               resize(&coms, &COM_MAX);
                       }
                       strcpy(coms[(ncoms)++], cpath);
               } else if (imfs && is_imf(cur->d_name)) {
                       if (nimfs == IMF_MAX) {
                               resize(&imfs, &IMF_MAX);
                       }
                       strcpy(imfs[(nimfs)++], cpath);
               } else if (icns && is_icn(cur->d_name)) {
                       if (nicns == ICN_MAX) {
                               resize(&icns, &ICN_MAX);
                       }
                       strcpy(icns[(nicns)++], cpath);
               } else if (ddns && is_ddn(cur->d_name)) {
                       if (nddns == DDN_MAX) {
                               resize(&ddns, &DDN_MAX);
                       }
                       strcpy(ddns[(nddns)++], cpath);
               } else if (dmls && is_dml(cur->d_name)) {
                       if (ndmls == DML_MAX) {
                               resize(&dmls, &DML_MAX);
                       }
                       strcpy(dmls[(ndmls)++], cpath);
               } else if (smcs && is_smc(cur->d_name)) {
                       if (nsmcs == SMC_MAX) {
                               resize(&smcs, &SMC_MAX);
                       }
                       strcpy(smcs[(nsmcs)++], cpath);
               } else if (upfs && is_upf(cur->d_name)) {
                       if (nupfs == UPF_MAX) {
                               resize(&upfs, &UPF_MAX);
                       }
                       strcpy(upfs[(nupfs)++], cpath);
               } else if (recursive && isdir(cpath, recursive)) {
                       list_dir(cpath, only_writable, only_readonly, recursive);
               } else if (nons && is_non(cur->d_name)) {
                       if (nnons == NON_MAX) {
                               resize(&nons, &NON_MAX);
                       }
                       strcpy(nons[nnons++], cpath);
               }
       }

       closedir(dir);
}

/* Return the first node matching an XPath expression. */
static xmlNodePtr first_xpath_node(xmlDocPtr doc, xmlNodePtr node, const char *xpath)
{
       xmlXPathContextPtr ctx;
       xmlXPathObjectPtr obj;
       xmlNodePtr first;

       ctx = xmlXPathNewContext(doc ? doc : node->doc);
       ctx->node = node;

       obj = xmlXPathEvalExpression(BAD_CAST xpath, ctx);

       if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
               first = NULL;
       } else {
               first = obj->nodesetval->nodeTab[0];
       }

       xmlXPathFreeObject(obj);
       xmlXPathFreeContext(ctx);

       return first;
}

/* Return the content of the first node matching an XPath expression. */
static xmlChar *first_xpath_value(xmlDocPtr doc, xmlNodePtr node, const char *xpath)
{
       return xmlNodeGetContent(first_xpath_node(doc, node, xpath));
}

/* Checks if a CSDB object is in the official state (inwork = 00). */
static int is_official_issue(const char *fname, const char *path)
{
       if (no_issue) {
               xmlDocPtr doc;
               xmlChar *inwork;
               int official;

               doc = read_xml_doc(path);

               if (!doc) {
                       return 1;
               }

               inwork = first_xpath_value(doc, NULL, "//@inWork|//@inwork");

               official = !inwork || xmlStrcmp(inwork, BAD_CAST "00") == 0;

               xmlFree(inwork);
               xmlFreeDoc(doc);

               return official;
       } else {
               char inwork[3] = "";
               int n;
               n = sscanf(fname, "%*[^_]_%*3s-%2s", inwork);
               return n < 1 || strcmp(inwork, "00") == 0;
       }
}

static int extract_latest_icns(char (*latest)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
       int i, nlatest = 0;
       for (i = 0; i < nfiles; ++i) {
               char *name1, *name2, *base1, *base2, *s;
               int n;

               name1 = strdup(files[i]);
               base1 = basename(name1);
               if (i > 0) {
                       name2 = strdup(files[i - 1]);
                       base2 = basename(name2);
               } else {
                       name2 = NULL;
               }

               s = strrchr(base1, '-');
               n = s - base1;

               if (i == 0 || strncmp(base1, base2, n - 3) != 0 || strcmp(s, base2 + n) != 0) {
                       strcpy(latest[nlatest++], files[i]);
               } else {
                       strcpy(latest[nlatest - 1], files[i]);
               }

               free(name1);
               free(name2);
       }
       return nlatest;
}

/* Copy only old issues of CSDB objects. */
static int remove_latest(char (*latest)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
       int i, nlatest = 0;
       for (i = 0; i < nfiles; ++i) {
               char *name1, *name3, *base1, *base3, *s;

               name1 = strdup(files[i]);
               base1 = basename(name1);

               s = strchr(base1, '_');
               if (!s || !strchr(s + 1, '_')) {
                       free(name1);
                       continue;
               }

               if (i < nfiles - 1) {
                       name3 = strdup(files[i + 1]);
                       base3 = basename(name3);
               } else {
                       name3 = NULL;
               }

               if (name3 && strncmp(base1, base3, s - base1) == 0) {
                       strcpy(latest[nlatest++], files[i]);
               }

               free(name1);
               free(name3);
       }
       return nlatest;
}
static int remove_latest_icns(char (*latest)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
       int i, nlatest = 0;
       for (i = 0; i < nfiles; ++i) {
               char *name1, *name3, *base1, *base3, *s;
               int n;

               name1 = strdup(files[i]);
               base1 = basename(name1);

               s = strrchr(base1, '-');
               if (!s) {
                       free(name1);
                       continue;
               }

               if (i < nfiles - 1) {
                       name3 = strdup(files[i + 1]);
                       base3 = basename(name3);
               } else {
                       name3 = NULL;
               }

               n = s - base1;

               if (name3 && strncmp(base1, base3, n - 3) == 0 && strcmp(s, base3 + n) == 0) {
                       strcpy(latest[nlatest++], files[i]);
               }

               free(name1);
               free(name3);
       }
       return nlatest;
}

/* Copy only official issues of CSDB objects. */
static int extract_official(char (*official)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
       int i, nofficial = 0;
       for (i = 0; i < nfiles; ++i) {
               char *name = strdup(files[i]);
               char *base = basename(name);

               if (is_official_issue(base, files[i])) {
                       strcpy(official[nofficial++], files[i]);
               }

               free(name);
       }
       return nofficial;
}

/* Copy a list, removing official CSDB objects. */
static int remove_official(char (*official)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
       int i, nofficial = 0;
       for (i = 0; i < nfiles; ++i) {
               char *name = strdup(files[i]);
               char *base = basename(name);

               if (!is_official_issue(base, files[i])) {
                       strcpy(official[nofficial++], files[i]);
               }

               free(name);
       }
       return nofficial;
}

/* Add a CSDB object to the appropriate list. */
static void list_path(const char *path, int only_writable, int only_readonly, int recursive)
{
       char tmp[PATH_MAX], *base;

       strcpy(tmp, path);
       base = basename(tmp);

       if (access(path, R_OK) != 0) {
               return;
       } else if (only_writable && access(path, W_OK) != 0) {
               return;
       } else if (only_readonly && access(path, W_OK) == 0) {
               return;
       } else if (dms && is_dm(base)) {
               if (ndms == DM_MAX) {
                       resize(&dms, &DM_MAX);
               }
               strcpy(dms[ndms++], path);
       } else if (pms && is_pm(base)) {
               if (npms == PM_MAX) {
                       resize(&pms, &PM_MAX);
               }
               strcpy(pms[npms++], path);
       } else if (coms && is_com(base)) {
               if (ncoms == COM_MAX) {
                       resize(&coms, &COM_MAX);
               }
               strcpy(coms[ncoms++], path);
       } else if (icns && is_icn(base)) {
               if (nicns == ICN_MAX) {
                       resize(&icns, &ICN_MAX);
               }
               strcpy(icns[nicns++], path);
       } else if (imfs && is_imf(base)) {
               if (nimfs == IMF_MAX) {
                       resize(&imfs, &IMF_MAX);
               }
               strcpy(imfs[nimfs++], path);
       } else if (ddns && is_ddn(base)) {
               if (nddns == DDN_MAX) {
                       resize(&ddns, &DDN_MAX);
               }
               strcpy(ddns[nddns++], path);
       } else if (dmls && is_dml(base)) {
               if (ndmls == DML_MAX) {
                       resize(&dmls, &DML_MAX);
               }
               strcpy(dmls[ndmls++], path);
       } else if (smcs && is_smc(base)) {
               if (nsmcs == SMC_MAX) {
                       resize(&smcs, &SMC_MAX);
               }
               strcpy(smcs[nsmcs++], path);
       } else if (upfs && is_upf(base)) {
               if (nupfs == UPF_MAX) {
                       resize(&upfs, &UPF_MAX);
               }
               strcpy(upfs[nupfs++], path);
       } else if (isdir(path, 0)) {
               list_dir(path, only_writable, only_readonly, recursive);
       } else if (nons && is_non(base)) {
               if (nnons == NON_MAX) {
                       resize(&nons, &NON_MAX);
               }
               strcpy(nons[nnons++], path);
       }
}

static void read_list(const char *path, int only_writable, int only_readonly, int recursive)
{
       FILE *f;
       char line[PATH_MAX];

       if (path) {
               if (!(f = fopen(path, "r"))) {
                       fprintf(stderr, E_BAD_LIST, path);
                       return;
               }
       } else {
               f = stdin;
       }

       while (fgets(line, PATH_MAX, f)) {
               strtok(line, "\t\r\n");
               list_path(line, only_writable, only_readonly, recursive);
       }

       if (path) {
               fclose(f);
       }
}

int main(int argc, char **argv)
{
       DIR *dir = NULL;

       char (*latest_dms)[PATH_MAX] = NULL;
       char (*latest_pms)[PATH_MAX] = NULL;
       char (*latest_smcs)[PATH_MAX] = NULL;
       char (*latest_imfs)[PATH_MAX] = NULL;
       char (*latest_dmls)[PATH_MAX] = NULL;
       char (*latest_icns)[PATH_MAX] = NULL;
       char (*latest_upfs)[PATH_MAX] = NULL;
       int nlatest_dms = 0, nlatest_pms = 0, nlatest_imfs = 0, nlatest_dmls = 0, nlatest_icns = 0, nlatest_smcs = 0, nlatest_upfs = 0;

       char (*issue_dms)[PATH_MAX] = NULL;
       char (*issue_pms)[PATH_MAX] = NULL;
       char (*issue_smcs)[PATH_MAX] = NULL;
       char (*issue_imfs)[PATH_MAX] = NULL;
       char (*issue_dmls)[PATH_MAX] = NULL;
       char (*issue_upfs)[PATH_MAX] = NULL;
       int nissue_dms = 0, nissue_pms = 0, nissue_imfs = 0, nissue_dmls = 0, nissue_smcs = 0, nissue_upfs = 0;

       int only_latest = 0;
       int only_official_issue = 0;
       int only_writable = 0;
       int only_readonly = 0;
       int only_old = 0;
       int only_inwork = 0;
       int recursive = 0;
       int show = 0;
       int list = 0;

       int i;

       const char *sopts = "0CDe:GiLlMPRrSwXoINnU7h?";
       struct option lopts[] = {
               {"version"   , no_argument      , 0, 0},
               {"help"      , no_argument      , 0, 'h'},
               {"null"      , no_argument      , 0, '0'},
               {"com"       , no_argument      , 0, 'C'},
               {"dm"        , no_argument      , 0, 'D'},
               {"exec"      , required_argument, 0, 'e'},
               {"icn"       , no_argument      , 0, 'G'},
               {"official"  , no_argument      , 0, 'i'},
               {"dml"       , no_argument      , 0, 'L'},
               {"latest"    , no_argument      , 0, 'l'},
               {"imf"       , no_argument      , 0, 'M'},
               {"pm"        , no_argument      , 0, 'P'},
               {"read-only" , no_argument      , 0, 'R'},
               {"recursive" , no_argument      , 0, 'r'},
               {"smc"       , no_argument      , 0, 'S'},
               {"writable"  , no_argument      , 0, 'w'},
               {"ddn"       , no_argument      , 0, 'X'},
               {"old"       , no_argument      , 0, 'o'},
               {"inwork"    , no_argument      , 0, 'I'},
               {"omit-issue", no_argument      , 0, 'N'},
               {"upf"       , no_argument      , 0, 'U'},
               {"other"     , no_argument      , 0, 'n'},
               {"list"      , no_argument      , 0, '7'},
               LIBXML2_PARSE_LONGOPT_DEFS
               {0, 0, 0, 0}
       };
       int loptind = 0;

       while ((i = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) {
               switch (i) {
                       case 0:
                               if (strcmp(lopts[loptind].name, "version") == 0) {
                                       show_version();
                                       return 0;
                               }
                               LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg)
                               break;
                       case '0': sep = '\0'; break;
                       case 'C': show |= SHOW_COM; break;
                       case 'D': show |= SHOW_DM; break;
                       case 'e': execstr = strdup(optarg); break;
                       case 'G': show |= SHOW_ICN; break;
                       case 'i': only_official_issue = 1; break;
                       case 'L': show |= SHOW_DML; break;
                       case 'l': only_latest = 1; break;
                       case 'M': show |= SHOW_IMF; break;
                       case 'P': show |= SHOW_PM; break;
                       case 'R': only_readonly = 1; break;
                       case 'r': recursive = 1; break;
                       case 'S': show |= SHOW_SMC; break;
                       case 'w': only_writable = 1; break;
                       case 'X': show |= SHOW_DDN; break;
                       case 'o': only_old = 1; break;
                       case 'I': only_inwork = 1; break;
                       case 'N': no_issue = 1; break;
                       case 'n': show |= SHOW_NON; break;
                       case 'U': show |= SHOW_UPF; break;
                       case '7': list = 1; break;
                       case 'h':
                       case '?': show_help();
                                 return 0;
               }
       }

       if (!show) show = SHOW_DM | SHOW_PM | SHOW_COM | SHOW_ICN | SHOW_IMF | SHOW_DDN | SHOW_DML | SHOW_SMC | SHOW_UPF;

       if (optset(show, SHOW_DM)) {
               dms = malloc(DM_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_PM)) {
               pms = malloc(PM_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_COM)) {
               coms = malloc(COM_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_ICN)) {
               icns = malloc(ICN_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_IMF)) {
               imfs = malloc(IMF_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_DDN)) {
               ddns = malloc(DDN_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_DML)) {
               dmls = malloc(DML_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_SMC)) {
               smcs = malloc(SMC_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_UPF)) {
               upfs = malloc(UPF_MAX * PATH_MAX);
       }
       if (optset(show, SHOW_NON)) {
               nons = malloc(NON_MAX * PATH_MAX);
       }

       if (optind < argc) {
               for (i = optind; i < argc; ++i) {
                       if (list) {
                               read_list(argv[i], only_writable, only_readonly, recursive);
                       } else {
                               list_path(argv[i], only_writable, only_readonly, recursive);
                       }
               }
       } else if (list) {
               read_list(NULL, only_writable, only_readonly, recursive);
       } else {
               list_dir(".", only_writable, only_readonly, recursive);
       }

       if (ndms) {
               qsort(dms, ndms, PATH_MAX, compare_basename);
               if (only_latest || only_old) latest_dms = malloc(ndms * PATH_MAX);
               if (only_official_issue || only_inwork) issue_dms = malloc(ndms * PATH_MAX);
       } else {
               free(dms);
       }
       if (npms) {
               qsort(pms, npms, PATH_MAX, compare_basename);
               if (only_latest || only_old) latest_pms = malloc(npms * PATH_MAX);
               if (only_official_issue || only_inwork) issue_pms = malloc(npms * PATH_MAX);
       } else {
               free(pms);
       }
       if (nsmcs) {
               qsort(smcs, nsmcs, PATH_MAX, compare_basename);
               if (only_latest || only_old) latest_smcs = malloc(nsmcs * PATH_MAX);
               if (only_official_issue || only_inwork) issue_smcs = malloc(nsmcs * PATH_MAX);
       } else {
               free(smcs);
       }
       if (nupfs) {
               qsort(upfs, nupfs, PATH_MAX, compare_basename);
               if (only_latest || only_old) latest_upfs = malloc(nupfs * PATH_MAX);
               if (only_official_issue || only_inwork) issue_upfs = malloc(nupfs * PATH_MAX);
       } else {
               free(upfs);
       }
       if (nimfs) {
               qsort(imfs, nimfs, PATH_MAX, compare_basename);
               if (only_latest || only_old) latest_imfs = malloc(nimfs * PATH_MAX);
               if (only_official_issue || only_inwork) issue_imfs = malloc(nimfs * PATH_MAX);
       } else {
               free(imfs);
       }
       if (ndmls) {
               qsort(dmls, ndmls, PATH_MAX, compare_basename);
               if (only_latest || only_old) latest_dmls = malloc(ndmls * PATH_MAX);
               if (only_official_issue || only_inwork) issue_dmls = malloc(ndmls * PATH_MAX);
       } else {
               free(dmls);
       }
       if (nicns) {
               qsort(icns, nicns, PATH_MAX, compare_icn);
               if (only_latest || only_old) latest_icns = malloc(nicns * PATH_MAX);
       } else {
               free(icns);
       }

       if (!ncoms) {
               free(coms);
       }
       if (!nddns) {
               free(ddns);
       }

       if (only_official_issue || only_inwork) {
               if (only_old) {
                       int (*f)(char (*)[PATH_MAX], char (*)[PATH_MAX], int);

                       if (ndms) {
                               nissue_dms = remove_latest(issue_dms, dms, ndms);
                               free(dms);
                       }
                       if (npms) {
                               nissue_pms = remove_latest(issue_pms, pms, npms);
                               free(pms);
                       }
                       if (nsmcs) {
                               nissue_smcs = remove_latest(issue_smcs, smcs, nsmcs);
                               free(smcs);
                       }
                       if (nupfs) {
                               nissue_upfs = remove_latest(issue_upfs, upfs, nupfs);
                               free(upfs);
                       }
                       if (nimfs) {
                               nissue_imfs = remove_latest(issue_imfs, imfs, nimfs);
                               free(imfs);
                       }
                       if (ndmls) {
                               nissue_dmls = remove_latest(issue_dmls, dmls, ndmls);
                               free(dmls);
                       }
                       if (nicns) {
                               nlatest_icns = remove_latest_icns(latest_icns, icns, nicns);
                               free(icns);
                       }

                       if (only_official_issue) {
                               f = extract_official;
                       } else {
                               f = remove_official;
                       }

                       if (nissue_dms) {
                               nlatest_dms = f(latest_dms, issue_dms, nissue_dms);
                       }
                       if (nissue_pms) {
                               nlatest_pms = f(latest_pms, issue_pms, nissue_pms);
                       }
                       if (nissue_smcs) {
                               nlatest_smcs = f(latest_smcs, issue_smcs, nissue_smcs);
                       }
                       if (nissue_upfs) {
                               nlatest_upfs = f(latest_upfs, issue_upfs, nissue_upfs);
                       }
                       if (nissue_imfs) {
                               nlatest_imfs = f(latest_imfs, issue_imfs, nissue_imfs);
                       }
                       if (nissue_dmls) {
                               nlatest_dmls = f(latest_dmls, issue_dmls, nissue_dmls);
                       }

                       free(issue_dms);
                       free(issue_pms);
                       free(issue_smcs);
                       free(issue_upfs);
                       free(issue_imfs);
                       free(issue_dmls);
               } else {
                       int (*f)(char (*)[PATH_MAX], char (*)[PATH_MAX], int);

                       if (only_official_issue) {
                               f = extract_official;
                       } else {
                               f = remove_official;
                       }

                       if (ndms) {
                               nissue_dms = f(issue_dms, dms, ndms);
                               free(dms);
                       }
                       if (npms) {
                               nissue_pms = f(issue_pms, pms, npms);
                               free(pms);
                       }
                       if (nsmcs) {
                               nissue_smcs = f(issue_smcs, smcs, nsmcs);
                               free(smcs);
                       }
                       if (nupfs) {
                               nissue_upfs = f(issue_upfs, upfs, nupfs);
                               free(upfs);
                       }
                       if (nimfs) {
                               nissue_imfs = f(issue_imfs, imfs, nimfs);
                               free(imfs);
                       }
                       if (ndmls) {
                               nissue_dmls = f(issue_dmls, dmls, ndmls);
                               free(dmls);
                       }

                       if (only_latest) {
                               if (nissue_dms) {
                                       nlatest_dms = extract_latest_csdb_objects(latest_dms, issue_dms, nissue_dms);
                               }
                               if (nissue_pms) {
                                       nlatest_pms = extract_latest_csdb_objects(latest_pms, issue_pms, nissue_pms);
                               }
                               if (nissue_smcs) {
                                       nlatest_smcs = extract_latest_csdb_objects(latest_smcs, issue_smcs, nissue_smcs);
                               }
                               if (nissue_upfs) {
                                       nlatest_upfs = extract_latest_csdb_objects(latest_upfs, issue_upfs, nissue_upfs);
                               }
                               if (nissue_imfs) {
                                       nlatest_imfs = extract_latest_csdb_objects(latest_imfs, issue_imfs, nissue_imfs);
                               }
                               if (nissue_dmls) {
                                       nlatest_dmls = extract_latest_csdb_objects(latest_dmls, issue_dmls, nissue_dmls);
                               }
                               if (nicns) {
                                       nlatest_icns = extract_latest_icns(latest_icns, icns, nicns);
                                       free(icns);
                               }

                               free(issue_dms);
                               free(issue_pms);
                               free(issue_smcs);
                               free(issue_upfs);
                               free(issue_imfs);
                               free(issue_dmls);
                       }
               }
       } else if (only_latest || only_old) {
               int (*f)(char (*)[PATH_MAX], char (*)[PATH_MAX], int);
               int (*icnf)(char (*)[PATH_MAX], char (*)[PATH_MAX], int);

               if (only_latest) {
                       f = extract_latest_csdb_objects;
                       icnf = extract_latest_icns;
               } else {
                       f = remove_latest;
                       icnf = remove_latest_icns;
               }
               if (ndms) {
                       nlatest_dms = f(latest_dms, dms, ndms);
                       free(dms);
               }
               if (npms) {
                       nlatest_pms = f(latest_pms, pms, npms);
                       free(pms);
               }
               if (nsmcs) {
                       nlatest_smcs = f(latest_smcs, smcs, nsmcs);
                       free(smcs);
               }
               if (nupfs) {
                       nlatest_upfs = f(latest_upfs, upfs, nupfs);
                       free(upfs);
               }
               if (nimfs) {
                       nlatest_imfs = f(latest_imfs, imfs, nimfs);
                       free(imfs);
               }
               if (ndmls) {
                       nlatest_dmls = f(latest_dmls, dmls, ndmls);
                       free(dmls);
               }
               if (nicns) {
                       nlatest_icns = icnf(latest_icns, icns, nicns);
                       free(icns);
               }
       }

       if (ncoms) {
               if (!only_old) {
                       printfiles(coms, ncoms);
               }
               free(coms);
       }

       if (nddns) {
               if (!only_old) {
                       printfiles(ddns, nddns);
               }
               free(ddns);
       }

       if (ndms) {
               if (only_latest || only_old) {
                       printfiles(latest_dms, nlatest_dms);
                       free(latest_dms);
               } else if (only_official_issue || only_inwork) {
                       printfiles(issue_dms, nissue_dms);
                       free(issue_dms);
               } else {
                       printfiles(dms, ndms);
                       free(dms);
               }
       }

       if (ndmls) {
               if (only_latest || only_old) {
                       printfiles(latest_dmls, nlatest_dmls);
                       free(latest_dmls);
               } else if (only_official_issue || only_inwork) {
                       printfiles(issue_dmls, nissue_dmls);
                       free(issue_dmls);
               } else {
                       printfiles(dmls, ndmls);
                       free(dmls);
               }
       }

       if (nicns) {
               if (only_inwork) {
                       if (only_latest || only_old) {
                               free(latest_icns);
                       } else {
                               free(icns);
                       }
               } else {
                       if (only_latest || only_old) {
                               printfiles(latest_icns, nlatest_icns);
                               free(latest_icns);
                       } else {
                               printfiles(icns, nicns);
                               free(icns);
                       }
               }
       }

       if (nimfs) {
               if (only_latest || only_old) {
                       printfiles(latest_imfs, nlatest_imfs);
                       free(latest_imfs);
               } else if (only_official_issue || only_inwork) {
                       printfiles(issue_imfs, nissue_imfs);
                       free(issue_imfs);
               } else {
                       printfiles(imfs, nimfs);
                       free(imfs);
               }
       }

       if (npms) {
               if (only_latest || only_old) {
                       printfiles(latest_pms, nlatest_pms);
                       free(latest_pms);
               } else if (only_official_issue || only_inwork) {
                       printfiles(issue_pms, nissue_pms);
                       free(issue_pms);
               } else {
                       printfiles(pms, npms);
                       free(pms);
               }
       }

       if (nsmcs) {
               if (only_latest || only_old) {
                       printfiles(latest_smcs, nlatest_smcs);
                       free(latest_smcs);
               } else if (only_official_issue || only_inwork) {
                       printfiles(issue_smcs, nissue_smcs);
                       free(issue_smcs);
               } else {
                       printfiles(smcs, nsmcs);
                       free(smcs);
               }
       }

       if (nupfs) {
               if (only_latest || only_old) {
                       printfiles(latest_upfs, nlatest_upfs);
                       free(latest_upfs);
               } else if (only_official_issue || only_inwork) {
                       printfiles(issue_upfs, nissue_upfs);
                       free(issue_upfs);
               } else {
                       printfiles(upfs, nupfs);
                       free(upfs);
               }
       }

       if (nnons) {
               printfiles(nons, nnons);
               free(nons);
       }

       if (dir) {
               closedir(dir);
       }

       free(execstr);

       xmlCleanupParser();

       return 0;
}