package Argon::Message;

use strict;
use warnings;
use Carp;
use Data::UUID;
use Storable qw(nfreeze thaw);
use MIME::Base64 qw(encode_base64 decode_base64);
use List::Util qw(first);
use Argon qw(:priorities);

use fields qw(
    id
    cmd
    pri
    payload
    key
);

sub new {
    my ($class, %param) = @_;
    defined $param{cmd} || croak 'expected parameter "cmd"';
    my $self = fields::new($class);
    $self->{id}  = $param{id}  || Data::UUID->new->create_str;
    $self->{cmd} = $param{cmd};
    $self->{pri} = $param{pri} || $PRI_NORMAL;
    $self->{key} = $param{key} || 'OPEN';
    $self->{payload} = $param{payload};
    return $self;
}

sub cmp     { $_[0]->{pri} <=> $_[1]->{pri} }
sub id      { $_[0]->{id}      }
sub cmd     { $_[0]->{cmd}     }
sub pri     { $_[0]->{pri}     }
sub key     { $_[0]->{key}     }
sub payload { $_[0]->{payload} }

sub encode {
    my $self = shift;

    local $Storable::Deparse = 1;
    my $data = defined $self->{payload}
        ? encode_base64(nfreeze([$self->{payload}]), '')
        : '-';

    my $line = join(
        $Argon::MSG_SEPARATOR,
        $self->{id},
        $self->{cmd},
        $self->{pri},
        $self->{key},
        $data,
    );

    return $line;
}

sub decode {
    my ($class, $line) = @_;
    my ($id, $cmd, $pri, $key, $payload) = split $Argon::MSG_SEPARATOR, $line;

    croak 'incomplete message'
        unless defined $id
            && defined $cmd
            && defined $pri
            && defined $key;

    if ($payload eq '-') {
        undef $payload;
    } else {
        local $Storable::Eval = 1;
        $payload = thaw(decode_base64($payload))->[0];
    }

    return $class->new(
        id         => $id,
        cmd        => $cmd,
        pri        => $pri,
        key        => $key,
        payload    => $payload,
    );
}

sub reply {
    my ($self, %param) = @_;
    return $self->new(%$self, %param)
}

1;
