package Padre::Wx::Dialog::OpenResource;

use 5.008;
use strict;
use warnings;
use Cwd              ();
use Padre::DB        ();
use Padre::Wx        ();
use Padre::Wx::Icon  ();
use Padre::MimeTypes ();

our $VERSION = '0.58';
our @ISA     = qw{
	Padre::Wx::Role::MainChild
	Wx::Dialog
};

use Class::XSAccessor {
	accessors => {
		_sizer                    => '_sizer',                    # window sizer
		_search_text              => '_search_text',              # search text control
		_matches_list             => '_matches_list',             # matches list
		_status_text              => '_status_text',              # status label
		_directory                => '_directory',                # searched directory
		_matched_files            => '_matched_files',            # matched files list
		_copy_button              => '_copy_button',              # copy button
		_popup_button             => '_popup_button',             # popup button for options
		_popup_menu               => '_popup_menu',               # options popup menu
		_skip_vcs_files           => '_skip_vcs_files',           # Skip VCS files menu item
		_skip_using_manifest_skip => '_skip_using_manifest_skip', # Skip using MANIFEST.SKIP menu item
	}
};

# -- constructor
sub new {
	my ( $class, $main ) = @_;

	# create object
	my $self = $class->SUPER::new(
		$main,
		-1,
		'',
		Wx::wxDefaultPosition,
		Wx::wxDefaultSize,
		Wx::wxDEFAULT_FRAME_STYLE | Wx::wxTAB_TRAVERSAL,
	);

	$self->init_search;

	# Dialog's icon as is the same as Padre
	$self->SetIcon(Padre::Wx::Icon::PADRE);

	# create dialog
	$self->_create;

	return $self;
}


#
# Initialize search
#
sub init_search {
	my $self = shift;

	#Check if we have an open file so we can use its directory
	my $doc = $self->current->document;
	my $filename = ( defined $doc ) ? $doc->filename : undef;
	my $dir;
	if ($filename) {

		# current document's project or base directory
		$dir = Padre::Util::get_project_dir($filename)
			|| File::Basename::dirname($filename);
	} else {

		# current working directory
		$dir = Cwd::getcwd();
	}


	my $old_dir = $self->_directory;
	if ( $old_dir && $old_dir ne $dir ) {

		# Restart search if the project/current directory is different
		$self->_matched_files(undef);
	}

	$self->_directory($dir);
	$self->SetLabel( Wx::gettext('Open Resource') . ' - ' . $dir );
}

# -- event handler

#
# handler called when the ok button has been clicked.
#
sub _on_ok_button_clicked {
	my ($self) = @_;

	my $main = $self->main;
	$self->Hide;

	#Open the selected resources here if the user pressed OK
	my @selections = $self->_matches_list->GetSelections();
	foreach my $selection (@selections) {
		my $filename = $self->_matches_list->GetClientData($selection);

		# Fetch the recently used files from the database
		require Padre::DB::RecentlyUsed;
		my $recently_used = Padre::DB::RecentlyUsed->select( "where type = ? and value = ?", 'RESOURCE', $filename )
			|| [];
		my $found = scalar @$recently_used > 0;

		eval {

			# try to open the file now
			if ( my $id = $main->find_editor_of_file($filename) ) {
				my $page = $main->notebook->GetPage($id);
				$page->SetFocus;
			} else {
				$main->setup_editors($filename);
			}
		};
		if ($@) {
			Wx::MessageBox(
				Wx::gettext('Error while trying to perform Padre action'),
				Wx::gettext('Error'),
				Wx::wxOK,
				$main,
			);
		} else {

			# And insert a recently used tuple if it is not found
			# and the action is successful.
			if ( not $found ) {
				Padre::DB::RecentlyUsed->create(
					name      => $filename,
					value     => $filename,
					type      => 'RESOURCE',
					last_used => time(),
				);
			} else {
				Padre::DB->do(
					"update recently_used set last_used = ? where name = ? and type = ?",
					{}, time(), $filename, 'RESOURCE',
				);
			}
		}
	}

}


# -- private methods

#
# create the dialog itself.
#
sub _create {
	my ($self) = @_;

	# create sizer that will host all controls
	my $sizer = Wx::BoxSizer->new(Wx::wxVERTICAL);
	$self->_sizer($sizer);

	# create the controls
	$self->_create_controls;
	$self->_create_buttons;

	# wrap everything in a vbox to add some padding
	$self->SetMinSize( [ 360, 340 ] );
	$self->SetSizer($sizer);

	# center/fit the dialog
	$self->Fit;
	$self->CentreOnParent;
}

