/*
 *
 * (c) 2004 Laurent Vivier <Laurent@lvivier.info>
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>

#include "partition.h"
#include "libemile.h"

int verbose = 0;

extern void scanbus(void);
extern void diskinfo(char*);


enum {
	ACTION_NONE =	 0x00,
	ACTION_FLAGS =	 0x01,
	ACTION_TYPE = 	 0x02,
	ACTION_STARTUP = 0x04,
	ACTION_SCANBUS = 0x08,
	ACTION_GET =	 0x10,
	ACTION_PUT =	 0x20,
};

enum {
	ARG_NONE = 0,
	ARG_SCANBUS,
	ARG_HELP = 'h',
	ARG_FLAGS = 'f',
	ARG_TYPE ='t',
	ARG_STARTUP ='s',
	ARG_VERBOSE = 'v',
	ARG_GET = 'g',
	ARG_PUT = 'p',
};

static struct option long_options[] =
{
	{"help",        0, NULL,        ARG_HELP        },
	{"flags",	1, NULL,	ARG_FLAGS	},
	{"type",	1, NULL,	ARG_TYPE	},
	{"startup",	0, NULL,	ARG_STARTUP	},
	{"scanbus",	0, NULL,	ARG_SCANBUS	},
	{"verbose",	0, NULL,	ARG_VERBOSE	},
	{"get-driver",	1, NULL,	ARG_GET		},
	{"put-driver",	1, NULL,	ARG_PUT		},
	{NULL,		0, NULL,	0		},
};

static void usage(int argc, char** argv)
{
	fprintf(stderr, "Usage: %s [--verbose|-v] [--scanbus|<disk>]\n", argv[0]);
	fprintf(stderr, "Usage: %s [--startup|--flags FLAGS][--type TYPE][--get-driver|-g FILE][--put-driver|-p FILE] [<partition>|<disk> <part number>]\n", argv[0]);
	fprintf(stderr, "\nbuild: \n%s\n", SIGNATURE);
}

static int get_driver(emile_map_t *map, int partition, char* appledriver)
{
	int driver;
	int block_size, block_count;
	int driver_number;
	int block, size, type, part;
	int partition_base, partition_size;
	int bootstart, bootsize, bootaddr, bootentry, checksum;
	char processor[16];
	unsigned char *code;
	int fd;
	int ret;

	emile_map_read(map, partition);
	if (strncmp(emile_map_get_partition_type(map), 
		    "Apple_Driver", strlen("Apple_Driver")) != 0)
	{
		fprintf(stderr, 
	"ERROR: the type of the partition must begin with \"Apple_Driver\"\n");
		return -1;
	}
	if (strcmp(emile_map_get_partition_name(map), "Macintosh") != 0)
	{
		fprintf(stderr, 
		"ERROR: the name of the partition must be \"Macintosh\"\n");
		return -1;
	}

	emile_map_geometry(map, &block_size, &block_count);
	printf("block size: %d\n", block_size);

	emile_map_get_partition_geometry(map, &partition_base, &partition_size);
	printf("partition base: %d, size %d\n", partition_base, partition_size);

	driver_number = emile_map_get_driver_number(map);
	if (driver_number == 0)
	{
		fprintf(stderr, "ERROR: no driver on this device\n");
		return -1;
	}

	for (driver = 0; driver < driver_number; driver++)
	{
		emile_map_get_driver_info(map, driver, &block, &size, &type);
		part  = emile_map_seek_driver_partition(map, 
						block * block_size / 512 );
		if (part == partition)
		{
			emile_map_read(map, part);
			if (emile_is_apple_driver(map))
				break;
		}
		part  = emile_map_seek_driver_partition(map, block);
		if (part == partition)
		{
			emile_map_read(map, part);
			if (emile_is_apple_driver(map))
				break;
		}
	}
	if (part != partition)
	{
		fprintf(stderr, 
			"ERROR: cannot find partition in driver table\n");
		return -1;
	}
	printf("Found driver %d for partition %d\n", driver, partition + 1);
	printf("base: %d size: %d type: %d\n", block * block_size / 512, 
					     size * block_size / 512 , type);
	emile_map_get_bootinfo(map, &bootstart, &bootsize, &bootaddr, 
				    &bootentry, &checksum, processor);
	printf("Bootstart: %d, Bootsize: %d, Bootaddr: %d, Bootentry: %d\n",
		bootstart, bootsize, bootaddr, bootentry);
	printf("Checksum: 0x%04x, Processor: %s\n", checksum, processor);

	if (strcmp(processor, "68000") != 0)
	{
		fprintf( stderr, 
		"ERROR: cannot manage processor %s (not 68000)\n", processor);
		return -1;
	}

	code = (unsigned char*)malloc(partition_size * 512);
	if (code == NULL)
	{
		fprintf(stderr, "ERROR: cannot malloc() to load driver in memory\n");
		return -1;
	}

	fd = open(emile_map_dev(map), O_RDONLY);
	if (fd == -1)
	{
		fprintf(stderr, "ERROR: cannot read driver (open())\n");
		free(code);
		return -1;
	}

	if (lseek(fd, block * block_size,SEEK_SET) != (block * block_size))
	{
		fprintf(stderr, "ERROR: cannot read driver (lseek())\n");
		free(code);
		return -1;
	}

	ret = read(fd, code, bootsize);
	close(fd);

	if (ret != bootsize)
	{
		fprintf(stderr, "ERROR: cannot read driver (read())\n");
		free(code);
		return -1;
	}

	ret = emile_checksum(code, bootsize);
	if (ret != checksum)
		fprintf(stderr, "WARNING: checksum is invalid (0x%x)\n",
				ret);
	else
		printf("Checksum OK\n");

	fd = open(appledriver, O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR);
	if (fd == -1)
	{
		fprintf(stderr, "ERROR: cannot open %s to save driver\n",
				appledriver);
		free(code);
		return -1;
	}

	ret = write(fd, code, bootsize);
	close(fd);
	free(code);

	if (ret != bootsize)
	{
		fprintf(stderr, "ERROR: cannot save driver to %s\n", 
				appledriver);
		return -1;
	}
	return 0;
}

static int put_driver(emile_map_t *map, int partition, char* appledriver)
{
	int block_size, block_count;
	int fd;
	int ret;
	unsigned char* code;
	struct stat st;
	int driver_number;
	int block, count, checksum;
	char part_name[16];

	emile_map_read(map, partition);

	if (strncmp(emile_map_get_partition_type(map), 
		    "Apple_Driver", strlen("Apple_Driver")) != 0)
	{
		fprintf(stderr, 
	"ERROR: the type of the partition must begin with \"Apple_Driver\"\n");
		return -1;
	}
	if (strcmp(emile_map_get_partition_name(map), "Macintosh") != 0)
	{
		fprintf(stderr, 
		"ERROR: the name of the partition must be \"Macintosh\"\n");
		return -1;
	}

	emile_map_geometry(map, &block_size, &block_count);

	/* read driver from file */

	fd = open(appledriver, O_RDONLY);
	if (fd == -1)
	{
		fprintf(stderr, "ERROR: cannot open file %s\n", appledriver);
		return -1;
	}

	ret = fstat(fd, &st);
	if (ret == -1)
	{
		fprintf(stderr, "ERROR: cannot stat file %s\n", appledriver);
		return -1;
	}

	code = malloc(st.st_size);
	if (code == NULL)
	{
		fprintf(stderr, "ERROR: cannot malloc %ld\n", st.st_size);
		return -1;
	}

	ret = read(fd, code, st.st_size);

	close(fd);

	if (ret != st.st_size)
	{
		fprintf(stderr, "ERROR: cannot read file %s\n", appledriver);
		return -1;
	}

	/* compute driver checksum */

	checksum = emile_checksum(code, st.st_size);
	printf("Driver checksum: 0x%x\n", checksum);

	/* write file in partition */

	sprintf(part_name, "%s%d", emile_map_dev(map), partition + 1);
	fd = open(part_name, O_WRONLY);
	if (fd == -1)
	{
		fprintf(stderr, "ERROR: cannot write driver (open())\n");
		free(code);
		return -1;
	}

	ret = write(fd, code, st.st_size);
	close(fd);
	free(code);

	if (ret != st.st_size)
	{
		fprintf(stderr, "ERROR: cannot write driver (write())\n");
		return -1;
	}

	/* set bootinfo */

	emile_map_set_bootinfo(map, 0, st.st_size, 0, 0, checksum, "68000");
	emile_map_partition_set_flags(map, 0x17F);
	
	/* add driver in drivers list */

	driver_number = emile_map_get_driver_number(map);
	if (driver_number == -1)
	{
		fprintf(stderr, "ERROR: cannot read drivers number\n");
		return -1;
	}

	ret = emile_map_get_partition_geometry(map, &block, &count);
	if (ret == -1)
	{
		fprintf(stderr, "ERROR: cannot read partition geometry\n");
		return -1;
	}

	ret = emile_map_set_driver_info(map, driver_number, 
					block / (block_size / 512) , 
					count / (block_size / 512), 1);
	if (ret == -1)
	{
		fprintf(stderr, "ERROR: cannot set driver info\n");
		return -1;
	}

	ret = emile_map_set_driver_number(map, driver_number + 1);
	if (driver_number == -1)
	{
		fprintf(stderr, "ERROR: cannot set drivers number\n");
		return -1;
	}

	ret = emile_map_write(map, partition);
	if (ret != partition)
	{
		fprintf(stderr, "ERROR: cannot set partition information\n");
		return -1;
	}

	ret = emile_block0_write(map);
	if (ret == -1)
	{
		fprintf(stderr, "ERROR: cannot set drivers information\n");
		return -1;
	}

	return 0;
}

