#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>

#include <parted/parted.h>
#include <parted/device.h>
#include <parted/filesys.h>


void
resize(const char* disk_device, int partition_number, long long new_size)
{
	PedDevice* device = ped_device_get(disk_device);
	if (!device)
	{
		fprintf(stderr, "ped_device_get failed\n");
		exit(EXIT_FAILURE);
	}

	PedDisk* disk = ped_disk_new(device);
	if (!disk)
	{
		fprintf(stderr, "ped_disk_new failed\n");
		exit(EXIT_FAILURE);
	}

	PedPartition* partition = ped_disk_get_partition(disk, partition_number);
	if (!partition)
	{
		fprintf(stderr, "ped_disk_get_partition\n");
		exit(EXIT_FAILURE);
	}

	if (partition->type != PED_PARTITION_NORMAL && partition->type != PED_PARTITION_LOGICAL)
	{
		fprintf(stderr, "wrong partition type\n");
	}

	PedGeometry* geom = ped_geometry_duplicate(&partition->geom);
	if (!geom)
	{
		fprintf(stderr, "ped_geometry_duplicate failed\n");
		exit(EXIT_FAILURE);
	}

	PedFileSystem* filesystem = ped_file_system_open(geom);
	if (!filesystem)
	{
		fprintf(stderr, "ped_file_system_open failed\n");
		exit(EXIT_FAILURE);
	}

	if (new_size != 0)
	{
		if (ped_geometry_set(geom, geom->start, new_size) != 1)
		{
			fprintf(stderr, "ped_geometry_set failed\n");
			exit(EXIT_FAILURE);
		}
	}

	if (ped_file_system_resize(filesystem, geom, NULL) != 1)
	{
		fprintf(stderr, "ped_file_system_resize failed\n");
		exit(EXIT_FAILURE);
	}

	if (ped_file_system_close(filesystem) != 1)
	{
		fprintf(stderr, "ped_file_system_close failed\n");
		exit(EXIT_FAILURE);
	}
}


/* Code parts have been taken from _ped_device_probe(). */
static void
probe_device(PedDevice **dev, const char *path)
{
	ped_exception_fetch_all();
	*dev = ped_device_get(path);
	if (!*dev)
		ped_exception_catch();
	ped_exception_leave_all();
}


int
main(int argc, char** argv)
{
	if (argc != 2 && argc != 3)
	{
		fprintf(stderr, "fatresize device [new_size]\n");
		exit(EXIT_FAILURE);
	}

	char* disk_device = NULL;
	int partition_number = 0;
	long long new_size = 0;	// new_size of 0 means size of partition

	int fd = open(argv[1], O_EXCL);
	if (fd == -1)
	{
		if (errno == EBUSY)
		{
			fprintf(stderr, "filesystem seems mounted\n");
			exit(EXIT_FAILURE);
		}
		else
		{
			fprintf(stderr, "exclusive open failed\n");
			exit(EXIT_FAILURE);
		}
	}

	if (strlen(argv[1]) <= 2)
	{
		fprintf(stderr, "invalid device\n");
		exit(EXIT_FAILURE);
	}

	char* pos = argv[1] + strlen(argv[1]) - 1;
	while (pos != argv[1] && isdigit(*pos))
		--pos;

	if (pos == argv[1] || pos == argv[1] + strlen(argv[1]) - 1)
	{
		fprintf(stderr, "invalid device\n");
		exit(EXIT_FAILURE);
	}

	disk_device = strndup(argv[1], pos - argv[1] + 1);
	partition_number = strtol(pos + 1, NULL, 10);

	PedDevice *peddev = NULL;
	/* check if the device really exists */
	while (pos != argv[1] && *pos != '/' && !peddev)
	{
		disk_device[pos - argv[1] + 1] = '\0';
		probe_device(&peddev, disk_device);
		--pos;
	}
	if (!peddev)
	{
		free(disk_device);
		fprintf(stderr, "invalid device\n");
		exit(EXIT_FAILURE);
	}
	ped_device_destroy(peddev);

	if (argc == 3)
	{
		char* endptr = NULL;
		new_size = strtoll(argv[2], &endptr, 10);
		if (endptr == NULL || *endptr != '\0')
		{
			fprintf(stderr, "invalid size\n");
			exit(EXIT_FAILURE);
		}
	}

	resize(disk_device, partition_number, 2 * new_size);

	close(fd);

	exit(EXIT_SUCCESS);
}
