#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "endian.h"
#include "sgidvh.h"

#define VERSION "0.13"

#define MAXLINE 256
#define MAXFILE 256
#define MAXRECURSE 8

char outfile[MAXFILE] = "a.iso";
int nrecurse;
char vfiles[NVDIR][MAXFILE];
char vfnames[NVDIR][VDNAMESIZE+1];
int vfsizes[NVDIR];
int vfoffs[NVDIR];
char vparts[NPARTAB][MAXFILE];
int vpsizes[NPARTAB];
int vpoffs[NPARTAB];
int lastf, lastp;
int pvol = -1, pdvh = -1, proot = -1;

int writevh(FILE *o)
{
	struct volume_header vh;
	unsigned int *pv = (unsigned int *)&vh, w, s = 0;
	int i;

	memset(&vh, 0, 512);
	vh.vh_magic = VHMAGIC;
	vh.vh_rootpt = proot;
	vh.vh_swappt = 0;
	strncpy(vh.vh_bootfile,"unix",BFNAMESIZE);

	for (i = 0; i < NVDIR; i++)
		if (vfiles[i][0]) {
			strncpy(vh.vh_vd[i].vd_name, vfnames[i], VDNAMESIZE);
			vh.vh_vd[i].vd_lbn = vfoffs[i];
			vh.vh_vd[i].vd_nbytes = vfsizes[i];
			swap_vol_dir(vh.vh_vd[i]);
		}

	for (i = 0; i < NPARTAB; i++)
		if (vparts[i][0]) {
			vh.vh_pt[i].pt_nblks = ((vpsizes[i] + 511) / 512);
			vh.vh_pt[i].pt_firstlbn = vpoffs[i];
			vh.vh_pt[i].pt_type = 3;

			if (i == pdvh)
				vh.vh_pt[i].pt_type = 0;

			if (i == pvol)
				vh.vh_pt[i].pt_type = 6;

			swap_par_tab(vh.vh_pt[i]);
		}

	swap_vol_hdr(vh);
	for (i = 0; i < 128; i++) {
		w = *pv;
		swap32(&w);
		s += w;
		pv++;
	}

	s = -s;
	swap32(&s);
	vh.vh_csum = s;
	fwrite(&vh, 512, 1, o);

	return 512;
}

int writefiller(FILE *o, int n)
{
	char zeros[512];
	int i = n, c;

	memset(zeros, 0, 512);
	if (i < 0)
		fprintf(stderr, "Internal error: negative filler encountered.\n");
	while (i > 0) {
		c = ((i > 512) ? 512 : i);
		fwrite(zeros, 1, c, o);
		i -= c;
	}

	return n;
}

int writefile(FILE *o, char *s, int n)
{
	char buf[512];
	FILE *f = fopen(s, "r");
	int i = n, c;
	size_t ret;

	while (i > 0) {
		c = ((i > 512) ? 512 : i);
		ret = fread(buf, 1, c, f);
		fwrite(buf, 1, c, o);
		i -= c;
	}

	fclose(f);

	return n;
}

int writeimage(void)
{
	FILE *o;
	int p, i;

	if (*outfile)
		o = fopen(outfile, "w");
	else
		o = stdout;

	if (!o) {
		fprintf(stderr, "Error: can't write output file '%s'.\n", outfile);
		return 0;
	}

	fprintf(stderr, "Writing image...");
	p = writevh(o);
	for (i = 0; i < NVDIR; i++)
		if (vfiles[i][0]) {
			p += writefiller(o, 512 * vfoffs[i] - p);
			p += writefile(o, vfiles[i], vfsizes[i]);
		}

	for (i = 0; i < NPARTAB; i++)
		if (vparts[i][0] && (i != pvol) && (i != pdvh)) {
			p += writefiller(o, 512 * vpoffs[i] - p);
			p += writefile(o, vparts[i], vpsizes[i]);
		}
	writefiller(o, (2048 * ((p + 2047) / 2048)) - p);

	if (*outfile)
		fclose(o);
	fprintf(stderr, " done.\n");

	return 1;
}

