# Arch Perl library, Copyright (C) 2004 Mikhael Goikhman, Enno Cramer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

use 5.006;
use strict;
use warnings;

package ArchWay::MainWindow::Changeset;

use base 'ArchWay::MainWindow::Base';

use Glib qw(TRUE FALSE);
use Gtk2;
use Gtk2::Gdk::Keysyms;

use Arch::Changes qw(:type);
use Arch::Changeset;
use Arch::Util qw(copy_dir run_tla);
use Arch::TempFiles qw(temp_dir_name);
use Arch::Session;

use ArchWay::Widget::ChangeList;
use ArchWay::Widget::Diff;

sub init ($) {
	my $self = shift;

	$self->SUPER::init;

	# items (name, stock id, label, accelerator, tooltip, callback)
	my @items = (
		[
			"OpenTree", "gtk-open", "Open _Tree...",
			undef, "View uncommitted changes in a project tree",
			sub { $self->open_tree }
		],
		[
			"OpenCSet", "gtk-open", "Open _CSet...",
			undef, "View changeset from local directory",
			sub { $self->open_cset }
		],
		[
			"OpenRevision", "gtk-open", "Open _Revision...",
			undef, "View changeset from archive",
			sub { $self->open_revision }
		],
		[
			"SaveCSet", "gtk-save-as", "_Save CSet...",
			undef, "Save CSet to local directory",
			sub { $self->save_cset }
		],
	);

	# menu/toolbar layout
	my $layout = <<_EOF_;
<ui>
	<menubar name="MenuBar">
		<menu action="FileMenu">
			<placeholder name="FileMenuItems">
				<menuitem action="OpenTree" />
				<menuitem action="OpenCSet" />
				<menuitem action="OpenRevision" />
				<menuitem action="SaveCSet" />
			</placeholder>
		</menu>
	</menubar>
</ui>
_EOF_

	# add menu/toolbar to ui
	my $actions = Gtk2::ActionGroup->new("CSetActions");
	$actions->add_actions(\@items, undef);

	$self->ui->insert_action_group($actions, 0);
	$self->ui->add_ui_from_string($layout);

	# add convenience shortcuts
	$self->signal_connect(
		'key_release_event' => sub { $self->handle_key_release($_[1]) }
	);

	# init ui
	$self->set_default_size(600, 600);
	$self->main_widget->set_position(155);

	$self->set_widget_sensitivity(
		FALSE,
		'/MenuBar/FileMenu/FileMenuItems/SaveCSet'
	);
}

sub main_widget ($) {
	my $self = shift;

	if (! exists $self->{main_widget}) {
		my $scwin_cl = Gtk2::ScrolledWindow->new();
		$scwin_cl->set_policy('automatic', 'automatic');
		$scwin_cl->add($self->change_list());

		my $scwin_dv = Gtk2::ScrolledWindow->new();
		$scwin_dv->set_policy('automatic', 'automatic');
		$scwin_dv->add($self->diff_view());

		my $vpaned = Gtk2::VPaned->new();
		$vpaned->pack1($scwin_cl, TRUE, TRUE);
		$vpaned->pack2($scwin_dv, TRUE, TRUE);
		
		$self->{main_widget} = $vpaned;
	}

	return $self->{main_widget};
}

sub change_list ($) {
	my $self = shift;

	if (! exists $self->{change_list}) {
		my $cl = ArchWay::Widget::ChangeList->new();

		$cl->get_selection()->signal_connect(
			changed => sub { $self->update_diff_view() }
		);

		# disable search
		$cl->set_enable_search(FALSE);

		$self->{change_list} = $cl;
	}

	return $self->{change_list};
}

sub diff_view ($) {
	my $self = shift;

	if (! exists $self->{diff_view}) {
		my $dv = ArchWay::Widget::Diff->new();

		$self->{diff_view} = $dv;
	}

	return $self->{diff_view};
}

sub cset ($) {
	my $self = shift;

	return $self->{cset};
}

sub changes ($;$) {
	my $self = shift;
	my $num  = shift;
	
	return $self->{changes}->[$num]
		if (defined $num);

	return $self->{changes};
}

sub clear ($) {
	my $self = shift;

	$self->{cset} = undef;
	$self->{changes} = undef;

	$self->change_list->clear;
	$self->diff_view->clear;
}

sub show ($$) {
	my $self = shift;

	$self->clear;

	$self->{cset}    = shift;
	$self->{changes} = $self->{cset}->get_changes;

	$self->set_widget_sensitivity(
		defined $self->cset,
		'/MenuBar/FileMenu/FileMenuItems/SaveCSet'
	);

	$self->change_list->show($self->changes);
}

sub show_string ($$;$) {
	my $self = shift;
	my $arg  = shift;

	if (@_) {
		# TODO: make this cleaner (no direct run_tla call)
		my $tempdir = temp_dir_name("arch-delta");
		run_tla('delta', $arg, $_[0], $tempdir);

		if ($?) {
			$self->alert(
				"Could not compute delta between '$arg' and '$_[0]'",
				"Delta failed"
			);
			return;
		}

		$arg = $tempdir;
	}

	my $session = Arch::Session->instance;
	my $cset = eval { $session->get_specified_changeset($arg) };

	if ($@) {
		my $msg = $@;
		$msg =~ s!(\s) +!$1!g;
		$msg =~ s!"(.*?)"!<b>$1</b>!g;
		$msg = "Changeset error\n$msg" unless $msg =~ /\n./;
		my ($title, $error) = $msg =~ m!(.*?\n)(.*)!s;
		$self->alert($error, $title);
	} else {
		$self->show($cset);
	}
}

