package DBIx::Data::Collection::Set;

use Moose;
use Method::Signatures::Simple;
use Data::Perl::Stream::Array;

has _store => (is => 'ro', required => 1, init_arg => 'store');

has _class => (is => 'ro', predicate => '_has_class');

has _set_over => (is => 'ro', required => 1, init_arg => 'set_over');

## member cache (all members)

has _member_cache => (
  is => 'rw', lazy_build => 1,
  predicate => '_member_cache_built',
);

method _build__member_cache {
  my $stream = $self->_new_raw_stream;
  my @cache;
  while (my ($raw) = $stream->next) {
    my $obj = do {
      if (my ($obj) = $self->_key_cache_get_raw($raw)) {
        $self->_merge($obj, $raw)
      } else {
        $self->_add_to_key_cache($self->_inflate($raw))
      }
    };
    push @cache, $obj;
  }
  \@cache
}

method _add_to_member_cache ($to_add) {
  return unless $self->_member_cache_built;
  push @{$self->_member_cache}, $to_add;
  $to_add
}

## key cache - by primary/unique key

has _key_cache => (is => 'ro', default => sub { {} });

method _add_to_key_cache ($to_add) {
  $self->_key_cache->{$self->_object_to_id($to_add)} = $to_add;
  $to_add
}

method _key_cache_has_raw ($raw) {
  exists $self->_key_cache->{$self->_raw_to_id($raw)}
}

method _key_cache_has_object ($obj) {
  exists $self->_key_cache->{$self->_object_to_id($obj)}
}

method _key_cache_get_raw ($raw) {
  my $id = $self->_raw_to_id($raw);
  exists $self->_key_cache->{$id}
    ? ($self->_key_cache->{$id})
    : ()
}

method _key_cache_get_object ($obj) {
  $self->_key_cache_get_raw($self->_deflate($obj))
}

## loading data

method _new_raw_stream {
  $self->_store->new_select_command([])->execute;
}

## thunking between the store representation and the set representation
#
# _inflate is raw data -> final repr
# _deflate is final repr -> raw data
# _merge takes final repr + raw data and updates the repr
#    (this is used for pk-generated values and later lazy loading)

method _inflate ($raw) {
  bless($raw, $self->_class) if $self->_has_class;
  $raw;
}

method _deflate ($obj) {
  +{ %$obj }
}

method _merge ($obj, $raw) {
  @{$obj}{keys %$raw} = values %$raw;
  $obj;
}

## methods to get ids

method _raw_to_id ($raw) {
  # XXX must escape this. or do something else.
  join ';', map $raw->{$_}, @{$self->_set_over}
}

method _object_to_id ($obj) {
  $self->_raw_to_id($self->_deflate($obj));
}

method flatten {
  @{$self->_member_cache};
}

method as_stream {
  Data::Perl::Stream::Array->new(array => $self->_member_cache);
}

method add ($new) {
  $self->_add_to_store($new);
  $self->_add_to_caches($new);
  $new;
}

method _add_to_store ($new) {
  my $new_raw = $self->_deflate($new);
  $self->_merge($new, $self->_store->new_insert_command($new_raw)->execute);
  $new;
}

method _add_to_caches ($new) {
  $self->_add_to_member_cache($new);
  $self->_add_to_key_cache($new);
  $new
}

1;