#
# create the buttons pane.
#
sub _create_buttons {
	my ($self) = @_;
	my $sizer = $self->_sizer;

	$self->{ok_button} = Wx::Button->new(
		$self, Wx::wxID_OK, Wx::gettext('&OK'),
	);
	$self->{ok_button}->SetDefault;
	$self->{cancel_button} = Wx::Button->new(
		$self, Wx::wxID_CANCEL, Wx::gettext('&Cancel'),
	);

	my $buttons = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
	$buttons->AddStretchSpacer;
	$buttons->Add( $self->{ok_button},     0, Wx::wxALL | Wx::wxEXPAND, 5 );
	$buttons->Add( $self->{cancel_button}, 0, Wx::wxALL | Wx::wxEXPAND, 5 );
	$sizer->Add( $buttons, 0, Wx::wxALL | Wx::wxEXPAND | Wx::wxALIGN_CENTER, 5 );

	Wx::Event::EVT_BUTTON( $self, Wx::wxID_OK, \&_on_ok_button_clicked );
}

#
# create controls in the dialog
#
sub _create_controls {
	my ($self) = @_;

	# search textbox
	my $search_label = Wx::StaticText->new(
		$self, -1,
		Wx::gettext('&Select an item to open (? = any character, * = any string):')
	);
	$self->_search_text(
		Wx::TextCtrl->new(
			$self,                 -1, '',
			Wx::wxDefaultPosition, Wx::wxDefaultSize,
		)
	);

	# matches result list
	my $matches_label = Wx::StaticText->new(
		$self, -1,
		Wx::gettext('&Matching Items:')
	);

	$self->_matches_list(
		Wx::ListBox->new(
			$self, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, [],
			Wx::wxLB_EXTENDED
		)
	);

	# Shows how many items are selected and information about what is selected
	$self->_status_text(
		Wx::TextCtrl->new(
			$self,                 -1,                Wx::gettext('Current Directory: ') . $self->_directory,
			Wx::wxDefaultPosition, Wx::wxDefaultSize, Wx::wxTE_READONLY
		)
	);

	my $folder_image = Wx::StaticBitmap->new(
		$self, -1,
		Padre::Wx::Icon::find("places/stock_folder")
	);

	$self->_copy_button(
		Wx::BitmapButton->new(
			$self, -1,
			Padre::Wx::Icon::find("actions/edit-copy")
		)
	);


	$self->_popup_button(
		Wx::BitmapButton->new(
			$self, -1,
			Padre::Wx::Icon::find("actions/down")
		)
	);
	$self->_popup_menu( Wx::Menu->new );
	$self->_skip_vcs_files(
		$self->_popup_menu->AppendCheckItem( -1, Wx::gettext("Skip version control system files") ) );
	$self->_skip_using_manifest_skip(
		$self->_popup_menu->AppendCheckItem( -1, Wx::gettext("Skip using MANIFEST.SKIP") ) );

	$self->_skip_vcs_files->Check(1);
	$self->_skip_using_manifest_skip->Check(1);

	my $hb;
	$self->_sizer->AddSpacer(10);
	$self->_sizer->Add( $search_label, 0, Wx::wxALL | Wx::wxEXPAND, 2 );
	$hb = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
	$hb->AddSpacer(2);
	$hb->Add( $self->_search_text,  1, Wx::wxALIGN_CENTER_VERTICAL, 2 );
	$hb->Add( $self->_popup_button, 0, Wx::wxALL | Wx::wxEXPAND,    2 );
	$hb->AddSpacer(1);
	$self->_sizer->Add( $hb,                  0, Wx::wxBOTTOM | Wx::wxEXPAND, 5 );
	$self->_sizer->Add( $matches_label,       0, Wx::wxALL | Wx::wxEXPAND,    2 );
	$self->_sizer->Add( $self->_matches_list, 1, Wx::wxALL | Wx::wxEXPAND,    2 );
	$hb = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
	$hb->AddSpacer(2);
	$hb->Add( $folder_image,       0, Wx::wxALL | Wx::wxEXPAND,    1 );
	$hb->Add( $self->_status_text, 1, Wx::wxALIGN_CENTER_VERTICAL, 1 );
	$hb->Add( $self->_copy_button, 0, Wx::wxALL | Wx::wxEXPAND,    1 );
	$hb->AddSpacer(1);
	$self->_sizer->Add( $hb, 0, Wx::wxBOTTOM | Wx::wxEXPAND, 5 );
	$self->_setup_events();

	return;
}

