#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <errno.h>

#define MAX_LINE 1000
#define MAX_PKGS 100

char *checksum_field=NULL;

static void oom_die(void)
{
    fputs("Out of memory!\n", stderr);
    exit(1);
}

static char *xvasprintf(const char *fmt, va_list ap) {
    char *ret;

    if (vasprintf (&ret, fmt, ap) < 0) {
        if (errno == ENOMEM)
            oom_die();
        return NULL;
    }
    return ret;
}

static char *xasprintf(const char *fmt, ...) {
    va_list ap;
    char *ret;

    va_start(ap, fmt);
    ret = xvasprintf(fmt, ap);
    va_end(ap);
    return ret;
}

static char *fieldcpy(char *dst, char *fld) {
    while (*fld && *fld != ':') 
        fld++;
    if (!*(fld++)) 
        return NULL;
    while (isspace(*fld)) fld++;
    return strcpy(dst, fld);
}

static void outputdeps(char *deps) {
    char *pch = deps;

    while (1) {
        while (isspace(*pch)) pch++;
        if (!*pch) break;

        while (*pch && *pch != '(' && *pch != '|' && *pch != ','
               && !isspace(*pch))
        {
            fputc(*pch++, stdout);
        }
        fputc('\n', stdout);
        while (*pch && *pch++ != ',') (void)NULL;
    }
}

