The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package App::DuckPAN::DDG;
our $AUTHORITY = 'cpan:DDG';
# ABSTRACT: DDG related functionality of duckpan
$App::DuckPAN::DDG::VERSION = '1019';
use Moo;
with 'App::DuckPAN::HasApp';

use Module::Pluggable::Object;
use Class::Load ':all';
use Data::Printer return_value => 'dump';
use List::Util qw (first);

# This function tells the user which modules / Instant Answers failed to load
sub show_failed_modules {
	my ($self, $failed_to_load) = @_;

	if (%$failed_to_load) {
		$self->app->emit_notice("These Instant Answers were not loaded:");
		$self->app->emit_notice(p($failed_to_load, colored => $self->app->colors));
		$self->app->emit_notice(
			"To learn more about installing Perl dependencies, please read http://docs.duckduckhack.com/resources/other-dev-environments.html#dealing-with-installation-issues.",
			"Note: You can ignore these errors if you're not working on these Instant Answers."
		) if first { /dependencies/ } values %$failed_to_load;
	}
}

# This function tells the user which modules / Instant Answers have no associated metadata
sub show_no_metadata_modules {
	my ($self, $no_metadata) = @_;

	if (@$no_metadata) {
		$self->app->emit_notice("No metadata was found for these Instant Answers:");
		$self->app->emit_notice(p($no_metadata, colored => $self->app->colors));
		$self->app->emit_notice(
			"Using temporary Metadata instead.",
			"If you have created an IA Page, please ensure it is in 'development' status or later.",
			"To update the IA Page status, you will need to open a Pull Request.",
			"More info: https://docs.duckduckhack.com/submitting/submitting-overview.html\n"
		);
	}
}

