#################
# Area::Dungeon #
#################

package Roguelike::Area::Dungeon;

use strict;
use warnings;
use Roguelike::Game;
use Roguelike::Utility;

use Roguelike::Tilemap;
use Roguelike::Area;
use Roguelike::Area::Structure::Room;
use Roguelike::Area::Structure::Hall;

sub generate {
  shift;  # We aren't really a class, no are we?
  my ($height, $width, $depth) = @_;
  my $map = new Roguelike::Tilemap $height, $width, ".";
  # Since we're an area, not a structure, we get a list of data.
  $map->{Exits} = [];
  $map->border;
  $map->fill([ 1, 1 ], [ $height - 1, $width - 1 ], " ");
  # If they want a blank map, they comply.
  if ($depth == 0) {
    $map->refresh;
    return $map;
  }
  say "<Green>Generating $depth branches of rooms...";
  $Game->{UI}->msgrefresh;
  # Can't use spawn cause it uses connect. So just manually do first room and
  # hallway set.
  # Place first room
  my $exits = { N => [], S => [], E => [], W => [] };
  my $first = new Roguelike::Area::Structure::Room 10, 15;  # Defaults!
  place($first, on => $map, at => [ int($height / 2), int($width / 2) ]);
  # Get the exit data
  foreach my $dir (qw(N S W E)) {
    foreach (0 .. $#{$first->{Exits}{$dir}}) {
      push @{ $exits->{$dir} }, { ID => 0, Wall => $_ };
    }
  }
  # HALLWAYS!
  my @halls;
  foreach my $dir (qw(N S W E)) {
    foreach (@{ $exits->{$dir} }) {
      my $hall;
      $hall = new Roguelike::Area::Structure::Hall random(2, 5), random(2, 4)
        if $dir eq "N" or $dir eq "S";
      $hall = new Roguelike::Area::Structure::Hall random(2, 4), random(3, 6)
        if $dir eq "W" or $dir eq "E";
      if (&connect
      (
        $hall,
        on => $map,
        to => $_->{ID},
        wall => $_->{Wall},
        dir => $dir,
        aligned => "middle"
      )) {
        push @halls, { ID => $#{ $map->{Exits} }, Wall => 0, Dir => $dir };
      }
    }
  }
  $Game->{Halls} = [];  # Fugly. Oh well.
  # Now whip out dat recurse thang!
  $depth--;
  if ($depth) {
    foreach (@halls) {
      spawn($map, $_, $depth);
    }
  } else {
    push @{ $Game->{Halls} }, @halls;
  }
  my $deadends = [ map {
    { Dir => $_->{Dir},
      %{ $map->{Exits}[ $_->{ID} ]{ $_->{Dir} }[0] },
      Tried => 0
    }
  } @{ $Game->{Halls} } ];
  delete $Game->{Halls};
  while (@$deadends > 1) {
    say "<Green>Connecting dead-ends, " . int(scalar(@$deadends) / 2) . " pairs left...";
    $Game->{UI}->msgrefresh;
    #debug scalar @$deadends;
    # Gimme two!
    my $one = delrand($deadends);
    my $two = delrand($deadends);
    my ($y1, $x1, $y2, $x2, $Y1, $X1, $Y2, $X2);
    if ($one->{Dir} eq "N" or $one->{Dir} eq "S") {
      $Y1 = $one->{Y1};
      $X1 = random($one->{X1} + 1, $one->{X2} - 1);
    } else {
      $Y1 = random($one->{Y1} + 1, $one->{Y2} - 1);
      $X1 = $one->{X1};
    }
    if ($two->{Dir} eq "N" or $two->{Dir} eq "S") {
      $Y2 = $two->{Y1};
      $X2 = random($two->{X1} + 1, $two->{X2} - 1);
    } else {
      $Y2 = random($two->{Y1} + 1, $two->{Y2} - 1);
      $X2 = $two->{X1};
    }
    ($y1, $x1) = dircoords($one->{Dir}, $Y1, $X1);
    if ($y1 <= 0 or $y1 >= $map->height or $x1 <= 0 or $x1 >= $map->width) {
      push @$deadends, $two;
      next;
    }
    ($y2, $x2) = dircoords($two->{Dir}, $Y2, $X2);
    if ($y2 <= 0 or $y2 >= $map->height or $x2 <= 0 or $x2 >= $map->width) {
      push @$deadends, $one;
      next;
    }
    my @path = $map->pathfind($y1, $x1, $y2, $x2, 1, 1, 1);
    if ($path[0] == -1) {
      # If no path, push them both back on and retry.
      # But if they just won't work...
      last if scalar @$deadends == 0;
      $one->{Tried}++;
      $two->{Tried}++;
      push @$deadends, $one unless $one->{Tried} == 2;
      push @$deadends, $two unless $two->{Tried} == 2;
      next;
    }
    # Yay! So carve out the path.
    $map->{Map}[$Y1][$X1]{_} = ".";
    $map->{Map}[$Y2][$X2]{_} = ".";
    foreach (@path) {
      my ($y, $x) = @$_;
      $map->{Map}[$y][$x]{_} = ".";
      # Put up some walls.
      foreach my $dir (qw(h j k l y u b n)) {
        ($y, $x) = keycoords($dir, @$_);
        # WEIRD bug.
        next if $y < 0 or $y > $map->height;
        next if $x < 0 or $x > $map->width;
        if ($map->{Map}[$y][$x]{_} eq " ") {
          $map->{Map}[$y][$x]{_} = "#";
        }
      }
    }
  }
  # Now we make things nicer. If there are two hallways next to each other that
  # could be connected... go for it. Horizontally first... it's just easier.
  # This code is very similar to findwall!
  say "<Green>Forging extra horizontal connections...";
  $Game->{UI}->msgrefresh;
  foreach my $y (1 .. $map->height - 1) { # Make life easier NOW
    my $mode = 0; # 0 = nothing so far   1 = we've got a wall!
    my $x1 = 0;
    my $x2 = 0;
    my $cnt = 0;
    my $id = 0;
    foreach my $x (1 .. $map->width - 1) {
      next if $y < 0 or $y > $map->height;
      next if $x < 0 or $x > $map->width;
      # I should also do the thing that creates logical connections. Bah. And
      # fix the data structures, harhar.
      if ($map->{Map}[$y - 1][$x]{_} eq " "
               or $map->{Map}[$y + 1][$x]{_} eq " "
              )
      {  # Failure
        $x1 = $x2 = $mode = 0;
      }
      my $tile = $map->{Map}[$y][$x]{_};
      if ($tile eq "." and $mode == 0) {  # Start
        next unless $map->{Map}[$y][$x + 1]{_} eq "#";  # Lookahead
        $x1 = $x;
        $mode = 1;
        $cnt = 0;
        $id = $map->{Map}[$y][$x]{ID};
      } elsif ($tile eq "." and $cnt == 1) { # Stop
        # Wait...
        #if ($map->{Map}[$y][$x]{ID} == $id) {
        #  # GAH
        #  $x1 = $x2 = $mode = 0;
        #  next;
        #}
        $x2 = $x;
        $mode = 0;
        $cnt = 0;
        $map->fill([$y, $x1], [$y, $x2], ".");
        $x1 = $x2 = 0;
      } elsif ($mode == 1 and $tile ne "#") { # Failure.
        $x1 = $x2 = $mode = 0;
      } elsif ($mode == 1 and $tile eq "#" and $cnt == 0) {
        $cnt = 1;
      }
    }
    # No leftovers, sorry.
  }
  # Now vertically!
  say "<Green>Forging extra vertical connections...";
  $Game->{UI}->msgrefresh;
  foreach my $x (1 .. $map->width - 1) { # Make life easier NOW
    my $mode = 0; # 0 = nothing so far   1 = we've got a wall!
    my $y1 = 0;
    my $y2 = 0;
    my $cnt = 0;
    my $id = 0;
    foreach my $y (1 .. $map->height - 1) {
      next if $y < 0 or $y > $map->height;
      next if $x < 0 or $x > $map->width;
      # I should also do the thing that creates logical connections. Bah. And
      # fix the data structures, harhar.
      if ($map->{Map}[$y][$x - 1]{_} eq " "
               or $map->{Map}[$y][$x + 1]{_} eq " "
              )
      {  # Failure
        $y1 = $y2 = $mode = 0;
      }
      my $tile = $map->{Map}[$y][$x]{_};
      if ($tile eq "." and $mode == 0) {  # Start
        next unless $map->{Map}[$y + 1][$x]{_} eq "#";  # Lookahead
        $y1 = $y;
        $mode = 1;
        $cnt = 0;
        $id = $map->{Map}[$y][$x]{ID};
      } elsif ($tile eq "." and $cnt == 1) { # Stop
        # Wait...
        #if ($map->{Map}[$y][$x]{ID} == $id) {
        #  # GAH
        #  $y1 = $y2 = $mode = 0;
        #  next;
        #}
        $y2 = $y;
        $mode = 0;
        $cnt = 0;
        $map->fill([$y1, $x], [$y2, $x], ".");
        $y1 = $y2 = 0;
      } elsif ($mode == 1 and $tile ne "#") { # Failure.
        $y1 = $y2 = $mode = 0;
      } elsif ($mode == 1 and $tile eq "#" and $cnt == 0) {
        $cnt = 1;
      }
    }
    # No leftovers, sorry.
  }
  #use Devel::Size::Report ("report_size");
  #my $test = [];
  #foreach my $y (0 .. $map->height) {
  #  foreach my $x (0 .. $map->width) {
  #    $test->[$y][$x] = {
  #      _ => $map->{Map}[$y][$x]{_},
  #      S => $map->{Map}[$y][$x]{Seen},
  #      C => $map->{Map}[$y][$x]{Changed},
  #      V => $map->{Map}[$y][$x]{Visible},
  #      D => $map->{Map}[$y][$x]{Display},
  #      I => $map->{Map}[$y][$x]{Inv}{ID},
  #      A => $map->{Map}[$y][$x]{Char}  # Actor
  #    };
  #  }
  #}
  #debug report_size($test);
  #die;
  $map->refresh;
  return $map;
}

sub spawn {
  my $map = shift;
  my $dat = shift;
  my $depth = shift;
  # $dat = { ID => of hall, Wall => 0 (always, duh), Dir => direction }
  # Place room
  my $exits = { N => [], S => [], E => [], W => [] }; # To place halls!
  my $room = new Roguelike::Area::Structure::Room random(4, 7), random(3, 8);  # Defaults!
  return -1 unless &connect
  (
    $room,
    on => $map,
    to => $dat->{ID},
    wall => 0,  # It's a hallway. No weird shapes.
    dir => $dat->{Dir},
    aligned => "middle"
  );
  # Get the exit data
  foreach my $dir (qw(N S W E)) {
    next if $dir eq opposite($dat->{Dir});  # There's just no point in TRYING
    foreach (0 .. $#{$room->{Exits}{$dir}}) {
      push @{ $exits->{$dir} }, { ID => $#{ $map->{Exits} }, Wall => $_ };
    }
  }
  # Now make hallways.
  my @halls;
  foreach my $dir (qw(N S W E)) {
    foreach (@{ $exits->{$dir} }) {
      my $hall;
      $hall = new Roguelike::Area::Structure::Hall random(2, 5), random(2, 4)
        if $dir eq "N" or $dir eq "S";
      $hall = new Roguelike::Area::Structure::Hall random(2, 4), random(3, 6)
        if $dir eq "W" or $dir eq "E";
      if (&connect
      (
        $hall,
        on => $map,
        to => $_->{ID},
        wall => $_->{Wall},
        dir => $dir,
        aligned => "middle"
      )) {
        push @halls, { ID => $#{ $map->{Exits} }, Dir => $dir };
      }
    }
  }
  # Recurse?
  $depth--;
  if ($depth) {
    foreach (@halls) {
      spawn($map, $_, $depth);
    }
  } else {
    push @{ $Game->{Halls} }, @halls;
  }
  return 1;
}

42;
