package TUI::Objects::NSCollection;
# ABSTRACT: defines the class TNSCollection

use 5.010;
use strict;
use warnings;

our $VERSION = '2.000_001';
$VERSION =~ tr/_//d;
our $AUTHORITY = 'cpan:BRICKPOOL';

use Exporter 'import';
our @EXPORT = qw(
  TNSCollection
  new_TNSCollection
);

use Carp ();
use Errno qw( EFAULT EINVAL );
use Hash::Util::FieldHash qw( id );
use TUI::toolkit;
use TUI::toolkit::Types qw(
  is_Object
  :types
);

use TUI::Objects::Const qw( 
  ccNotFound 
  maxCollectionSize
);
use TUI::Objects::Object;

sub TNSCollection() { __PACKAGE__ }
sub new_TNSCollection { __PACKAGE__->from(@_) }

extends TObject;

# predeclare global variable
our %ITEMS = ();

# protected attributes
has items        => ( is => 'ro', default => [] );
has count        => ( is => 'ro', default => 0 );
has limit        => ( is => 'ro', default => 0 );
has delta        => ( is => 'ro', default => 0 );
has shouldDelete => ( is => 'ro', default => 0 );

# predeclare private methods
my (
  $freeItem,
);

sub BUILDARGS {    # \%args (|%args)
  state $sig = signature(
    method => 1,
    named => [
      limit => Int, { alias => 'aLimit', optional => 1 },
      delta => Int, { alias => 'aDelta', optional => 1 },
    ],
    caller_level => +1,
  );
  my ( $class, $args ) = $sig->( @_ );
  return { %$args };
}

sub BUILD {    # void (\%args)
  my ( $self, $args ) = @_;
  assert ( @_ == 2 );
  assert ( is_Object $self );
  $self->setLimit( $self->{limit} );
  return;
} #/ sub BUILD

sub from {    # $obj ($aLimit, $aDelta)
  state $sig = signature(
    method => 1,
    pos => [
      Int, { optional => 1 },
      Int, { optional => 1 },
    ],
  );
  my ( $class, @args ) = $sig->( @_ );
  SWITCH: for ( scalar @args ) {
    $_ == 0 and return $class->new();
    $_ == 2 and return $class->new( limit => $args[0], delta => $args[1] );
  }
  return;
}

sub DEMOLISH {    # void ($in_global_destruction)
  my ( $self, $in_global_destruction ) = @_;
  assert ( @_ == 2 );
  assert ( is_Object $self );
  $self->shutDown();
  return;
}

sub shutDown {    # void ()
  state $sig = signature(
    method => Object,
    pos    => [],
  );
  my ( $self ) = $sig->( @_ );
  if ( $self->{shouldDelete} ) {
    $self->freeAll();
  }
  else {
    $self->removeAll();
  }
  $self->setLimit( 0 );
  $self->SUPER::shutDown();
  return;
} #/ sub shutDown

sub at {    # $item ($index)
  state $sig = signature(
    method => Object,
    pos    => [Int],
  );
  my ( $self, $index ) = $sig->( @_ );
  $self->error( EINVAL, "Index out of bounds" )
    if $index < 0 || $index >= $self->{count};
  return $ITEMS{ $self->{items}->[$index] };
}

sub atRemove {    # void ($index)
  state $sig = signature(
    method => Object,
    pos    => [Int],
  );
  my ( $self, $index ) = $sig->( @_ );
  $self->error( EINVAL, "Index out of bounds" )
    if $index < 0 || $index >= $self->{count};
  $self->{count}--;
  splice( @{ $self->{items} }, $index, 1 );
  return;
}

sub atFree {    # void ($index)
  state $sig = signature(
    method => Object,
    pos    => [Int],
  );
  my ( $self, $index ) = $sig->( @_ );
  my $item = $self->at( $index );
  $self->atRemove( $index );
  $self->$freeItem( $item );
  return;
}

