package Unit;

use strict;
use warnings;
use include;

use Obj;

Obj->newclass("Unit" =>
  # Meta
  Stages => ["-"],
  # Instance Data
  Map      => undef,
  Y        => undef,
  X        => undef,
  Num      => undef,
  Last     => undef,
  Laster   => undef,
  Goals    => undef,
  StuckMem => undef,
  #Stucker  => undef,
  # Class stuff
  Actions => ["go", "magnet"],
  Methods => ["symbol", "trapt"],
  init    => sub {
    my $self = shift;
    return unless $self->{Instance};
    die "Instance lacks a Map!\n" unless $self->{Map};
    $self->{Last} = [-1, -1];
    $self->{Laster} = [-1, -1];
    $self->{Goals} = [];
    $self->{StuckMem} = {};
    #$self->{Stucker} = {};
    my ($y, $x) = ($self->{Y}, $self->{X});
    # Choose a random starting point.
    ($y, $x) = $self->{Map}->getpt(".") unless defined $y and defined $x;
    $self->go("-warp", $y, $x);
  }
);

sub symbol {
  my $self = shift;
  my $symbol = $self->{Num};
  $symbol = "@" if $symbol > 9;
  my $color = "cyan";
  $color = "yellow" if $self->trapt;
  return ($symbol, $color);
}

sub trapt {
  # Misleadingly named...
  my $self = shift;
  return unless @{ $self->{Goals} };   # not really but visually...
  return if $self->{Goals}[-1][0]{Y} == $self->{Goals}[0][0]{Y} and $self->{Goals}[-1][0]{X} == $self->{Goals}[0][0]{X};
  return 1;
}

sub go {
  my ($self, $act) = @_;
  my ($y, $x) = args $act;

  # CHECKS
  die "thats not a map" unless ref $self->{Map} eq "BDSM::Map";
  die "go where?" unless defined $y and defined $x;
  die "bounds!" if $y < 0 or $y > $self->{Map}->height;
  die "bounds!" if $x < 0 or $x > $self->{Map}->width;
  die "cant go there!" if $self->{Map}{Map}[$y][$x]{_} ne ".";

  # DO IT
  # Leave old tile unless we just appeared
  $self->{Map}->mod($self->{Y}, $self->{X}, ".") unless $act->{warp};

  # Enter the new tile
  $self->{Y} = $y;
  $self->{X} = $x;
  $self->{Map}->mod($y, $x, $self);
}

sub magnet {
  my $self = shift;
  my $noY = shift;
  my $noX = shift;
  my $magnet = $self->{Goals}[-1];
  my ($toY, $toX) = offset($magnet);

  my @best;
  # Determine the best direction in which to move.
  foreach my $dir (1 .. 9) {
    # don't wait just cause thats our only option!
    last if $#best == -1 and $dir == 9;
    my $y = $self->{Y};
    my $x = $self->{X};
    $y-- if $dir == 1 or $dir == 5 or $dir == 6;
    $y++ if $dir == 2 or $dir == 7 or $dir == 8;
    $x-- if $dir == 3 or $dir == 5 or $dir == 7;
    $x++ if $dir == 4 or $dir == 6 or $dir == 8;
    next if $self->{Num} != 1 and defined $noY and $noY == $y and $noX == $x;

    # Adjacent to a non-buddy? Move, then!
    if ($dir == 9) {
      # Anyone next to us?
      my $move = 0;
      foreach my $DIR (1 .. 8) {
        my $Y = $self->{Y};
        my $X = $self->{X};
        $Y-- if $DIR == 1 or $DIR == 5 or $DIR == 6;
        $Y++ if $DIR == 2 or $DIR == 7 or $DIR == 8;
        $X-- if $DIR == 3 or $DIR == 5 or $DIR == 7;
        $X++ if $DIR == 4 or $DIR == 6 or $DIR == 8;
        my $block = $self->{Map}{Map}[$Y][$X]{_};
        next unless ref $block;
        # Only move if of a lower rank. That way both dont move and cause that
        # thing where you both go left and right and never break the loop!
        # And lower numbers are higher ranks. Heh!
        $move++, last if $self->{Num} > $block->{Num};
      }
      next if $move;
    }

    # Backtracking is ok. Moving into stuff is not (unless we're chillin')
    next if $dir != 9 and $self->{Map}{Map}[$y][$x]{_} ne ".";

    # If we can directly follow our buddy and go on the tile they were
    # literally just on, then by golly, do it!
    # But not if we're in movement mode. Cause then we wanna probably form a
    # shappe!
    unless ($noY) {
      if ($magnet->[0]{Laster} and @{ $magnet->[0]{Laster} } and $y == $magnet->[0]{Laster}[0] and $x == $magnet->[0]{Laster}[1]) {
        @best = (0, $y, $x, $dir);
        last;
      }
    }

    # Evaluate sum of the distances between the magnet.
    my $dist = euclid($y, $x, offset($magnet));

    # Is this the way to go?
    @best = ($dist, $y, $x, $dir) if $#best == -1 or $dist <= $best[0];
  }

  # If we're literally surrounded, just chill and pray people get moving.
  debug("$self->{Num} trapped!!!!!!!!"), return if $#best == -1;
  # So do we go now?
  return 42 if $best[1] == $self->{Y} and $best[2] == $self->{X};  # Chillin'.

  $self->go($best[1], $best[2]);
  $self->{Laster} = $self->{Last};
  $self->{Last} = [$best[1], $best[2]];
  return $best[3];
}

sub euclid {
  my ($y1, $x1, $y2, $x2) = @_;
  return sqrt((($y1 - $y2) ** 2) + (($x1 - $x2) ** 2));
}

sub offset {
  my $magnet = shift;
  return ($magnet->[0]{Y} + $magnet->[1], $magnet->[0]{X} + $magnet->[2]);
}

42;
