The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Games::Lacuna::Task::Action::Excavate;

use 5.010;
our $VERSION = $Games::Lacuna::Task::VERSION;

use Moose;
extends qw(Games::Lacuna::Task::Action);
with qw(Games::Lacuna::Task::Role::Stars
    Games::Lacuna::Task::Role::Ships
    Games::Lacuna::Task::Role::PlanetRun);

use List::Util qw(sum);
use Games::Lacuna::Client::Types qw(ore_types);

has 'min_ore' => (
    is              => 'rw',
    isa             => 'Int',
    documentation   => 'Only select bodies with mininimal ore quantities [Default 5000]',
    default         => 5000,
    required        => 1,
);

has 'ores' => (
    is              => 'rw',
    isa             => 'HashRef',
    traits          => ['Hash','NoGetopt'],
    default         => sub {
        return {
            map { $_ => 0 } ore_types()
        }
    },
    required        => 1,
    handles         => {
        get_ore         => 'get',
    }
);

has 'excavated_bodies' => (
    is          => 'rw',
    isa         => 'ArrayRef[Int]',
    traits      => [qw(NoGetopt Array)],
    traits          => ['Array','NoGetopt'],
    handles         => {
        add_excavated_body => 'push',
    }
);

sub description {
    return q[Building and dispatch excavators to best suited bodies];
}

sub process_planet {}

sub run {
    my ($self) = @_;
    
    my %planets;
    
    foreach my $planet_stats ($self->get_planets) {
        $self->log('info',"Processing planet %s",$planet_stats->{name});
        my $available = $self->current_excavators($planet_stats);
        
        if ($available) {
            $planets{$planet_stats->{id}} = $available;
        }
    }
    
    my $ore_type_count = scalar ore_types();
    my $total_ores = sum(values %{$self->ores});
    while (my ($key,$value) = each %{$self->ores}) {
        $self->ores->{$key} = (1/$ore_type_count) / ($value / $total_ores);
    }
    
    while (my ($key,$value) = each %planets) {
        my $planet_stats = $self->my_body_status($key);
        $self->dispatch_excavators($planet_stats,$value);
    }
    
}

sub current_excavators {
    my ($self,$planet_stats) = @_;
    
    # Get archaeology ministry
    my $archaeology_ministry = $self->find_building($planet_stats->{id},'Archaeology');
    
    return
        unless defined $archaeology_ministry;
    return
        unless $archaeology_ministry->{level} >= 11;
    
    my $archaeology_ministry_object = $self->build_object($archaeology_ministry);
    
    my $response = $self->request(
        object  => $archaeology_ministry_object,
        method  => 'view_excavators',
    );
    
    my $possible_excavators = $response->{max_excavators} - scalar @{$response->{excavators}} - 1 - $response->{travelling};
    
    # Get all excavated bodies
    foreach my $excavator (@{$response->{excavators}}) {
        while (my ($key,$value) = each %{$excavator->{body}{ore}}) {
            $self->ores->{$key} += $value * ($excavator->{glyph} / 100);
        }
        next
            if $excavator->{id} == 0;
        
        $self->add_excavated_body($excavator->{body}{id});
    }
    
    return $possible_excavators;
}

sub dispatch_excavators {
    my ($self,$planet_stats,$possible_excavators) = @_;
    
    # Get space port
    my $spaceport = $self->find_building($planet_stats->{id},'Space Port');
    
    return 
        unless defined $spaceport;
    
    my $spaceport_object = $self->build_object($spaceport);
    
    # Get available excavators
    my @avaliable_excavators = $self->get_ships(
        planet          => $planet_stats,
        quantity        => $possible_excavators,
        travelling      => 1,
        type            => 'excavator',
        build           => 1,
    );
    
    # Check if we have available excavators
    return
        unless (scalar @avaliable_excavators);
    
    $self->log('debug','%i excavators available at %s',(scalar @avaliable_excavators),$planet_stats->{name});
    
    my @available_bodies;
    
    $self->search_stars_callback(
        sub {
            my ($star_data) = @_;
            
            my @possible_bodies;
            # Check all bodies
            foreach my $body (@{$star_data->{bodies}}) {
                # Check if solar system is inhabited by hostile empires
                return 1
                    if defined $body->{empire}
                    && $body->{empire}{alignment} =~ m/hostile/;
                
                # Check if body is inhabited
                next
                    if defined $body->{empire};
                
                # Check if already excavated
                next
                    if defined $body->{is_excavated}
                    && $body->{is_excavated};
                
                next
                    if $body->{id} ~~ $self->excavated_bodies;
                
                # Check body type
                next 
                    unless ($body->{type} eq 'asteroid' || $body->{type} eq 'habitable planet');
                
                my $total_ore = sum values %{$body->{ore}};
                
                # Check min ore
                next
                    if $total_ore < $self->min_ore;
                
                push(@possible_bodies,$body);
            }
            
            # All possible bodies
            foreach my $body (@possible_bodies) {
                my $weighted_ores = 0;
                foreach my $ore (keys %{$body->{ore}}) {
                    $weighted_ores += $body->{ore}{$ore} * $self->get_ore($ore);
                }
                push(@available_bodies,[ $weighted_ores, $body ]);
            }
            
            return 0
                if scalar @available_bodies > 30;

            return 1;
        },
        x           => $planet_stats->{x},
        y           => $planet_stats->{y},
        is_known    => 1,
        distance    => 1,
    );
    
    foreach my $body_data (sort { $b->[0] <=> $a->[0] } @available_bodies) {
        
        my $body = $body_data->[1];
        my $excavator = pop(@avaliable_excavators);
        
        return
            unless defined $excavator;
        
        $self->log('notice',"Sending excavator from %s to %s",$planet_stats->{name},$body->{name});
        
        $self->add_excavated_body($body->{id});
        
        # Send excavator to body
        my $response = $self->request(
            object  => $spaceport_object,
            method  => 'send_ship',
            params  => [ $excavator,{ "body_id" => $body->{id} } ],
            catch   => [
                [
                    1010,
                    qr/already has an excavator from your empire or one is on the way/,
                    sub {
                        $self->log('debug',"Could not send excavator to %s",$body->{name});
                        push(@avaliable_excavators,$excavator);
                        return 0;
                    }
                ],
                [
                    1009,
                    qr/Can only be sent to asteroids and uninhabited planets/,
                    sub {
                        $self->log('debug',"Could not send excavator to %s",$body->{name});
                        push(@avaliable_excavators,$excavator);
                        return 0;
                    }
                ]
            ],
        );
        
        # Set body exacavated
        $self->set_body_excavated($body->{id});
    }
}

after 'run' => sub {
    my ($self) = @_;
    
    my $excavated = join(',',@{$self->excavated_bodies});
    
    $self->log('debug',"Updating excavator cache");
    
    $self->storage_do('UPDATE body SET is_excavated = 0 WHERE is_excavated = 1 AND id NOT IN ('.$excavated.')');
};

__PACKAGE__->meta->make_immutable;
no Moose;
1;