package Perl::LanguageServer::IO ;

use Moose::Role ;

use Coro ;
use Coro::AIO ;
use Data::Dump qw{dump} ;

no warnings 'uninitialized' ;

has 'out_fh' =>
    (
    is => 'rw',
    #isa => 'Int',
    ) ;

has 'in_fh' =>
    (
    is => 'rw',
    #isa => 'Int',
    ) ;

# ---------------------------------------------------------------------------

our $windows=  ($^O =~ /Win/)?1:0 ;

# ---------------------------------------------------------------------------

sub _read
    {
    my ($self, $data, $length, $dataoffset, $fh, $readline) = @_ ;

    $fh ||= $self -> in_fh ;

    if (ref ($fh) =~ /^Coro::Handle/)
        {
        if ($readline)
            {
            $$data = $fh -> readline ;
            return length ($$data) ;
            }
        return $fh -> sysread ($$data, $length, $dataoffset) ;
        }
    if (!$windows || !ref $fh)
        {
        return aio_read ($fh, undef, $length, $$data, $dataoffset) ;
        }

    my $timeout = 0.01 ;

    my $s = IO::Select -> new ();
    $s -> add($fh) ;
    my @ready ;
    while (!(@ready = $s -> can_read (0)))
        {
        Coro::AnyEvent::sleep ($timeout) ;
        }
    $length = length ($$data) if (!defined ($length)) ;
    return sysread ($fh, $$data, $length, $dataoffset) ;
    }

# ---------------------------------------------------------------------------

sub _write
    {
    my ($self, $data, $length, $dataoffset) = @_ ;

    my $fh = $self -> out_fh ;
    if (ref ($fh) =~ /^Coro::Handle/)
        {
        return $fh -> syswrite ($data, $length, $dataoffset) ;
        }

    if (!$windows || !ref $fh)
        {
        return aio_write ($fh, undef, $length, $data, $dataoffset) ;
        }

    $length = length ($data) if (!defined ($length)) ;
    return syswrite ($fh, $data, $length, $dataoffset) ;
    }

# ---------------------------------------------------------------------------

 sub run_async
    {
    my ($self, $cmd, $on_stdout, $on_stderr, $on_exit) = @_ ;

    $on_stdout ||= 'on_stdout' ;
    $on_stderr ||= 'on_stderr' ;
    $on_exit   ||= 'on_exit' ;

    my($wtr, $rdr, $err);

    if ( ref($cmd) )
        {
        $self -> logger ("start @$cmd\n") ;
        } else
        {
        $self -> logger ("start $cmd\n") ;        
        }

    require IPC::Open3 ;
    require Symbol ;
    $err = Symbol::gensym () ;
    my $pid;
    if ( ref($cmd) )
        {
        $pid = IPC::Open3::open3($wtr, $rdr, $err, @$cmd) or die "Cannot run @$cmd" ;
        } else
        {
        $pid = IPC::Open3::open3($wtr, $rdr, $err, $cmd) or die "Cannot run $cmd" ;
        }

    $self -> out_fh ($wtr) ;
    $self -> in_fh  ($rdr) ;

    if ( ref($cmd) )
        {
        $self -> logger ("@$cmd started\n") ;
        } else
        {
        $self -> logger ("$cmd started\n") ;
        }

    async
        {
        my $data ;
        while ($self -> _read (\$data, 8192))
            {
            $self -> logger ("stdout ", $data, "\n") ;
            $self -> $on_stdout ($data) ;
            }
        waitpid( $pid, 0 );
        $self -> logger ("@$cmd ended\n") ;
        Coro::cede_notself () ;
        $self -> $on_exit ($?)  ;
        } ;

    async
        {
        my $data ;
        while ($self -> _read (\$data, 8192, undef, $err))
            {
            $self -> logger ("stderr ", $data, "\n") ;
            $self -> $on_stderr ($data) ;
            }
        } ;

    return $pid ;
    }


1 ;