sub get_blocks_from_current_dir {
	my ($self, @args) = @_;

	$self->emit_and_exit(1, 'You need to have the DDG distribution installed', 'To get the installation command, please run: duckpan check')
		unless ($self->app->get_local_ddg_version);

	my $type   = $self->app->get_ia_type;
	my $finder = Module::Pluggable::Object->new(
		search_path => [$type->{dir}],
	);
	if (scalar @args == 0) {
		$self->app->emit_and_exit(1, "No Fathead ID passed as argument.", "Please specify a Fathead ID e.g. 'duckpan server mdn_css'") if $type->{name} eq "Fathead";
		my @plugins = $finder->plugins;
		push @args, sort { $a cmp $b } @plugins;
		@args = map {
			$_ =~ s!/!::!g;
			my @parts = split('::', $_);
			shift @parts;
			join('::', @parts);
		} @args;
	}
	else {
		my @fatheads = grep {
			$type->{name} eq "Fathead"
		} @args;
		$self->app->fathead->selected(@fatheads);

		@args = grep {
			$type->{name} eq "Spice" || $type->{name} eq "Goodie"
		} @args;
	}
	require lib;
	lib->import('lib');
	$self->app->emit_info("Loading Instant Answers...");

	# This list contains all of the classes that loaded successfully.
	my @successfully_loaded;

	# This hash contains all of the modules that failed.
	# The key contains the module name and the value contains the dependency that wasn't met.
	my %failed_to_load;

	my @no_metadata;

	my (%blocks_plugins, @UC_TRIGGERS);
	# This loop goes through each Goodie / Spice, and it tries to load it.
	foreach my $arg (@args) {

		# Let's try to load each Goodie / Spice module
		# and see if they load successfully.
		my $class;

		# Attempt to load Metadata based on passed ID
		if (my $ia = $self->app->get_ia_by_name($arg)) {
			$class = $ia->{perl_module};
		}

		# Check if input resembles an ID
		# i.e. lowercased alphanumeric string, possibly containing underscores
		# Assumes no lowercased Perl package names exist (e.g. DDG::Goodie::lowercase)
		elsif ($arg =~ /^[a-z0-9\_]+$/) {
			my @msg = (
				"Could not retrieve Instant Answers Metadata for ID: $arg.",
				"If a local Perl Module exists, please provide the module name instead of the ID.",
				"Or, if you have created an IA Page for any of these, please ensure its status is 'development' or 'testing'.",
				"To update the IA Page status, you will need to open a Pull Request.",
				"More info: https://docs.duckduckhack.com/submitting/submitting-overview.html\n"
			);
			$self->app->emit_notice(@msg);
			$failed_to_load{$arg} = "No metadata found. See details above.";
			next;
		}

		# Check if Perl Package name provided
		# We don't have Goodie packages with
		elsif ($arg =~ /^(DDG::(?:Goodie|Spice|Fathead)::)?[A-Z]+[a-zA-Z0-9]*$/) {
			push @no_metadata, $arg;
			$class = $1 ? $arg : "DDG::$type->{name}::$arg";
		}

		# Bad input
		else {
			$failed_to_load{$arg} = "Invalid Instant Answer ID or Perl package name";
			next;
		}

		my ($load_success, $load_error_message) = try_load_class($class);

		# If they load successfully, $load_success would be a 1.
		# Otherwise, it would be a 0.
		if ($load_success) {
			# Since we only want the successful classes to trigger, we
			# collect all of the ones that triggered successfully in a temporary list.
			push @successfully_loaded, $class;

			# Display to the user when a class has been successfully loaded.
			$self->app->emit_debug(" - $class (" . $class->triggers_block_type . ")");

			unless ($blocks_plugins{$class->triggers_block_type}) {
				$blocks_plugins{$class->triggers_block_type} = [];
			}

			my $triggers_block_type = $class->triggers_block_type;
			push @{$blocks_plugins{$triggers_block_type}}, $class;

			# We could potentially do other IA-specific checks here
			# Check for useless uppercase Words triggers so we can warn
			if($triggers_block_type eq 'Words'){
				my $trigger_types = $class->get_triggers;

				UC_TRIGGER: for my $triggers (values %$trigger_types){
					for my $t (@$triggers){
						if($t =~ /\p{Uppercase}/){
							push @UC_TRIGGERS, $class;
							#warn "is $t uppercase?\n";
							# the first one found should be sufficient.
							last UC_TRIGGER;
						}
					}
				}
			}
		}
		else {
			# Get the module name that needs to be installed by the user.
			if ($load_error_message =~ /Can't locate ([^\.]+).pm in \@INC/) {
				$load_error_message = $1;
				$load_error_message =~ s/\//::/g;

				$failed_to_load{$class} = "Please install $load_error_message and any other required dependencies to use this instant answer.";
			}
			else {
				# We just set the value to whatever the error message was if it failed for some other reason.
				$failed_to_load{$class} = $load_error_message;
			}
		}
	}

	# Since @args can contain modules that we don't want to trigger (since they didn't load in the first place),
	# and @successfully_loaded does, we just use what's in @successfully_loaded.
	@args = @successfully_loaded;

	# Now let's tell the user which Instant Answers have no metadata
	$self->show_no_metadata_modules(\@no_metadata);

	# Now let's tell the user why some of the modules failed.
	$self->show_failed_modules(\%failed_to_load);

	# Always bail if we have no Instant Answers to work with.
	unless (@successfully_loaded || $self->app->fathead->selected) {
		$self->app->emit_and_exit(1, "No Instant Answers loaded.");
	}

	if(@UC_TRIGGERS){
		$self->app->emit_notice('Detected potential UPPERCASE triggers in the following Instant Answers. If yours is listed, check it out! Only lowercase will work.' . "\n"
			. p(@UC_TRIGGERS, colored => $self->app->colors));
	}

	my @blocks;
	for (keys %blocks_plugins) {
		my $block_class = 'DDG::Block::' . $_;
		load_class($block_class);
		push @blocks,
		  $block_class->new(
			plugins    => $blocks_plugins{$_},
			return_one => 0
		  );
	}
	load_class('DDG::Request');
	return \@blocks;
}

1;

__END__

=pod

=head1 NAME

App::DuckPAN::DDG - DDG related functionality of duckpan

=head1 VERSION

version 1019

=head1 AUTHOR

DuckDuckGo <open@duckduckgo.com>, Zach Thompson <zach@duckduckgo.com>, Zaahir Moolla <moollaza@duckduckgo.com>, Torsten Raudssus <torsten@raudss.us> L<https://raudss.us/>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2013 by DuckDuckGo, Inc. L<https://duckduckgo.com/>.

This is free software, licensed under:

  The Apache License, Version 2.0, January 2004

=cut