sub atInsert {    # void ($index, $item|undef)
  state $sig = signature(
    method => Object,
    pos    => [Int, Item],
  );
  my ( $self, $index, $item ) = $sig->( @_ );
  $self->error( EINVAL, "Index out of bounds" )
    if $index < 0;
  $self->setLimit( $self->{count} + $self->{delta} )
    if $self->{count} == $self->{limit};

  my $id = id($item) || 0;
  $ITEMS{ $id } = $item;
  $self->{count}++;

  splice( @{ $self->{items} }, $index, 0, $id );
  return;
}

sub atPut {    # void ($index, $item|undef)
  state $sig = signature(
    method => Object,
    pos    => [Int, Item],
  );
  my ( $self, $index, $item ) = $sig->( @_ );
  $self->error( EINVAL, "Index out of bounds" )
    if $index >= $self->{count};

  my $id = id($item) || 0;
  $ITEMS{ $id } = $item;
  $self->{items}->[$index] = $id;
  return;
}

sub remove {    # void ($item)
  state $sig = signature(
    method => Object,
    pos    => [Item],
  );
  my ( $self, $item ) = $sig->( @_ );
  $self->atRemove( $self->indexOf( $item ) );
  return;
}

sub removeAll {    # void ()
  state $sig = signature(
    method => Object,
    pos    => [],
  );
  my ( $self ) = $sig->( @_ );
  $self->{count} = 0;
  $self->{items} = [];
  return;
}

sub free {    # void ($item)
  state $sig = signature(
    method => Object,
    pos    => [Item],
  );
  my ( $self, $item ) = $sig->( @_ );
  $self->remove( $item );
  $self->$freeItem( $item );
  return;
}

sub freeAll {    # void ()
  state $sig = signature(
    method => Object,
    pos    => [],
  );
  my ( $self ) = $sig->( @_ );
  $self->$freeItem( $self->at( $_ ) ) 
    for 0 .. $self->{count} - 1;
  $self->{count} = 0;
  return;
}

sub indexOf {    # $index ($item|undef)
  state $sig = signature(
    method => Object,
    pos    => [Item],
  );
  my ( $self, $item ) = $sig->( @_ );
  for my $i ( 0 .. $self->{count} - 1 ) {
    my $id = id($item) || 0;
    return $i if $self->{items}->[$i] eq $id;
  }
  $self->error( EFAULT, "Item not found" );
  return ccNotFound;
}

sub insert {    # $index ($item|undef)
  state $sig = signature(
    method => Object,
    pos    => [Item],
  );
  my ( $self, $item ) = $sig->( @_ );
  my $loc = $self->{count};
  $self->atInsert( $self->{count}, $item );
  return $loc;
}

sub error {    # void ($code, $info)
  state $sig = signature(
    method => Object,
    pos    => [Int, Str],
  );
  my ( $self, $code, $info ) = $sig->( @_ );
  Carp::croak sprintf("Error code: %d, Info: %s\n", $code, $info);
}

sub firstThat {    # $item|undef (\&Test, $arg|undef)
  state $sig = signature(
    method => Object,
    pos    => [CodeRef, Any],
  );
  my ( $self, $Test, $arg ) = $sig->( @_ );
  my $that;
  for my $i ( 0 .. $self->{count} - 1 ) {
    local $_ = $ITEMS{ $self->{items}->[$i] };
    return $_ if $Test->( $_, $arg );
  }
  return undef;
}

sub lastThat {    # $item|undef (\&Test, $arg|undef)
  state $sig = signature(
    method => Object,
    pos    => [CodeRef, Any],
  );
  my ( $self, $Test, $arg ) = $sig->( @_ );
  my $that;
  for my $i ( reverse 0 .. $self->{count} - 1 ) {
    local $_ = $ITEMS{ $self->{items}->[$i] };
    return $_ if $Test->( $_, $arg );
  }
  return undef;
}