#
# Adds various events
#
sub _setup_events {
	my $self = shift;

	Wx::Event::EVT_CHAR(
		$self->_search_text,
		sub {
			my $this  = shift;
			my $event = shift;
			my $code  = $event->GetKeyCode;

			$self->_matches_list->SetFocus
				if ( $code == Wx::WXK_DOWN )
				or ( $code == Wx::WXK_NUMPAD_PAGEDOWN )
				or ( $code == Wx::WXK_PAGEDOWN );

			$event->Skip(1);
		}
	);

	Wx::Event::EVT_TEXT(
		$self,
		$self->_search_text,
		sub {

			if ( not $self->_matched_files ) {
				$self->_search();
			}
			$self->_update_matches_list_box;

			return;
		}
	);

	Wx::Event::EVT_LISTBOX(
		$self,
		$self->_matches_list,
		sub {
			my $self         = shift;
			my @matches      = $self->_matches_list->GetSelections();
			my $num_selected = scalar @matches;
			if ( $num_selected == 1 ) {
				$self->_status_text->ChangeValue( $self->_path( $self->_matches_list->GetClientData( $matches[0] ) ) );
				$self->_copy_button->Enable(1);
			} elsif ( $num_selected > 1 ) {
				$self->_status_text->ChangeValue( $num_selected . " items selected" );
				$self->_copy_button->Enable(0);
			} else {
				$self->_status_text->ChangeValue('');
				$self->_copy_button->Enable(0);
			}

			return;
		}
	);

	Wx::Event::EVT_LISTBOX_DCLICK(
		$self,
		$self->_matches_list,
		sub {
			$self->_on_ok_button_clicked();
		}
	);

	Wx::Event::EVT_BUTTON(
		$self,
		$self->_copy_button,
		sub {
			my @matches      = $self->_matches_list->GetSelections();
			my $num_selected = scalar @matches;
			if ( $num_selected == 1 ) {
				if ( Wx::wxTheClipboard->Open() ) {
					Wx::wxTheClipboard->SetData(
						Wx::TextDataObject->new( $self->_matches_list->GetClientData( $matches[0] ) ) );
					Wx::wxTheClipboard->Close();
				}
			}
		}
	);

	Wx::Event::EVT_MENU(
		$self,
		$self->_skip_vcs_files,
		sub { $self->_restart_search; },
	);
	Wx::Event::EVT_MENU(
		$self,
		$self->_skip_using_manifest_skip,
		sub { $self->_restart_search; },
	);

	Wx::Event::EVT_BUTTON(
		$self,
		$self->_popup_button,
		sub {
			my ( $self, $event ) = @_;
			$self->PopupMenu(
				$self->_popup_menu,
				$self->_popup_button->GetPosition->x,
				$self->_popup_button->GetPosition->y + $self->_popup_button->GetSize->GetHeight
			);
		}
	);

	$self->_show_recent_while_idle;
}

#
# Restarts search
#
sub _restart_search {
	my $self = shift;
	$self->_search();
	$self->_update_matches_list_box;
}

#
# Focus on it if it shown or restart its state and show it if it is hidden.
#
sub show {
	my $self = shift;

	$self->init_search;

	if ( $self->IsShown ) {
		$self->SetFocus;
	} else {
		my $editor = $self->current->editor;
		if ($editor) {
			my $selection        = $editor->GetSelectedText;
			my $selection_length = length $selection;
			if ( $selection_length > 0 ) {
				$self->_search_text->ChangeValue($selection);
				$self->_restart_search;
			} else {
				$self->_search_text->ChangeValue('');
			}
		} else {
			$self->_search_text->ChangeValue('');
		}

		$self->_show_recent_while_idle;

		$self->Show(1);
	}
}

#
# Shows recently opened stuff while idle
#
sub _show_recent_while_idle {
	my $self = shift;

	Wx::Event::EVT_IDLE(
		$self,
		sub {
			$self->_show_recently_opened_resources;

			# focus on the search text box
			$self->_search_text->SetFocus;

			# unregister from idle event
			Wx::Event::EVT_IDLE( $self, undef );
		}
	);
}

