#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mspack.h>
#include <sys/stat.h>
#include <dirent.h>

#include <md5_fh.h>
#include <error.h>

/**
 * Matches a cabinet's filename case-insensitively in the filesystem and
 * returns the case-correct form.
 *
 * @param origcab if this is non-NULL, the pathname part of this filename
 *                will be extracted, and the search will be conducted in
 *                that directory.
 * @param cabname the internal CAB filename to search for.
 * @return a copy of the full, case-correct filename of the given cabinet
 *         filename, or NULL if the specified filename does not exist on disk.
 */
static char *find_cabinet_file(char *origcab, char *cabname) {
    struct dirent *entry;
    struct stat st_buf;
    int found = 0, len;
    char *tail, *cab;
    DIR *dir;

    /* ensure we have a cabinet name at all */
    if (!cabname || !cabname[0]) return NULL;

    /* find if there's a directory path in the origcab */
    tail = origcab ? strrchr(origcab, '/') : NULL;
    len = (tail - origcab) + 1;

    /* allocate memory for our copy */
    if (!(cab = (char *) malloc((tail ? len : 2) + strlen(cabname) + 1))) return NULL;

    /* add the directory path from the original cabinet name, or "." */
    if (tail) memcpy(cab, origcab, (size_t) len);
    else      cab[0]='.', cab[1]='/', len=2;
    cab[len] = '\0';

    /* try accessing the cabinet with its current name (case-sensitive) */
    strcpy(&cab[len], cabname);
    if (stat(cab, &st_buf) == 0) {
	found = 1;
    }
    else {
	/* cabinet was not found, look for it in the current dir */
	cab[len] = '\0';
	if ((dir = opendir(cab))) {
	    while ((entry = readdir(dir))) {
		if (strcasecmp(cabname, entry->d_name) == 0) {
		    strcat(cab, entry->d_name);
		    found = (stat(cab, &st_buf) == 0);
		    break;
		}
	    }
	    closedir(dir);
	}
    }

    if (!found || !S_ISREG(st_buf.st_mode)) {
	/* cabinet not found, or not a regular file */
	free(cab);
	cab = NULL;
    }

    return cab;
}


int main(int argc, char *argv[]) {
    struct mscab_decompressor *cabd;
    struct mscabd_cabinet *cab, *c, *c2;
    struct mscabd_file *file;
    char *cabname, *newname;
    int err;

    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    /* if self-test reveals an error */
    MSPACK_SYS_SELFTEST(err);
    if (err) return 1;

    if (!(cabd = mspack_create_cab_decompressor(&read_files_write_md5))) {
	fprintf(stderr, "can't make decompressor\n");
	return 1;
    }

    for (argv++; (cabname = *argv); argv++) {
	printf("*** %s\n", cabname);

	if (!(cab = cabd->open(cabd, cabname))) {
	    fprintf(stderr, "cab open error: %s\n", ERROR(cabd));
	    continue;
	}

	/* prepend any spanning cabinets */
	for (c = cab; c && (c->flags & MSCAB_HDR_PREVCAB); c = c->prevcab) {
	    if (!(newname = find_cabinet_file(cabname, c->prevname))) {
		fprintf(stderr, "%s: can't find \"%s\" to prepend\n",
			cabname, c->prevname);
		break;
	    }
	    if (!(c2 = cabd->open(cabd, newname))) {
		fprintf(stderr, "%s: error opening \"%s\" for prepend: %s\n",
			cabname, newname, ERROR(cabd));
		break;
	    }
	    if (cabd->prepend(cabd, c, c2) != MSPACK_ERR_OK) {
		fprintf(stderr, "%s: error prepending \"%s\": %s\n",
			cabname, newname, ERROR(cabd));
		break;
	    }
	}

	/* append any spanning cabinets */
	for (c = cab; c && (c->flags & MSCAB_HDR_NEXTCAB); c = c->nextcab) {
	    if (!(newname = find_cabinet_file(cabname, c->nextname))) {
		fprintf(stderr, "%s: can't find \"%s\" to append\n",
			cabname, c->nextname);
		break;
	    }
	    if (!(c2 = cabd->open(cabd, newname))) {
		fprintf(stderr, "%s: error opening \"%s\" for append: %s\n",
			cabname, newname, ERROR(cabd));
		break;
	    }
	    if (cabd->append(cabd, c, c2) != MSPACK_ERR_OK) {
		fprintf(stderr, "%s: error appending \"%s\": %s\n",
			cabname, newname, ERROR(cabd));
		break;
	    }
	}

	/* extract files */
	for (file = cab->files; file; file = file->next ) {
	    if (cabd->extract(cabd, file, NULL) == MSPACK_ERR_OK) {
		printf("%s  %s\n", md5_string, file->filename);
	    }
	    else {
		fprintf(stderr, "%s: error extracting \"%s\": %s\n",
			cabname, file->filename, ERROR(cabd));
	    }
	}

	/* free all resources */
	for (c2 = cab->prevcab; c2; c2 = c2->prevcab) free((void*)c2->filename);
	for (c2 = cab->nextcab; c2; c2 = c2->nextcab) free((void*)c2->filename);
	cabd->close(cabd, cab);
    }
    mspack_destroy_cab_decompressor(cabd);
    return 0;
}