/* handles the PartitionSpec type and operations we do on them */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include "libfdisk.h"

/* some globals used to keep up with last allocation error (sortof like errno) */
enum allocReason LastAllocStat = ALLOC_UNDEF;

static char *reasons[] = { "Reason Undefined",
	"Allocation Succeeded",
	"Not enough free space",
	"Requested Start",
	"Boot partition too big",
	"Requested drive(s)",
	"No free primary",
	"Extended Failed",
	"No free slots",
	NULL
};

/* returns pointers to a const char string of why */
char *GetReasonString( enum allocReason reason ) {
    int i;
    
    for (i=0; i < (int) reason && reasons[i]; i++);
    if (!reasons[i])
	return "Unknown reason";
    else
	return reasons[i];
}




/* give priority to the constraints of a partition */
/* if priority is < 0 then there was an error      */
int fdiskGetConstraintPriority( Partition *p ) {
    int pri;
    
    pri = 0;

    if (p->immutable)
	pri += 16384;
    if (fdiskDriveSetIsActive( &p->drive ))
	pri += 128;
    if (fdiskConstraintIsActive( &p->endcyl))
	pri +=  64;
    if (fdiskConstraintIsActive( &p->start))
	pri +=  32;
    if (fdiskConstraintIsActive( &p->size))
	pri +=  16;

    return pri;
}


/* sort in order of priority                                   */
/* we now worry about <1024 cyl contraints if necessary        */
int fdiskSortPartitionSpec( PartitionSpec *spec ) {
    unsigned int i, j;
    int          f;
    unsigned int pri1, pri2;
    unsigned int lsize1, lsize2;
    unsigned int msize1, msize2;
    unsigned int csize1, csize2;
    unsigned int act1,   act2;
    Partition *p1, *p2;
    PartitionSpecEntry tmpe;


    /* now see if there is anything to sort */
    if (spec->num < 2)
	return FDISK_SUCCESS;

    for (i=0; i<spec->num-1; i++) {
	p1 = &spec->entry[i].partition;

#if 0
	if (p1->immutable)
	    continue;
#endif
	
	pri1 = fdiskGetConstraintPriority(p1);
	fdiskGetConstraint(&p1->size,&csize1,&lsize1,&msize1,&act1 );
	for (j=i+1; j < spec->num; j++) {
	    p2 = &spec->entry[j].partition;

	    /* we dont want to reorder immutable specs */
	    /* but we want to bubble them to top over  */
	    /* user specifified partitions             */
	    if (p1->immutable && p2->immutable)
		continue;
	    
	    pri2 = fdiskGetConstraintPriority(p2);

	    if (pri1 < pri2)
		f = 1;
	    else if (pri1 > pri2)
		f = -1;
	    else {
		fdiskGetConstraint(&p2->size,&csize2,&lsize2,&msize2,&act2 );
		f = (lsize1 < lsize2);
	    }

	    if (f > 0) {
		memcpy(&tmpe, &spec->entry[i], sizeof(PartitionSpecEntry));
		memcpy(&spec->entry[i], &spec->entry[j],
		       sizeof(PartitionSpecEntry));
		memcpy(&spec->entry[j], &tmpe, sizeof(PartitionSpecEntry));

	    }
	}
    }
    return FDISK_SUCCESS;
}

/* check for special partitions and setup constraints if they exist */
int fdiskHandleSpecialPartitions( PartitionSpec *spec ) {
    unsigned int i, j;
    Partition *p1, *p2;

    /* oh man are PCs braindead */
#if defined(__i386__)
    /* see if we have any special partitions (like bootable) */
    i = fdiskReturnPartitionSpec( spec, "/",     &p1 );
    j = fdiskReturnPartitionSpec( spec, "/boot", &p2 );
    
    if (j == FDISK_SUCCESS) {
	/* if "/" exists make it a normal partition again */
	if (i==FDISK_SUCCESS) {
	    fdiskSetConstraint(&p1->endcyl,
			       0,FDISK_ENDCYL_MIN,FDISK_ENDCYL_MAX,0);
	    fdiskActivateAllDriveSet( &p1->drive );
	    fdiskModifyPartitionSpec( spec, "/", p1, REQUEST_PENDING );
	    free(p1);
	}
	
	/* set "/boot" as bootable partition */
	fdiskSetConstraint(&p2->endcyl, 0,0,1023,1);
	fdiskDeactivateAllDriveSet( &p2->drive );
	fdiskActivateDriveSet( &p2->drive, 1 );
	fdiskActivateDriveSet( &p2->drive, 2 );
	fdiskModifyPartitionSpec( spec, "/boot", p2, REQUEST_PENDING );
	free(p2);
    } else if (i == FDISK_SUCCESS) {
	/* make "/" bootable */
	fdiskSetConstraint(&p1->endcyl,0,0,1023,1);
	fdiskDeactivateAllDriveSet( &p1->drive );
	fdiskActivateDriveSet( &p1->drive, 1 );
	fdiskActivateDriveSet( &p1->drive, 2 );
	fdiskModifyPartitionSpec( spec, "/", p1, REQUEST_PENDING );
	free(p1);
    }

    fdiskSortPartitionSpec( spec );
#endif
    return FDISK_SUCCESS;
}


