#!/usr/bin/perl
use strict;
use warnings;
use lib "../adv";
use include;

# CONFIGURATION
die "Usage: sim.pl [unit_size] [map] [single|multi] [rndfile|-]\n" unless @ARGV
== 4;
my ($unit_size, $area, $mode, $rnds) = @ARGV;
# Rnd Starting Positions
CONFIG->rndload("../sim/$rnds") unless $rnds eq "-";
#my $unit_size = 10;     # [small], [large]
#my $area = "halls";      # beard, halls, squiggle
#my $mode = "multi";   # single, multi

# SET UP UI
use HexedUI;
my $ui = create HexedUI {
  Main => {
    Type   => "Map",
    At     => [0, 0],
    Size   => ["100%", "100%"],
    Border => 1,
    Render => sub {
      my $tile = shift;
      my ($char, $color);
      if (ref $tile->{_}) {
        ($char, $color) = $tile->{_}->symbol;
      } elsif ($tile->{color}) {
        $char = $tile->{_};
        $color = $tile->{color};
      } else {
        $char = $tile->{_};
        $color = {
          "#" => "orange",
          "." => "grey"
        }->{$char};
        $color ||= "yellow";
      }
      return ord($char) | HexedUI::paint($color);
    }
  },
};
GAME->{UI} = $ui;

# SET UP MAP
use BDSM::Transform;
my $map = loadmap("../maps/$area");
$ui->{Main}->setup($map);
$map->{OnChange} = sub {
  my ($map, $y, $x, $new) = @_;
  $ui->{Main}->_mod($y, $x, $map->{Map}[$y][$x]);
};
use Waypoint;
preprock($map);

sub run {
  # <RANDOM> ###################################################################
  # Initialize units
  use Unit;
  my @units;
  foreach (1 .. $unit_size) {
    my $unit = GAME->{Classes}{Unit}->new(Map => $map, Num => $_);
    if ($_ != 1) {
      # Whilst assembling, just form a line.
      push @{ $unit->{Goals} }, [ $units[-1], 0, +1 ];
    }
    push @units, $unit;
  }

  # Leader assembles to a random position
  my ($ass_y, $ass_x);
  FIND: while (1) {
    ($ass_y, $ass_x) = $map->getpt(".");
    # Ensure there's enough room for everyone to line up
    next if $ass_x + $unit_size - 1 > $map->width;
    foreach my $x ($ass_x .. $ass_x + $unit_size - 1) {
      next FIND if $map->{Map}[$ass_y][$x]{_} eq "#";
    }
    last;
  }
  push @{ $units[0]->{Goals} }, [ { Y => $ass_y, X => $ass_x }, 0, 0 ];

  # Choose a random destination.
  my ($goy, $gox) = $map->getpt(".");
  # </RANDOM> ##################################################################

  # PART ONE: Assembly

  # Single: Find our buddy, then yield control to the next unit.
  # Multi: Everyone find everybody at once.

  GAME->{AssCnt} = 0;
  if ($mode eq "single") {
    foreach my $unit (@units) {
      while (1) {
        GAME->{UI}{Main}->draw;
        last if assemble($unit) == 16;
      }
    }
  } else {
    while (1) {
      GAME->{UI}{Main}->draw;
      my $cnt = 0;
      foreach my $unit (@units) {
        $cnt++ if assemble($unit) == 16;
      }
      last if $cnt == @units;
    }
  }

  $units[0]->{Goals} = [[
    { Y => $goy, X => $gox }, 0, 0
  ]];
  # PART TWO: Movement

  # Single & Multi: Follow our buddy until everybody reaches the destination.

  GAME->{MvCnt} = 0;
  while (1) {
    GAME->{UI}{Main}->draw;
    debug "to $goy, $gox. at $units[0]->{Y}, $units[0]->{X}" if $map->{Map}[$goy][$gox]{_} ne ".";
    foreach my $unit (@units) {
      ($unit->{Num} == 1) ? ctrl($goy, $gox, @units) : $unit->magnet($goy, $gox);
    }
  }
}

sub assemble {
  my $unit = shift;
  my ($Y, $X) = offset($unit->{Goals}[-1]);
  if ($unit->{Y} == $Y and $unit->{X} == $X) {
    # If we're already where we need to be, just snooze.
    if ($#{ $unit->{Goals} }) {
      pop @{ $unit->{Goals} };
    } else {
      # So our buddy doesn't get confused.
      $unit->{Laster}[0] = $unit->{Y};
      $unit->{Laster}[1] = $unit->{X};
      return 16;
    }
  }
  GAME->{AssCnt}++ unless shift;  # We might be moving the leader.
  my $code = $unit->magnet;
  if ($code and $code == 42) {
    # Er, just kidding. (Not deadlocked if adjacent to buddy.)
    # Leader is exception. Actually, any with a dead goal.
    return if $unit->{Goals}[-1][0]{Num} and abs($unit->{Y} - $unit->{Goals}[-1][0]{Y}) <= 1 and abs($unit->{X} - $unit->{Goals}[-1][0]{X}) <= 1;
    $unit->deadmag;
  }
  $code ||= 0;
  return $code;
}

sub ctrl {
  my $goy = shift;
  my $gox = shift;
  my $me = shift;
  debug "to $goy, $gox. at $me->{Y}, $me->{X}";
  my @units = @_;

  GAME->{MvCnt}++;
  my $code = assemble($me, "mv");
  die "SUCCESS. Ass " . GAME->{AssCnt} . ". Mv " . GAME->{MvCnt} . "." if $me->{Y} == $goy and $me->{X} == $gox;
  return unless $code;
  return if $code == 9 or $code == 42; # maintain position or something
  my $in = {
    1 => "k",
    2 => "j",
    3 => "h",
    4 => "l",
    5 => "y",
    6 => "u",
    7 => "b",
    8 => "n",
  }->{$code};

  # Update the vectors for the troops.
  my $vecta;
  $vecta = {
    l => [0, -1],
    h => [0, +1],
    k => [+1, 0],
    j => [-1, 0],
    y => [+1, +1],
    u => [+1, -1],
    b => [-1, +1],
    n => [-1, -1],
  }->{$in};
  $_->{Goals}[0] = [ $_->{Goals}[0][0], @$vecta ] foreach @units;
}

run;