int addpart(char *p, int n);

int buildimage(void)
{
	int lb = 4, i, n;

	if (pdvh == -1)
		if (!addpart("#dvh", -1))
			return 0;

	if (pvol == -1)
		if (!addpart("#volume", -1))
			return 0;

	fprintf(stderr, "Volume header:\n");
	for (i = 0; i < NVDIR; i++)
		if (vfiles[i][0]) {
			n = ((vfsizes[i] + 511) / 512);
			vfoffs[i] = lb;
			lb += n;
			fprintf(stderr, "\t%2d: %-9s%9d B @ %6d LBA = %s\n",
				i, vfnames[i], vfsizes[i], vfoffs[i], vfiles[i]);
		}

	vpsizes[pdvh] = (lb * 512);
	for (i = 0; i < NPARTAB; i++)
		if (vparts[i][0] && (i != pvol) && (i != pdvh)) {
			if (proot == -1)
				proot = i;
			n = ((vpsizes[i] + 511) / 512);
			vpoffs[i] = lb;
			lb += n;
		}

	if (proot == -1)
		proot = pvol;
	vpsizes[pvol] = (lb * 512);

	fprintf(stderr, "Partition table:\n");
	for (i = 0; i < NPARTAB; i++)
		if (vparts[i][0])
			fprintf(stderr, "\t%2d:          %9d B @ %6d LBA = %s\n",
				i, vpsizes[i], vpoffs[i], vparts[i]);

	return 1;
}

int addfile(char *p, char *v, int n)
{
	int i = lastf;
	FILE *f = fopen(p, "r");

	if (!f) {
		fprintf(stderr, "Error: can't read data file '%s'.\n", p);
		return 0;
	}

	if (n == -1) {
		while (vfiles[i][0]) {
			i = ((i + 1) % NVDIR);
			if (i == lastf) {
				fprintf(stderr, "Error: number of files exceeds %d.\n", NVDIR);
				fclose(f);
				return 0;
			}
		}
	} else {
		i = n;
	}

	if ((i < 0) || (i >= NVDIR)) {
		fprintf(stderr, "Error: file number %d outside the [0;%d] range.\n",
			i, NVDIR);
		return 0;
	}

	if (vfiles[i][0]) {
		fprintf(stderr, "Error: file number %d is already used.\n", i);
		return 0;
	}

	lastf = i;
	strncpy(vfiles[i], p, MAXFILE);
	vfiles[i][MAXFILE - 1] = 0;

	if (!v)
		v = p;

	strncpy(vfnames[i], v, VDNAMESIZE);
	vfnames[i][VDNAMESIZE] = 0;
	fseek(f, 0, SEEK_END);
	vfsizes[i] = ftell(f);
	fclose(f);

	return 1;
}

int addpart(char *p, int n)
{
	int i = lastp;
	FILE *f;

	if (n == -1) {
		while (vparts[i][0]) {
			i = ((i + 1) % NPARTAB);
			if (i == lastp) {
				fprintf(stderr, "Error: number of partitions exceeds %d.\n",
					NPARTAB);
				return 0;
			}
		}
	} else {
		i = n;
	}

	if ((i < 0) || (i >= NPARTAB)) {
		fprintf(stderr, "Error: partition number %d outside the [0;%d] range.\n",
			i, NPARTAB);
		return 0;
	}

	if (vparts[i][0]) {
		fprintf(stderr, "Error: partition number %d is already used.\n", i);
		return 0;
	}

	lastp = i;
	if (!strcmp(p, "#dvh")) {
		if (pdvh != -1) {
			fprintf(stderr, "Error: DVH partition already defined.\n");
			return 0;
		}

		strcpy(vparts[i], p);
		pdvh = i;
		return 1;
	}

	if (!strcmp(p, "#volume")) {
		if (pvol != -1) {
			fprintf(stderr, "Error: volume partition already defined.\n");
			return 0;
		}

		strcpy(vparts[i], p);
		pvol = i;
		return 1;
	}

	f = fopen(p, "r");
	if (!f) {
		fprintf(stderr, "Error: can't read partition file '%s'.\n", p);
		return 0;
	}

	strncpy(vparts[i], p, MAXFILE);
	vparts[i][MAXFILE - 1] = 0;
	fseek(f, 0, SEEK_END);
	vpsizes[i] = ftell(f);
	fclose(f);

	return 1;
}

