#
# Copyright (c) 2003-2021 Christian Jaeger, copying@christianjaeger.ch
#
# This is free software, offered under either the same terms as perl 5
# or the terms of the Artistic License version 2 or the terms of the
# MIT License (Expat version). See the file COPYING.md that came
# bundled with this file.
#

=head1 NAME

Chj::IO::Dir

=head1 SYNOPSIS

=head1 DESCRIPTION

See L<Chj::xopendir>.

=head1 NOTE

This is alpha software! Read the status section in the package README
or on the L<website|http://functional-perl.org/>.

=cut

package Chj::IO::Dir;

use strict;
use warnings;
use warnings FATAL => 'uninitialized';

use Symbol;
use Carp;
use Chj::singlequote ();
use POSIX qw(EEXIST EBADF ENOENT);
use FP::Carp;

my %metadata;    # -> [ is_open, path ]
$foo::foo = \%metadata;

sub path {
    my $self = shift;
    $metadata{ pack "I", $self }[1]
}

sub xopendir {
    my $class = shift;
    my $hdl   = gensym;
    $! = undef;
    if (opendir $hdl, $_[0]) {
        bless $hdl, $class;
        $metadata{ pack "I", $hdl } = [1, $_[0]];
        return $hdl;
    } else {
        croak "xopendir " . Chj::singlequote::singlequote_many(@_) . ": $!";
    }
}

# *new = \&xopendir;  really? no.

sub opendir {
    my $class = shift;
    my $hdl   = gensym;
    $! = undef;
    if (opendir $hdl, $_[0]) {
        bless $hdl, $class;
        $metadata{ pack "I", $hdl } = [1, $_[0]];
        return $hdl;
    } else {
        undef
    }
}

sub perhaps_opendir {
    my $class = shift;
    $! = undef;
    if (defined(my $fh = $class->opendir(@_))) {
        $fh
    } else {
        ()
    }
}

# (adapted copy of perhaps_xopen of File.pm)
# die on all errors except ENOENT
sub perhaps_xopendir {
    my $proto = shift;
    if (my ($fh) = $proto->perhaps_opendir(@_)) {
        $fh
    } elsif ($! == ENOENT) {
        ()
    } else {
        croak "xopen @_: $!";
    }
}

sub new {
    my $class = shift;
    my $self  = gensym;
    bless $self, $class
}

sub read {
    my $self = shift;
    $! = undef;
    readdir $self
}

sub xread {
    my $self = shift;
    $! = undef;

    # ^ Needed, CORE::readdir will not set it to 0. Thus maybe it will
    # not even set any error? Hm, well, at least on end of dir it sets
    # it to Bad file descriptor.
    if (wantarray) {    ## no critic
        my $res = [CORE::readdir $self];

        # we *hope* that [ ] will never copy until the end as opposed
        # to @res = which *might* (well probably (or I think IIRC I've
        # even tested and confirmed it) does) copy all elements.
        if ($!) {
            croak "xread: $!";
        }
        @$res
    } else {
        my $res = CORE::readdir $self;
        if ($! and $! != EBADF) {
            croak "xread: $!";

    #croak "xread: $! (".($!+0).")";   ## exception objects would still be coool
        }
        $res
    }
}

sub nread {    # ignore . and .. entries
    my $self = shift;
    $! = undef;
    if (wantarray) {    ## no critic
        grep { $_ ne '.' and $_ ne '..' } readdir $self
    } else {
        while (defined(my $item = readdir $self)) {
            return $item unless $item eq '.' or $item eq '..';
        }
        undef
    }
}

sub xnread {
    my $self = shift;
    $! = undef;
    if (wantarray) {    ## no critic
        my $res = [grep { $_ ne '.' and $_ ne '..' } readdir $self];
        @$res
    } else {
        while (defined(my $item = readdir $self)) {
            return $item unless $item eq '.' or $item eq '..';
        }
        undef
    }
}

sub telldir {
    my $self = shift;
    $! = undef;
    CORE::telldir $self
}

sub seekdir {
    my $self = shift;
    @_ == 1 or fp_croak_arity 1;
    my ($pos) = @_;
    $! = undef;
    CORE::seekdir $self, $pos
}

sub xseekdir {
    my $self = shift;
    @_ == 1 or fp_croak_arity 1;
    my ($pos) = @_;
    $! = undef;
    CORE::seekdir $self, $pos or croak "xseekdir (UNTESTED): $!";    ##
}

sub xrewind {
    my $self = shift;
    $! = undef;
    CORE::seekdir $self, 0 or croak "xrewind (UNTESTED): $!";        ##
}

sub xclose {
    my $self = shift;

    #(maybe check metadata is_open first? not really useful)
    $! = undef;
    closedir $self or croak "xclose: $!";
    $metadata{ pack "I", $self }[0] = 0
}

sub DESTROY {
    my $self = shift;
    local ($@, $!, $?, $_);
    if ($metadata{ pack "I", $self }[0]) {
        closedir $self or carp "$self DESTROY: $!";
    }
    delete $metadata{ pack "I", $self };
}

1;