#!/usr/bin/perl

#
# Ohjelma lukemaan Mikon, OH2AUE OH-TNC2:sta Ilmari-2007:n
# (OH2SIX-11) antamia paketteja ja muuntamaan ne APRS-IS:n
# muotoon ja jakeluun..
# 
# Ohjelma ei valttamatta ole hyodyllinen muualla kuin
# Cape Vesijako lennonjohdossa...
#
# Maililukemanne voi poiketa dokumenteissa olevista esimerkeista,
# yms.  Enimmat saadettavat detaljit ovat aivan taman skriptin
# alussa.
#

$aprsuser = $ARGV[0];
if ((!defined($aprsuser))
    or (!($aprsuser =~ m/[A-Z]+[0-9][A-Z]+/))
    ) {
    usage("Sorry sir, must have your HAM-callsign as run parameter!");
}
shift @ARGV;

$asyname = $ARGV[0];
if ((!defined($asyname))
    or (! -c $asyname)
    or (! -r $asyname)) {
    usage("Sorry sir, /dev/ttyXX parameter bad");
}
shift @ARGV;

$asyspeed = 2400;
$asyspeed = $ARGV[0];
if ((!defined($asyspeed))) {
    usage("Sorry sir, tty port speed parameter missing");
}
shift @ARGV;


chdir "/home/oh2mqk" || chdir "/tmp";

#$aprsuser .= sprintf("%05d", rand(100000));
#$aprsuser = substr($aprsuser, 0, 9);


$inputsimulator = 0;
if ($ARGV[0] eq '--simulator') {
    $inputsimulator = 1;
}

open LOG, ">>", "ilmari.log";
autoflush LOG 1;

open ALOG, ">>", "ilmari.aprs";
autoflush ALOG 1;


@lines = ('','','','','','','');

use POSIX;      # POSIX facilities (for serial port)
use IO;         # Char by char IO
use Term::Cap;  # Terminal capabilities strings
use Math::Trig; # grad2rad()/rad2grad()

autoflush STDOUT 1;

$terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
$terminal->Trequire(qw/cl cm/);
#$terminal->Tgoto('cm', $col, $row, $FH);
#$terminal->Tputs('dl', $count, $FH);
#$terminal->Tpad($string, $count, $FH);

#$CursorVisible = $terminal->Tputs('vs',1); # StandOut cursor
#$CursorInvisible = $terminal->Tputs('vi',1); # Invisible cursor
#$B1 = $terminal->Tputs('md',1); # Start Bold
#$B0 = $terminal->Tputs('me',1); # End Bold (actually any)
#$TermHome = "\e[?5h".$terminal->Tputs('cl',1);

#$termios = new POSIX::Termios;
#$conio   = IO::Handle->new_from_fd(fileno(STDIN),'r')
#    or die "IO::Handle:n avaus STDIN:iin epäonnistui.";


#system "stty","sane";
#system "stty","-icanon","min","1","time","0", "-echo";

pipe ASYRDR,ASYWRT;

$pid = fork();
if ($pid == 0) { # Child!
    close ASYRDR;

    $ASY = POSIX::open($asyname,O_RDWR,0)
	or die "ei saa avattua sarjaporttia: ".$asyname;

    die "ei saa avattua sarjaporttia: ".$asyname if ($ASY < 0);


    $asyio   = IO::Handle->new_from_fd($ASY,'w+')
	or die "IO::Handle:n avaus sarjaporttiin epäonnistui.";

    $asyio->autoflush(true);
    
    #&asycanon ();

    #$sttycmd='stty sane -echo raw istrip speed 2400 -opost evenp inpck';
    #$sttycmd='stty speed 2400 evenp inpck istrip cread icanon -echo';
    #$sttycmd='stty speed 2400 clocal cread pass8 raw min 1';
    $sttycmd='stty speed '.$asyspeed.' clocal cread pass8 cooked min 1';

    system($sttycmd.' > /dev/null < '.$asyname);
    system($sttycmd.' > /dev/null < '.$asyname);
    #system('stty -a < '.$asyname);
    #sleep 1;

    #printf "Sarjaportti auki, kaapelitesti!\n";

    POSIX::tcflush($ASY, &POSIX::TCIOFLUSH);
    POSIX::tcflow ($ASY, &POSIX::TCION);


    & asyreader ;   # Does not return
}