int parseparm(char *p);

int recurseconfig(char *n)
{
	FILE *f = fopen(n, "r");
	char l[MAXLINE];

	if (!f) {
		fprintf(stderr, "Error: can't read config file '%s'.\n", n);
		return 0;
	}

	if (nrecurse > MAXRECURSE) {
		fprintf(stderr, "Error: maximum recursion level exceeded.\n");
		return 0;
	}

	nrecurse++;
	while (!feof(f)) {
		if (!fgets(l, MAXLINE, f))
			continue;

		if (l[strlen(l) - 1] == '\n')
			l[strlen(l) - 1] = 0;

		if (!*l)
			continue;

		if (!parseparm(l)) {
			nrecurse--;
			fclose(f);
			return 0;
		}
	}
	nrecurse--;
	fclose(f);

	return 1;
}

int parseparm(char *p)
{
	char *q = strchr(p, '='), *r, t, n, *en;

	if (!q) {
		fprintf(stderr, "Error: parameter form has to be '<type>[<num>]=<name>[@<virtual>]'.\n");
		return 0;
	}

	*q = 0;
	q++;
	t = *(p++);

	if (*p) {
		n = strtol(p, &en, 10);
		if (*en) {
			fprintf(stderr, "Error: numeric constant is incorrect.\n");
			return 0;
		}
	} else {
		n = -1;
	}

	r = strchr(q, '@');
	if (r) {
		*r = 0;
		r++;
	}

	switch (t) {
	case 'o':
		if (n != -1) {
			fprintf(stderr, "Error: '%c' type does not need a numeric constant.\n", t);
			return 0;
		}

		if (r) {
			fprintf(stderr, "Error: '%c' type does not need a virtual name.\n", t);
			return 0;
		}

		strncpy(outfile, q, MAXFILE);
		outfile[MAXFILE - 1] = 0;
		return 1;

	case 'c':
		if (n != -1) {
			fprintf(stderr, "Error: '%c' type does not need a numeric constant.\n", t);
			return 0;
		}

		if (r) {
			fprintf(stderr, "Error: '%c' type does not need a virtual name.\n", t);
			return 0;
		}

		return recurseconfig(q);

	case 'f':
		return addfile(q, r, n);

	case 'p':
		if (r) {
			fprintf(stderr, "Error: '%c' type does not need a virtual name.\n", t);
			return 0;
		}

		return addpart(q, n);
	}

	fprintf(stderr, "Error: parameter type '%c' not known.\n", t);

	return 0;
}

void showinfo(void)
{
	printf("\nsgibootcd " VERSION " - bootable volume image builder for SGI MIPS machines\n"
		"Copyright (c) 2005 Stanislaw Skowronek\n\n"
		"Options:\n"
		"\to=<file.iso>\t\t\tset output file name\n"
		"\tc=<file.cfg>\t\t\tinclude configuration file\n"
		"\tp[<index>]=<file.img>\t\tcreate a partition (optional index)\n\t\t\t\t\tfrom a disk image\n"
		"\tf[<index>]=<file.img>[@<name>]\tcreate a file (optional index and name)\n\t\t\t\t\tin the DVH\n"
		"\nConfiguration file syntax is one command-line option per line.\n\n");
}

int main(int argc, char **argv)
{
	int i;
	char l[MAXLINE];

	if (argc < 2) {
		showinfo();
		return 1;
	}

	for (i = 1; i < argc; i++) {
		strncpy(l, argv[i], MAXLINE);
		l[MAXLINE - 1] = 0;

		if (!parseparm(l))
			return 1;
	}

	if (!buildimage())
		return 1;
	if (!writeimage())
		return 1;

	return 0;
}
