package Javonet::Core::Protocol::CommandDeserializer;
use strict;
use warnings;
use Moose;
use lib 'lib';
use aliased 'Javonet::Sdk::Core::PerlCommand' => 'PerlCommand';
use aliased 'Javonet::Sdk::Core::Type' => 'Type', qw(get_type);
use aliased 'Javonet::Core::Protocol::TypeDeserializer' => 'TypeDeserializer';

use Exporter qw(import);
our @EXPORT = qw(new isAtEnd decode readObject readCommand readCommandRecursively readString readInt readBool readFloat readByte readChar readLongLong readDouble readUnsignedLongLong readUnsignedInt readUndef);

our $position = 0;
our @buffer = [];
our $command;

sub new {
    my ($self, $array_ref) = @_;
    @buffer = @$array_ref;
    $command = PerlCommand->new(runtime => $buffer[0],
        command_type                    => $buffer[10], payload => []);
    $position = 11;
    return $self;
}

sub isAtEnd {
    my $length = @buffer;
    return $position == $length;
}

sub decode {
    while (!isAtEnd()) {
        $command = $command->addArgumentToPayload([ readObject($buffer[$position]) ]);
    }
    return $command;
}

sub readObject {
    my $type_num = $_[0];
    if ($type_num == Type->get_type('Command')) {
        return readCommand();
    }
    elsif ($type_num == Type->get_type('JavonetString')) {
        return readString();
    }
    elsif ($type_num == Type->get_type('JavonetInteger')) {
        return readInt();
    }
    elsif ($type_num == Type->get_type('JavonetBool')) {
        return readBool();
    }
    elsif ($type_num == Type->get_type('JavonetFloat')) {
        return readFloat();
    }
    elsif ($type_num == Type->get_type('JavonetByte')) {
        return readByte();
    }
    elsif ($type_num == Type->get_type('JavonetChar')) {
        return readChar();
    }
    elsif ($type_num == Type->get_type('JavonetLongLong')) {
        return readLongLong();
    }
    elsif ($type_num == Type->get_type('JavonetDouble')) {
        return readDouble();
    }
    elsif ($type_num == Type->get_type('JavonetUnsignedLongLong')) {
        return readUnsignedLongLong();
    }
    elsif ($type_num == Type->get_type('JavonetUnsignedInteger')) {
        return readUnsignedInt();
    }
    elsif ($type_num == Type->get_type('JavonetNull')) {
        return readUndef();
    }
    else {
        die "Type is not supported: $type_num";
    }
}

sub readCommand {
    my $p = $position;
    my @int_buffer = @buffer[$p + 1 .. $p + 4];
    my $numberOfArgumentInPayload = TypeDeserializer->deserializeInt(\@int_buffer);
    my $runtime = $buffer[$p + 5];
    my $commandType = $buffer[$p + 6];

    $position += 7;
    my $returnCommand = PerlCommand->new(runtime => $runtime, command_type => $commandType);
    return readCommandRecursively($numberOfArgumentInPayload, $returnCommand);
}

sub readCommandRecursively {
    my $numberOfArgumentInPayloadLeft = $_[0];
    my $cmd = $_[1];

    if ($numberOfArgumentInPayloadLeft == 0) {
        return $cmd;
    }
    else {
        my $p = $position;
        my @int_buffer = @buffer[$p + 1 .. $p + 4];
        my $argSize = TypeDeserializer->deserializeInt(\@int_buffer);
        $cmd = $cmd->addArgumentToPayload([ readObject($buffer[$p]) ]);
        return readCommandRecursively($numberOfArgumentInPayloadLeft - 1, $cmd);
    }
}

sub readString {
    my $p = $position;
    my $string_encoding_mode = $buffer[$p + 1];
    my @int_buffer = @buffer[$p + 2 .. $p + 5];
    my $size = TypeDeserializer->deserializeInt(\@int_buffer);
    $position += 6;
    $p = $position;
    $position += $size;
    my @string_buffer = @buffer[$p .. $p + $size - 1];
    my $decodedString = TypeDeserializer->deserializeString($string_encoding_mode, \@string_buffer);

    return $decodedString
}

sub readInt {
    $position += 2;
    my $p = $position;
    $position += 4;
    my @int_buffer = @buffer[$p .. $p + 3];
    return TypeDeserializer->deserializeInt(\@int_buffer);
}

sub readBool {
    $position += 2;
    my $p = $position;
    $position += 1;
    my $bool_buffer = $buffer[$p];
    return TypeDeserializer->deserializeBool($bool_buffer);
}

sub readFloat {
    $position += 2;
    my $p = $position;
    $position += 4;
    my @float_buffer = @buffer[$p .. $p + 3];
    return TypeDeserializer->deserializeFloat(\@float_buffer);
}

sub readByte {
    $position += 2;
    my $p = $position;
    $position += 1;
    my $byte_buffer = $buffer[$p];
    return TypeDeserializer->deserializeByte($byte_buffer);
}

sub readChar {
    $position += 2;
    my $p = $position;
    $position += 1;
    my $char_buffer = $buffer[$p];
    return TypeDeserializer->deserializeChar($char_buffer);
}

sub readLongLong {
    $position += 2;
    my $p = $position;
    $position += 8;
    my @longlong_buffer = @buffer[$p .. $p + 7];
    return TypeDeserializer->deserializeLongLong(\@longlong_buffer);
}

sub readDouble {
    $position += 2;
    my $p = $position;
    $position += 8;
    my @double_buffer = @buffer[$p .. $p + 7];
    return TypeDeserializer->deserializeDouble(\@double_buffer);
}

sub readUnsignedLongLong {
    $position += 2;
    my $p = $position;
    $position += 8;
    my @ulonglong_buffer = @buffer[$p .. $p + 7];
    return TypeDeserializer->deserializeULongLong(\@ulonglong_buffer);
}

sub readUnsignedInt {
    $position += 2;
    my $p = $position;
    $position += 4;
    my @uint_buffer = @buffer[$p .. $p + 3];
    return TypeDeserializer->deserializeUInt(\@uint_buffer);
}

sub readUndef {
    $position += 2;
    my $p = $position;
    $position += 1;
    my $undef_buffer = $buffer[$p];
    return TypeDeserializer->deserializeUndef($undef_buffer);
}

no Moose;
1;