package HexedUI;

use strict;
use warnings;
use include;

use Hexed::Util;
use Curses;

use Hexed::Fixed;
use Hexed::Term;
use Hexed::Map;

use File::Slurp;

# Clean up the terminal when we're finished.
sub cleanup {
  system("clear");
  endwin;
  exit;
}

END { cleanup }
$SIG{INT} = \&cleanup;

# Make an interface to one or more windows.
sub create {
  my ($class, $layout) = @_;

  # Initialize ncurses.
  initscr;
  start_color;
  noecho;
  keypad(1);
  curs_set(0);  # The prompt can turn it back on
  # For whatever reason, we have to push a key onto the buffer and then read it
  # off so that getch() doesn't screw up the display.
  ungetch(".");
  getch;

  # Yo mama uses a monochrome terminal.
  my @colors = ( qw(BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE) );
  # Magically initialize all possible combos of foregrounds and backgrounds.
  my $cnt = 0;
  no strict "refs";   # Naughty!
  foreach my $bg (@colors) {
    foreach my $fg (@colors) {
      init_pair(++$cnt, &{ "COLOR_$fg" }, &{ "COLOR_$bg" });
    }
  }
  use strict "refs";

  # Size *does* matter!
  getmaxyx(my $rows, my $cols);
  my $self = bless {}, $class;

  # HexedUI provides support for temporary popups of various sorts. We need a
  # window the size of our whole screen to do that. Thus, a window named
  # "Screen" is reserved.
  die "The window called Screen is ours! Rawr!\n" if $layout->{Screen};
  $layout->{Screen} = {
    Type   => "Fixed",
    At     => [0, 0],
    Size   => ["100%", "100%"],
    Border => 1
  };
  # We don't have to worry about hiding this window or anything since its
  # interior is blank and its border matches up with individual borders of
  # other windows. (Presumably.) TODO: guarentee this.

  # Likewise, a fullscreen pad is needed.
  die "The window called FullScreen is ours! Rawr!\n" if $layout->{FullScreen};
  $layout->{FullScreen} = {
    Type   => "Term",
    At     => [0, 0],
    Size   => ["100%", "100%"],
    Border => 1,
    Prompt => 0
  };

  # Create each window in the layout.
  while (my ($name, $dat) = each %$layout) {
    # Any time a size or position says a percent, change that to whatever
    # percent of either the actual rows or columns of our terminal is.
    $dat->{Size}[0] =~ s#(\d+)%#$1 * $rows / 100#;
    $dat->{Size}[1] =~ s#(\d+)%#$1 * $cols / 100#;
    $dat->{At}[0] =~ s#(\d+)%#$1 * $rows / 100#;
    $dat->{At}[1] =~ s#(\d+)%#$1 * $cols / 100#;

    # And evaluate that value.
    my $sizeY = int eval($dat->{Size}[0]) - 1;
    my $sizeX = int eval($dat->{Size}[1]) - 1;
    my $posY = int eval($dat->{At}[0]) - 1;
    my $posX = int eval($dat->{At}[1]) - 1;

    # Arrays have an offset of 0, but ncurses' windows start at 1.
    my $win = newwin($sizeY + 1, $sizeX + 1, $posY + 1, $posX + 1);
    $win->box(0, 0) if $dat->{Border};
    $win->refresh unless $name eq "Screen" or $name eq "FullScreen";

    # If there's a border, then there's automatically an X and Y offset of 1
    # each side. So we lose 2 rows and 2 columns. Reflect that visually.
    # Additionally, the window itself may have more padding.
    my $off = 0;
    $off = 2 if $dat->{Border};
    $off += 2 if $dat->{Pad};

    my $handler = "Hexed::$dat->{Type}";
    my %dat = (
      # These are the coordinates of what we're actually allowed to draw in.
      Y1     => $posY + ($off / 2) + 1,
      X1     => $posX + ($off / 2) + 1,
      Height => $sizeY - $off,
      Width  => $sizeX - $off
    );
    $dat{Y2} = $dat{Y1} + $dat{Height};
    $dat{X2} = $dat{X1} + $dat{Width};

    $self->{$name} = $handler->init({
      Win    => $win,
      Border => $dat->{Border},
      Off    => $off,
      %dat,
      Opts   => $dat
    });
  }
  return $self;
}

# After using the fullscreen, redraw all the windows.
sub restore {
  my $self = shift;
  $self->{Screen}->cls;
  foreach my $win (keys %$self) {
    next if $win eq "Screen" or $win eq "FullScreen";
    $self->{$win}{Win}->box(0, 0) if $self->{$win}{Border};
    $self->{$win}->draw(1);
  }
  return 1;
}

# Display a pop-up message in the center of the screen.
sub btw {
  my ($ui, $msg) = @_;
  my $self = $ui->{Screen};

  # Make sure there's room!
  my $width = int($self->{Width} / 2);
  my @lines = wrap($width, $msg);
  die "The popup message is too big!\n" if $#lines > $self->{Height};

  # Visually pad the message box with an extra space.
  # First find the longest line.
  my $long = 0;
  foreach (@lines) {
    $long = length($_) if length($_) > $long;
  }
  $long += 2;   # to account for the pad.

  # Now pad each line.
  @lines = ("", @lines, "");
  foreach (0 .. $#lines) {
    $lines[$_] = " $lines[$_]";
    $lines[$_] .= " " x ($long - length($lines[$_]));
  }

  # Make the Screen visible...
  $self->cls;

  # Draw the message in the center of the screen.
  $self->place("center", "center", join("\n", @lines), CONFIG->box_color);
  $self->draw;

  # Now leave the message up there till the user presses a key.
  my $in = $ui->input;
  $ui->restore;

  return $in;
}

# Allows complete editing of text through an external editor.
sub edit {
  my ($self, $text) = @_;

  # Put the text somewhere.
  my $tmp = CONFIG->tmp_file;
  my $editor = CONFIG->editor;
  open my $fh, ">$tmp" or die "Can't write to $tmp: $!\n";
  print $fh "$text\n";
  close $fh;

  # Very lazily just delegate our task... But we do have to kill curses for a
  # bit.
  def_prog_mode;
  endwin;
  system($editor, $tmp);
  reset_prog_mode;
  refresh;

  # And suck the text back in! Restore the screen, too.
  my @edited = read_file($tmp);
  @edited = map { s/\n//; $_ } @edited;
  unlink $tmp;

  $self->restore;

  if (wantarray) {
    return @edited;
  } else {
    return join " ", @edited;
  }
}

# Invisible one-character input.
sub input {
  return getch;
}

# Display fullscreen stuff.
sub fs {
  my ($self, @msgs) = @_;
  my $win = $self->{FullScreen};
  $win->{Win}->box(0, 0) if $win->{Border};

  # Display all the messages.
  $win->msg($_) foreach @msgs;

  # Draw it.
  $win->draw(1);

  # Pause...
  $self->input;

  # Clear it and make a new pad for next time.
  $win->{OffY} = $win->{Total} = 0;
  $win->{Msgs} = [];
  $win->{Pad}->clear;
  $self->restore;

  return 1;
}

42;
