#include <ctype.h>
#include <fcntl.h>
#include <linux/hdreg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/wait.h>
#include <unistd.h>

#include "hd.h"
#include "install.h"
#include "intl.h"
#include "libfdisk/libfdisk.h"
#include "log.h"
#include "newt.h"
#include "run.h"
#include "scsi.h"
#include "windows.h"

#define MAX_HARDDRIVES 16

extern int testing;

/* shame we can't get this from any header files */
#define BLKRRPART  _IO(0x12,95)	/* re-read partition table */

struct fdiskTag {
    int tag;
    int type;
    char * tagName;
} ;

#if defined(__sparc__)
const struct fdiskTag fdiskTags[] =  {
	{ 0x05,	PART_IGNORE, N_("Whole disk") },
	{ 0x82,	PART_SWAP, N_("Linux swap") },
	{ 0x83,	PART_EXT2, N_("Linux native") },
	{ 0, 0 },
};
#else
const struct fdiskTag fdiskTags[] =  {
	{ 0x01,	PART_DOS, N_("DOS 12-bit FAT") },
	{ 0x04,	PART_DOS, N_("DOS 16-bit <32M") },
	{ 0x05,	PART_IGNORE, "Extended" },
	{ 0x06,	PART_DOS, N_("DOS 16-bit >=32") },
	{ 0x07,	PART_HPFS, N_("OS/2 HPFS") },
	{ 0x0b,	PART_FAT32, N_("Win95 FAT32") },
	{ 0x0c,	PART_FAT32, N_("Win95 FAT32") },
	{ 0x0e,	PART_FAT32, N_("Win95 FAT32") },
	{ 0x82,	PART_SWAP, N_("Linux swap") },
	{ 0x83,	PART_EXT2, N_("Linux native") },
	{ 0, 0 },
};
#endif

#define MAX_NUM_DRIVES 40

static int findDrivesPresent(struct deviceInfo * drives, int * numPtr);

static int findPartitions(char * hdname, int * numPartitions, 
			  struct partition partitions[15]) {
    char devBuf[20];
    int j, i;
    int status;
    HardDrive * hd = NULL;
    PartitionSpec spec;

    *numPartitions = 0;

    /* don't bother with any of this if the main device doesn't exist 
       or doesn't look like a hard drive */
    sprintf(devBuf, "/tmp/%s", hdname);
    
    if (devMakeInode(hdname, devBuf)) {
	return 0;
    }

    status = fdiskOpenDevice(devBuf, 0, &hd);
    if (status != FDISK_SUCCESS) {
	unlink(devBuf);
	return 0;
    }

    status = fdiskReadPartitions(hd);
    if (status != FDISK_SUCCESS) {
	fdiskCloseDevice(hd);
	unlink(devBuf);
	return 0;
    }

    fdiskCloseDevice(hd);
    unlink(devBuf);

    /* Translate into a PartitionSpec */
    memset(&spec, 0, sizeof(PartitionSpec));
    fdiskSetupPartitionSpec(&hd, 1, &spec );

    *numPartitions = spec.num;
    for (i = 0; i < spec.num; i++) {
	memset(partitions + i, 0, sizeof(*partitions));
	sprintf(partitions[i].device, "%s%d",
		hdname, spec.entry[i].partition.num.current);
	/* the /2 converts from 512 byte sectors to 1k blocks */
	partitions[i].size = spec.entry[i].partition.size.current / 2;
	partitions[i].begin = spec.entry[i].partition.start.current;
	partitions[i].end = spec.entry[i].partition.endcyl.current;

	for (j = 0; fdiskTags[j].tag; j++) {
	    if (fdiskTags[j].tag == spec.entry[i].partition.type.current) {
		partitions[i].type = fdiskTags[j].type;
		strcpy(partitions[i].tagName, fdiskTags[j].tagName);
		break;
	    }
	}

    }

    return 0;
}

int findAllPartitions(struct deviceInfo * devices, 
		     struct partitionTable * table) {
    int numPartitions;
    int i;
    struct partition parts[16];
    struct partition * allParts = NULL;
    int numAllParts = 0;

    if (!devices) {
	/* FIXME: memory leak for internal structures */
	devices = alloca(sizeof(*devices) * MAX_NUM_DRIVES);
	if (findDrivesPresent(devices, NULL)) return INST_ERROR;
    }

    winStatus(30, 3, _("Hard Drives"), _("Scanning hard drives..."));

    for (i = 0; devices[i].deviceName; i++) {
	findPartitions(devices[i].deviceName, &numPartitions, parts);
	if (!numPartitions) continue;
	
	if (!numAllParts) {
	    numAllParts = numPartitions;
	    allParts = malloc(sizeof(*allParts) * numAllParts);
	    memcpy(allParts, parts, sizeof(*allParts) * numAllParts);
	} else {
	    allParts = realloc(allParts, sizeof(*allParts) * 
				(numAllParts + numPartitions));
	    memcpy(allParts + numAllParts, parts, 
		   sizeof(*allParts) * numPartitions);
	    numAllParts += numPartitions;
	}
    }

    table->count = numAllParts;
    table->parts = allParts;

    newtPopWindow();

    return 0;
}

