package System::Introspector::State;
use Moo;
use Data::YAML::Writer;
use Object::Remote;
use Object::Remote::Future;
use System::Introspector::Gatherer;

has config => (is => 'ro', required => 1);

has introspectors => (is => 'lazy');

has host => (is => 'ro');

has storage => (is => 'ro', required => 1);

has node_path => (is => 'lazy');

sub fetch {
    my ($self) = @_;
    my $gatherer = $self->_create_gatherer;
    my $spec     = $self->introspectors;
    my %report;
    for my $class_base (sort keys %$spec) {
        $report{ $class_base } = $gatherer
            ->gather($class_base, $spec->{ $class_base });
    }
    return \%report;
}

sub fetch_and_store {
    my ($self) = @_;
    return $self->_store($self->fetch);
}

sub _build_node_path {
    my ($self) = @_;
    return defined($self->host)
        ? sprintf('host/%s', $self->host)
        : 'local';
}

sub _build_introspectors {
    my ($self) = @_;
    return $self->config->{introspect};
}

sub _store {
    my ($self, $data) = @_;
    my $yaml    = Data::YAML::Writer->new;
    my $storage = $self->storage;
    my @files;
    for my $class (sort keys %$data) {
        my $file = sprintf '%s.yml', join '/',
            node => $self->node_path,
            map lc, map {
                s{([a-z0-9])([A-Z])}{${1}_${2}}g;
                $_;
            } split m{::}, $class;
        my $fh = $storage->open('>:utf8', $file, mkpath => 1);
        print "Writing $file\n";
        $yaml->write($data->{$class}, $fh);
        push @files, $storage->file($file);
    }
    $self->_cleanup(\@files);
    return 1;
}

sub _cleanup {
    my ($self, $known_files) = @_;
    my %known = map { ($_ => 1) } @$known_files;
    my $data_dir = $self->storage->file(node => $self->node_path);
    my @files = $self->storage->find_files('yml', node =>  $self->node_path);
    for my $file (@files) {
        next if $known{$file};
        print "Removing $file\n";
        unlink($file)
            or die "Unable to remove '$file': $!\n";
    }
    return 1;
}

sub _create_gatherer {
    my ($self) = @_;
    if (defined( my $host = $self->host )) {
        return System::Introspector::Gatherer->new::on($host);
    }
    return System::Introspector::Gatherer->new;
}

1;

=head1 NAME

System::Introspector::State - Gather system state

=head1 SYNOPSIS

    my $state = System::Introspector::State->new(
        host    => 'foo.example.com',
        storage => $storage_obj,
        config  => {
            introspect => [qw( ProbeName )],
        },
    );

    my $data = $state->fetch;
    $state->fetch_and_store;

=head1 DESCRIPTION

Gathers system introspection data based on configuration and stores
it with a L<File::Tree::Snapshot> object.

=head1 ATTRIBUTES

=head2 config

A hash reference containing a C<introspect> key with an array reference
value containing a list of probe names without the
C<System::Introspector::Probe::> prefix. This attribute is required.

=head2 host

An optional hostname. If no hostname is supplied, the local configuration
data will be fetched.

=head2 storage

A L<File::Tree::Snapshot> object.

=head1 METHODS

=head2 fetch

    my $data = $state->fetch;

Fetches all probe data.

=head2 fetch_and_store

    $state->fetch_and_store;

Fetches all probe data and stores it in the L</storage>.

=cut
