#!/usr/bin/perl

use Data::Dumper;

use strict;
use warnings;

use SL;
use SL::Package qw( installed );

writelog( "Started with @ARGV" );

my $commandlist = {
	autorm => {
		brief => 'Remove unneeded packages',
		help => [
			'Display this help text',
			'List unneeded packages without removing',
			'An optional target may be specified to remove'
				. ' packages in a separate directory/file'
				. ' system'
			],
		options => [
			'[-h,--help]',
			'[-l,--list]',
			'[-t,--target TARGET]'
			]
		},
	dump => {
		brief => 'Extract files from package',
		help => [
			'PKGNAME or FILE is required',
			'Display this help text',
			'Specify a destination directory to override '
				. ' the default of ./PKGNAME-VERSION'
			],
		options => [
			'<PKGNAME|FILE>',
			'[-h,--help]',
			'[-d,--directory DIRECTORY]'
			]
		},
	files => {
		brief => 'List files in package',
		help => [
			'PKGGNAME or FILE is required. If PKGNAME is used'
				. ' it must be an installed package',
			'List all files and directories',
			'Display this help text',
			'An optional target may be specified to query an'
				. ' installed package in a separate'
				. ' directory/file system',
			'Show full manifest details'
			],
		options => [
			'<PKGNAME|FILE>',
			'[-a,--all]',
			'[-h,--help]',
			'[-t,--target TARGET]',
			'[-v,--verbose]'
			]
		},
	genpkg => {
		brief => 'Create package build directory',
		help => [
			'PKGNAME is required. This will create a directory'
				. ' of the same name and populate it with a'
				. ' basic set of files and directories'
				. ' which are required to build a Snaplinux'
				. ' package',
			'Display this help text'
			],
		options => [
			'<PKGNAME>',
			'[-h,--help]'
			]
		},
	help => {
		brief => 'Print brief usage information',
		help => [
			'Print details for all commands',
			'Display this help text'
			],
		options => [
			'[-a,--all]',
			'[-h,--help]'
			]
		},
	info => {
		brief => 'List package info',
		help => [
			'One or more PKGNAME or FILE is required. If a'
				. ' package name is specified it must be'
				. ' an installed package',
			'Display this help text',
			'An optional target may be specified to query a'
				. ' separate directory/file system'
			],
		options => [
			'<PKGNAME|FILE>',
			'[-h,--help]',
			'[-t,--target TARGET]'
			]
		},
	install => {
		brief => 'Install package',
		help => [
			'PKGNAME or FILE is required. A version string'
				. ' can optionally be provided with the'
				. ' PKGNAME as packagename=x.x.x',
			'Display this help text',
			'Install the package without dependencies',
			'An optional target may be specified to install'
				. ' the package to a separate directory/file'
				. ' system',
			'Proceed without prompting'
			],
		options => [
			'<PKGNAME[=VER]|FILE>',
			'[-h,--help]',
			'[-n,--nodeps]',
			'[-t,--target TARGET]',
			'[-y,--yes]'
			]
		},
	list => {
		brief => 'List installed packages',
		help => [
			'One or more strings can be supplied as filters',
			'Display this help text',
			'An optional target may be specified to query a'
				. ' different directory/file system',
			'List full package details'
			],
		options => [
			'[STRING]',
			'[-h,--help]',
			'[-t,--target TARGET]',
			'[-v,--verbose]'
			]
		},
	provides => {
		brief => 'List packages that provide a file',
		help => [
			'STRING is required. All packages will be searched'
				. ' and any which have files matching the'
				. ' string will be listed',
			'Display this help text',
			'Use search string as regular expression',
			'An optional target may be specified to query a'
				. ' different directory/file system',
			],
		options => [
			'<STRING>',
			'[-h,--help]',
			'[-r,--regex]',
			'[-t,--target TARGET]'
			]
		},
	purge => {
		brief => 'Remove package along with any associated files',
		help => [
			'PKGNAME is required',
			'Display this help text',
			'An optional target may be specified to purge the'
				. ' package from a separate directory/file'
				. ' system',
			'Proceed without prompting'
			],
		options => [
			'<PKGNAME>',
			'[-h,--help]',
			'[-t,--target TARGET]',
			'[-y,--yes]'
			]
		},
	rebuild => {
		brief => 'Rebuild package DB',
		help => [
			'Display this help text',
			'An optional target may be specified to repair the'
				. ' DB of a separate directory/file system',
			'Proceed without prompting'
			],
		options => [
			'[-h,--help]',
			'[-t,--target TARGET]',
			'[-y,--yes]'
			]
		},
	refresh => {
		brief => 'Update package list cache for all sources',
		help => [
			'Display this help text',
			'An optional target may be specified to refresh the'
				. ' package list of a separate directory/file'
				. 'system',
			],
		options => [
			'[-h,--help]',
			'[-t,--target TARGET]'
			]
		},
	reinstall => {
		brief => 'Re-install package',
		help => [
			'PKGNAME is required',
			'Display this help text',
			'An optional target may be specified to re-install'
				. ' the package on a separate directory/file'
				. ' system',
			'Proceed without prompting'
			],
		options => [
			'<PKGNAME>',
			'[-h,--help]',
			'[-t,--target TARGET]',
			'[-y,--yes]'
			]
		},
	remove => {
		brief => 'Remove a package',
		help => [
			'PKGNAME is required',
			'Display this help text',
			'An optional target may be specified to remove'
				. ' the package from a separate'
				. ' directory/file system',
			'Proceed without prompting'
			],
		options => [
			'<PKGNAME>',
			'[-h,--help]',
			'[-t,--target TARGET]',
			'[-y,--yes]'
			]
		},
	revdep => {
		brief => 'List installed packages that depend on <PKGNAME>',
		help => [
			'PKGNAME is required',
			'Display this help text',
			'An optional target may be specified to query'
				. ' the packages on a separate'
				. ' directory/file system',
			],
		options => [
			'<PKGNAME>',
			'[-h,--help]',
			'[-t,--target TARGET]'
			]
		},
	search => {
		brief => 'Search repositories for packages',
		help => [
			'STRING is optional. If STRING is not provided all'
				. ' repo packages are listed. An optional'
				. ' version string may be used',
			'Return all versions from all repos',
			'Display this help text',
			'Specify a list of KEY:VALUE to limit search',
			'Print verbose output'
			],
		options => [
			'[STRING[=VER]]',
			'[-a,--all]',
			'[-h,--help]',
			'[-k,--keys KEYS]',
			'[-t,--target TARGET]',
			'[-v,--verbose]'
			]
		},
	source => {
		brief => 'Retrieve package source',
		help => [
			'PKGNAME is required. By default the source for the'
				. ' currently installed version will be'
				. ' retrieved. A version string can optionally'
				. ' be provided with the PKGNAME as'
				. ' packagename=x.x.x',
			'Display this help text',
			'Retrieve the latest version'
			],
		options => [
			'<PKGNAME[=VER]>',
			'[-h,--help]',
			'[-l,--latest]'
			]
		},
	upgrade => {
		brief => 'Upgrade packages',
		help => [
			'With no arguments all packages are upgraded'
				. 'otherwise only the specified package',
			'Display this help text',
			'An optional target may be specified to upgrade'
				. ' the package on a separate directory/file'
				. ' system',
			'Proceed without prompting'
			],
		options => [
			'[PKGNAME]',
			'[-h,--help]',
			'[-t,--target TARGET]',
			'[-y]'
			]
		},
	verify => {
		brief => 'Verify integrity of installed packages',
		help => [
			'With no arguments all packages are verified'
				. ' otherwise only the specified package'
				. ' is checked',
			'Display this help text',
			'An optional target may be specified to verify'
				. ' the package on a separate directory/file'
				. ' system',
			'Print verbose output',
			'Proceed without prompting'
			],
		options => [
			'[PKGNAME]',
			'[-h,--help]',
			'[-t,--target TARGET]',
			'[-v,--verbose]',
			'[-y,--yes]'
			]
		},
	version => {
		options => [],
		brief => 'Display version information',
		help => []
		}
	};