/* get index of partition spec requested */
/* return value is non-zero if not found */
int fdiskIndexPartitionSpec(PartitionSpec *spec, char *name,
			    unsigned int *index ) {

    unsigned int j, found;
    
    /* see if it already exists */
    found = 0;
    for (j=0; j<spec->num && !found;)
	if (!strcmp(spec->entry[j].name, name))
	    found = 1;
	else
	    j++;

    if (found) {
	*index = j;
	return FDISK_SUCCESS;
    } else {
	return FDISK_ERR_BADNUM;
    }
}

/* insert new specification into existing PartitionSpec */
int fdiskInsertPartitionSpec( PartitionSpec *spec,
			      char          *name,
			      Partition     *p,
			      unsigned int  status) {
    unsigned int num;
    unsigned int j, found;

    if ((num=spec->num) >=MAX_PARTITION_SPEC)
	return FDISK_ERR_NOFREE;

    /* see if it already exists */
    found = 0;
    for (j=0; j<spec->num && !found;)
	if (!strcmp(spec->entry[j].name, name))
	    found = 1;
	else
	    j++;

    if (found)
	return FDISK_ERR_INUSE;
    
    /* Ok insert the bugger */
    spec->entry[num].name  = strdup(name);
    memcpy(&spec->entry[num].partition, p, sizeof(Partition));
    spec->entry[num].status = status;
    spec->num++;

    fdiskSortPartitionSpec( spec );
    return FDISK_SUCCESS;
}

/* delete specification based on mount point */
int fdiskDeletePartitionSpec( PartitionSpec *spec,
			      char          *name ) {

    unsigned int j, k, found;

    found = 0;
    for (j=0; j<spec->num && !found;)
	if (!strcmp(spec->entry[j].name, name))
	    found = 1;
	else
	    j++;

    if (!found)
	return FDISK_ERR_BADNUM;
    else {
	/* cant delete immutable partitions */
	if (spec->entry[j].partition.immutable)
	    return FDISK_ERR_BADNUM;
	
	if (spec->entry[j].name)
	    free(spec->entry[j].name);
	for (k=j; k < spec->num-1; k++)
	    memcpy(&spec->entry[k], &spec->entry[k+1],
		   sizeof(PartitionSpecEntry));

	memset(&spec->entry[spec->num-1], 0, sizeof(PartitionSpecEntry));
	spec->num--;
    }

    return FDISK_SUCCESS;
}

/* complete erase contents of a PartitionSpec - frees up names */
int fdiskWipePartitionSpec( PartitionSpec *spec ) {
    unsigned int i;

    for (i=0; i<spec->num; i++)
	free(spec->entry[i].name);

    memset(spec, 0, sizeof(PartitionSpec));
    spec->num = 0;
    return FDISK_SUCCESS;
}


	
/* update 'original' drives in partitionspec, in case any were removed  */
/* leave specs the user has added since we started alone                */
int fdiskCleanOriginalSpecs( HardDrive **hdarr, unsigned int numhd,
			     PartitionSpec *spec ) {
    unsigned int j, k, l;
    unsigned int drive, start, size;
    unsigned int tsize, tstart;
    unsigned int first, last;
    unsigned int remove;
    unsigned int found;
    Partition *p;

    for (j=0; j<spec->num; )
	if (spec->entry[j].status != REQUEST_ORIGINAL) {
	    j++;
	    continue;
	} else {
	    p = &spec->entry[j].partition;
	    
	    fdiskGetCurrentDriveSet(&p->drive, &drive );
	    fdiskGetCurrentConstraint(&p->size , &size  );
	    fdiskGetCurrentConstraint(&p->start, &start );

	    /* find the HardDrive containing the drive we want */
	    for ( l=0; l<numhd; l++)
		if (hdarr[l]->num == drive)
		    break;

	    /* shouldnt happen */
	    if (l == numhd)
		return FDISK_ERR_BADNUM;
	    else
		drive = l;
	    
	    /* see if the partition still exists */
	    remove = 0;
	    found  = 0;
	    if (fdiskFirstPartition(hdarr[drive], &first)) {
		remove = 1;
	    } else {
		fdiskLastPartition(hdarr[drive], &last);

		for (l=first; l <=last && !found; l++) {
		    if (fdiskGetAttrPartition(hdarr[drive], l, &p) ==
			FDISK_SUCCESS) {
			fdiskGetCurrentConstraint(&p->size , &tsize  );
			fdiskGetCurrentConstraint(&p->start, &tstart );
			if (tstart == start && tsize == size) {
			    found = 1;
			    /* lets update partition info */
			    memcpy(&spec->entry[j].partition, p,
				   sizeof(Partition));
			}

			free(p);
		    }
		}
		if (!found)
		    remove = 1;
	    }
		    
	    if (remove) {
		if (spec->entry[j].name)
		    free(spec->entry[j].name);
		for (k=j; k < spec->num-1; k++) 
		    memcpy(&spec->entry[k], &spec->entry[k+1],
			   sizeof(PartitionSpecEntry));
		memset(&spec->entry[spec->num-1],0,sizeof(PartitionSpecEntry));
		spec->num--;
	    } else {
		j++;
	    }
	}
    fdiskSortPartitionSpec( spec );
    return FDISK_SUCCESS;
}