close ASYWRT;

$stop_now = 0;

local $APRSIS = APRS::IS->new("rotate.aprs.net:14580", $aprsuser, ".");

if (!defined($APRSIS)) {
    printf "aprsazel: Failed to open APRS-IS socket!\n";
    exit 4;
}

$SIG{'QUIT'} = \&sighandler;
$SIG{'TERM'} = \&sighandler;


while ( &iomux(999) ) { };

kill(1, $pid) if ($pid > 1);


#system "stty","sane";
#printf "%s\n",$CursorVisible;

exit 0;


sub usage {
    local ($msg) = @_;

    printf("ilmari-ohtnc-to-aprsis.pl GW-Callsign /dev/ttyS7 2400\n");
    printf("  %s\n", $msg);

    exit 64;
}


sub sighandler {
    $stop_now = 1;
}


sub asycanon {
    $termios->getattr($ASY) or die "ei saanut getattr() dataa";
    $termios->setispeed(&POSIX::B19200);
    $termios->setospeed(&POSIX::B19200);
    $termios->setcflag( &POSIX::CS8   | &POSIX::CREAD | # 8N1, Rx on
			&POSIX::CLOCAL               ); # ignore modem
    $termios->setiflag( &POSIX::IGNPAR );    # no input parity checking
    $termios->setoflag( 0 );	# No output-flags
    $termios->setlflag( &POSIX::ICANON ); # Pick lines..

    $termios->setattr($ASY, &POSIX::TCSANOW); # Set the attribute set
}


sub asygetline {
    local $c;
    local $line = '';

    if ($inputsimulator) {
	$line = <SIMU>;
	select(undef, undef, undef, 0.01);

    } else {

	while ($asyio->sysread($c,1) && $c ne "\n") {
	    #printf "SYSGETLINE c= 0x%02x = '%s'\n",ord($c),$c;
	    $line .= $c;
	}
    }
    return $line;
}

sub asyreadn {
    local $num = shift;

    local $line = '';
    local $c;

    for (;$num > 0; --$num) {
	$asyio->sysread($c,1);
	$line .= $c;
    }
    $line;
}