static void dogetdeps(char *pkgsfile, char **in_pkgs, int pkgc) {
    char buf[MAX_LINE];
    char cur_pkg[MAX_LINE];
    char cur_deps[MAX_LINE];
    char cur_predeps[MAX_LINE];
    char prev_pkg[MAX_LINE];
    char *pkgs[MAX_PKGS];
    int i;
    int skip;
    FILE *f;
    int output_pkg = -1;

    cur_pkg[0] = cur_deps[0] = cur_predeps[0] = prev_pkg[0] = '\0';

    for (i = 0; i < pkgc; i++) pkgs[i] = in_pkgs[i];

    f = fopen(pkgsfile, "r");
    if (f == NULL) {
        perror(pkgsfile);
        exit(1);
    }

    skip = 1;
    while (fgets(buf, sizeof(buf), f)) {
        if (*buf && buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0';
        if (strncasecmp(buf, "Package:", 8) == 0) {
            int any = 0;
            skip = 1;
            fieldcpy(cur_pkg, buf);
            if (strcmp(cur_pkg, prev_pkg) != 0) {
                if (output_pkg != -1)
                    pkgs[output_pkg] = NULL;
                if (cur_deps[0])
                    outputdeps(cur_deps);
                if (cur_predeps[0])
                    outputdeps(cur_predeps);
                strcpy(prev_pkg, cur_pkg);
            }
            cur_deps[0] = cur_predeps[0] = '\0';
            output_pkg = -1;
	    for (i = 0; i < pkgc; i++) {
		if (!pkgs[i]) continue;
		any = 1;
                if (strcmp(cur_pkg, pkgs[i]) == 0) {
                    skip = 0;
                    output_pkg = i;
                    break;
                }
            }
            if (!any) break;
        } else if (!skip && strncasecmp(buf, "Depends:", 8) == 0)
            fieldcpy(cur_deps, buf);
        else if (!skip && strncasecmp(buf, "Pre-Depends:", 12) == 0)
            fieldcpy(cur_predeps, buf);
    }
    if (cur_deps[0])
        outputdeps(cur_deps);
    if (cur_predeps[0])
        outputdeps(cur_predeps);
    fclose(f);
}

static void dopkgmirrorpkgs(int uniq, char *mirror, char *pkgsfile, 
        char *fieldname, char **in_pkgs, int pkgc) 
{
    char buf[MAX_LINE];
    char cur_field[MAX_LINE];
    char cur_pkg[MAX_LINE];
    char cur_ver[MAX_LINE];
    char cur_arch[MAX_LINE];
    char cur_size[MAX_LINE];
    char cur_checksum[MAX_LINE];
    char cur_filename[MAX_LINE];
    char prev_pkg[MAX_LINE];
    char *pkgs[MAX_PKGS];
    int i;
    FILE *f;
    char *output = NULL;
    int output_pkg = -1;

    cur_field[0] = cur_pkg[0] = cur_ver[0] = cur_arch[0] = cur_filename[0] = prev_pkg[0] = '\0';

    for (i = 0; i < pkgc; i++) pkgs[i] = in_pkgs[i];

    f = fopen(pkgsfile, "r");
    if (f == NULL) {
        perror(pkgsfile);
        exit(1);
    }
    while (fgets(buf, sizeof(buf), f)) {
        if (*buf && buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0';
        if (strncasecmp(buf, fieldname, strlen(fieldname)) == 0) {
            fieldcpy(cur_field, buf);
	}
        if (strncasecmp(buf, "Package:", 8) == 0) {
            fieldcpy(cur_pkg, buf);
            if (strcmp(cur_pkg, prev_pkg) != 0) {
                if (output)
                    fputs(output, stdout);
                if (uniq && output_pkg != -1)
                    pkgs[output_pkg] = NULL;
                strcpy(prev_pkg, cur_pkg);
            }
            free(output);
            output = NULL;
            output_pkg = -1;
        } else if (strncasecmp(buf, "Version:", 8) == 0) {
            fieldcpy(cur_ver, buf);
        } else if (strncasecmp(buf, "Architecture:", 13) == 0) {
            fieldcpy(cur_arch, buf);
        } else if (strncasecmp(buf, "Size:", 5) == 0) {
            fieldcpy(cur_size, buf);
        } else if (strncasecmp(buf, checksum_field, strlen(checksum_field)) == 0
	           && buf[strlen(checksum_field)] == ':') {
            fieldcpy(cur_checksum, buf);
        } else if (strncasecmp(buf, "Filename:", 9) == 0) {
            fieldcpy(cur_filename, buf);
        } else if (!*buf) {
	    int any = 0;
	    for (i = 0; i < pkgc; i++) {
		if (!pkgs[i]) continue;
		any = 1;
                if (strcmp(cur_field, pkgs[i]) == 0) {
                    free(output);
                    output = xasprintf("%s %s %s %s %s %s %s\n", cur_pkg, cur_ver, cur_arch, mirror, cur_filename, cur_checksum, cur_size);
                    output_pkg = i;
		    break;
		}
            }
	    if (!any) break;
            cur_field[0] = '\0';
        }
    }
    if (output)
        fputs(output, stdout);
    if (uniq && output_pkg != -1)
        pkgs[output_pkg] = NULL;
    fclose(f);

    /* any that weren't found are returned as "pkg -" */
    if (uniq) {
        for (i = 0; i < pkgc; i++) {
            if (pkgs[i]) {
                printf("%s -\n", pkgs[i]);
            }
        }
    }
}

static void dopkgstanzas(char *pkgsfile, char **pkgs, int pkgc)
{
    char buf[MAX_LINE];
    char *accum;
    size_t accum_size = 0, accum_alloc = MAX_LINE * 2;
    char cur_pkg[MAX_LINE];
    FILE *f;

    accum = malloc(accum_alloc);
    if (!accum)
        oom_die();

    f = fopen(pkgsfile, "r");
    if (f == NULL) {
        perror(pkgsfile);
        free(accum);
        exit(1);
    }
    while (fgets(buf, sizeof(buf), f)) {
        if (*buf) {
	    size_t len = strlen(buf);
            if (accum_size + len + 1 > accum_alloc) {
                accum_alloc = (accum_size + len + 1) * 2;
                accum = realloc(accum, accum_alloc);
                if (!accum)
                    oom_die();
            }
            strcpy(accum + accum_size, buf);
	    accum_size += len;
        }
        if (*buf && buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0';
        if (strncasecmp(buf, "Package:", 8) == 0) {
            fieldcpy(cur_pkg, buf);
        } else if (!*buf) {
            int i;
            for (i = 0; i < pkgc; i++) {
                if (!pkgs[i]) continue;
                if (strcmp(cur_pkg, pkgs[i]) == 0) {
                    fputs(accum, stdout);
		    if (accum[accum_size - 1] != '\n')
			fputs("\n\n", stdout);
		    else if (accum[accum_size - 2] != '\n')
			fputc('\n', stdout);
                    break;
                }
            }
            *accum = '\0';
            accum_size = 0;
        }
    }
    fclose(f);

    free(accum);
}

static int dotranslatewgetpercent(int low, int high, int end, char *str) {
    int ch;
    int val, lastval;
    int allow_percentage;

    /* print out anything that looks like a % on its own line, appropriately
     * scaled */

    lastval = val = 0;
    allow_percentage = 0;
    while ( (ch = getchar()) != EOF ) {
	if (isspace(ch)) {
	    allow_percentage = 1;
	} else if (allow_percentage && isdigit(ch)) {
	    val *= 10; val += ch - '0';
	} else if (allow_percentage && ch == '%') {
	    float f = (float) val / 100.0 * (high - low) + low;
	    if (str) {
	    	printf("P: %d %d %s\n", (int) f, end, str);
	    } else {
	    	printf("P: %d %d\n", (int) f, end);
	    }
	    lastval = val;
	} else {
	    val = 0;
	    allow_percentage = 0;
	}
    }
    return lastval == 100;
}

int main(int argc, char *argv[]) {
    checksum_field=getenv("DEBOOTSTRAP_CHECKSUM_FIELD");
    if (checksum_field == NULL) {
        checksum_field="MD5sum";
    }

    if ((argc == 6 || argc == 5) && strcmp(argv[1], "WGET%") == 0) {
	if (dotranslatewgetpercent(atoi(argv[2]), atoi(argv[3]), 
	                           atoi(argv[4]), argc == 6 ? argv[5] : NULL))
	{
	    exit(0);
	} else {
	    exit(1);
	}
    } else if (argc >= 4 && strcmp(argv[1], "GETDEPS") == 0) {
        int i;
        for (i = 3; argc - i > MAX_PKGS; i += MAX_PKGS) {
	    dogetdeps(argv[2], argv+i, MAX_PKGS);
	}
	dogetdeps(argv[2], argv+i, argc-i);
	exit(0);
    } else if (argc >= 5 && strcmp(argv[1], "PKGS") == 0) {
        int i;
        for (i = 4; argc - i > MAX_PKGS; i += MAX_PKGS) {
	    dopkgmirrorpkgs(1, argv[2], argv[3], "Package:", argv+i, MAX_PKGS);
	}
	dopkgmirrorpkgs(1, argv[2], argv[3], "Package:", argv+i, argc-i);
	exit(0);
    } else if (argc >= 6 && strcmp(argv[1], "FIELD") == 0) {
        int i;
        for (i = 5; argc - i > MAX_PKGS; i += MAX_PKGS) {
	    dopkgmirrorpkgs(0, argv[3], argv[4], argv[2], argv+i, MAX_PKGS);
	}
	dopkgmirrorpkgs(0, argv[3], argv[4], argv[2], argv+i, argc-i);
	exit(0);
    } else if (argc >= 4 && strcmp(argv[1], "STANZAS") == 0) {
	int i;
	for (i = 3; argc - i > MAX_PKGS; i += MAX_PKGS) {
	    dopkgstanzas(argv[2], argv+i, MAX_PKGS);
	}
	dopkgstanzas(argv[2], argv+i, argc-i);
	exit(0);
    } else {
        fprintf(stderr, "usage: %s PKGS mirror packagesfile pkgs..\n", argv[0]);
        fprintf(stderr, "   or: %s FIELD field mirror packagesfile pkgs..\n", 
                argv[0]);
        fprintf(stderr, "   or: %s GETDEPS packagesfile pkgs..\n", argv[0]);
        fprintf(stderr, "   or: %s STANZAS packagesfile pkgs..\n", argv[0]);
	fprintf(stderr, "   or: %s WGET%% low high end reason\n", argv[0]);
        exit(1);
    }
}
