package App::Zealc::Command::query;

use 5.014000;
use strict;
use warnings;

our $VERSION = '0.000_001';

use App::Zealc '-command';

use IO::Prompter;
use File::Slurp qw/write_file/;
use File::Temp qw//;
use File::Which;
use HTML::FormatText;
use Term::Pager;

use Encode qw/encode/;

# These set --format=html and --with=something
use constant BROWSERS => [ qw/www-browser lynx links2 elinks w3m x-www-browser firefox/ ];
my @BROWSERS = map { [$_, "Shorthand for --format=html --with=$_", {implies => {with => $_, format => 'html'}}] } @{BROWSERS()};

# These set -format=something
use constant FORMATTERS => [
	[lynxdump => 'lynx -dump -nolist'],
	[links2dump => 'links2 -dump'],
	[elinksdump => 'elinks -dump -eval "set document.dump.references = 0" -eval "set document.dump.numbering = 0"'],
];
my @FORMATTERS = map {
	my ($name, $format) = @$_;
	[$name, "Shorthand for --format='$format'", {implies => {format => $format}}]
} @{FORMATTERS()};

# These set --with=something
use constant PAGERS => [ qw/pager less most more pg/ ];
my @PAGERS = map { [$_, "Shorthand for --with=$_", {implies => {with => $_}}] } @{PAGERS()};

sub opt_spec {(
	['with|w=s', 'Open with this program'],
	['format|f=s', 'Convert html to this format'],

	[], @BROWSERS,
	[], @FORMATTERS,
	[], @PAGERS,
)}

sub usage_desc { "%c query %o term" };

sub validate_args {
	my ($self, $opts, $args) = @_;
	my @args = @$args;
	$self->usage_error("No query specified") unless @args;
	$self->usage_error("Too many arguments") if @args > 1;
	$opts->{with}   //= $self->app->config->{with};
	$opts->{format} //= $self->app->config->{format};

	# If output is not a tty, do not choose browsers/pagers
	$opts->{with} //= '' unless -t select;

	# Try to default to a browser
	if (!defined $opts->{with} && !defined $opts->{format}) {
		for my $browser (@{BROWSERS()}) {
			warn "Trying to use $browser as a browser\n" if $self->app->verbose;
			next unless which $browser;
			$opts->{with}   = $browser;
			$opts->{format} = 'html';
			last
		}
	}

	# If no browsers were found, try to choose a formatter
	unless (defined $opts->{format}) {
		for my $formatter (map { $_->[1] } @{FORMATTERS()}) {
			my ($exec) = $formatter =~ /^(\w+)/s;
			warn "Trying to use $exec ($formatter) as a formatter\n" if $self->app->verbose;
			next unless which $exec;
			$opts->{format} = $formatter;
			last
		}
	}

	# If no browsers were found, try to choose a pager
	unless (defined $opts->{with}) {
		for my $pager (@{PAGERS()}) {
			warn "Trying to use $pager as a pager\n" if $self->app->verbose;
			next unless which $pager;
			$opts->{with} = $pager;
			last
		}
	}

	# Default to HTML::FormatText and the internal pager
	$opts->{format} //= 'text';
	$opts->{with} //= '';
}

sub html_to_format {
	my ($html, $format) = @_;
	return $html if $format eq 'html';
	return HTML::FormatText->format_string($html) if $format eq 'text';
	my $tmp = File::Temp->new(TEMPLATE => 'zealcXXXX', SUFFIX => '.html');
	write_file $tmp, $html;
	my $fn = $tmp->filename;
	`$format $fn`
}

sub show_document {
	my ($self, $doc, %opts) = @_;
	my $html = $doc->fetch;

	say "Format: $opts{format}" if $self->app->verbose;
	say "Open with: ", $opts{with} // 'internal pager' if $self->app->verbose;;

	my $text = encode 'UTF-8', html_to_format $html, $opts{format};

	if ($opts{with}) {
		my $tmp = File::Temp->new(TEMPLATE => 'zealcXXXX', SUFFIX => '.html');
		write_file $tmp, $text;
		system $opts{with} . ' ' . $tmp->filename;
	} elsif (!-t select) {
		say $text
	} else {
		my ($rows, $cols) = split ' ', `stty size`;
		my $pager = Term::Pager->new(text => $text, rows => $rows, cols => $cols);
		$pager->more;
	}
}

sub execute {
	my ($self, $opts, $args) = @_;
	my %opts = %$opts;
	my ($query) = @$args;
	my $zeal = $self->app->zeal;
	my @results = $zeal->query($query);
	@results    = $zeal->query("$query%") unless @results;
	die "No results found for $query\n" unless @results;

	my $doc = $results[0];
	if (@results > 1 && -t select) {
		my @args = '-single';
		@args = '-number' if @results > 52;
		my %menu = map {
			my $ds = $results[$_]->docset->name;
			$results[$_]->name . " ($ds)" => $_
		} 0 .. $#results;
		$doc = prompt 'Choose a document', -menu => \%menu, '-stdio', @args;
		return unless $doc;
		$doc = $results[$doc];
	}

	$self->show_document($doc, %opts);
}

1;
__END__

=encoding utf-8

=head1 NAME

App::Zealc::Command::query - view the documentation for a term

=head1 SYNOPSIS

  zealc query perldoc
  # displays documentation for perldoc

  zealc query --w3m perl:%Spec
  # finds File::Spec and perlpodspec

=head1 DESCRIPTION

The query command displays the documentation for a given term. The
term is a SQL LIKE pattern. If no documents are found, the search is
retried after appending a % to the term. If multiple documents are
found, the user will be prompted to choose one of them.

A SQL LIKE pattern is similar to a shell glob. The "%" character
matches zero or more characters (like "*" in a shell glob or ".*" in a
regex) and "_" matches exactly one character (like "?" in a shell glob
or "." in a regex). Matching is case-insensitive.

=head1 SEE ALSO

L<zealc>, L<Zeal>

=head1 AUTHOR

Marius Gavrilescu, E<lt>marius@ieval.roE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2015 by Marius Gavrilescu

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.20.1 or,
at your option, any later version of Perl 5 you may have available.


=cut