/* given a hard drive with original partitions, add to partition spec */
/* any user added partition specs are untouched                       */
int fdiskSetupPartitionSpec( HardDrive **hdarr, unsigned int numhd,
			     PartitionSpec *spec ) {
    unsigned int first, last, i, j;
    int          status;
    Partition    *p;
    char         *name;
    
    /* make sure there are any partitions to process */
    status = 0;
    for (i=0; i<numhd; i++)
	if (fdiskFirstPartition(hdarr[i], &first ) == FDISK_SUCCESS) {
	    status = 1;
	    break;
	}

    /* no partitions to process */
    if (status == 0)
	return FDISK_SUCCESS;
    
    /* go thru all drives and insert all pre-existing partitions */
    for (i=0; i<numhd; i++) {
	if (fdiskFirstPartition(hdarr[i], &first ) != FDISK_SUCCESS) 
	    continue;
	fdiskLastPartition(hdarr[i], &last );
	/* insert existing partitions using a temporary name */
	for (j=first; j <= last; j++) {
	    status = fdiskGetAttrPartition(hdarr[i], j, &p);
	    if (status == FDISK_SUCCESS) {
		if (p->type.current == LINUX_SWAP_PARTITION)
		    fdiskMakeSwapSpecName( spec, &name);
		else {
		    name = malloc(16);
		    sprintf(name, "Exist%03d%03d", hdarr[i]->num, j);
		}
		fdiskInsertPartitionSpec( spec, name, p, REQUEST_ORIGINAL );
		free(name);
		free(p);
	    }
	}
    }

    fdiskSortPartitionSpec( spec );
    
    return FDISK_SUCCESS;
}
	    

/* return specification based on mount point */
int fdiskReturnPartitionSpec( PartitionSpec *spec,
			      char          *name,
			      Partition     **p) {

    unsigned int j, found;

    found = 0;
    for (j=0; j<spec->num && !found;)
	if (!strcmp(spec->entry[j].name, name))
	    found = 1;
	else
	    j++;

    if (!found)
	return FDISK_ERR_BADNUM;
    else {
	*p = (Partition *) malloc( sizeof(Partition) );
	memcpy(*p, &spec->entry[j].partition, sizeof(Partition));
	return FDISK_SUCCESS;
    }
}

/* rename a spec */
int fdiskRenamePartitionSpec( PartitionSpec *spec,
			      char          *name,
			      char          *newname ) {
    unsigned int j, found;

    found = 0;
    for (j=0; j<spec->num && !found;)
	if (!strcmp(spec->entry[j].name, name))
	    found = 1;
	else
	    j++;

    if (!found)
	return FDISK_ERR_BADNUM;
    else {
	free(spec->entry[j].name);
	spec->entry[j].name = strdup(newname);
	return FDISK_SUCCESS;
    }
}
    
/* return specification based on mount point */
int fdiskModifyPartitionSpec( PartitionSpec *spec,
			       char          *name,
			       Partition     *p,
			       unsigned int  status) {

    unsigned int j, found;

    found = 0;
    for (j=0; j<spec->num && !found;)
	if (!strcmp(spec->entry[j].name, name))
	    found = 1;
	else
	    j++;

    if (!found)
	return FDISK_ERR_BADNUM;
    else {
	/* dont change immutable partitions! */
	if (p->immutable)
	    return FDISK_ERR_BADNUM;
	else {
	    memcpy(&spec->entry[j].partition, p, sizeof(Partition));
	    spec->entry[j].status = status;
	    fdiskSortPartitionSpec( spec );
	    return FDISK_SUCCESS;
	}
    }
}

/* make a unique name for a partition spec */
int fdiskMakeUniqSpecName( PartitionSpec *spec, char *base, char **name ) {
    int  i;
    char *s;
    unsigned int j;

    s = malloc(4+strlen(base));
    for (i=0; i < 1000; i++) {
	snprintf(s, 4+strlen(base), "%s%03d", base, i);
	if (fdiskIndexPartitionSpec(spec, s, &j) != FDISK_SUCCESS)
	    break;
    }
	
    *name = s;
    return FDISK_SUCCESS;
}


/* make a unique name for a swap partition spec */
int fdiskMakeSwapSpecName( PartitionSpec *spec, char **name ) {
    int rc;
    
    rc = fdiskMakeUniqSpecName( spec, "Swap", name );
    return rc;
}
	