sub handle_key_release($$) {
	my ($self, $event) = @_;

	# page up/down
	if (
		($event->keyval == $Gtk2::Gdk::Keysyms{space}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{F}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{f}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{Z}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{z})
	) {
		$self->diff_view->signal_emit("move-viewport", "pages", 1);

	} elsif (
		($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{underscore}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{minus}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{B}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{b}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{W}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{w})
	) {
		$self->diff_view->signal_emit("move-viewport", "pages", -1);

	# line up/down
	} elsif (
		($event->keyval == $Gtk2::Gdk::Keysyms{J}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{j}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{E}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{e})
	) {
		$self->diff_view->signal_emit("move-viewport", "steps", 1);

	} elsif (
		($event->keyval == $Gtk2::Gdk::Keysyms{K}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{k}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{Y}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{y})
	) {
		$self->diff_view->signal_emit("move-viewport", "steps", -1);

	# horizontal scroll
	} elsif (
		($event->keyval == $Gtk2::Gdk::Keysyms{L}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{l})
	) {
		$self->diff_view->signal_emit("move-viewport", "horizontal-steps", 1);

	} elsif (
		($event->keyval == $Gtk2::Gdk::Keysyms{H}) ||
		($event->keyval == $Gtk2::Gdk::Keysyms{h})
	) {
		$self->diff_view->signal_emit("move-viewport", "horizontal-steps", -1);

	# next/previous diff
	# don't handle shift-n/p. they are already bound by TreeView.
	} elsif ($event->keyval == $Gtk2::Gdk::Keysyms{n}) {
		$self->change_list->grab_focus;
		$self->change_list->signal_emit("move-cursor", "display-lines", 1);

	} elsif ($event->keyval == $Gtk2::Gdk::Keysyms{p}) {
		$self->change_list->grab_focus;
		$self->change_list->signal_emit("move-cursor", "display-lines", -1);
	}

	return 0;
}

sub update_diff_view ($) {
	my $self = shift;

	my $num = $self->change_list->get_selected_number;

	# don't show diff if nothing is selected
	if (! defined $num) {
		$self->diff_view->clear;
		return;
	}

	my $change = $self->changes->get($num);

	my $patch = '*** unsupported change type diff ****';
	if ($change->{type} eq META_MODIFY) {
		$patch = '*** meta data patch not yet implemented ***';

	} elsif ($change->{type} eq RENAME) {
		$patch = "Rename: $change->{arguments}->[0]\n" .
		         "     => $change->{arguments}->[1]";

	} elsif ($change->{is_dir}) {
		$patch = '*** no diff for directory changes ***';

	} else {
		my $path  = $change->{arguments}->[0];
		my $ondisk_file;

		if ($change->{type} eq MODIFY) {
			($patch, $ondisk_file) = $self->cset->get_patch($path, 1);
		} elsif ($change->{type} eq ADD) {
			($patch, $ondisk_file) = $self->cset->get_patch($path, 2);
		} elsif ($change->{type} eq REMOVE) {
			($patch, $ondisk_file) = $self->cset->get_patch($path, 3);
		}

		$patch = '*** binary content not displayed ***'
			if (-B $ondisk_file);
	}

	$self->diff_view->show($patch);
}

sub open_tree ($) {
	my $self = shift;

	my $path = $self->prompt_select_dir;
	if (defined $path) {
		if (! -d "$path/{arch}") {
			$self->alert(
				"Can't open <b>$path</b>.\nNot an Arch project tree.",
				"No project tree"
			);
			return;
		}
		
		$self->show(
			Arch::Tree->new($path)->get_changeset($self->temp->dir_name)
		);
	}
}

sub open_cset ($) {
	my $self = shift;

	my $path = $self->prompt_select_dir;
	if (defined $path) {
		if (! -f "$path/mod-dirs-index") {
			$self->alert(
				"Can't open <b>$path</b>.\nNot an Arch changeset directory.",
				"No changeset directory"
			);
			return;
		}
		
		$self->show(
			Arch::Changeset->new('none', $path)
		);
	}
}

sub open_revision ($) {
	my $self = shift;

	$self->info(
		"<b>Open Revision</b> is currently not implemented.\nPatches welcome.",
		"Missing functionality"
	);
}

sub save_cset ($) {
	my $self = shift;

	my $path = $self->prompt_create_dir;
	if (defined $path) {
		if (-e $path) {
			$self->alert(
				"Directory $path does already exist.\nNot saving changeset.",
				"Directory exists"
			);
			return;
		}

		copy_dir($self->cset->{dir}, $path);
		$self->alert(
			"An error occurred while trying to save the changeset.",
			"Saving changeset failed"
		) unless -d $path;
	}
}

1;

__END__
