#include "magiskboot.h"

// Global pointer of output
static int ofd, opos;

static size_t restore(const char *filename) {
	int fd = open(filename, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Cannot open %s\n", filename);
		exit(1);
	}
	size_t size = lseek(fd, 0, SEEK_END);
	lseek(fd, 0, SEEK_SET);
	if (sendfile(ofd, fd, NULL, size) < 0) {
		fprintf(stderr, "Cannot write %s\n", filename);
		exit(1);
	}
	close(fd);
	opos += size;
	return size;
}

static void restore_buf(size_t size, const void *buf) {
	if (write(ofd, buf, size) != size) {
		fprintf(stderr, "Cannot dump from input file\n");
		exit(1);
	}
	opos += size;
}

static void page_align() {
	uint32_t pagemask = hdr.page_size - 1L;
	if (opos & pagemask) {
		opos += hdr.page_size - (opos & pagemask);
	}
	ftruncate(ofd, opos);
	lseek(ofd, 0, SEEK_END);
}

void repack(const char* image) {
	// Load original image
	int ifd = open(image, O_RDONLY);
	if (ifd < 0)
		error(1, "Cannot open %s", image);
	size_t isize = lseek(ifd, 0, SEEK_END);
	lseek(ifd, 0, SEEK_SET);
	unsigned char *orig = mmap(NULL, isize, PROT_READ, MAP_SHARED, ifd, 0);

	// Parse original image
	parse_img(orig, isize);

	// Create new boot image
	ofd = open("new-boot.img", O_RDWR | O_CREAT | O_TRUNC, 0644);

	char name[PATH_MAX];
	#define EXT_NUM 6
	char *ext_list[EXT_NUM] = { "gz", "lzo", "xz", "lzma", "bz2", "lz4" };
	
	// Set all sizes to 0
	hdr.kernel_size = 0;
	hdr.ramdisk_size = 0;
	hdr.second_size = 0;
	hdr.dt_size = 0;

	// Skip a page for header
	ftruncate(ofd, hdr.page_size);
	lseek(ofd, 0, SEEK_END);
	opos += hdr.page_size;

	// Restore kernel
	if (mtk_kernel) {
		restore_buf(512, kernel);
		hdr.kernel_size += 512;
	}
	hdr.kernel_size += restore(KERNEL_FILE);
	page_align();

	// Restore ramdisk
	if (mtk_ramdisk) {
		restore_buf(512, ramdisk);
		hdr.ramdisk_size += 512;
	}

	if (access(RAMDISK_FILE, R_OK) == 0) {
		// If we found raw cpio, recompress to original format
		int rfd = open(RAMDISK_FILE, O_RDONLY);
		if (rfd < 0)
			error(1, "Cannot open " RAMDISK_FILE);

		size_t cpio_size = lseek(rfd, 0, SEEK_END);
		lseek(rfd, 0, SEEK_SET);
		unsigned char *cpio = mmap(NULL, cpio_size, PROT_READ, MAP_SHARED, rfd, 0);

		switch (ramdisk_type) {
			case GZIP:
				sprintf(name, "%s.%s", RAMDISK_FILE, "gz");
				gzip(0, name, cpio, cpio_size);
				break;
			case LZOP:
				sprintf(name, "%s.%s", RAMDISK_FILE, "lzo");
				break;
			case XZ:
				sprintf(name, "%s.%s", RAMDISK_FILE, "xz");
				break;
			case LZMA:
				sprintf(name, "%s.%s", RAMDISK_FILE, "lzma");
				break;
			case BZIP2:
				sprintf(name, "%s.%s", RAMDISK_FILE, "bz2");
				break;
			case LZ4:
				sprintf(name, "%s.%s", RAMDISK_FILE, "lz4");
				break;
			default:
				// Never happens
				break;
		}

		printf("Re-compressed %s to %s\n", RAMDISK_FILE, name);
		munmap(cpio, cpio_size);
		close(rfd);
	} else {
		// If no raw cpio found, find compressed ones
		int found = 0;
		for (int i = 0; i < EXT_NUM; ++i) {
			sprintf(name, "%s.%s", RAMDISK_FILE, ext_list[i]);
			if (access(name, R_OK) == 0) {
				found = 1;
				break;
			}
		}
		if (!found) {
			error(1, "No ramdisk exists!");
		}
	}

	hdr.ramdisk_size += restore(name);
	page_align();

	// Restore second
	if (access(SECOND_FILE, R_OK) == 0) {
		hdr.second_size += restore(SECOND_FILE);
		page_align();
	}

	// Restore dtb
	if (access(DTB_FILE, R_OK) == 0) {
		hdr.dt_size += restore(DTB_FILE);
		page_align();
	}

	// Write header back
	lseek(ofd, 0, SEEK_SET);
	write(ofd, &hdr, sizeof(hdr));

	munmap(orig, isize);
	close(ifd);
	close(ofd);
	if (opos > isize) {
		error(2, "Boot partition too small!");
	}
}