my $conf = readconf();
my $command = shift( @ARGV ) || '';
my $commands = SL::Commands->new( $commandlist );
my $sources = SL::Sources->new( $conf->{'sources'} );
my $opts = $commands->parseopts( $command );

if ( $opts->{'help'} ) {
	$commands->commandhelp( $command );
	}
elsif ( $command eq 'autorm' ) {
	my $packages = [];
	my $bytes = 0;
	my $cnt = 0;
	my $termsize = SL->termsize();
	my $virtfs = 0;

	$sources->readpkgs();

	foreach my $pkgname ( sort( keys( %{$sources->{'status'}} ) ) ) {
		my $package = $sources->{'status'}{$pkgname};
		my $revdeps = [];

		if ( $package->{'status'} ne 'Installed dependency' ) {
			next;
			}

		$package->revdeps( $sources, $revdeps, { noreq => 1 } );

		if ( grep( $_->{'status'} eq 'Installed', @$revdeps ) ) {
			next;
			}

		push( @$packages, $package );
		}

	if ( ! @$packages ) {
		print "No unneeded packages to remove\n";

		exit;
		}

	foreach my $package ( @$packages ) {
		if ( ! $cnt ) {
			print "\nThe following packages will be removed";

			if ( $sl->{'target'} ) {
				print " from " . $sl->{'target'};
				}

			print ":\n  ";
			}

		if ( $termsize->{'col'} - ( length(
		$package->{'name'} ) + 3 ) <= 0 ) {
			print "\n  ";

			$termsize = SL->termsize();
			}

		print "$package->{'name'} ";

		$termsize->{'col'} -= length( $package->{'name'} ) + 1;
		$cnt++;

		$bytes += $package->{'bytes'};
		}

	print "\n\n" . human( $bytes ) . " will be recovered.";

	if ( ! $opts->{'yes'} ) {
		print " Continue (y/n): ";

		chkyes();
		}

	print "\n";

	$cnt = 0;

	foreach my $package ( @$packages ) {
		if ( ! $virtfs ) {
			$virtfs = virtfs( 'mount' );
			}

		if ( $cnt ) {
			print "\n";
			}

		$package->remove( $sources );

		$cnt++;
		}

	if ( $virtfs ) {
		virtfs( 'umount' );
		}
	}
