package Net::MQTT::Simple::One_Shot_Loader;
use strict;
use warnings;
require Net::MQTT::Simple; #skip import

our $VERSION = '0.02';

=head1 NAME

Net::MQTT::Simple::One_Shot_Loader - Perl package to add one_shot method to Net::MQTT::Simple

=head1 SYNOPSIS

  require Net::MQTT::Simple::One_Shot_Loader;
  use Net::MQTT::Simple; #or Net::MQTT::Simple::SSL
  my $mqtt  = Net::MQTT::Simple->new($host);
  my $obj   = $mqtt->one_shot($topic_sub, $topic_pub, $message_pub, $timeout_seconds); #isa Net::MQTT::Simple::One_Shot_Loader::Response
  my $value = $obj->message;

=head1 DESCRIPTION

This package loads the C<one_shot> method into the L<Net::MQTT::Simple> name space to provide a well tested remote procedure call (RPC) via MQTT.  Many IoT devices only support MQTT as a protocol so, in order to query state or settings these properties need to be requested by sending a message on one queue and receiving a response on another queue.

Due to the way L<Net::MQTT::Simple::SSL> was implemented as a super class of L<Net::MQTT::Simple> and since the author of L<Net::MQTT::Simple> did not want to implement this method in his package (ref L<GitHub|https://github.com/Juerd/Net-MQTT-Simple/pull/22#pullrequestreview-1340685240>), we implemented this method in a method loader package.

=head1 METHODS

=head2 one_shot

Returns an object representing the first message that matches the subscription topic after publishing the message on the message topic.  Returns an object with the error set to a true value on error like timeout.

  my $response = $mqtt->one_shot($topic_sub, $topic_pub, $message_pub, $timeout_seconds);

  if (not $response->error) {
    my $message  = $response->message;
  }

=cut

{
  package Net::MQTT::Simple::One_Shot_Loader::Response;
  use strict;
  use warnings;
  sub error   {shift->{'error'}};
  sub topic   {shift->{'topic'}};
  sub message {shift->{'message'}};
  sub time    {shift->{'time'}};
}

{
  package Net::MQTT::Simple;
  use strict;
  use warnings;
  use Time::HiRes qw{};

  sub one_shot {
    my $self        = shift; #isa Net::MQTT::Simple or Net::MQTT::Simple::SSL
    my $topic_sub   = shift or die('Error: subscribe topic is required');
    my $topic_pub   = shift or die('Error: publish topic is required');
    my $message     = shift;
    $message        = '' unless defined $message; #default '', allow 0 and support perl 5.8
    my $timeout     = shift || 1.5; #seconds

    my $found       = 0; #anonymous sub updates these variables
    my $topic_out   = $topic_sub;
    my $message_out = '';

    $self->subscribe($topic_sub => sub {
                                        unless ($found) { #stop after first found but we get multiple calls per tick
                                          $found       = 1;
                                          $topic_out   = shift;
                                          $message_out = shift;
                                        }
                                       }
    );

    my $timer       = Time::HiRes::time();
    $self->publish($topic_pub => $message);

    my $future      = Time::HiRes::time() + $timeout;
    while (Time::HiRes::time() < $future) {
      $self->tick($timeout); #it takes a few ticks to clear out LWT
      last if $found;
    }
    $timer          = Time::HiRes::time() - $timer;
    my $error       = $found ? '' : sprintf('subscribe timeout (%0.1f s)', $timer);

    $self->unsubscribe($topic_sub); #must unsubscribe to do one_shot back to back
    return bless {
                  error   => $error,
                  topic   => $topic_out,
                  message => $message_out,
                  time    => $timer,
                 }, 'Net::MQTT::Simple::One_Shot_Loader::Response';
  }
}

=head1 SEE ALSO

L<Net::MQTT::Simple>

=head1 AUTHOR

Michael R. Davis

=head1 COPYRIGHT AND LICENSE

MIT License

Copyright (c) 2023 Michael R. Davis

=cut

1;