package LasTres::EventLoop;

use v5.36.0;

use strict;
use warnings;

use feature 'signatures';

use Moo;
use JSON qw/to_json from_json/;

use LasTres::Battle;

has _app => ( is => 'lazy' );

has _schema => ( is => 'lazy' );

has _redis => ( is => 'lazy' );

sub _build__app {
    require LasTres;
    return LasTres->new;
}

sub _build__schema {
    require LasTres::Schema;
    return LasTres::Schema->Schema;
}

sub _build__redis {
    require LasTres::Redis;
    return LasTres::Redis->new;
}

sub _exit_the_program {
    exit 0;
}

$SIG{CHLD} = 'IGNORE';
$SIG{INT}  = \&_exit_the_program;
$SIG{QUIT} = \&_exit_the_program;
$SIG{TERM} = \&_exit_the_program;

sub loop ($self) {
    my $child_pid = fork;
    if ( !$child_pid ) {
        $self->_real_loop;
        exit(0);
    }
}

sub _work_frame ($self) {
    $self->_increment_frame_for_movers();
    $self->_increment_frame_for_combats;
}

sub _increment_frame_for_combats ($self) {
    my $schema           = $self->_schema;
    my $result_set_teams = $schema->resultset('Team');
    my @teams            = $result_set_teams->search(
        {
            current_battle => { '!=' => undef },
        },
    );
    for my $battle_uuid ( map { $_->current_battle } @teams ) {
        $self->_increment_frame_for_combat($battle_uuid); 
    }
}

sub _expire_battle($self, $battle_uuid) {
    my $schema           = $self->_schema;
    my $result_set_teams = $schema->resultset('Team');
    my @teams_in_expired_battle = $result_set_teams->search(
        {
            current_battle => $battle_uuid
        }
    );
    for my $team (@teams_in_expired_battle) {
        $team->current_battle(undef);
        $team->update;
    }
}

sub _increment_frame_for_combat($self, $battle_uuid) {
    my $battle = LasTres::Battle->get_redis($battle_uuid);
    if (!defined $battle) {
        $self->_expire_battle($battle_uuid);
        return;
    }
    if (!$battle->sanity_check) {
        $battle->end;
    }
    my @combat_members = (@{$battle->team1->combat_members}, @{$battle->team2->combat_members});
    @combat_members = sort { $b->speed <=> $a->speed } @combat_members;
    for my $entity (@combat_members) {
        $battle->try_turn($entity);
        if (my $winner = $battle->winner) {
            last;
        }
        $battle->save_redis;
    }
    if (my $winner = $battle->winner) {
        my $loser = $battle->loser;
        $winner->on_win_combat;
        $loser->on_lose_combat;
        $battle->end;
    }
    $battle->save_redis;
}

sub _increment_frame_for_movers ($self) {
    my $schema           = $self->_schema;
    my $result_set_teams = $schema->resultset('Team');
    my @teams            = $result_set_teams->search(
        { -bool => 'is_moving', current_battle => undef } );
    for my $team (@teams) {
        $self->_increment_frame_for_mover($team);
    }
}

sub _increment_frame_for_mover ( $self, $team ) {
    $team = $team->get_from_storage;
    my $frame = $team->action_frame;
    $team->action_frame( ++$frame );
    $team->update;
    my $location = $team->location;
    my $target_location =
      LasTres::Location::get( from_json( $team->moving_to )->@* );
    if ( $frame >= $location->parent->frames_to_move ) {
        $team->action_frame(0);
        $team->is_moving(0);
        $team->moving_to('null');
        $team->update;
        $target_location->place_team($team);
        return;
    }
    $team->send_frame_to_members;
    $team->location->parent->on_move_explore_frame($team);
}

sub _real_loop ($self) {
    eval {
        my $redis = $self->_redis;
        while (1) {
            if ( !defined $redis->db->get( $redis->executing_frame_key ) ) {
                $redis->db->setex( $redis->executing_frame_key, 10, 1 );
                $self->_work_frame;
            }
            my $ttl = $redis->db->ttl( $redis->executing_frame_key );
        }
    };
    if ($@) {
        warn $@;
    }
}
1;
