/*
 * This file is part of DesktopBSD, see the README file.
 *
 * Author: Peter Hofer <ph@desktopbsd.net>
 *			(C) 2004, 2005
 *
 * Copyright: BSD, see the COPYING file included in this distribution
 *
 */

#include <dbsdglobal.h>

#include <chunk.h>

#include <moddisksel.h>
#include <frmmodulecontainer.h>
#include <dialogs.h>
#include <variables.h>

#include <qinputdialog.h>
#include <qptrlist.h>

#include <sys/param.h>
#include <sys/mount.h>

/* Define SHOW_ZERO_SIZE to show slices with
 * (converted!) size = 0
 *
 * #define SHOW_ZERO_SIZE
 */



int  modDiskSetup::selItem() {
	QListViewItem *litem = lstPartitions->selectedItem();
	if(litem == NULL)
		return -1;

	return litem->text(3).toInt();
}


/**
 * Reads all chunks on the disk.
 */
void modDiskSetup::readChunks() {
	if(dsk == NULL) {
		QString  dskName = Variables::getValue("disk.activeDisk");

		if(dskName.isEmpty()) {
			frmModuleContainer *c = new frmModuleContainer(this);
			c->setCaption(tr("DesktopBSD"));
			c->setDBSDModule(new modDiskSel(c));
			c->exec();
			dskName = Variables::getValue("disk.activeDisk");

			delete c;
		}

		if(dskName.isEmpty()) {
			Dialogs::warnDlg(tr("No disk selected for partitioning."));
			this->setEnabled(false);
			return;
		}

		dsk = new Disk(dskName);
		if(dsk->isMounted()) {
			Dialogs::warnDlg(tr("One or more partitions from this disk are mounted, therefore\n"
										 "it's partition layout can't be altered.\n"
									  "\n"
										 "Unmount the particular partitions first or use a bootable\n"
									  "CD-ROM operating system to partition this disk if this is\n"
									  "a system partition that cannot be unmounted (e.g. / or /dev)."));

			btnNewSlice->setEnabled(false);
			btnDelSlice->setEnabled(false);
			btnEntireDisk->setEnabled(false);
			btnInstall->setEnabled(false);
			btnUndo->setEnabled(false);
		} else {
			/* Only display hints if not mounted */
			
			if(!dsk->saneBIOSGeom()) {
				dsk->sanitizeBIOSGeom();

				if(!Dialogs::question(tr("A logical geometry for this drive was calculated:\n"
												 "%1 Cylinders / %2 Heads / %3 Sectors\n"
												 "\n"
												 "This geometry will ONLY work if this disk uses logical\n"
												 "block addressing (LBA). Please check the settings in\n"
												 "the BIOS if you are not sure.\n"
												 "\n"
												 "Do you want to use this geometry? Click 'No' to specify\n"
												 "a geometry yourself.")
													 .arg(dsk->getBIOSCyl())
													 .arg(dsk->getBIOSHeads())
													 .arg(dsk->getBIOSSectors())))
				{
					QString newGeom = QString("%1/%2/%3").arg(dsk->getBIOSCyl())
																	 .arg(dsk->getBIOSHeads())
																	 .arg(dsk->getBIOSSectors());
					bool	done = true,
							clickedOk;
					do {
						newGeom = Dialogs::strInput(tr("Please enter a new geometry.\n"
																 "\n"
																 "- For IDE drives, look up the geometry in your BIOS setup.\n"
																 "- For SCSI drives, enter the translation mode of your controller.\n"
																 "\n"
																 "Do NOT use the \"physical geometry\" of your disk.\n"
																 "\n"
																 "Input format: Cylinders/Heads/Sectors\n"),
															 &clickedOk,
															 newGeom);

						if(clickedOk) {
							done = false;

							QStringList stringValues = QStringList::split("/", newGeom);
							if(stringValues.count() == 3) {
								unsigned long chs[3];
	
								done = false;
								
								bool conversionOk;
								for(int i = 0; conversionOk && (i < 3); ++i)
									chs[i] = stringValues[i].toULong(&conversionOk);
		
								if(conversionOk) {
									dsk->setBIOSGeometry(chs[0], chs[1], chs[2]);
									done = true;
								}
							}
	
							if(!done)
								Dialogs::warnDlg(tr("The geometry you entered is invalid."));
								
						} else
							/* User clicked 'Cancel' */
							done = Dialogs::question(tr("Use the automatically calculated geometry?"));
							
					} while(!done);
				}
			}
		}
	}

	/* Save selected Item's position and reselect it
	  * after the new list has been built.
	  */
	int oldSel = selItem();

	/* Clear ListView */
	lstPartitions->clear();
	lstPartitions->setSortColumn(3);

	/* Read all chunks and insert them into the QListView */
	int chunks = dsk->chunkCount();
	for(int i = 0; i < chunks; ++i) {
		Disk::Chunk cnk = (*dsk)[i];

#ifndef SHOW_ZERO_SIZE
		if(cnk.getSize().get(Size::MB) == 0)
			continue;
#endif

		DBSDIconLoader  *iloader = DBSDGlobal::getIconLoader();
		Disk::Chunk::FSType type = cnk.getFSType();
		QPixmap *pixm = NULL;

		if(type == Disk::Chunk::Unused)
			/* unused */
			pixm = new QPixmap(iloader->loadIcon("empty", DBSDIconLoader::LargeListItem));
		else if(type == Disk::Chunk::UFS)
			pixm = new QPixmap(iloader->loadIcon("desktopbsd", DBSDIconLoader::LargeListItem));
		else
			pixm = new QPixmap(iloader->loadIcon("binary", DBSDIconLoader::LargeListItem));

		Size::Unit unit = (cnk.getSize().get(Size::GB) < 10) ? Size::MB : Size::GB;
		QListViewItem* newIt = new QListViewItem(lstPartitions,
															  "",
															  cnk.getSize().toString(unit),
															  cnk.getDesc(),
															  QString::number(i));
		newIt->setPixmap(0, *pixm);
		if(i == oldSel)
			lstPartitions->setCurrentItem(newIt);

		delete pixm;
	}
}