elsif ( $command eq 'dump' ) {
	my $packages = [];
	my $downloads = [];
	my $cnt = 0;
	$sources->readpkgs();

	if ( ! @ARGV ) {
		SL->error( -2, "Failed to specify a package name or file" );
		}

	foreach my $arg ( @ARGV ) {
		my $package;

		if ( -f $arg && SL->ispkg( $arg ) ) {
			$package = SL::Package->new( $arg );
			}
		else {
			$package = ( $sources->search(
				{ name => $arg } ) )->[-0];
			}

		if ( ! $package ) {
			SL->error( -1, "$arg: No such package found" );
			}

		if ( $package->{'path'} =~ /https*:\/\// ) {
			( my $filename = $package->{'path'} ) =~ s/.*\///;

			if ( ! -f "$sl->{'pkgdir'}/$filename" ) {
				push( @$downloads, $package );
				}
			else {
				$package->{'path'} =
					"$sl->{'pkgdir'}/$filename";
				}
			}

		push( @$packages, $package );
		}

	if ( @$downloads ) {
		print "Downloading packages\n";

		foreach my $package ( @$downloads ) {
			( my $filename = $package->{'path'} ) =~ s/.*\///;

			if ( ! -f "$sl->{'pkgdir'}/$filename" ) {
				SL->httpget( $package->{'path'},
					"$sl->{'pkgdir'}/$filename", 0644 );
				}

			$package->{'path'} = "$sl->{'pkgdir'}/$filename";
 
			if ( SL->sha256( $package->{'path'} ) ne
			$package->{'sha256'} ) {
				SL->error( 1, "$filename: "
					. "sha256 does not match" );
				}
			}

		print "\n";
		}

	foreach my $package( @$packages ) {
		if ( $cnt ) {
			print "\n";
			}

		$package->dump( $opts );

		$cnt++;
		}
	}
elsif ( $command eq 'files' ) {
	my $packages = [];

	if ( ! @ARGV ) {
		$commands->commandhelp( $command );

		SL->error( -1, "Failed to provide package" );
		}

	foreach my $arg ( @ARGV ) {
		my $package = SL::Package->new( $arg );

		if ( -f $arg || $package->installed() ) {
			$package->files( $opts );

			push( @$packages, $package );
			}
		else {
			print "$arg is not installed\n";
			}
		}

	foreach my $package ( @$packages ) {
		foreach my $file ( sort( keys( %{$package->{'files'}} ) ) ) {
			my $sha = $package->{'files'}{$file}{'sha'};
			my $perms = $package->{'files'}{$file}{'perms'};

			if ( $opts->{'verbose'} ) {
				print "$sha\t$perms\t$file\n";
				}
			else {
				print "$file\n";
				}
			}
		}
	}
elsif ( $command eq 'genpkg' ) {
	if ( ! @ARGV ) {
		$commands->commandhelp( $command );

		SL->error( -1, "Failed to provide package name" );
		}

	foreach my $arg ( @ARGV ) {
		genpkg( $arg );
		}
	}
elsif ( $command eq 'help' ) {
	$commands->help( $opts );
	}
elsif ( $command eq 'info' ) {
	my $cnt = 0;

	if ( ! @ARGV ) {
		$commands->commandhelp( $command );
		SL->error( -1, "Failed to provide package" );
		}

	foreach my $arg ( @ARGV ) {
		my $package = SL::Package->new( $arg );

		if ( $cnt ) {
			print "\n";
			}

		$package->printself();

		$cnt++;
		}
	}