sub asyreader {
    $SIG{'PIPE'} = 'DEFAULT'; # "death"

    local $line;

    autoflush STDERR 1;
    autoflush ASYWRT 1;

    # OH2SIX-11>APRS:!6120.99N/02502.44EO233338h/A=088.2,Mm

    if ($inputsimulator) {
	open SIMU, "<", "ilmari.sim";
    }


    local $poscount = 0;

    for(;;) {
	# TODO: Recognize Telemetry packet and preprocess it into oneliner
	$line = & asygetline;
	chomp $line;chomp $line;


	printf(LOG "%s\n", $line) if (!$inputsimulator);

	next if (length($line) < 2);

	#printf STDERR "ASY1:  %s\n", $line;

	if ($line =~ m/APRS[^:]*:!/o) {
	    # fix the APRS position fix message
	    # <-- OH2SIX-11>APRS,WIDE3-3:!6121.00N/02502.44EO135623h/A=00109.4m
	    # <-- OH2SIX-11>APRS:!6120.99N/02502.43EO035055h/A=091.7,Mm
	    # --> OH2SIX-12>APRS,OH2AUE,I:!6120.99N/02502.43EO/A=000289 035055h

	    local $out = $line;

	    ++$poscount;

	    if ($poscount & 1) {
		$tailtext = 'Ilmari-07a Balloon';
	    } else {
		$tailtext = 'http://wiki.ham.fi/Ilmari-2007';
	    }

	    if ($line =~ m%^OH2SIX-11>APRS[^:]*:!(\d{4}\.\d{2}[nNsS])/(\d{5}\.\d{2}[eEwW])(.)(\d{6}h)/A=(\d+)[.,mM]%o) {
		$out = sprintf("OH2SIX-12>APRS,%s,I:!%s/%s%s/A=%06d %s %s",
			       $aprsuser, $1, $2, $3, 3.28084* POSIX::strtol($5, 10), $4, $tailtext);
	    }
	    if ($line =~ m%^(OH2SIX-11>APRS[^:]*):(.*)%o) {
		$out2 = sprintf("%s,%s,I:%s", $1, $aprsuser, $2);
	    }

	    #printf STDERR "ASY2: <-  %s\n", $line;
	    #printf STDERR "ASY2: ->  %s\n", $out;
	    printf ASYWRT "%s\n", $out;
	    select(undef, undef, undef, 0.5);
	    printf ASYWRT "%s\n", $out2;
	    select(undef, undef, undef, 0.5);
	    next;
	}

	if ($line =~ m/:>Status:/o) {
	    # Glob 25 lines of input..
	    local $i, $l, $ll;
	    $ll = '';

	    for ($i=0; $i < 25; ++$i) {
		$l = & asygetline;
		chomp $l;
		printf(LOG "%s\n", $l) if (!$inputsimulator);
		#printf STDERR "ASY3:  %s\n", $l;
		chomp $l; chomp $l;
		$ll .= sprintf("%s ", $l) if (length($l) > 0);
	    }

	    #$ll =~ m{  }{ }go;  # Only single spaces..
	    $line = sprintf('OH2SIX-12>APRS,%s,I:"STAT: %s', $aprsuser, $ll);
	    #printf STDERR "ASY3: ->  %s\n", $line;
	    printf ASYWRT "%s\n", $line;
	    select(undef, undef, undef, 0.5);
	    next;
	}

	if ($line =~ m/:TeIemetry:/o) {
	    # Glob 25 lines of input..
	    local $i, $l, $ll;
	    $ll = '';

	    for ($i=0; $i < 25; ++$i) {
		$l = & asygetline;
		printf(LOG "%s\n", $l) if (!$inputsimulator);
		chomp $l;
		#printf STDERR "ASY4:  %s\n", $l;
		chomp $l; chomp $l;
		$ll .= sprintf("%s ", $l) if (length($l) > 0);
	    }
	    #$ll =~ m{  }{ }go;  # Only single spaces..
	    $line = sprintf('OH2SIX-12>APRS,%s,I:"TLEM: %s', $aprsuser, $ll);
	    #printf STDERR "TNC: ->  %s\n", $line;
	    printf ASYWRT "%s\n", $line;
	    select(undef, undef, undef, 0.5);
	    next;
	}

	next if ($line eq '    ');

	#printf STDERR "ASY5: ->  '%s'\n", $line;
	printf ASYWRT "%s\n", $line;
	select(undef, undef, undef, 0.5);
    }
}



sub iomux {
    local ($timeout) = @_;
    local $rin,$rout;

#printf "IOMUX(%.2f,%d) ",$timeout,$keyinput;

    $rin = '';
    #vec($rin,fileno(STDIN),1) = 1;
    vec($rin,fileno(ASYRDR),1) = 1;
    vec($rin,$APRSIS->fileno,1) = 1;


    return 0 if ($stop_now);


    local($nfound,$timeleft) = select($rout=$rin, undef, undef, $timeout);

    if ($keyinput) {
	printf "%s",$CursorInvisible;
    }

    if ($nfound == 0) {
#	printf "IOMUX-TIMEOUT!";
	return 1;
    }


    # What operator console tells ...
    while (0 && vec($rout,fileno(STDIN),1)) {

	local $line;

	$conio->blocking(0);
	$conio->sysread($line, 1);
	$conio->blocking(1);

	printf("CONread len=%d line='%s'\n", length($line), $line);

	if ($line eq 'X' or $line eq 'x') {
	    return 0;
	}

#	&komentotulkki($line);
	system "stty","-icanon","min","1","time","0";

	last;
    }

    # What OH-TNC tells...

    # OH2SIX-11>APRS:!6120.99N/02502.44EO233338h/A=088.2,Mm

    while (vec($rout,fileno(ASYRDR),1)) {
	local $line = <ASYRDR>;
	chomp $line;chomp $line;
	last if (length($line) < 1);
	last if (!($line =~ m/OH2SIX/o));

	printf "TNC:  %s\n",$line;
	printf ALOG "%d\t%s\n", time, $line if (!$inputsimulator);

	# Laheta positiopaketit APRS-IS verkkoon
	if (
	    # $line =~ m/[A-Z]:!\d\d/o  &&
	    !$inputsimulator
	    ) {
	    $APRSIS->sendline($line);
	}
	last;
    }

    # APRS has something for us..
    while (vec($rin,$APRSIS->fileno,1)) {
	local $line = $APRSIS->getline;
	if ($line =~ m/OH2SIX/o) {
	    printf "APRS: %s\n",$line;
	}
	last;
    }



    return 1;
}