void modDiskSetup::initModule() {
	// Calculate/set predefined minimum sizes
	Size minsize(6, Size::GB), recsize(12, Size::GB);
	struct statfs buf;
	if(!statfs("/usr", &buf))
	{
		uint64_t usrsize = (buf.f_bsize * (buf.f_blocks - buf.f_bfree));
		minsize.set(usrsize * 5 / 2, Size::Byte);
		recsize.set(usrsize * 4, Size::Byte);

		// Round up to the whole Gigabytes
		minsize.set(minsize.get(Size::GB) + 1, Size::GB);
		recsize.set(recsize.get(Size::GB) + 1, Size::GB);
	}
	this->minSizeBytes = minsize.get(Size::Byte);
	this->recommendedSizeBytes = recsize.get(Size::Byte);

	lblExplanation->setText(
		tr("You now have to create a partition on your hard disk for DesktopBSD. "
			"The recommended size for this partition is %1 GB (%2 MB), the minimum is %3 GB (%4 MB). "
			"When done, select the partition where you want to install DesktopBSD and click \"Install into selected partition\".")
		.arg(recsize.get(Size::GB))
		.arg(recsize.get(Size::MB))
		.arg(minsize.get(Size::GB))
		.arg(minsize.get(Size::MB)));

	readChunks();

	/* Make every column 50 % larger */
	for(int i = 0; i < lstPartitions->columns(); ++i)
		lstPartitions->setColumnWidth(i, int(lstPartitions->columnWidth(i) * 1.5));
}


void modDiskSetup::allFreeBSD() {
	dsk->allFreeBSD();

	emit changed();
	readChunks();
}


