package Exobrain;

use v5.010;
use strict;
use warnings;
use autodie;
use Moose;
use Method::Signatures;
use Carp qw(croak);
use POSIX qw(tzset);

# ABSTRACT: Core Exobrain accessor class

# VERSION: Generated by DZP::OurPkg:Version

use Exobrain::Bus;
use Exobrain::Config;
use Exobrain::Message;
use Exobrain::Message::Raw;

has 'config' => (
    is => 'ro',
    isa => 'Exobrain::Config',
    builder => '_build_config',
);

# Pub/Sub interfaces to our bus. These don't get generated unless
# our end code actually asks for them. Many things will only require
# one, or will use higher-level functions to do their work.

has 'pub' => (
    is => 'ro',
    isa => 'Exobrain::Bus',
    builder => '_build_pub',
    lazy => 1,
);

has 'sub' => (
    is => 'ro',
    isa => 'Exobrain::Bus',
    builder => '_build_sub',
    lazy => 1,
);

# Right now we make sure anything using Exobrain is going to use
# the user's timezone (if set).

method BUILD(...) {
    $self->_set_timezone();
    return;
}

# This sets our timezone based upon what's in our config file,
# so any use of localtime() should use the user's local timezone.

method _set_timezone($tz?) {
    $tz //= $self->config->{General}{timezone};

    if ($tz) {
        $ENV{TZ} = $tz;     ## no critic RequireLocalizedPunctuationVars
        tzset();
    }
    return;
}

sub _build_config { return Exobrain::Config->new; };
sub _build_pub    { return Exobrain::Bus->new(type => 'PUB', exobrain => shift) }
sub _build_sub    { return Exobrain::Bus->new(type => 'SUB', exobrain => shift) }


method watch_loop(
    Str     :$class!,
    CodeRef :$filter,
    CodeRef :$then!,
    CodeRef :$debug?,
) {

    # Load our component, because that means we immediately get
    # an error if that class doesn't exist.

    $self->_load_component($class);

    while (my $event = $self->sub->get) {
        next unless $event->namespace eq $class;

        $event = $event->to_class($class);

        $debug->($event) if $debug;

        if ($filter) {

            # Check our filter, and skip if required
            local $_ = $event;
            next unless $filter->($event);

        }

        # Everything passes! Trigger our callback
        { local $_ = $event; $then->($event); }
    }
}


use constant NOTIFY => 'Notify';

method notify($message, @args) {
    return $self->intent( NOTIFY,
        message => $message,
        @args,
    )->send_msg;
}


method message(@args) {
    return Exobrain::Message::Raw->new(
        exobrain => $self,
        @args,
    );
}

method message_class($class, @args) {
    $class = $self->_load_component($class);

    return $class->new(
        exobrain => $self,
        @args,
    );
}


use constant MEASURE_PREFIX => 'Measurement::';

method measure($type, @args) {

    my $class = $self->_load_component( MEASURE_PREFIX . $type );

    return $class->new(
        exobrain => $self,
        @args,
    );
}


use constant INTENT_PREFIX => 'Intent::';

method intent($type, @args) {
    my $class = $self->_load_component( INTENT_PREFIX . $type );

    return $class->new(
        exobrain => $self,
        @args,
    );
}

use constant CLASS_PREFIX => 'Exobrain::';

# Loads a class, automatically adding Exobrain if
# required. Returns the class loaded.

method _load_component(Str $class) {
    $class = CLASS_PREFIX . $class;

    eval "require $class";
    croak $@ if $@;

    return $class;
}


1;

__END__

=pod

=head1 NAME

Exobrain - Core Exobrain accessor class

=head1 VERSION

version 0.05

=head1 METHODS

=head2 watch_loop

    $exobrain->watch_loop(
        class  => 'Measurement::Geo',
        filter => sub { $_->is_me },
        then   => sub { ... },
    );

When we see packets of a particular class, do a particular thing.

If the optional C<debug> option is passed with a coderef,  that will be run for
every event in the desired class, before the filter is evaluated.

The event is passed as the first argument to all coderefs. As a
convenience, it is also placed inside C<$_>.

Never returns, just runs the loop forever.

=head2 notify

    $exobrain->notify($msg
        priority => -1,
    );

Takes a mandatory message, and any arguments that can be passeed to
L<Exobrain::Intent::Notify>, and notifies the user.  At the time of
writing, notifications are done by the pushover end-point by default.

This is a thin wrapper around C< $exobrain->intent('Notify', ... >.

=head2 message

    $exobrain->message( ... )->send_msg;

Shortcut to create a 'raw' message. The exobrain parameter will be passed
to the class constructor automatically.

=head2 measure

    $exobrain->measure( 'Mailbox',
        count  => 42,
        user   => 'pjf',
        server => 'imap.example.com',
        fodler => 'INBOX',
    )->send;

Preferred shortcut for creating a measurement of the desired class. The
C<exobrain> parameter will be passed to the measurement class constructor
automatically.

=head2 intent

    my $intent = $exobrain->intent( 'Tweet',
        tweet => 'Hello World',
    );

Preferred shortcut for making an intent of the desired class. The
C<exobrain> parameter will be passed to the intent class constructor
automatically.

=for Pod::Coverage BUILD DEMOLISH CLASS_PREFIX INTENT_PREFIX MEASURE_PREFIX NOTIFY

=head1 AUTHOR

Paul Fenwick <pjf@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Paul Fenwick.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
