###########
# Tilemap #
###########

package Roguelike::Tilemap;

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

use Roguelike::PriorityQueue;
use Roguelike::Container;

sub new {
  my $class = shift;
  my $map = [];
  if (ref $_[0] eq "ARRAY") {
    $map = shift;
  } else {
    my $height = shift;
    my $width = shift;
    my $tile = shift || " ";
    foreach my $y (0 .. $height) {
      foreach my $x (0 .. $width) {
        $map->[$y][$x] = {
          _    => $tile,
          Char => undef,
          ID   => -1
        };
        #$map->[$y][$x]{Inv} = Roguelike::Container->new(0, $map->[$y][$x]);
      }
    }
  }
  my $self = bless { Map => $map }, $class;
  foreach my $y (0 .. $self->height) {
    foreach my $x (0 .. $self->width) {
      $self->{Map}[$y][$x]{Visible} = -1;
      $self->{Map}[$y][$x]{Changed} = 0;
      $self->{Map}[$y][$x]{Seen} = $Game->{Testing} ? 1 : 0;
    }
  }
  return $self;
}

sub refresh {
  my $self = shift;
  foreach my $y (0 .. $self->height) {
    foreach my $x (0 .. $self->width) {
      render($self->{Map}[$y][$x]);
    }
  }
}

sub height {
  my $self = shift;
  return $#{ $self->{Map} };
}

sub width {
  my $self = shift;
  return $#{ $self->{Map}[0] }; # We assume every row is equal in width.
}

sub select {
  my ($self, $y1, $x1, $y2, $x2) = @_;
  my @rows;
  foreach my $y ($y1 .. $y2) {
    my $cols = [];
    foreach my $x ($x1 .. $x2) {
      push @{ $cols }, $self->{Map}[$y][$x];
    }
    push @rows, $cols;
  }
  return @rows;
}

sub fill {
  my $self = shift;
  my $from = shift;
  my ($y1, $x1) = @$from;
  my $to = shift;
  my ($y2, $x2) = @$to;
  my $tile = shift;
  my $nc = shift;
  my $color = shift;
  # Reversify the coordinates if they need reversifying.
  ($y1, $y2) = ($y2, $y1) if $y2 < $y1;
  ($x1, $x2) = ($x2, $x1) if $x2 < $x1;
  foreach my $y ($y1 .. $y2) {
    foreach my $x ($x1 .. $x2) {
      return 1 if $nc and $self->{Map}[$y][$x]{_} ne " ";
      $self->{Map}[$y][$x]{_} = $tile;
      $self->{Map}[$y][$x]{Color} = $color if $color;
    }
  }
  return 1;
}

sub border {
  # Here be dragons...
  my $self = shift;
  my $map = $self->{Map};
  my $cnty = 0;
  foreach my $y (0 .. $self->height) {
    my $cntx = 0;
    foreach my $x (0 .. $self->width) {
      $map->[$y][$x]{_} = "#"
      if
        $y != 0
      and
        $map->[$y - 1][$x]{_} eq "."
      and
        $map->[$y][$x]{_} eq " ";
      $map->[$y][$x]{_} = "#"
      if
        $y != 0
      and
        $x != 0
      and
        $map->[$y - 1][$x - 1]{_} eq "."
      and
        $map->[$y][$x]{_} eq " ";
      $map->[$y][$x]{_} = "#"
      if
        $y != 0
      and
        defined $map->[$y - 1][$x + 1]
      and
        $map->[$y - 1][$x + 1]{_} eq "."
      and
        $map->[$y][$x]{_} eq " ";
      next unless $map->[$y][$x]{_} eq ".";
      $map->[$y][$x]{_} = "#" if $cnty == 0 or $cntx == 0;
      $map->[$y][$x]{_} = "#" if $y != 0 and $map->[$y - 1][$x]{_} eq " ";
      $map->[$y][$x]{_} = "#" if $x != 0 and $map->[$y][$x - 1]{_} eq " ";
      $map->[$y][$x]{_} = "#"
      if
        defined $map->[$y][$x + 1]
      and
        $map->[$y][$x + 1]{_} eq " ";
      $map->[$y][$x]{_} = "#"
      if
        $y != 0
      and
        $x != 0
      and
        $map->[$y - 1][$x - 1]{_} eq " ";
      $map->[$y][$x]{_} = "#"
      if
        $y != 0
      and
        defined $map->[$y - 1][$x + 1]
      and
        $map->[$y - 1][$x + 1]{_} eq " ";
      $map->[$y][$x]{_} = "#" if $cnty == $self->height;
      $cntx++;
    }
    $map->[$y][-1]{_} = "#" if $map->[$y][-1]{_} eq ".";
    $cnty++;
  }
  return 1;
}

sub row {
  my $self = shift;
  my $y = shift;
  return @{$self->{Map}[$y]};
}

sub column {
  my $self = shift;
  my $x = shift;
  my @columns;
  push @columns, $self->{Map}[$_][$x] foreach (0 .. $self->height);
  return @columns;
}

sub barrier {
  my ($self, $y, $x) = @_;
  debug $y, $x unless $self->{Map}[$y][$x]{_};
  return $Game->{UI}{Tiles}{ $self->{Map}[$y][$x]{_} }{Barrier} ? 1 : 0;
}

