# Copyright (c) 2008 Landry Breuil <landry@openbsd.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

use strict;
use warnings;
package OpenBSD::PackageManager::View;
use Curses::UI;
use Curses;
use Text::Wrap;

$Text::Wrap::columns = 72;

# moved to $self
# my $this = "pkg_mgr";
# my $self->{model};
# my $self->{controller};
# my %w;
# my $self->{prevlist};

sub new
{
	my($class, $m, $c) = @_;
	my $self = {};
	$self->{name} ="pkg_mgr";
	$self->{model} = $m;
	$self->{controller} = $c;
	undef $self->{wid};
	undef $self->{prevlist};
	bless $self, $class;
	$self->init;
	return $self;
}

sub show_msg
{
	my $self = shift;
	$self->{wid}{help_textviewer}->text(shift);
	$self->{wid}{help_textviewer}->draw();
}

sub focus_categories
{
	my $self = shift;
	$self->show_msg("[space/enter] select\t[i] show installed\t[q] quit\n[o] show orphaned\t[s] search");
	$self->{wid}{categories_listbox}->clear_selection();
	$self->{wid}{categories_listbox}->focus();
}

sub focus_prevlist
{
	my $self = shift;
	if ($self->{prevlist} eq "ports") {
		$self->focus_portslist();
	} else {
		$self->focus_pkginstlist();
	}
}
#broken, must call show_installed and clear selection if command succeeded
sub focus_pkginstlist
{
	my $self = shift;
	$self->show_msg("[space] select for uninstallation\t[enter/right] details\t\t[left] back\t\t[q] quit\n".
		"[u] uninstall selected\t\t\t[s] simulate uninstallation\t[/] find");
	$self->{wid}{pkginst_win}->focus;
}

sub focus_portslist
{
	my $self = shift;
	$self->show_msg("[space] select for installation\t[enter/right] details\t\t[left] back\t\t[q] quit\n".
		"[i] install selected\t\t[s] simulate installation\t[/] find");
	$self->{wid}{ports_win}->focus;
}

sub focus_portdescr
{
	my $self = shift;
	$self->show_msg("[left] back\t [q] quit");
	$self->{wid}{port_textviewer}->focus;
}

sub show_category
{
	my $self = shift;
	my $cat = $self->{wid}{categories_listbox}->get_active_value();
	$self->show_msg("Getting ports for category $cat");
	my $rh = $self->{model}->get_ports_for_category($cat);
	my $nports = keys %$rh;

	my @values = sort { $rh->{$a}->{fullpkgname} cmp $rh->{$b}->{fullpkgname}} keys %$rh;
	my @values_installed;
	for (my $i = 0 ; $i < $#values ; $i++) {
		push @values_installed, $i if ($self->{model}->is_installed($values[$i]));
	}
	$self->{wid}{ports_win}->title("$cat, $nports ports in category (".@values_installed." installed)");
	$self->{wid}{ports_listbox}->values(\@values);
	$self->{wid}{ports_listbox}->set_selection(@values_installed);
	# padding : pkgname -- spaces -- comment
	my %h;
	$h{$_} = $rh->{$_}->{fullpkgname} . " " x (45 - length($rh->{$_}->{fullpkgname})) . $rh->{$_}->{comment} foreach (keys %$rh);
	$self->{wid}{ports_listbox}->labels(\%h);
	$self->{prevlist}="ports";
	$self->focus_portslist();
}

sub show_installed
{
	my $self = shift;
	my $mode = shift;
	$self->{wid}{pkginst_listbox}->clear_selection();
	my $rh;
	if ($mode eq "installed") {
		$self->show_msg("Getting installed package list");
		$rh = $self->{model}->get_installed_list();
		my $nports = keys %$rh;
		$self->{wid}{pkginst_win}->title("$nports ports installed");
	}
	else {
		$self->show_msg("Getting orphaned package list");
		$rh = $self->{model}->get_orphaned_list();
		my $nports = keys %$rh;
		$self->{wid}{pkginst_win}->title("$nports ports orphaned (installed and nothings depends on them)");
	}
	$self->{wid}{pkginst_listbox}->values([sort { $rh->{$a}->{instpkgname} cmp $rh->{$b}->{instpkgname}} keys %$rh]);
	# padding : pkgname -- spaces -- comment
	my %h;
	$h{$_} = $rh->{$_}->{instpkgname} . " " x (45 - length($rh->{$_}->{instpkgname})) . $rh->{$_}->{comment} foreach (keys %$rh);
	$self->{wid}{pkginst_listbox}->labels(\%h);
	$self->{prevlist}=$mode;
	$self->focus_pkginstlist($mode);
}

