#!/usr/bin/perl -w

use strict;
package Tty;

use POSIX qw(:termios_h :unistd_h);
use Fcntl;
use Data::Dumper;
use Common;
use TtyTermios;

# you must open the port nonblocking, since if CRTSCTS is set
# and if the hw flow control pin is indicating stop, then open
# won't return till the pin input is changed
#  Soo, open nonblocking, set the termios to whatever you want and
# set the port flag to blocking.

########################################

# /usr/include/bits/ioctl-types.h
sub TIOCM_DTR { 0x0002 }
# /usr/include/asm-generic/ioctls.h
sub TIOCMBIS  { 0x5416 }
sub TIOCMBIC  { 0x5417 }

sub st_idle   { 0 }
sub st_locked { 1 }
sub st_opened   { 1<<1 }
sub st_confed   { 1<<2 }
sub st_blocked  { 1<<3 }

sub tty_lock($) { # TODO
    my $dev = shift;
    0;
}
sub tty_unlock($) { # TODO
    my $dev = shift;
    0;
}
sub tty_open($;$&) {
    my $dev = shift;
    my $baudbits = shift;
    my $ff  = shift;
    my %save = (
	dev     => $dev,
	state   => st_idle,
	fh      => undef,
	fd      => undef,
	termios => undef,
    );

    {
	if (tty_lock($dev) != 0) {
	    return \%save;
	}
	$save{state} |= st_locked;
    }

    {
	my $fh;
	if (!sysopen($fh, $dev, O_RDWR|O_NOCTTY|O_NONBLOCK)) {
	    Close(\%save);
	    die("cannot open $dev");
	}
	$save{fh} = $fh;
	$save{state} |= st_opened;
    }

    if (!isatty($save{fh})) {
	Close(\%save);
	die("$dev is not a tty");
    }

    $save{fd} = fileno($save{fh});


    # it isn't clear from doc how to copy a POSIX::Termios,
    # so I'll just get it twice.
    $save{termios} = POSIX::Termios->new();
    if (! $save{termios}->getattr($save{fd}) ) {
	Close(\%save);
	die("$dev termios->getattr() failure");
    }

    my $new_termios = POSIX::Termios->new();
    if (! $new_termios->getattr($save{fd}) ) {
	Close(\%save);
	die("$dev termios->getattr() failure");
    }

    if (defined($ff)) {
	&$ff($new_termios);
    }
    if (defined($baudbits)) {
	$new_termios->setispeed($baudbits);
	$new_termios->setospeed($baudbits);
    }

    if (! $new_termios->setattr($save{fd}) ) {
	Close(\%save);
	die("$dev termios->setattr() failure");
    }
    $save{state} |= st_confed;

    $save{fh}->autoflush(1);

    {
	my $flags = fcntl($save{fh}, F_GETFL, 0) ||
	    die "$dev: fcntl getfl failure\n";

	fcntl($save{fh}, F_SETFL, $flags & ~O_NONBLOCK) ||
	    die "$dev: fcntl setfl failure\n";
	$save{state} |= st_blocked;
    }

    %save;
}

sub tty_close($) {
    my $save = shift;

    return unless (defined($save));
    return unless (ref($save) eq "HASH");
    #my $dev     = $$save{dev};
    my $fh      = $$save{fh};
    my $fd      = $$save{fd};
    my $termios = $$save{termios};

    return unless ($fh && $fd >= 0);


    if ($$save{state} & st_blocked) {
	# don't care
	$$save{state} &= ~st_blocked;
    }
    if ($$save{state} & st_confed ) {
	$termios->setattr($fd) || warn("$$save{dev} termios setattr() failure");
	$$save{state} &= ~st_confed;
    }
    if ($$save{state} & st_opened ) {
	close($fh);
	$$save{state} &= ~st_opened;
    }
    if ($$save{state} & st_locked ) {
	tty_unlock($$save{dev});
	$$save{state} &= ~st_locked;
    }

    $save;
}

sub tty_modem_DTR($$) {
    my $fd = shift;
    my $dtr = shift;

    my $bits = TIOCM_DTR;

    if ($dtr) {
	ioctl($fd, TIOCMBIS, \$bits);
    } else {
	ioctl($fd, TIOCMBIC, \$bits);
    }
}

1;