sub trace {
  my $self = shift;
  my $y = shift;
  my $x = shift;
  my $dir = shift;
  my $more = shift;
  my $cursor = shift;
  my ($anim, $fg, $bg);
  if ($cursor) {
    if (ref $cursor) {
      $anim = shift @$cursor;
      if (@$cursor == 2) {
        ($fg, $bg) = @$cursor;
      } else {
        $fg = shift @$cursor;
      }
    } else {
      $anim = $cursor;
    }
    $fg ||= "white";
    $bg ||= "black";
  }
  # We could possibly end up going out of player's LOS, but oh well.
  my @list;
  my ($Y, $X);
  while (1) {
    ($y, $x) = keycoords($dir, $y, $x);
    # What do we have here?
    my $tile = $self->{Map}[$y][$x];
    if ($tile->{Char}) {
      push @list, $tile->{Char};
      last unless $more;
    } elsif ($self->barrier($y, $x)) {
      last;
    }
    #$Game->{UI}->prompt;
    #if ($cursor) {
    #  # This'd better be the current level.
    #  # Erase old cursor.
    #  $Game->{UI}{Interface}->cursor(
    #    2 + $Game->{Top} + $Y,
    #    3 + $Game->{Left} + $X,
    #    $self->{Map}[$Y][$X]{_COLOR_},
    #    $self->{Map}[$Y][$X]{_SYMBOL_},
    #  );
    #  # Drawn new one.
    #  $Game->{UI}{Interface}->cursor(
    #    2 + $Game->{Top} + $y,
    #    3 + $Game->{Left} + $x,
    #    $Game->{UI}{Interface}->trans_color($fg, $bg),
    #    $anim
    #  );
    #  ($Y, $X) = ($y, $x);
    #}
  }
  #if ($cursor) {
  #  # Erase old cursor.
  #  $Game->{UI}{Interface}->cursor(
  #    2 + $Game->{Top} + $Y,
  #    3 + $Game->{Left} + $X,
  #    $self->{Map}[$Y][$X]{_COLOR_},
  #    $self->{Map}[$Y][$X]{_SYMBOL_},
  #  );
  #}
  return @list;
}

sub pathfind {
  my $self = shift;
  my $y1 = shift;
  my $x1 = shift;
  my $y2 = shift;
  my $x2 = shift;
  my $overlap = shift;
  my $nodiag = shift;
  my $full = shift;
  my $open = new Roguelike::PriorityQueue;
  $open->add({
    Priority => abs($y1 - $y2) + abs($x1 - $x2),
    Y => $y1,
    X => $x1
  });
  # Set up a temp copy of the map to hold data.
  my $map = [];
  foreach my $y (0 .. $self->height) {
    foreach my $x (0 .. $self->width) {
      $map->[$y][$x] = {};
    }
  }
  $map->[$y1][$x1]{Used} = 1;
  $map->[$y1][$x1]{Backref} = "Start";
  my (@path, $node);
  ITER: while ($node = $open->extract) {
    if ($node->{Y} == $y2 and $node->{X} == $x2) {
      my $y = $node->{Y};
      my $x = $node->{X};
      push @path, [$y, $x] if $full;
      while (ref $map->[$y][$x]{Backref} eq "ARRAY") {
        my $backref = $map->[$y][$x]{Backref};
        $y = $backref->[0];
        $x = $backref->[1];
        unshift @path, $backref;
      }
      shift @path unless $full;
      last;
    }
    $map->[$node->{Y}][$node->{X}]{Used} = 1;
    foreach my $dir (qw(h j k l y u b n)) {
      next if $nodiag and ($dir eq "y" or $dir eq "u" or $dir eq "b" or $dir eq "n");
      my ($y, $x) = keycoords($dir, $node->{Y}, $node->{X});
      #if ($clear) { # TODO: make this work
      #  # We don't want this path all up in our goodies!
      #  #foreach (qw(h j k l y u b n)) {
      #    #my ($Y, $X) = keycoords($_, $y, $x);
      #    #next ITER if $self->{Map}[$Y][$X]{_} ne " ";
      #    #say "$Y, $X" if $self->{Map}[$Y][$X]{_} ne " ";
      #    #}
      #}
      unless (
        ($overlap and $self->{Map}[$y][$x]{Path})
          or
        ($self->{Map}[$y][$x]{_} eq "#")  # <--- WAS A MAJOR BUG
        #($self->barrier($y, $x) and $self->{Map}[$y][$x]{_} ne "+")
      ) {
        next if $map->[$y][$x]{Used};
        $map->[$y][$x]{Used} = 1;
        $map->[$y][$x]{Backref} = [$node->{Y}, $node->{X}];
        my $h = abs($y - $y2) + abs($x - $x2);
        $open->add({
          Priority => $h, 
          Y => $y,
          X => $x
        });
      }
    }
  }
  return @path ? @path : -1;
}

sub quickdump {
  my $self = shift;
  foreach my $y (0 .. $self->height) {
    print STDERR join("", map { $_->{_} } @{ $self->{Map}[$y] }), "\n";
  }
  return 1;
}

sub inv {
  # What a horrible way to Flyweight, eh?
  my $self = shift;
  my $y = shift;
  my $x = shift;
  my $method = shift;
  if ($self->{Map}[$y][$x]{Inv}) {  # Gasp.
    return $self->{Map}[$y][$x]{Inv}->$method(@_);
  } elsif ($method eq "get") {
    return undef;
  } elsif ($method eq "all") {
    return @{ [] };
  } else {
    # Now we create a REAL container. Wow.
    $self->{Map}[$y][$x]{Inv} = Roguelike::Container->new(0, $self->{Map}[$y][$x]);
    return $self->{Map}[$y][$x]{Inv}->$method(@_);
  }
}

42;