sub forEach {    # void (\&action, $arg|undef)
  state $sig = signature(
    method => Object,
    pos    => [CodeRef, Any],
  );
  my ( $self, $action, $arg ) = $sig->( @_ );
  for my $i ( 0 .. $self->{count} - 1 ) {
    local $_ = $ITEMS{ $self->{items}->[$i] };
    $action->( $_, $arg );
  }
  return;
}

sub pack {    # void ()
  state $sig = signature(
    method => Object,
    pos    => [],
  );
  my ( $self ) = $sig->( @_ );
  my $count = 0;
  for my $i ( 0 .. $self->{count} - 1 ) {
    if ( $ITEMS{ $self->{items}->[$i] } ) {
      $count++;
    }
    else {
      splice( @{ $self->{items} }, $i, 1 );
    }
  }
  if ( $self->{count} != $count ) {
    my $n = $self->{count} - $count;
    push( @{ $self->{items} }, ( 0 ) x $n );
    $self->{count} = $count;
  }
  return;
}

sub setLimit {    # void ($aLimit)
  state $sig = signature(
    method => Object,
    pos    => [Int],
  );
  my ( $self, $aLimit ) = $sig->( @_ );
  $aLimit = $self->{count} if $aLimit < $self->{count};
  $aLimit = maxCollectionSize if $aLimit > maxCollectionSize;
  if ( $aLimit != $self->{limit} ) {
    my $size = @{ $self->{items} };
    if ( $aLimit > $size ) {
      my $n = $aLimit - $size;
      push( @{ $self->{items} }, ( 0 ) x $n );
    } elsif ( $aLimit < $size ) {
      splice( @{ $self->{items} }, $aLimit );
    }
    $self->{limit} = $aLimit;
  }
  return;
} #/ sub setLimit

sub getCount {    # $count ()
  state $sig = signature(
    method => Object,
    pos    => [],
  );
  my ( $self ) = $sig->( @_ );
  return $self->{count};
};

$freeItem = sub {
  my ( $self, $item ) = @_;
  assert ( @_ == 2 );
  assert ( is_Object $self );
  my $id = id($item) || 0;
  delete $ITEMS{ $id } if $id;
  return;
};

1

__END__

=pod

=head1 NAME

TUI::Objects::NSCollection - internal non-storable collection base class

=head1 DESCRIPTION

C<TNSCollection> is the non-storable base variant of the Turbo Vision collection
classes. It exists primarily for internal use, most notably by the stream and
resource management infrastructure.

This class provides the same operational behavior as C<TCollection>, but is not
intended to be used directly by application code. Public-facing code should
always use C<TCollection> or one of its derived classes instead.

The non-storable variants are required to separate internal framework
mechanisms from the storable collection types used elsewhere in the library.

=head1 VARIABLES

=head2 %ITEMS

Internal global hash used to maintain item references for all
C<TCollection> objects.

The collection elements are stored as references in this hash.
Keys are derived using
L<Hash::Util::FieldHash::id|Hash::Util::FieldHash>, ensuring stable and
unique identification of collection items.

This variable is for internal use only and mirrors the reference
handling approach of the original Turbo Vision implementation.

See also L<Scalar::Util::refaddr|Scalar::Util>.

=head1 NOTE

This class is considered internal.

Although C<TNSCollection> implements a full collection interface, it should not
be instantiated or subclassed directly outside of the framework itself.

=head1 SEE ALSO

L<TUI::Objects::Collection>,
L<TUI::Objects::SortedCollection>,
L<TUI::Objects::StringCollection>

=head1 AUTHORS

=over

=item Borland International (original Turbo Vision design)

=item J. Schneider <brickpool@cpan.org> (Perl implementation and maintenance)

=back

=head1 COPYRIGHT AND LICENSE

Copyright (c) 1990-1994, 1997 by Borland International

Copyright (c) 2021-2026 the L</AUTHORS> as listed above.

This software is licensed under the MIT license (see the LICENSE file, which is
part of the distribution).

=cut