void modDiskSetup::createSlice() {
	int	i = selItem();
	if(i < 0) {
		Dialogs::infoDlg(tr("Select a slice in which you want to\n"
								  "create a partition first."));
		return;
	}

	Disk::Chunk cnk = (*dsk)[i];
	if(cnk.inUse()) {
		Dialogs::warnDlg(tr("The selected slice already contains a partition.\n"
								  "\n"
								  "Select an empty slice or delete one to create\n"
								  "a new partition in it."));
	} else {
		if(dsk->usedChunkCount() == 4) {
			Dialogs::warnDlg(tr("You can not have more than four partitions on a single disk.\n"
									"\n"
									"(Installing DesktopBSD to a logical partition is not supported)"));
			return;
		}

		bool  ok;
		if(Dialogs::question(tr("Use all the space available in this slice?"))) {
			ok = cnk.create(FSDevice::UFS, cnk.getSize());
		} else {
			int  size = Dialogs::intInput(tr("Size of new partition in MB (1 GB = 1024 MB):"),
													&ok,
													cnk.getSize().get(Size::MB),
													0,
													cnk.getSize().get(Size::MB));
			if(!ok) return;

			ok = cnk.create(FSDevice::UFS, Size(size, Size::MB));
		}

		if(ok) {
			emit changed();
			readChunks();
		} else {
			Dialogs::warnDlg(tr("Error: Cannot create this partition."));
		}
	}
}


void modDiskSetup::delSlice(){
	int i = selItem();
	if(i < 0) {
		Dialogs::infoDlg(tr("Select the partition you want to delete first."));
		return;
	}

	Disk::Chunk cnk = (*dsk)[i];
	if(!cnk.inUse()) {
		Dialogs::warnDlg(tr("This slice does not contain a partition\n"
								  "that could be deleted."));
		return;
	}

	cnk.free();

	emit changed();
	readChunks();
}


void modDiskSetup::reset()
{
	if(Dialogs::question(tr("Undo all changes you made?"))) {
		dsk->undoChanges();
		readChunks();
	}

	emit changed(false);
}





void modDiskSetup::destroy() {
	if(wasInitialized())
		delete dsk;
}


void modDiskSetup::init() {
	/* Initialize Graphics */
	dsk = NULL;

	DBSDIconLoader  *iloader = DBSDGlobal::getIconLoader();
	btnNewSlice->setIconSet(iloader->loadIcon("filenew", DBSDIconLoader::Button));
	btnDelSlice->setIconSet(iloader->loadIcon("editdelete", DBSDIconLoader::Button));
	btnEntireDisk->setIconSet(iloader->loadIcon("hdd_unmount", DBSDIconLoader::Button));
	btnInstall->setIconSet(iloader->loadIcon("filesave", DBSDIconLoader::Button));
	btnUndo->setIconSet(iloader->loadIcon("undo", DBSDIconLoader::Button));
	pixHint->setPixmap(iloader->loadIcon("info", DBSDIconLoader::HintIcon));
	pixPartition->setPixmap(iloader->loadIcon("partition"));
}


int modDiskSetup::getSupportedOperations() {
	return  (Apply | Reset);
}


void modDiskSetup::install() {
	Size minsize(minSizeBytes, Size::Byte), recsize(recommendedSizeBytes, Size::Byte);

	int i = selItem();
	if(i == -1) {
		Dialogs::warnDlg(tr("You have to select a destination partition\n"
								  "for the installation first."));
		return;
	}

	Disk::Chunk cnk = (*dsk)[i];
	if(cnk.getSize() < minsize || cnk.getFSType() != FSDevice::UFS) {
		Dialogs::infoDlg(tr("The destination partition for the installation must be at\n"
								  "least %1 Megabytes large and must have an UFS/FFS filesystem\n"
								  "(recommended size is %2 MB).")
										.arg(minsize.get(Size::MB))
										.arg(recsize.get(Size::MB)));
		return;
	}

	if(!Dialogs::question(tr("This can NOT be UNDONE.\n"
									 "\n"
									 "Are you sure you want to proceed?")))
		return;

	Variables::setValue("disk.activeSlice", cnk.getName());

	bool loaderSet = false;
	int loader = Variables::getValue("disk.bootLoader").toInt(&loaderSet);
	if(loaderSet)
	{
		switch(loader)
		{
			case 1:
				dsk->setBoot(Disk::StandardMBR);
				break;
				
			case 2:
				dsk->setBoot(Disk::BootMenu);
				break;
		}
	}
	
	if(dsk->writeChanges())
	{
		emit changed(false);
		emit done();
	} else
		Dialogs::warnDlg(tr("Unexpected error: Unable to write to disk."));
}