sub port_entered
{
	my $self = shift;
	my $lb = shift;
	my $port = $lb->get_active_value();
	$self->{wid}{port_textviewer}->title("Informations for $port");
	my $r = $self->{model}->get_info_for_port($port);
	unless (defined $r) {
		$self->{wid}{port_textviewer}->text("not found in sqlports");
		$self->{wid}{port_textviewer}->focus;
		return;
	}
	my $s = "Package name: ";
	if ($self->{model}->is_installed($port)) {
		$s .= $r->{instpkgname}." (Installed, takes ".$r->{size}.")";
		$s .= "\n$r->{fullpkgname} available as update" if ($r->{fullpkgname} ne $r->{instpkgname});
	}
	else {
		$s .= $r->{fullpkgname};
	}
	$s .= "\n\n".$r->{comment}."\n\nMaintainer: ".$r->{maintainer};
	$s .= "\nWWW: ".$r->{homepage} if defined $r->{homepage};
	$s .= "\n". "-" x 72;
	#tosee use text_wrap
	$s .= "\n".wrap('','', "Used by: ".$r->{used_by}) if defined $r->{used_by};
	$s .= "\n".wrap('','', "Library dependencies: ".$r->{lib_depends}) if defined $r->{lib_depends};
	$s .= "\n".wrap('','', "Runtime dependencies: ".$r->{run_depends}) if defined $r->{run_depends};
	$s .= "\n\n\n".$r->{descr};
	$self->{wid}{port_textviewer}->text($s);
	$self->focus_portdescr();
}

sub port_select_changed
{
	my $self = shift;
	my $lb = $self->{wid}{ports_listbox};
	my @get = $lb->get();
#	my @id = $lb->id();
#	$self->{wid}{help_textviewer}->text(@get." ports selected for inst : @get (indexes=@id)");
	#reselect an installed port if deselected
	$lb->set_selection($lb->get_active_id()) if ($self->{model}->is_installed($lb->get_active_value()));
	$self->{model}->candidates_for_installation(@get);
	$self->{wid}{ports_win}->focus;
}

sub pkginst_select_changed
{
	my $self = shift;
	my $lb = $self->{wid}{pkginst_listbox};
	my @get = $lb->get();
#	my @id = $lb->id();
#	$self->{wid}{help_textviewer}->text(@get." ports selected for removal : @get (indexes=@id)");
	$self->{model}->candidates_for_removal(@get);
	$self->{wid}{pkginst_win}->focus;
}

sub show_searchbox
{
	my $self = shift;
	my $req = $self->{wid}{cui}->question("Enter search term (will match package name and comment) :");
	$self->show_msg("Searching ports for keyword \"$req\"");
	my $rh = $self->{model}->get_ports_matching_keyword($req);
	my $nports = keys %$rh;

	my @values = sort { $rh->{$a}->{fullpkgname} cmp $rh->{$b}->{fullpkgname}} keys %$rh;
	my @values_installed;
	for (my $i = 0 ; $i < $#values ; $i++) {
		push @values_installed, $i if ($self->{model}->is_installed($values[$i]));
	}
	$self->{wid}{ports_win}->title("$nports ports match keyword \"$req\" (".@values_installed." installed)");
	$self->{wid}{ports_listbox}->values(\@values);
	$self->{wid}{ports_listbox}->set_selection(@values_installed);
	# padding : pkgname -- spaces -- comment
	my %h;
	$h{$_} = $rh->{$_}->{fullpkgname} . " " x (45 - length($rh->{$_}->{fullpkgname})) . $rh->{$_}->{comment} foreach (keys %$rh);
	$self->{wid}{ports_listbox}->labels(\%h);
	$self->{prevlist}="ports";
	$self->focus_portslist();
}

sub install_selected
{
	my $self = shift;
	my $mode = shift;
	#todo : uninstall
	$self->show_msg("Getting list of packages to $mode");
	my $pkgs = ($mode =~ /install/) ? $self->{model}->get_candidates_for_installation() : $self->{model}->get_candidates_for_removal();
	unless (@$pkgs) {
		$self->show_msg("No packages to $mode ??");
		sleep 2;
		$self->focus_prevlist();
		return;
	}
	$self->show_msg("Going to $mode ".@$pkgs." packages");
	my ($cmd, $yes) = ("", 1);
	# fallback to sudo if UID != 0
	$cmd = "sudo " if $<;

	if ($mode eq "install") {
		$cmd .= "pkg_add -i @$pkgs";
	} elsif ($mode eq "sim-install") {
		$cmd = "pkg_add -ni @$pkgs";
	} elsif ($mode eq "remove") {
		$cmd .= "pkg_delete -i @$pkgs"
	} elsif ($mode eq "sim-remove") {
		$cmd = "pkg_delete -ni @$pkgs";
	}

	if ($mode !~ /sim-/) {
		$yes = $self->{wid}{cui}->dialog(
			-message => "Ready to $mode ?",
			-buttons => ['yes','no'],
			-values  => [1,0]);
	}
	if ($yes) {
		$self->{wid}{cui}->leave_curses();
		my $r = $self->{controller}->my_system($cmd);
		$self->{wid}{cui}->reset_curses();
#		todo : status() ?
		if ($r == 0) {
 			if ($mode !~ /sim-/) {
				$self->show_msg("Updating installed/orphaned package list");
				$self->{model}->update_installed();
				$self->{model}->update_orphaned();
			}
			if ($self->{prevlist} eq "ports") {
				$self->{model}->candidates_for_installation("");
				$self->{wid}{ports_listbox}->clear_selection();
				$self->show_category($self->{wid}{categories_listbox});
			} else {
				$self->{model}->candidates_for_removal("");
				$self->{wid}{pkginst_listbox}->clear_selection();
				$self->show_installed($self->{prevlist});
			}
			return;
		}
		else {
			$self->show_msg("Something failed.. $r");
		}
	}
	$self->focus_prevlist();
}