elsif ( $command eq 'install' ) {
	my @attribs = qw( source repo );
	my $string = "@ARGV";
	my $packages = [];
	my $downloads = [];
	my $bytes = 0;
	my $virtfs = 0;
	my $ldconfig = 0;

	if ( setup() ) {
		print "\n";
		}

	foreach my $attrib ( @attribs ) {
		if ( $string =~ /$attrib\s*:\s*(\S+)/ ) {
			$opts->{$attrib} = $1;

			$string =~ s/$attrib\s*:\s*(\S+)? //;
			}
		}

	$sources->readpkgs();

	####################################################
	#
	# This replaces source packages with any package
	# files supplied on the command line.
	#
	# This allows the user to override the source
	# with local packages.
	#
	####################################################

	foreach my $arg ( split( /\s+/, $string ) ) {
		if ( -f $arg && SL->ispkg( $arg ) ) {
			my $package = SL::Package->new( $arg );
			$sources->{'pkgs'}{$package->{'name'}} = [];
			push( @{$sources->{'pkgs'}{$package->{'name'}}},
				$package );
			}
		}

	####################################################
	#
	# Need to have sl-base, coreutils, and dash at
	# the very least. Here we're doing them in reverse
	# order so that if sl-base is missing it'll
	# be installed first!
	#
	####################################################

	if ( ! installed( 'dash' ) ) {
		$string =~ 's/(^|\s+)dash(\s+|$)//g';
		$string = "dash $string";
		}
	if ( ! installed( 'coreutils' ) ) {
		$string =~ 's/(^|\s+)coreutils(\s+|$)//g';
		$string = "coreutils $string";
		}
	if ( ! installed( 'sl-base' ) ) {
		$string =~ 's/(^|\s+)sl-base(\s+|$)//g';
		$string = "sl-base $string";
		}

	foreach my $arg ( split( /\s+/, $string ) ) {
		my $package;

		if ( -f $arg && SL->ispkg( $arg ) ) {
			$package = SL::Package->new( $arg );
			}
		else {
			my ( $name, $version ) =
				split( /(((<|>)=?|=)(.*))/, $arg );
			$opts->{'name'} = $name;
			$opts->{'version'} = $version;

			####################################
			#
			# We do this to retrieve the package
			# itself rather than the array which
			# contains it
			#
			####################################

			$package = ( $sources->search( $opts ) )->[0];
			}

		if ( ! $package ) {
			writelog( "$arg: Invalid package" );
			SL->error( 1, "$arg: Invalid package" );
			}

		if ( ! $opts->{'nodeps'} ) {
			print "Resolving dependencies for"
				. " $package->{'name'}\n";

			$package->depends( $sources, $packages );
			}
		else {
			writelog( "Ignoring dependencies for"
				. " $package->{'name'}" );
			print "Ignoring dependencies for $package->{'name'}\n";
			}

		if ( ! grep( $_->{'name'} eq $package->{'name'},
		@$packages ) ) {
			push( @$packages, $package );
			}
		}

	foreach my $package ( @$packages ) {
		( my $filename = $package->{'path'} ) =~ s/.*\///;

		if ( -f $package->{'path'} ) {
			next;
			}
		elsif ( $package->{'path'} =~ /^http/ &&
		! -f "$sl->{'pkgdir'}/$filename" ) {
			push( @$downloads, $package );
			}
		else {
			$package->{'path'} =
				"$sl->{'pkgdir'}/$filename";
			}
		}

	if ( @$downloads ) {
		print "\nDownloading packages\n";

		foreach my $package ( @$downloads ) {
			( my $filename = $package->{'path'} ) =~ s/.*\///;

			if ( ! -f "$sl->{'pkgdir'}/$filename" ) {
				writelog( "Downloading $package->{'path'}" );
				SL->httpget( $package->{'path'},
					"$sl->{'pkgdir'}/$filename", 0644 );
				}

			$package->{'path'} = "$sl->{'pkgdir'}/$filename";

			if ( SL->sha256( $package->{'path'} ) ne
			$package->{'sha256'} ) {
				SL->error( 1, "$filename: "
					. "sha256 does not match" );
				}
			}
		}

	for ( my $i = 0; $i <= $#$packages; $i++ ) {
		my $package = $packages->[$i];
		my $oldpkg = $sources->{'status'}{$package->{'name'}};
		my $oldbytes;
		my $chk;

		$bytes += $package->{'bytes'};

		if ( ! $oldpkg || ! $oldpkg->installed() ) {
			writelog( "Installing $package->{'name'}"
				. " $package->{'version'}" );

			if ( $package->{'status'} ne 'installing dependency' ) {
				$package->{'status'} = 'installing';
				}

			next;
			}

		$chk = SL->vercmp( $oldpkg->{'version'},
			$package->{'version'} );

		if ( $chk == -1 ) {
			writelog( "Upgrading $package->{'name'} from"
				. " $oldpkg->{'version'} to"
				. " $package->{'version'}" );

			$package->{'status'} = 'upgrading';
			$bytes -= $oldpkg->{'bytes'};
			}
		elsif ( $chk == 0 ) {
			writelog( "Package $package->{'name'}="
				. "$package->{'version'}"
				. " is already installed" );
			print "Package $package->{'name'}="
				. "$package->{'version'}"
				. " is already installed\n";

			splice( @$packages, $i, 1 );

			$i--;

			next;
			}
		elsif ( $chk == 1 && $opts->{'version'} ) {
			writelog( "Downgrading $package->{'name'} from"
				. " $oldpkg->{'version'} to"
				. " $package->{'version'}" );

			$package->{'status'} = 'downgrading';
			$bytes -= $oldpkg->{'bytes'};
			}
		elsif ( $chk == 1 ) {
			writelog( "The latest version of $oldpkg->{'name'}"
				. " is already installed" );
			print "The latest version of $oldpkg->{'name'}"
				. " ($oldpkg->{'version'}) is already"
				. " installed";

			splice( @$packages, $i, 1 );

			$i--;

			next;
			}
		}

	if ( ! @$packages ) {
		print "\nNothing to do\n";

		exit;
		}

	foreach my $status ( qw( installing upgrading downgrading ) ) {
		my $termsize = SL->termsize();
		my $cnt = 0;

		foreach my $package ( sort { $a->{'name'} cmp $b->{'name'} }
		( @$packages ) ) {
			if ( $package->{'status'} !~ /^$status/ ) {
				next;
				}

			if ( ! $cnt ) {
				print "\nThe following packages will be";
				}

			if ( ! $cnt && substr( $status, 0, 10 )
			eq 'installing' ) {
				print " installed:\n  ";
				}
			elsif ( ! $cnt && $status eq 'upgrading' ) {
				print " upgraded:\n  ";
				}
			elsif ( ! $cnt && $status eq 'downgrading' ) {
				print " downgraded:\n  ";
				}

			if ( $termsize->{'col'} - ( length(
			$package->{'name'} ) + 3 ) <= 0 ) {
				print "\n  ";

				$termsize = SL->termsize();
				}

			print "$package->{'name'} ";

			$termsize->{'col'} -= length( $package->{'name'} ) + 1;
			$cnt++;
			}
		}

	print "\n";

	if ( $bytes < 0 ) {
		print "\nInstall will recover " . human( -1 * $bytes )
		}
	else {
		print "\nInstall will require " . human( $bytes )
		}

	if ( ! $opts->{'yes'} ) {
		print " Continue? (y/n): ";

		chkyes();
		}
	else {
		print "\n";
		}

	foreach my $package ( @$packages ) {
		if ( ! $virtfs ) {
			$virtfs = virtfs( 'mount' );
			}

		$package->conflicts( $sources );
		$package->install( $sources );

		writelog( "Done installing $package->{'name'}" );

		print "\n";
		}

	####################################################
	#
	# This executes processes such as install-info,
	# grub-mkconfig, mkinitramfs, etc if needed.
	#
	####################################################

	trigger();

	if ( $virtfs ) {
		virtfs( 'umount' );
		}
	}