int main(int argc, char** argv)
{
	emile_map_t *map;
	int ret;
	int disk;
	int partition = 0;
	char *disk_name;
	char buffer[16];
	int driver;
	int action = ACTION_NONE;
	char *dev_name = NULL;
	char *appledriver = NULL;
	int c;
	int option_index;
	int flags;
	char *type;

	while(1)
	{
		c = getopt_long(argc, argv, "hvg:p:sf:t:", long_options,
				&option_index);
		if (c == -1)
			break;
		switch(c)
		{
		case ARG_VERBOSE:
			verbose++;
			break;
		case ARG_HELP:
			usage(argc, argv);
			return 0;
		case ARG_FLAGS:
			action |= ACTION_FLAGS;
			flags = strtol(optarg, NULL, 0);
			break;
		case ARG_TYPE:
			action |= ACTION_TYPE;
			type = optarg;
			break;
		case ARG_STARTUP:
			action |= ACTION_STARTUP;
			break;
		case ARG_SCANBUS:
			action |= ACTION_SCANBUS;
			break;
		case ARG_GET:
			action |= ACTION_GET;
			appledriver = optarg;
			break;
		case ARG_PUT:
			action |= ACTION_PUT;
			appledriver = optarg;
			break;
		}
	}
	if (optind < argc)
		dev_name = argv[optind++];
	if (optind < argc)
	{
		partition = strtol(argv[optind++], NULL, 0);
		if (partition == 0)
		{
			fprintf(stderr,
	"ERROR: partition number cannot be 0 !\n");
			return 1;
		}
	}

	if ( !action && dev_name)
	{
		diskinfo(dev_name);
		return 0;
	}
	if (action & ACTION_SCANBUS) {
		if (action & ~ACTION_SCANBUS) {
			fprintf(stderr,
	"ERROR: \"--scanbus\" cannot be used with other arguments\n");
			return 1;
		}

		scanbus();
		return 0;
	}
	if ((action & ACTION_GET) && (action & ACTION_PUT)) {
		fprintf(stderr, "You should use --get-driver OR --put-driver\n");
		return 1;
	}

	if (dev_name == NULL)
	{
		fprintf(stderr, "ERROR: you must specify a device\n");
		return 1;
	}

	if (partition == 0)
		ret = emile_scsi_get_rdev(dev_name, &driver, &disk, &partition);
	else
		ret = emile_scsi_get_rdev(dev_name, &driver, &disk, NULL);
	if ( (ret == -1) && (optind < argc))
	{
		disk_name = dev_name;
		driver = 0;
		disk = 0;
		partition = atoi(argv[optind++]);
	}
	else if (ret == -1)
	{
		fprintf(stderr, "ERROR: cannot find disk of %s\n", dev_name);
		return 1;
	}
	else
	{
		emile_get_dev_name(buffer, driver, disk, 0);
		disk_name = buffer;
	}

	if (partition == 0)
	{
		fprintf(stderr, 
			"ERROR: you must provide device of a partition\n");
		return 1;
	}


	if (action & ACTION_STARTUP)
	{
		if (action & ~ACTION_STARTUP)
		{
			fprintf(stderr, 
			"ERROR: don't use --startup with other flags\n");
			return 2;
		}

		ret = emile_map_set_startup(disk_name, partition - 1);
		if (ret == -1)
			return 3;
	}

	if (action & ACTION_GET)
	{
		map = emile_map_open(disk_name, O_RDONLY);
		if (map == NULL)
		{
			fprintf(stderr, "ERROR: cannot open partition map\n");
			return 4;
		}

		if (appledriver == NULL) {
			fprintf(stderr, "ERROR: filename missing\n");
			emile_map_close(map);
			return 6;
		}

		ret = get_driver(map, partition - 1, appledriver);
		if (ret == -1) {
			fprintf(stderr, 
		"ERROR: cannot put driver from partition %d to file %s\n", 
				partition, appledriver);
			emile_map_close(map);
			return 6;
		}

		emile_map_close(map);
		return 0;
	}

	if (action & ACTION_PUT)
	{
		map = emile_map_open(disk_name, O_RDWR);
		if (map == NULL)
		{
			fprintf(stderr, "ERROR: cannot open partition map\n");
			return 4;
		}

		if (appledriver == NULL) {
			fprintf(stderr, "ERROR: filename missing\n");
			emile_map_close(map);
			return 6;
		}

		ret = put_driver(map, partition - 1, appledriver);
		if (ret == -1) {
			fprintf(stderr, 
		"ERROR: cannot put driver to partition %d from file %s\n", 
				partition, appledriver);
			emile_map_close(map);
			return 6;
		}

		emile_map_close(map);
		return 0;
	}

	map = emile_map_open(disk_name, O_RDWR);
	if (map == NULL)
	{
		fprintf(stderr, "ERROR: cannot open partition map\n");
		return 4;
	}

	ret = emile_map_read(map, partition - 1);
	if (ret != partition - 1)
	{
		fprintf(stderr, 
			"ERROR: cannot read partition %d info\n", partition);
		return 5;
	}

	if (action & ACTION_FLAGS)
	{
		emile_map_partition_set_flags(map, flags);
	}

	if (action & ACTION_TYPE)
	{
		ret = emile_map_set_partition_type(map, type);
		if (ret == -1)
		{
			fprintf(stderr, 
			"ERROR: cannot set partition type to %s\n", type);
			return 7;
		}
	}

	ret = emile_map_write(map,  partition - 1);
	if (ret != partition - 1)
	{
		fprintf(stderr, 
			"ERROR: cannot write partition %d info\n", partition);
		return 8;
	}

	emile_map_close(map);

	return 0;
}