#
# Shows the recently opened resources
#
sub _show_recently_opened_resources {
	my $self = shift;

	# Fetch them from Padre's RecentlyUsed database table
	require Padre::DB::RecentlyUsed;
	my $recently_used = Padre::DB::RecentlyUsed->select( "where type = ?", 'RESOURCE' ) || [];
	my @recent_files = ();
	foreach my $e (@$recently_used) {
		push @recent_files, $self->_path( $e->value );
	}
	@recent_files = sort { File::Basename::fileparse($a) cmp File::Basename::fileparse($b) } @recent_files;

	# Show results in matching items list
	$self->_matched_files( \@recent_files );
	$self->_update_matches_list_box;

	# No need to store them anymore
	$self->_matched_files(undef);
}

#
# Search for files and cache result
#
sub _search {
	my $self = shift;

	$self->_status_text->ChangeValue( Wx::gettext("Reading items. Please wait...") );

	require Padre::Task::OpenResource::SearchTask;
	my $search_task = Padre::Task::OpenResource::SearchTask->new(
		dialog                   => $self,
		directory                => $self->_directory,
		skip_vcs_files           => $self->_skip_vcs_files->IsChecked,
		skip_using_manifest_skip => $self->_skip_using_manifest_skip->IsChecked,
	);
	$search_task->schedule;

	return;
}

#
# Update matches list box from matched files list
#
sub _update_matches_list_box {
	my $self = shift;

	return if not $self->_matched_files;

	my $search_expr = $self->_search_text->GetValue();

	#quote the search string to make it safer
	#and then tranform * and ? into .* and .
	$search_expr = quotemeta $search_expr;
	$search_expr =~ s/\\\*/.*?/g;
	$search_expr =~ s/\\\?/./g;

	#Populate the list box now
	$self->_matches_list->Clear();
	my $pos = 0;
	foreach my $file ( @{ $self->_matched_files } ) {
		my $filename = File::Basename::fileparse($file);
		if ( $filename =~ /^$search_expr/i ) {

			# Display package name if it is a Perl file
			my $pkg = '';
			my $mime_type = Padre::MimeTypes->guess_mimetype( undef, $file );
			if ( $mime_type eq 'application/x-perl' or $mime_type eq 'application/x-perl6' ) {
				my $contents = Padre::Util::slurp($file);
				if ( $contents && $$contents =~ /\s*package\s+(.+);/ ) {
					$pkg = "  ($1)";
				}
			}
			$self->_matches_list->Insert( $filename . $pkg, $pos, $file );
			$pos++;
		}
	}
	if ( $pos > 0 ) {
		$self->_matches_list->Select(0);
		$self->_status_text->ChangeValue( $self->_path( $self->_matches_list->GetClientData(0) ) );
		$self->_status_text->Enable(1);
		$self->_copy_button->Enable(1);
		$self->{ok_button}->Enable(1);
	} else {
		$self->_status_text->ChangeValue('');
		$self->_status_text->Enable(0);
		$self->_copy_button->Enable(0);
		$self->{ok_button}->Enable(0);
	}

	return;
}

#
# Cleans a path on various platforms
#
sub _path {
	my ( $self, $path ) = @_;
	if (Padre::Constant::WIN32) {
		$path =~ s/\//\\/g;
	}
	return $path;
}

1;

__END__

=head1 NAME

Padre::Wx::Dialog::OpenResource - Open Resource dialog

=head1 DESCRIPTION

=head2 Open Resource (Shortcut: C<Ctrl+Shift+R>)

This opens a nice dialog that allows you to find any file that exists
in the current document or working directory. You can use C<?> to replace
a single character or C<*> to replace an entire string. The matched files list
are sorted alphabetically and you can select one or more files to be opened in
Padre when you press the B<OK> button.

You can simply ignore F<CVS>, F<.svn> and F<.git> folders using a simple check-box
(enhancement over Eclipse).

=head1 AUTHOR

Ahmad M. Zawawi C<< <ahmad.zawawi at gmail.com> >>

=head1 COPYRIGHT & LICENSE

Copyright 2008-2010 The Padre development team as listed in Padre.pm.

This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

=cut

# Copyright 2008-2010 The Padre development team as listed in Padre.pm.
# LICENSE
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl 5 itself.