elsif ( $command eq 'list' ) {
	my $cnt = 0;
	$sources->readpkgs();

	foreach my $pkgname ( sort( keys( %{$sources->{'status'}} ) ) ) {
		my $package = $sources->{'status'}{$pkgname};

		if ( @ARGV && ! grep( $package->{'name'} =~ /$_/, @ARGV ) ) {
			next;
			}

		if ( $opts->{'verbose'} ) {
			if ( $cnt ) {
				print "\n";
				}

			$package->printself();
			}
		else {
			$package->printbrief();
			}

		$cnt++;
		}

	if ( $cnt <= 0 ) {
		if ( @ARGV ) {
			print "No installed packages found matching '@ARGV'\n";
			}
		else {
			print "No installed packages found\n";
			}
		}
	}
elsif ( $command eq 'provides' ) {
	( my $string = $ARGV[0] ) =~ s/^\/*/\//;
	my $len = length( $string );

	$sources->readpkgs();

	foreach my $pkgname ( sort( keys( %{$sources->{'status'}} ) ) ) {
		my $package = $sources->{'status'}{$pkgname};
		my $cnt = 0;

		if ( ! $package->installed() ) {
			next;
			}

		$package->files();

		foreach my $file ( sort{ $a cmp $b }(
		keys( %{$package->{'files'}} ) ) ) {
			if ( ( $opts->{'regex'} && "/$file" =~ /$string/ ) ||
			( substr( "/$file", -$len, $len ) eq $string ) ) {
				if ( ! $cnt ) {
					print "[$pkgname]\n";
					}

				print "  * $sl->{'target'}/$file\n";

				$cnt++;
				}
			}

		if ( $cnt ) {
			print "\n";
			}
		}
	}
elsif ( $command eq 'purge' ) {
	my $packages = [];
	my $revdeps = [];
	my $bytes = 0;
	my $cnt = 0;
	my $termsize = SL->termsize();
	my $virtfs = 0;

	$sources->readpkgs();

############################################################
#
# We're working on PURGE! We're going to need to do a
# $package->remove() on packages that are not yet removed.
# We're going to need to $package->remove() on any packages
# that depend on this package. Similar to the remove
# command, but a separate array will be needed for the
# packages to be removed because they depend on this one.
# Those will be removed first, then this package
# removed (if not yet removed), then finally purge this
# package!
#
# IF THE PACKAGE IS ALREADY REMOVED JUST IGNORE DEPS?
#
# Also need to make sure to only report the amount of
# space to be recovered if the package is being removed.
# Perhaps this output should be handled by the remove()
# sub instead?
#
############################################################

	foreach my $arg ( @ARGV ) {
		my $package = SL::Package->new( $arg );

		if ( substr( $package->{'status'}, 0, 1 ) eq 'N' ) {
			writelog( "Package $package->{'name'} is not present" );
			print "Package $package->{'name'} is not present\n";

			next;
			}
		if ( ! $opts->{'nodeps'} &&
		substr( $package->{'status'}, 0, 1 ) ne 'R' ) {
			print "Resolving reverse dependencies for"
				. " $package->{'name'}\n";

			$package->revdeps( $sources, $packages,
				{ noreq => 1 } );
			}
		elsif( substr( $package->{'status'}, 0, 1 ) ne 'R' ) {
			print "Ignoring reverse dependencies for"
				. " $package->{'name'}\n";
			}

		if ( ! grep( $_->{'name'} eq $package->{'name'},
		@$packages ) ) {
			push( @$packages, $package );
			}
		}

	foreach my $package ( @$packages ) {
		if ( ! $cnt ) {
			print "\nThe following packages will be purged";

			if ( $sl->{'target'} ) {
				print " from $sl->{'target'}";
				}

			print ":\n  ";
			}

		if ( $termsize->{'col'} - ( length(
		$package->{'name'} ) + 3 ) <= 0 ) {
			print "\n  ";

			$termsize = SL->termsize();
			}

		print "$package->{'name'} ";

		$termsize->{'col'} -= length( $package->{'name'} ) + 1;
		$cnt++;

		if ( substr( $package->{'status'}, 0, 1 ) ne 'R' ) {
			$bytes += $package->{'bytes'};
			}
		}

	print "\n\n" . human( $bytes ) . " will be recovered."
		. " Continue? (y/n): ";

	if ( ! $opts->{'yes'} ) {
		chkyes();
		}

	print "\n";

	$cnt = 0;

	foreach my $package ( @$packages ) {
		if ( ! $virtfs ) {
			$virtfs = virtfs( 'mount' );
			}

		if ( $cnt ) {
			print "\n";
			}

		$package->files();

		$package->remove( $sources );
		writelog( "Purged $package->{'name'}-$package->{'version'}" );

		$package->purge();

		$cnt++;
		}

	trigger();

	if ( $virtfs ) {
		virtfs( 'umount' );
		}
	}
elsif ( $command eq 'rebuild' ) {
	print "Not yet implemented\n";

	exit( -1 );
	}
elsif ( $command eq 'refresh' ) {
	if ( ! setup() ) {
		$sources->refresh();
		}
	}
elsif ( $command eq 'reinstall' ) {
	my $termsize = SL->termsize();
	my $packages = [];
	my $virtfs = 0;
	my $cnt = 0;

	setup();

	$sources->readpkgs();

	foreach my $pkgname ( @ARGV ) {
		my $package;

		if ( -f $pkgname ) {
			$package = SL::Package->new( $pkgname );
			}
		else {
			if ( ! $sources->{'status'}{$pkgname} ) {
				SL->error( -1, "$pkgname not installed" );
				}

			$package = $sources->{'status'}{$pkgname};
			$package = ( $sources->search( {
				name => $package->{'name'},
				version => $package->{'version'},
				} ) )->[0];
			}

		if ( $package->{'path'} =~ /https*:\/\// ) {
			( my $filename = $package->{'path'} ) =~ s/.*\///;

			if ( ! -f "$sl->{'pkgdir'}/$filename" ) {
				SL->httpget( $package->{'path'},
					"$sl->{'pkgdir'}/$filename", 0644 );
				}

			$package->{'path'} = "$sl->{'pkgdir'}/$filename";

			if ( SL->sha256( $package->{'path'} ) ne
			$package->{'sha256'} ) {
				SL->error( 1, "$filename: "
					. "sha256 does not match" );
				}
			}

		$package->files( $opts );

		if ( ! grep( $_->{'name'} eq $package->{'name'},
		@$packages ) ) {
			push( @$packages, $package );
			}
		}

	foreach my $package ( sort { $a->{'name'} cmp $b->{'name'} }
	( @$packages ) ) {
		if ( ! $cnt ) {
			print "The following packages will be"
				. " reinstalled:\n  ";

			$cnt++;
			}

		if ( $termsize->{'col'} - ( length(
		$package->{'name'} ) + 3 ) <= 0 ) {
			print "\n  ";

			$termsize = SL->termsize();
			}

		print "$package->{'name'} ";

		$termsize->{'col'} -= length( $package->{'name'} ) + 1;
		}

	print "\n";

	if ( ! $opts->{'yes'} ) {
		print "\nContinue? (y/n): ";

		chkyes();
		}

	foreach my $package ( @$packages ) {
		if ( ! $virtfs ) {
			$virtfs = virtfs( 'mount' );
			}

		print "\n";

		$package->files();
		$package->install( $sources );

		writelog( "Re-installed $package->{'name'}"
			. "-$package->{'version'}" );
		}

	print "\n";

	trigger();

	virtfs( 'umount' );
	}
elsif ( $command eq 'remove' ) {
	my $packages = [];
	my $bytes = 0;
	my $cnt = 0;
	my $termsize = SL->termsize();
	my $virtfs = 0;

	$sources->readpkgs();

	foreach my $arg ( @ARGV ) {
		my $package = SL::Package->new( $arg );

		if ( $package->{'status'} =~ /^(R|N)/ ) {
			writelog( "Package $package->{'name'}"
				. " is not installed" );
			print "Package $package->{'name'} is not installed\n";

			next;
			}
		if ( ! $package ) {
			SL->error( 1, "$arg: No such package found" );
			}

		if ( ! $opts->{'nodeps'} ) {
			print "Resolving reverse dependencies for"
				. " $package->{'name'}\n";

			$package->revdeps( $sources, $packages,
				{ noreq => 1 } );
			}
		else {
			print "Ignoring reverse dependencies for"
				. " $package->{'name'}\n";
			}

		if ( ! grep( $_->{'name'} eq $package->{'name'},
		@$packages ) ) {
			push( @$packages, $package );
			}
		}

	foreach my $package ( sort { $a->{'name'} cmp $b->{'name'} }
	( @$packages ) ) {
		if ( ! $cnt ) {
			print "\nThe following packages will be removed";

			if ( $sl->{'target'} ) {
				print " from $sl->{'target'}";
				}

			print ":\n  ";
			}

		if ( $termsize->{'col'} - ( length(
		$package->{'name'} ) + 3 ) <= 0 ) {
			print "\n  ";

			$termsize = SL->termsize();
			}

		print "$package->{'name'} ";

		$termsize->{'col'} -= length( $package->{'name'} ) + 1;
		$cnt++;

		$bytes += $package->{'bytes'};
		}

	if ( ! @$packages ) {
		print "\nNothing to do\n";

		exit;
		}

	print "\n\n" . human( $bytes ) . " will be recovered."
		. " Continue? (y/n): ";

	if ( ! $opts->{'yes'} ) {
		chkyes();
		}

	print "\n";

	$cnt = 0;

	foreach my $package ( @$packages ) {
		if ( ! $virtfs ) {
			$virtfs = virtfs( 'mount' );
			}

		if ( $cnt ) {
			print "\n";
			}

		$package->files();
		$package->remove( $sources );

		writelog( "Removed package $package->{'name'}"
			. " $package->{'version'}" );

		$cnt++;
		}

	trigger();

	if ( $virtfs ) {
		virtfs( 'umount' );
		}
	}
elsif ( $command eq 'revdep' ) {
	my $revdeps = [];

	$sources->readpkgs();

	foreach my $arg ( @ARGV ) {
		my $package = SL::Package->new( $arg );

		$package->revdeps( $sources, $revdeps, { noreq => 1 } );
		}

	foreach ( sort { $a->{'name'} cmp $b->{'name'} } ( @$revdeps ) ) {
		print "$_->{'name'}\n";
		}

	if ( ! @$revdeps ) {
		print "No reverse dependencies found\n";
		}
	}
elsif ( $command eq 'search' ) {
	my $packages = [];
	my $cnt = 0;
	$sources->readpkgs();

	if ( ! @ARGV ) {
		push( @ARGV, '.*' );
		}

	foreach ( @ARGV ) {
		$opts->{'search'} = $_;

		foreach my $package ( @{$sources->search( $opts )} ) {
			if ( $opts->{'all'} || ! grep( $_->{'name'} eq
			$package->{'name'}, @$packages ) ) {
				push( @$packages, $package );
				}
			}
		}

	foreach my $package ( sort{ $a->{'name'} cmp $b->{'name'} }(
	@$packages ) ) {
		if ( $cnt && $opts->{'verbose'} ) {
			print "\n";
			}

		if ( $opts->{'verbose'} ) {
			$package->printself();
			}
		else {
			$package->printbrief();
			}

		$cnt++;
		}

	if ( ! $cnt ) {
		print "No matching packages found\n";
		}
	}
elsif ( $command eq 'source' ) {
	$sources->readpkgs();

	foreach my $arg ( @ARGV ) {
		my $package;# = SL::Package->new( $arg );

		if ( $sources->{'installed'}{$arg} && ! $opts->{'latest'} ) {
			$package = $sources->{'installed'}{$arg};
			}
		else {
			my ( $name, $version ) =
				split( /(((<|>)=?|=)(.*))/, $arg );
			$opts->{'name'} = $name;
			$opts->{'version'} = $version;
			$package = ( $sources->search( $opts ) )->[0];
			}

		$package->source();
		}
	}
elsif ( $command eq 'upgrade' ) {
	my $packages = [];
	my $downloads = [];
	my $termsize = SL->termsize();
	my $bytes = 0;
	my $cnt = 0;
	my $virtfs = 0;
	$sources->readpkgs();

	if ( ! @ARGV ) {
		foreach my $pkgname ( keys( %{$sources->{'installed'}} ) ) {
			my $package = $sources->{'installed'}{$pkgname};

			if ( ! $sources->{'installed'}{$pkgname} ) {
				print "Package '$pkgname' not installed\n";

				next;
				}
			else {
				$package = $sources->{'installed'}{$pkgname};
				}

			if ( $sources->{'pkgs'}{$pkgname} && SL->vercmp(
			$sources->{'pkgs'}{$pkgname}[-1]{'version'},
			$package->{'version'} ) == 1 ) {
				push( @$packages,
					$sources->{'pkgs'}{$pkgname}[-1] );
				}
			else {
				if ( $sl->{'debug'} ) {
					print "No upgrade available for"
						. " $pkgname\n";
					}
				}
			}
		}
	else {
		foreach my $pkgname ( @ARGV ) {
			my $package;

			if ( ! $sources->{'installed'}{$pkgname} ) {
				print "Package '$pkgname' not installed\n";

				next;
				}
			else {
				$package = $sources->{'installed'}{$pkgname};
				}

			if ( $sources->{'pkgs'}{$pkgname} && SL->vercmp(
			$sources->{'pkgs'}{$pkgname}[-1]{'version'},
			$package->{'version'} ) == 1 ) {
				push( @$packages,
					$sources->{'pkgs'}{$pkgname}[-1] );
				}
			else {
				print "No upgrade available for $pkgname\n";
				}
			}
		}

	if ( ! @$packages ) {
		print "No packages require upgrade\n";

		exit;
		}

	foreach my $package ( @$packages ) {
		( my $filename = $package->{'path'} ) =~ s/.*\///;

		if ( ! $opts->{'nodeps'} ) {
			print "Resolving dependencies for"
				. " $package->{'name'}\n";

			$package->depends( $sources, $packages );
			}
		else {
			print "Ignoring dependencies for $package->{'name'}\n";
			}

		if ( -f $package->{'path'} ) {
			next;
			}
		elsif ( $package->{'path'} =~ /^http/ &&
		! -f "$sl->{'pkgdir'}/$filename" ) {
			push( @$downloads, $package );
			}
		else {
			$package->{'path'} =
				"$sl->{'pkgdir'}/$filename";
			}
		}

	if ( @$downloads ) {
		print "\nDownloading packages\n";

		foreach my $package ( @$downloads ) {
			( my $filename = $package->{'path'} ) =~ s/.*\///;

			if ( ! -f "$sl->{'pkgdir'}/$filename" ) {
				SL->httpget( $package->{'path'},
					"$sl->{'pkgdir'}/$filename", 0644 );
				}

			$package->{'path'} = "$sl->{'pkgdir'}/$filename";

			if ( SL->sha256( $package->{'path'} ) ne
			$package->{'sha256'} ) {
				SL->error( 1, "$filename: "
					. "sha256 does not match" );
				}
			}
		}

	foreach my $package ( sort { $a->{'name'} cmp $b->{'name'} }
	( @$packages ) ) {
		my $oldpkg = $sources->{'installed'}{$package->{'name'}};
		$bytes += $package->{'bytes'};

		if ( $oldpkg ) {
			$bytes -= $oldpkg->{'bytes'};

			writelog( "Upgrading $oldpkg->{'name'}"
				. "-$oldpkg->{'version'} to"
				. " $package->{'name'}-$package->{'version'}" );
			}

		if ( ! $cnt ) {
			print "\nThe following packages will be"
				. " upgraded:\n  ";
			}

		if ( $termsize->{'col'} - ( length(
		$package->{'name'} ) + 3 ) <= 0 ) {
			print "\n  ";

			$termsize = SL->termsize();
			}

		print "$package->{'name'} ";

		$termsize->{'col'} -= length( $package->{'name'} ) + 1;
		$cnt++;
		}

	if ( $bytes < 0 ) {
		print "\n\nUpgrade will recover " . human( -1 * $bytes );
		}
	else {
		print "\n\nUpgrade will require " . human( $bytes );
		}

	if ( ! $opts->{'yes'} ) {
		print ". Continue? (y/n): ";

		chkyes();
		}
	else {
		print "\n";
		}

	foreach my $package ( @$packages ) {
		if ( ! $virtfs ) {
			$virtfs = virtfs( 'mount' );
			}

		print "\n";

		$package->conflicts( $sources );
		$package->install( $sources );

		writelog( "Finished installing $package->{'name'}"
			. "-$package->{'version'}" );
		}

	trigger();

	if ( $virtfs ) {
		virtfs( 'umount' );
		}
	}
elsif ( $command eq 'verify' ) {
	my $packages = [];
	my $failedcnt = 0;
	$sources->readpkgs();

	if ( @ARGV ) {
		foreach my $arg ( sort{ $a cmp $b }( @ARGV ) ) {
			my $package = $sources->{'status'}{$arg} ||
				SL->error( 1, "$arg: not installed" );

			push( @$packages, $package );
			}
		}
	else {
		foreach my $pkgname ( sort{ $a cmp $b }
		keys( %{$sources->{'status'}} ) ) {
			my $package = $sources->{'status'}{$pkgname};

			push( @$packages, $package );
			}
		}

	foreach my $package ( @$packages ) {
		my $result = $package->verify( $opts );

		if ( ! @{$result->{'failed'}} ) {
			writelog( "$package->{'name'}-$package->{'version'}"
				. " passed verification" );
			print "$package->{'name'}: OK\n";
			}
		else {
			writelog( "$package->{'name'}-$package->{'version'}"
				. " failed verification" );
			print "$package->{'name'}: " . @{$result->{'failed'}}
				. " files failed verification\n";

			foreach ( sort{ $a cmp $b }
			( @{$result->{'failed'}} ) ) {
				$failedcnt++;

				print "  * $_\n";
				}
			} 
		}

	if ( $failedcnt ) {
		exit( 1 );
		}
	}
elsif ( $command eq 'version' ) {
	print "$sl->{'version'}\n";
	}
elsif ( $command ) {
	SL->error( 0, "'$command': Invalid command" );
	$commands->help();
	exit( 2 );
	}
