package t::_Util;

use warnings;
use strict;
use File::Spec ();

use base 'Exporter';

our @EXPORT = qw( STD_NAMES TEST_TYPES );

sub STD_NAMES() { qw( STDIN STDOUT STDERR ) }
sub TEST_TYPES() { qw( closed null pipe file tty ) }

sub stirb {
  # FIXME - write to a logfile or osmething?
  exit 255;
}

sub stdio_state_errors {
  my $args = @_ > 1 ? { @_ } : $_[0];

  my $ret;

  for my $tester ( TEST_TYPES ) {
    my $child = fork();

    stirb "Unable to fork: $!"
      if ! defined $child;

    if (!$child) {

      if ($args->{set_stdio}) {
        my $to_close = {};

        for my $fd (0,1,2) {
          local $SIG{__WARN__} = sub {
            # direction mismatch is something only visible under io warnings
            # intercept here, and mark for explicit closure
            $to_close->{$fd} = 1
              if $_[0] =~ /Filehandle \w+ reopened as \w+ only for/;

            # swallow warnings altogether (I know, sloppy)
          };

          if(my $fh_and_opts = $args->{set_stdio}[$fd] ) {

            close( (\%{main::})->{(STD_NAMES)[$fd]} )
              or stirb "Unable to close FD$fd: $!";

            open (
              (\%{main::})->{(STD_NAMES)[$fd]},
              $fh_and_opts->[1] . '&' . fileno( $fh_and_opts->[0] ),
            ) or stirb "Unable to reopen $fd: $!";
          }
          else {
            $to_close->{$fd} = 1;
          }
        }

        close( (\%{main::})->{(STD_NAMES)[$_]} ) or stirb "Unable to close FD$_: $!"
          for keys %$to_close;
      }

      exec( map { $_ eq '%%' ? ( 't/bin/stdio_is_X', $tester ) : ($_) } @{$args->{command}} )
        or stirb "Unable to exec: $!";
    }
    else {
      waitpid( $child, 0 );

      my $wstat = $?;

      stirb "UTTER PANIC - \$wstat ($wstat) is too wide after executing '$tester'"
        if $wstat > 0xFFFF_FFFF;

      if ( ( $wstat & 0x1FFF ) != 0b0_1000_0000_0000 ) {
        push @$ret, sprintf (
          "Unexpected lower 13 bits while testing for '$tester': %03b.%b--%b.%03b--%04b--%04b ($wstat)",

          # together adding up to 16 bits
          ($wstat >> 13)  & 0b111,
          ($wstat >> 12)  & 1,
          ($wstat >> 11)  & 1,
          ($wstat >> 8)   & 0b111,
          ($wstat >> 4)   & 0xF,
          $wstat          & 0xF,
        );
      }
      else {
        for my $fd (0, 1, 2) {
          if (
            ( my $result_for_fd = ( $wstat >> ( 15 - $fd ) ) & 1 )
              xor
            ( my $expected_for_fd = ( $args->{expected}[$fd] eq $tester ) ? 1 : 0 )
          ) {
            push @$ret,
              "Unexpected state of FD$fd under '$tester' (got $result_for_fd, want $expected_for_fd)";
          }
        }
      }
    }
  }

  $ret;
}

1;
