The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# -*- coding: utf-8 -*-
# Copyright (C) 2011-2012 Rocky Bernstein <rocky@cpan.org>
# Debugger Socket Input/Output Interface.

use warnings; use strict;

use rlib '../../..';

# Debugger Client Input/Output Socket.
package Devel::Trepan::IO::TCPClient;
use English qw ( -no_match_vars );
use IO::Socket qw(SOCK_STREAM);

use Devel::Trepan::IO::TCPPack;
use Devel::Trepan::Util qw(hash_merge);
our(@ISA);

use constant CLIENT_SOCKET_OPTS => {
      host    => 'localhost', # Symbolic name
      port    => 1027,  # Arbitrary non-privileged port
      open    => 1,
      logger  => undef,  # Complaints should be sent here.
};

#   attr_reader :state

sub open($;$);

sub new($;$)
{
    my ($class, $opts) = @_;
    $opts    = hash_merge($opts, CLIENT_SOCKET_OPTS);
    my $self = {
        addr      => undef,
        buf       => '',
        input     => $opts->{input},
        line_edit => 0, # Our name for GNU readline capability
        logger    => $opts->{logger},
        output    => $opts->{output},
        state     => 'disconnected',
    };
    bless $self, $class;
    $self->open($opts) if $opts->{open};
    return $self;
}

# Closes both input and output
sub close($)
{
    my $self = shift;
    $self->{state} = 'closing';
    if ($self->{inout}) {
        $self->{inout}->shutdown(2);
        close($self->{inout})
    }
    $self->{state} = 'disconnected';
    $self->{input} = $self->{output} = undef;
    print {$self->{logger}} "Disconnected\n" if $self->{logger};
}

sub is_disconnected($)
{
    my $self = shift;
    return 'disconnected' eq $self->{state};
}

sub open($;$)
{
    my ($self, $opts) = @_;
    $opts = hash_merge($opts, CLIENT_SOCKET_OPTS);
    $self->{host} = $opts->{host};
    $self->{port} = $opts->{port};
    $self->{input} = $opts->{input} ||
        IO::Socket::INET->new(PeerAddr=> $self->{host},
                              PeerPort => $self->{port},
                              Proto    => 'tcp',
                              Type     => SOCK_STREAM
        );
    $self->{output} = $self->{input};
    if ($self->{input}) {
        $self->{state} = 'connected';
    } else {
        my $msg = sprintf("Open client for host %s on port %s gives error: %s",
                          $self->{host}, $self->{port}, $EVAL_ERROR);
        die $msg;
    }
}

sub is_empty($)
{
    my($self) = @_;
    0 == length($self->{buf});
}

# Read one message unit. It's possible however that
# more than one message will be set in a receive, so we will
# have to buffer that for the next read.
# EOFError will be raised on EOF.
sub read_msg($)
{
    my($self) = @_;
    if ($self->{state} eq 'connected') {
        if (!$self->{buf} || is_empty($self)) {
            $self->{input}->recv($self->{buf}, TCP_MAX_PACKET);
            if (is_empty($self)) {
                $self->close;
                $self->{state} = 'disconnected';
                die "EOF while reading on socket";
            }
        }
        my $data;
        ($self->{buf}, $data) = unpack_msg($self->{buf});
        return $data;
    } else {
        die sprintf("read_msg called in state: %s.", $self->{state});
    }
}

sub have_term_readline($)
{
    return 0;
}

# This method the debugger uses to write a message unit.
sub write($$)
{
    my ($self, $msg) = @_;
    # FIXME: do we have to check the size of msg and split output?
    $self->{output}->send(pack_msg($msg));
}

sub writeline($$)
{
    my ($self, $msg) = @_;
    $self->write($msg . "\n");
}

# Demo
unless (caller) {
     if (scalar @ARGV) {
         # my $pid = fork();
         #if ($pid) {
             print "Connecting...\n";
             my $client = Devel::Trepan::IO::TCPClient-> new({'open' => 1});
             $client->writeline("Hi there\n");
             # for (;;) {
             #   undef $!;
             #   my $line;
             #   unless (defined( $line = <> )) {
             #       if (eof) {
             #           print "Got EOF\n";
             #           last;
             #       }
             #       if ($!) {
             #           print STDERR $!;
             #           last;
             #       }
             #       chomp $line;
             #       last if $line eq 'quit';
             #       $line = $client->writeline($line);
             #       # print "Got: #{client.read_msg.chomp}\n";
             #   }
             # }
             $client->close;
         #} else {
             # server = TCPServer.new('localhost', 1027);
             # session = server.accept;
             # while 'quit' != (line = session.gets);
             # session.puts line ;
         #   exec "nc -l 1027";
         # }
     }
}

1;