#printf "'%s'\n", &within_viestikallio($x,$y);

sub within_viestikallio {
    local ($x,$y) = @_; # Easting, Northing

# Data pairs: Easting, Northing
    local (@poly) = ( 613.69, 548.36,
		       669.00, 510.00,
		       593.27, 440.77,
		       398.07, 497.69,
		       425.46, 593.46,
		       517.44, 533.63 );
    
    #@poly = ( 0, 0,  100, 0, 100, 100, 0, 100 );

# Number of numbers in the poly array
    local ($N) = 1+$#poly;

# Separate the corner coordinate vector into X and Y components
    local (@x)=();
    local (@y)=();
    local ($i,$ii);
    for ($i = 0; $i < $N; $i += 2) {
	$ii = $i/2;
	$x[$ii] = $poly[$i+0];
	$y[$ii] = $poly[$i+1];
    }

    $N = $N / 2;

    local ($allwithin)  = 1;
    local ($alloutside) = 1;

    local ($smallestinsidedist)  = 999999999999;
    local ($smallestoutsidedist) = 999999999999;
    local ($smallest_s1)         = 999999999999;

    local ($lowIsLeft)  =  99999999999;
    local ($highIsLeft) = -99999999999;

    local ($i1, $i2, $isLeft);

    for ($i1 = 0; $i1 < $N; ++$i1) {
	$i2 = ($i1 + 1) % $N;

	$isLeft = ( ($x[$i2] - $x[$i1]) * ($y - $y[$i1]) -
		    ($y[$i2] - $y[$i1]) * ($x - $x[$i1]) );

	$lowIsLeft  = $isLeft if ($lowIsLeft  > $isLeft);
	$highIsLeft = $isLeft if ($highIsLeft < $isLeft);

	printf "Vector %d  isLeft: %.3f\n", $i1, $isLeft;

	local ($l2) = ( ($x[$i1] - $x[$i2]) * ($x[$i1] - $x[$i2]) +
			($y[$i1] - $y[$i2]) * ($y[$i1] - $y[$i2]) );

	local ($u)  = ( ( ($x - $x[$i1]) * ($x[$i2] - $x[$i1]) +
			  ($y - $y[$i1]) * ($y[$i2] - $y[$i1]) ) / $l2 );

	local ($x_) = $x[$i1] + $u * ($x[$i2] - $x[$i1]);
	local ($y_) = $y[$i1] + $u * ($y[$i2] - $y[$i1]);

	local ($s)  = vdist( $x, $y, $x_, $y_);
	local ($s1) = vdist( $x, $y, $x[$i1], $y[$i1] );

	local ($within) = ( iswithin($x[$i1], $x_, $x[$i2]) &&
			    iswithin($y[$i1], $y_, $y[$i2]) );

	$smallest_s1 = $s1 if ($smallest_s1 > $s1);

	if ($within) {
	    $alloutside = 0;
	    $smallestinsidedist  = $s if ($smallestinsidedist > $s);
	} else {
	    $allwithin  = 0;
	    #$smallestoutsidedist = $s if ($smallestoutsidedist > $s);
	}


	printf("Edge: %d xsect: %9.3f/%9.3f is%s within this edge; dist: %.3f\n",
	       $i1, $x_, $y_,
	       $within ? "   " : "n't",
	       $s);

    }

    printf "OUTSIDE\n" if ($alloutside);
    printf "INSIDE\n"  if ($allwithin);

    printf "IsLeft low=%.1f high=%.1f\n", $lowIsLeft, $highIsLeft;

    printf("Smallest cross-sect distance to within an edge: %.3f\n",
	   $smallestinsidedist);
    printf("Smallest cross-sect distance to edge extensions: %.3f\n",
	   $smallestoutsidedist);
    printf("Smallest corner distance: %.3f\n", $smallest_s1);
    
    if (($lowIsLeft >= 0 && $highIsLeft >= 0) ||
	($lowIsLeft <= 0 && $highIsLeft <= 0) ) {
	printf "INSIDE\n";
	$inside = 1;
    } else {
	printf "OUTSIDE\n";
	$inside = 0;
    }

    $dpydist = $smallestinsidedist;
    if ($dpydist > $smallest_s1) {
	$dpydist = $smallest_s1;
	$inside = 2;
    }

    $dpydist = 99.9 if ($dpydist > 99.9);

    if ($dpydist < 10) {
	return sprintf("5.9. %d %4.2f", $inside, $dpydist);
    } else {
	return sprintf("5.9. %d %4.1f", $inside, $dpydist);
    }
}