sub init
{
	my $self = shift;
	$self->{wid}{cui} = new Curses::UI(
		-clear_on_exit => 1,
#		-debug => 1,
		-color_support => 1);
	$self->{wid}{main_win} = $self->{wid}{cui}->add(
		'main_win', 'Window',
		-title => $self->{name},
		-titlereverse => 0,
		-border => 1);

	$self->{wid}{categories_win} = $self->{wid}{main_win}->add(
		'categories_win', 'Window',
		-padbottom => 2,
		-titlereverse => 0,
		-title => "categories",
		-border => 1);

	my $v = $self->{model}->get_categories();
	$self->{wid}{categories_listbox} = $self->{wid}{categories_win}->add(
		'categories_listbox', 'Listbox',
		-values => $v,
		-onchange => sub { $self->show_category });

	$self->{wid}{ports_win} = $self->{wid}{main_win}->add(
		'ports_win', 'Window',
		-padbottom => 2,
		-titlereverse => 0,
		-border => 1);

	$self->{wid}{ports_listbox} = $self->{wid}{ports_win}->add(
		'ports_listbox', 'Listbox',
		-onchange => sub { $self->port_select_changed },
		-multi => 1);

	$self->{wid}{pkginst_win} = $self->{wid}{main_win}->add(
		'pkginst_win', 'Window',
		-padbottom => 2,
		-titlereverse => 0,
		-border => 1);

	$self->{wid}{pkginst_listbox} = $self->{wid}{pkginst_win}->add(
		'pkginst_listbox', 'Listbox',
		-onchange => sub { $self->pkginst_select_changed },
		-multi => 1);

	$self->{wid}{help_textviewer} = $self->{wid}{main_win}->add(
		'help_textviewer', 'TextViewer',
		-padtop => ($self->{wid}{main_win}->height - 4),
		-text => "Welcome to ".$self->{name}."\n");

	$self->{wid}{port_textviewer} = $self->{wid}{main_win}->add(
		'portviewer', 'TextViewer',
		-padbottom => 2,
#		-htmltext => 1,
		-titlereverse => 0,
		-border => 1);

	# set keybindings
	$self->{wid}{ports_listbox}->set_binding( sub { $self->focus_categories }, KEY_LEFT());
	$self->{wid}{pkginst_listbox}->set_binding( sub { $self->focus_categories }, KEY_LEFT());
	$self->{wid}{port_textviewer}->set_binding( sub { $self->focus_prevlist }, KEY_LEFT());
	$self->{wid}{ports_listbox}->set_binding( sub { $self->port_entered(shift) }, (KEY_RIGHT(), KEY_ENTER()));
	$self->{wid}{pkginst_listbox}->set_binding( sub { $self->port_entered(shift) }, (KEY_RIGHT(), KEY_ENTER()));
	$self->{wid}{categories_listbox}->set_binding( sub { $self->show_category }, KEY_RIGHT());
	$self->{wid}{categories_listbox}->set_binding( sub { $self->show_searchbox }, "s");
	$self->{wid}{categories_listbox}->set_binding( sub { $self->show_installed("installed") }, "i");
	$self->{wid}{categories_listbox}->set_binding( sub { $self->show_installed("orphaned") }, "o");
	$self->{wid}{ports_listbox}->set_binding( sub { $self->install_selected("install") }, "i");
	$self->{wid}{ports_listbox}->set_binding( sub { $self->install_selected("sim-install") }, "s");
	$self->{wid}{pkginst_listbox}->set_binding( sub { $self->install_selected("remove") }, "u");
	$self->{wid}{pkginst_listbox}->set_binding( sub { $self->install_selected("sim-remove") }, "s");
	$self->{wid}{cui}->set_binding( sub { exit}, "q");
	$self->focus_categories();
	$self->{wid}{cui}->mainloop();
}
1;