static int findDrivesPresent(struct deviceInfo * drives, int * numPtr) {
    struct deviceInfo * scsi = NULL, * ide = NULL;
    int num = 0; 
    int i;

    /* FIXME: this results in memory leaks via the strings inside of
       the decice structures! */

    if (scsiDeviceAvailable()) {
	if (scsiGetDevices(&scsi)) return INST_ERROR;
    }

    if (ideGetDevices(&ide)) return INST_ERROR;

    i = 0, num = 0;
    while (ide[i].deviceName) {
	if (ide[i].type == DEVICE_HD) 
	    drives[num++] = ide[i];
	i++;
    }

    i = 0;
    while (scsi && scsi[i].deviceName) {
	if (scsi[i].type == DEVICE_HD) 
	    drives[num++] = scsi[i];
	i++;
    }

    drives[num].deviceName = NULL;

    if (numPtr) *numPtr = num;

    return 0;
}

int getDriveList(char *** drives, int * num) {
    struct deviceInfo drivesPresent[MAX_NUM_DRIVES];
    int rc;
    int i;

    if ((rc = findDrivesPresent(drivesPresent, num))) return rc;

    *drives = malloc(sizeof(char *) * (*num + 1));

    for (i = 0; i < *num; i++) {
	(*drives)[i] = drivesPresent[i].deviceName;
    }

    (*drives)[i] = NULL;

    return 0;
}
    
int partitionDrives(void) {
    struct deviceInfo drivesPresent[MAX_NUM_DRIVES];
    int numDrivesPresent = 0;
    int childpid;
    int i, fd;
    int haveEdited = 0;
    char devBuf[100], idBuf[3];
    char * driveText[50];
    struct deviceInfo * currhd;
    int status;
    int reboot = 0;
    char * cmd;
    char * text;
    int rc, driveNum = 0;
 
    if (!access("/sbin/fdisk", X_OK))
	cmd = "/sbin/fdisk";
    else
	cmd = "/usr/bin/fdisk";

    findDrivesPresent(drivesPresent, &numDrivesPresent);

    if (!numDrivesPresent) {
	newtWinMessage(_("Hard Drives"), _("Ok"), 
			   _("You don't have any hard drives available! "
			     "You probably forgot to configure a SCSI "
			     "controller."));

	return INST_ERROR;
    }

#ifdef __i386__
    text = _("To install Red Hat Linux, you must have at least one parition "
	     "of 150 MB dedicated to Linux. We suggest placing that partition "
	     "on one of the first two hard drives in your system so you can "
	     "boot into Linux with LILO.");
#else
    text = _("To install Red Hat Linux, you must have at least one parition "
	     "of 150 MB dedicated to Linux.");
#endif

    for (i = 0; i < numDrivesPresent; i++) {
	sprintf(devBuf, "/dev/%s", drivesPresent[i].deviceName);

	if (!strncmp(drivesPresent[i].deviceName, "sd", 2)) {
	    sprintf(idBuf, "%d", drivesPresent[i].id);
	    strcat(devBuf, " - SCSI ID ");
	    strcat(devBuf, idBuf);
	} 

	strcat(devBuf, " - Model ");
	strcat(devBuf, drivesPresent[i].info);

	/* truncate at 50 columns for now */
	devBuf[50] = '\0';

        driveText[i] = alloca(strlen(devBuf) + 1);
        strcpy(driveText[i], devBuf);
    }
    driveText[i] = NULL;

    do {
	rc = newtWinMenu(_("Partition Disks"), text, 56, 5, 6, 5,
			 driveText, &driveNum, _("Done"), _("Edit"),
			 _("Back"), NULL);

	if (rc == 2 || rc == 0) {
	    haveEdited = 1;
	    currhd = drivesPresent + driveNum;

	    sprintf(devBuf, "/tmp/%s", currhd->deviceName);
	    if (devMakeInode(currhd->deviceName, devBuf)) return 0;

	    newtSuspend();
	    for (i = 0; i < 25; i++) puts("");
	    printf("This is the fdisk program for partitioning your drive. It "
		   "is running\non /dev/%s.\n\n", currhd->deviceName);

	    logMessage("running fdisk on %s", devBuf);
	    
	    if (!(childpid = fork())) {
		execl(cmd, cmd, devBuf, NULL);
	 	return -1;
	    }

	    waitpid(childpid, &status, 0);

	    newtResume();
	}
    } while (rc == 2 || rc == 0);

    if (haveEdited) {
	for (i = 0; i < numDrivesPresent; i++) {
	    sprintf(devBuf, "/tmp/%s", drivesPresent[i].deviceName);
	    if (devMakeInode(drivesPresent[i].deviceName, devBuf)) 
		return INST_ERROR;
	    fd = open(devBuf, O_RDONLY);
	    unlink(devBuf);
	    if (fd < 0) reboot = 1;

	    if (ioctl(fd, BLKRRPART, 0)) reboot = 1;
	    close(fd);
	}
    }

    if (reboot) needReboot();

    if (rc == 3)
	return INST_CANCEL;

    return 0;
}

void needReboot(void) {
    newtWinMessage(_("Reboot Needed"), _("Ok"),
		    _("The kernel is unable to read your new partitioning "
		    "information, probably because you modified extended "
		    "partitions. While this is not critical, you must "
		    "reboot your machine before proceeding. Insert the "
		    "Red Hat boot disk now and press Return to reboot "
		    "your system.\n\n"
		    "If you have a ZIP or JAZ drive, make sure there is "
		    "a disk in the drive as an empty SCSI drive can also "
		    "cause this problem."));

    newtFinished();
    exit(0);
}