sub iswithin {
    local ($low, $test, $high) = @_;

    return 1 if ($low <= $test && $test <= $high);
    # If the values are in reverse order..
    return 1 if ($low >= $test && $test >= $high);
    # Not within the end points
    return 0;
}

sub vdist {
    local ($x1, $y1, $x2, $y2) = @_;

    return sqrt( ($x1 - $x2) * ($x1 - $x2) +
		 ($y1 - $y2) * ($y1 - $y2) );
}

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


package APRS::IS;

use 5.006;
use strict;
use warnings;

use IO::Socket::INET;
use IO::Select;

sub aprspass {
    my ($a, $h) = (0, 0);
    map($h ^= ord(uc) << ($a^=8),
	pop =~ m/./g);
    return ($h ^ 29666);
}


sub new {
    my $that = shift;
    my $class = ref($that) || $that;
    # my %atts = @_;
    my ($url, $mycall, $target_filter_re) = @_; # Just one arg: APRS-IS URL (host:port)

    # Register the callers package.
    my $self = { caller_pkg => (caller)[0] };

    bless ($self, $class);

    # parse attrs
    $self->{sock} = IO::Socket::INET->new($url);

    if (!defined($self->{sock})) {
        die(__PACKAGE__.": APRS::IS->new(".$url.")  failure: ".$!."\n");
    }


    $self->{select} = IO::Select->new( $self->{sock} );

    $self->{aprsmycall} = uc( $mycall );
    my $mycallbase = $mycall;
    if ($mycallbase =~ m/^([^-]+)-.*/) {
	$mycallbase = $1;
    }
    $self->{aprspass}   = aprspass( $mycallbase );

    # $self->{filterre} = $target_filter_re;

#   printf ( "APRS::IS->new()  mycall='%s'  aprspass=%d   filterre='%s'\n",
#            $self->{aprsmycall}, $self->{aprspass}, $self->{filterre} );


##
##    *  Need to send on initial connect the following logon line:
##      user callsign pass passcode vers appname versionnum rest_of_line
##
##      callsign = login callsign-SSID
##      passcode = login passcode per APRS-IS algorithm, -1 = read-only
##      appname = application name (1 word)
##      versionnum = application version number (no spaces)
##      rest_of_line = server command if connecting to a port that supports commands (see Server Commands)
##
##      (appname and versionnum should not exceed 15 characters)
##
##       
##    * Need to recognize both TCPIP and TCPXX as TCP/IP stations
##    * Need to provide a means to perform the user validation. This can either be a user entered password,
##      or a client program can automatically figure out the password given the callsign.
##      If the later is used, it is the client programmer's responsibility to be certain that non-amateurs
##      are not given registrations that can validate themselves in APRS-IS.
##    * Probably a good idea to perform some feedback about the limitations of TCPIP without a registration number.
##

    $self->{sock}->blocking(1);
    $self->{sock}->printf( "user %s pass %s vers %s\n",
			   $self->{aprsmycall},
			   $self->{aprspass}, # -- but we are read-only !
			   $main::VERSION
    );
    $self->{sock}->flush;

    $self->{sock}->blocking(1);

    # my $discard = $self->getline();

    $self;
}

# -------------------------------------------------------------------------
# Get socket fileno for select..

sub fileno {
    my $self = shift;

    return $self->{sock}->fileno;
}

# -------------------------------------------------------------------------
# Get a line, or wait 1 sec


sub getline {
    my $self = shift;

    my @ready;

    if (@ready = $self->{select}->can_read(1)) { # Wait at most 1.0 seconds

	# We have only one socket...

	return $self->{sock}->getline;

    }

    undef;
}

sub sendline {
    my $self = shift;
    my $line = shift;

    my @ready;

    $self->{sock}->blocking(1);
    $self->{sock}->printf( "%s\n", $line);
    $self->{sock}->flush;

    $self->{sock}->blocking(1);

    undef;
}

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