#!/usr/bin/perl
# 20140930 from : http://seb.marque.free.fr/fichiers/scripts/perl/plandevol
# 20141001 - Add a log file
# l'option -w a été enlevée pour éviter l'affichage des warnings inutiles décrits ci-dessous:
# Use of implicit split to @_ is deprecated at fgfs/plandevol-dev line ...
# main::construction_route() called too early to check prototype at fgfs/plandevol-dev line ...
#######################################################################################################################################################
##             ***********************************************
##             ***** TRES IMPORTANT ***** VERY IMPORTANT *****
##             ***********************************************
##
## CE SCRIPT NE PROPOSE PAS UNE SOLUTION DE VOL FIABLE POUR LA CONSTRUCTION D'UN PLAN DE VOL RÉEL!!!!!!!!
## IL N'EST QU'UNE SOLUTION POUR PROPOSER UN TRAJET DE _LOISIR_ AVEC FLIGHTGEAR FS ET NE GARANTIT EN AUCUN CAS LA FIABILITÉ DES INFORMATIONS
## QU'IL DÉLIVRE
##
#######################################################################################################################################################
######################################################################################################################################################
##
## script écrit par seb marque, paris, france
##
## plandevol, version 0.5.9 qui s'approche dangereusement de la version 0.6.0
## --help pour une aide sur l'utilisation du script
##
## script placé sous licence GPL par Sébastien MARQUE
## texte complet disponible sur http://www.gnu.org/licenses/gpl.txt
##
# This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#######################################################################################################################################################
##
## les fonctions connect, set_prop, get_prop et send proviennent du script telnet.pl trouvé dans le code source de fgfs 0.98 (de Curtis L. Olson,
##   avec "courtesy" pour Melchior Franz.
##
## les fonctions round, ll2xyz, xyz2ll, llll2dir (dont provient llll2dir_), distance (dont provient distance_) et coord_dist_sqr proviennent du script
##   de Melchior Franz "freq" trouvé sur http://members.aon.at/mfranz/freq, en attente de leur remplacement éventuel par les fonctions de Math::Trig
##
######################################################################################################################################################
##
## bugs connus: si une balise est situé dans l'enceinte du point d'arrivée elle n'est pas détectée... c'est dommage
##      fixé la compatibilité avec les différentes versions de nav.dat.gz
##
## version 0.6 -> réglage auto des instruments de vol
##             -> intégration des fix dans le plan de vol si nécessaire
##             -> gestion des sid/star
##             -> nettoyage du code
##
######################################################################################################################################################

use strict;
use POSIX qw(ceil floor);
use Getopt::Long;       # pour récupérer les options en ligne de commande
use IO::Socket;         # pour la connexion telnet avec FlightGear
use Env qw(HOME FGROOT FGHOME);    # pour lire HOME FGHOME et FGROOT
my $os = $^O;
my $perl_dir = '/home/geoff/bin';
my $PATH_SEP = '/';
my $temp_dir = '/tmp';
if ($os =~ /win/i) {
    $perl_dir = 'C:\GTools\perl';
    $temp_dir = $perl_dir;
    $PATH_SEP = "\\";
}
unshift(@INC, $perl_dir);
require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl' Check paths in \@INC...\n";
require 'fg_wsg84.pl' or die "Unable to load fg_wsg84.pl ...\n";
# log file stuff
our ($LF);
my $pgmname = $0;
if ($pgmname =~ /(\\|\/)/) {
    my @tmpsp = split(/(\\|\/)/,$pgmname);
    $pgmname = $tmpsp[-1];
}
my $outfile = $temp_dir.$PATH_SEP."temp.$pgmname.txt";
my $out_xg = $temp_dir.$PATH_SEP."tempout.xg";

open_log($outfile);

## DECLARATION DES VARIABLES GLOBALES
#####################################
my @depart = (undef, "LFPG", undef, undef,undef);
                # tableau contenant des infos sur l'aéroport de départ (sid, ICAO, lat, lon, nom complet de l'aéroport)
my @arrivee = (undef, "LFBD", undef, undef,undef, undef);
                # tableau contenant des infos sur l'aéroport d'arrivée (star, ICAO, lat, lon, nom complet de l'aéroport)

my $fgfs;           # socket de connexion à fgfs
my @route;          # contient la route à suivre
my ($navaid, $fix);     # pointent sur les tableaux contenant les aides à la navigation
my $erreur;         # contient les messages d'erreur éventuels
my $version;            # pour la compatibilité avec différentes version de nav.dat.gz
my $sous_fonction;      # pointe vers des sous fonctions anonymes définies localement
my $quiet = 1;  # extra output

# VARIABLES DES OPTIONS DU SCRIPT
#################################
my $FGROOT = (exists $ENV{FGROOT})? $FGROOT : "F:\\fgdata";
my $FGHOME = (exists $ENV{FGHOME})? $FGHOME : "$HOME/.fgfs";
my $vor_a_vor;          # exclusivement du vor
my $vor_preferes;       # si on veut du vor, mais sinon du ndb
my $deviation_max = 30;     # virage maximal
my $dist_min = 10;      # distance minimale entre deux waypoints
my $km;             # affichage des distances en kilomètres
my $help;           # demande d'aide
my $csv_conf=':,';      # les séparateurs pour les fichiers .csv
my $no_stdout;          # pas de sortie sur le terminal
my ($sidx, $starx);     # protocole sid/star demandé sans spécifier de piste
my ($sid, $star);       # protocole sid/star demandé en spécifiant une piste
my $add_couleur;     # applique des couleurs à la sortie standard
my ($com, $com_dep, $com_app);  # pour afficher les fréqences de communication
my $INSTRFILE;          # création d'un fichier .xml
my $wpt;            #enregistrer la route
my $WPFILE;         # le fichier dans lequel on enregistre les points de passage
my $CSVFILE;        # le fichier dans lequel on enregistre les points de passage
my $XGFILE;         # le fichier dans lequel on enregistre les points de passage

my $options = GetOptions ( "v|vor-a-vor" => \$vor_a_vor,
               "preferer-vor"=> \$vor_preferes,
               "km"          => \$km,
               "dev-max=i"   => \$deviation_max,
               "dist-min=i"  => \$dist_min,
               "fg-root=s"   => \$FGROOT,
               "wpt"         => \$wpt,
               "instr"       => \$INSTRFILE,
               "csv=s"       => \$CSVFILE,
               "xg=s"        => \$XGFILE,
               "csv-conf=s"  => \$csv_conf,
               "d|dep=s"     => \$depart[1],
               "a|arr=s"     => \$arrivee[1],
               "no-stdout"   => \$no_stdout,
               "help"        => \$help,
               "sidx"    => \$sidx,
               "starx"   => \$starx,
               "sid=s"   => \$sid,
               "star=s"  => \$star,
               "com"     => \$com,
               "com-dep"     => \$com_dep,
               "com-app"     => \$com_app,
               "ansi"     => \$add_couleur);

($com_dep, $com_app) = ($com, $com) if $com;

## FICHIERS UTILISÉS PAR LE SCRIPT
## à modifier selon sa propre configuration
## accepte les fichiers en clair, ou gzipés
###########################################
my $NAVFILE       = "$FGROOT/Navaids/nav.dat.gz";   # le fichier contenant les aides à la navigation
my $FIXFILE       = "$FGROOT/Navaids/fix.dat.gz";   # le fichier contenant les fix
my $SIDFILE       = "$FGROOT/NavAids/sid.dat";      # le fichier contenant les procédures SID
my $STARFILE      = "$FGROOT/NavAids/star.dat";     # le fichier contenant les procédure STAR
my $APTFILE       = "$FGROOT/Airports/apt.dat.gz";  # le fichier contenant les aéroports

## DÉCLARÉ COMME VARIABLE MAIS UTILISÉ COMME CONSTANTE
######################################################
my $texte_aide = <<EOH;
plandevol, v. 0.6.0

trouve une route de balises entre deux points.

syntaxe: plandevol [-v | --vor-a-vor] [--preferer-vor] [--km]
                   [--fg-root </PATH/TO/FG_DATA_FILES>]
                   [--wpt]
                   [--csv </PATH/TO/CSV_FILE>]
                   [--csv-conf <colonnedécimal>]
                   [--xg </PATH/TO/XG_FILE>]
                   [-d | --dep <point de départ>]
                   [-a | --arr <point d'arrivée>]
                   [--dev-max <angle en degrés>]
                   [--dist-min <distance en km>]
                   [--sid <nom de piste>][--star <nom de piste>]
                   [--sidx][--starx]
                   [--com-dep][--com-app][--com]
                   [--ansi]
                   [--help]

-v | --vor-a-vor : ne sélectionne que les balises VOR et VOR-DME (pas de TACAN)

--preferer-vor   : route construite avec NDB et VOR, avec une préférence pour
                   les balises VOR

--km             : affiche la distance en km (défaut: affichage en nm)

--fg-root        : chemin contenant les fichiers de FlightGear
                   défaut: $FGROOT

--wpt            : enregistre la route dans \$FGROOT/Routes (nommage auto)
                   directory must exist. It will not be created.

--csv            : fichier CSV ( séparateur = : , virgule décimale = , ) pour
                   affichage du trajet en graphique (via oocalc par exemple)

--xg             : fichier XG. Can be viewed by polyView2D. See
                   https://sites.google.com/site/polyview2d/

--csv-conf       : paramètre les séparateurs de colonne et de décimale pour la
                   fabrication du fichier csv.
                   format = séparateurdécimale (ex: --csv-conf=?ù) pour des
                            colonnes séparées par le caractère '?', et la
                            virgule représentée par le caractère 'ù'. par défaut
                            --csv-conf=$csv_conf

-d | --dep       : point de départ. il est possible de spécifier:
                     - soit le code oaci de l'aéroport (ex: --dep=lfqq),
                       défaut --dep=lfpt --arr=lfbd
                     - soit la position actuelle de l'avion dans fgfs
                       (ex: --dep=telnet:5401)
                     - soit une position exprimée en lat, long
                       (ex: --dep=[45.564,-2.066])

-a | --arr       : point d'arrivée. même possibilités que l'option --dep

--dev-max        : déviation maximale d'une balise à une autre par rapport au
                   trajet en cours (défaut: $deviation_max°)

--dist-min       : distance minimale entre deux balises (défaut: $dist_min km)

--sid --star     : cherche le trajet en tenant compte de la procédure sid (ou
                   star) de la piste <nom de la piste> codé sur deux ou trois
                   caractères (ex: --sid 09 --star 23, ou --sid 09R --star 23)
                   si aucun indicatif de piste (R, C ou L) n'est fourni ils
                   seront tous les trois inclus dans la recherche de procédure

--sidx, --starx  : idem que --sid et --star, mais avec une piste choisie par le
                   programme:
                   - pour le moment, le choix se porte sur la piste dont la
                     procédure rapproche du point de départ/arrivée
                   - dans le futur, il est prévu une implémentation avec les
                     METAR (décollage/atterrissage face au vent)
                   - selon l'évolution du fichier apt.dat, on peut imaginer un
                     choix en fonction des pistes réellement usitées

--com-dep,
--com-app    : affichent respectivement les fréquences COM pour le départ (dep)
               ou l'approche (app)

--com        : affiche les fréquences COM pour le départ et l'arrivée (équi-
               valent de --com-dep --com-app)

--ansi      : affiche les étapes en couleurs, pour les terminaux qui ne
               supportent pas la norme ANSI, ou pour redirection du résultat

--help           : affiche ce message d'aide et quitte (même si d'autres options
                   ont été spécifiées)
EOH

my $PI   = 3.1415926535897932384626433832795029;
my $D2R  = $PI / 180;
my $R2D  = 180 / $PI;
my $ERAD = 6378138.12;
#my $ERAD = 6378;
my $NDB  = 2;
my $VOR  = 3;
my $KM2FEET = 3280.84;

### sub prt($) { print shift; }

# FONCTIONS DE CONNEXION AVEC FGFS PAR TELNET
#############################################
sub get_prop($$) {
    my( $handle ) = shift;

    &send( $handle, "get " . shift );
    eof $handle and die "\nconnection closed by host";
    $_ = <$handle>;
    s/\015?\012$//;
    /^-ERR (.*)/ and die "\nfgfs error: $1\n";

    return $_;
}


sub set_prop($$$) {
    my( $handle ) = shift;
    my( $prop ) = shift;
    my( $value ) = shift;

    &send( $handle, "set $prop $value");

    # eof $handle and die "\nconnection closed by host";
}

sub send($$) {
    my( $handle ) = shift;

    print $handle shift, "\015\012";
}

sub connect($$$) {
    my( $host ) = shift;
    my( $port ) = shift;
    my( $timeout ) = (shift || 120);
    my( $socket );
    STDOUT->autoflush(1);
    while ($timeout--) {
        if ($socket = IO::Socket::INET->new( Proto => 'tcp',
                                             PeerAddr => $host,
                                             PeerPort => $port) )
        {
            $socket->autoflush(1);
            return $socket;
        }
        prt( "Attempting to connect to $host ... " . $timeout . "\n" );
        sleep(1);
    }
    return 0;
}

# FONCTIONS DE CALCULS GEOGRAPHIQUES
# par Frank Melchior
####################################

sub round($)
{
    my $i = shift;
    my $m = (shift or 1);
    $i /= $m;
    $i = $i - &floor($i) >= 0.5 ? &ceil($i) : &floor($i);
    $i *= $m;
    return $i;
}

sub coord_dist_sq($$$$$$)
{
    my ($xa, $ya, $za, $xb, $yb, $zb) = @_;
    my $x = $xb - $xa;
    my $y = $yb - $ya;
    my $z = $zb - $za;
    return $x * $x + $y * $y + $z * $z;
}

sub ll2xyz($$)
{
    my $lat = (shift) * $D2R;
    my $lon = (shift) * $D2R;
    my $cosphi = cos $lat;
    my $di = $cosphi * cos $lon;
    my $dj = $cosphi * sin $lon;
    my $dk = sin $lat;
    return ($di, $dj, $dk);
}

# return km distance
sub distance_($)
{
    my $t  = shift;
    my @ll1 = ll2xyz($t->[0], $t->[1]);
    my @ll2 = ll2xyz($t->[2], $t->[3]);
    return $ERAD * sqrt(coord_dist_sq($ll1[0], $ll1[1], $ll1[2], $ll2[0], $ll2[1], $ll2[2])) / 1000;
}

sub llll2dir_($) {
    my $t = shift;
  
    my $latA = ($t->[0]) * $D2R;
    my $lonA = ($t->[1]) * $D2R;
    my $latB = ($t->[2]) * $D2R;
    my $lonB = ($t->[3]) * $D2R;
    my $xdist = sin($lonB - $lonA) * $ERAD * cos(($latA + $latB) / 2);
    my $ydist = sin($latB - $latA) * $ERAD;
    my $dir = atan2($xdist, $ydist) * $R2D;
    $dir += 360 if $dir < 0;
    return $dir;
}

# FONCTIONS DE CALCUL DU TRAJET
# passed reference to
# my @depart = (undef, "LFPG", undef, undef,undef);
# or @arrivee ...
###############################

sub configure_extremite ($$$) {
    my ($extremite, $proc, $procx) = @_;
    my $extremite_ok;   # positionné à 1 si l'extrémité a pu être configuré,
                # sera la valeur de retour de la fonction

    sub getPositionParTelnet ($) {
        # si on est pas déjà connecté, alors on se connecte
        if (!$fgfs) {
            if ( !($fgfs = &connect("localhost", $_[0], 5)) ) {
                prt("Impossible de se connecter\n");
            }
        }

        # on récupère la position actuelle de l'appareil
        my $lat = get_prop ($fgfs,"/position/latitude-deg[0]");
        my $lon = get_prop ($fgfs, "/position/longitude-deg[0]");

        # si la postion est trouvée (limitation: ~ est différente de 0°00'00''N 0°00'00''E)
        if ($lat && $lon) {
            $extremite_ok = 1;
            return $lat, $lon;
        } else {
            $erreur = "Impossible de déterminer la position actuelle de l'appareil\n";
        }
    }

    $sous_fonction = sub {
        my @donnees_aeroport;
        my ($ver,$rtype);
        # si le fichier de base d'aéroports existe, on l'ouvre sinon on termine le programme
        if ( -e $APTFILE ) {
            open (APT, "gzip -d -c $APTFILE|") or die "je ne peux pas ouvrir $APTFILE\n" ;
            while (<APT>) {
                if (/^(\d+)\s+Version\s+/) {
                    $ver = $1;
                    last;
                }
            }
        } else {
            prt("fichier $APTFILE introuvable\n");
            prt("veuillez vérifier \$FGROOT\n");
            prt("ou utilisez l'option --fg-root=répertoire\n");
            prt("ou encore modifiez le script ver ligne 80\n");
            exit 1;
        }
        if ($ver) {
            prt("Searching file $APTFILE, version $ver, for ${$extremite}[1]... moment...\n") if (!$quiet);
        } else {
            close APT;
            prt("Failed to find version in $APTFILE!\n");
            exit 1;
        }
        
        # on parcours le fichier à la recherche de l'aéroport souhaité
        while (<APT>) {
            if (/^1\s+-?\d+\s\d\s\d\s(\w+)\s(.+)/ && $1 eq $_[0]->[1]) {
                chomp;
                prt("Airport: $_\n") if (!$quiet);
                my @header = split (/\s+/, $_, 6);
                push @donnees_aeroport, \@header;
                my $autre_bout;
                foreach (<APT>) {
                    last if /^\s*$/;
                    my @donnee = split (/\s+/, $_);
                    $rtype = $donnee[0];
                    # s'il s'agit d'une piste, on la renomme en ajoutant son autre extrémité
                    if ($rtype == 10 && $donnee[3] ne 'xxx') {
                        # 0   1          2          3   4       5    6         7         8   9       0 1 2 3    4 5
                        # 10  36.962213  127.031071 14x 131.52  8208 1595.0620 0000.0000 150 321321  1 0 3 0.25 0 0300.0300
                        # 10  36.969145  127.020106 xxx 221.51   329 0.0       0.0        75 161161  1 0 0 0.25 0 
                        $donnee[3] =~ /(..)(.)/;
                        $autre_bout = ($1 > 18)? $1 - 18 : $1 + 18;
                        $autre_bout = '0'.$autre_bout if ($autre_bout < 10);
                        $autre_bout .= 'L' if ($2 eq 'R');
                        $autre_bout .= 'R' if ($2 eq 'L');
                        $autre_bout .= 'C' if ($2 eq 'C');
                        if ($2 eq 'x') {
                            $donnee[3]   = $1.' ';
                            $autre_bout .= ' ';
                        }
                        $donnee[3] = $donnee[3].'/'.$autre_bout;
                        push (@donnees_aeroport, \@donnee)
                    } elsif ($rtype == 100) {
                        # See version 1000 specs
                        # 0   1     2 3 4    5 6 7 8  9           10           11   12   13 14 15 16 17 18          19           20   21   22 23 24 25
                        # 100 29.87 3 0 0.00 1 2 1 16 43.91080605 004.90321905 0.00 0.00 2  0  0  0  34 43.90662331 004.90428974 0.00 0.00 2  0  0  0
                        my $rwy = $donnee[8];
                        my $rwynm = $rwy;
                        $rwynm =~ /(..)(.)/;
                        $autre_bout = ($1 > 18)? $1 - 18 : $1 + 18;
                        $autre_bout = '0'.$autre_bout if ($autre_bout < 10);
                        $autre_bout .= 'L' if ($2 eq 'R');
                        $autre_bout .= 'R' if ($2 eq 'L');
                        $autre_bout .= 'C' if ($2 eq 'C');
                        if ($2 eq 'x') {
                            $rwy = $1.' ';
                            $autre_bout .= ' ';
                        }
                        $rwynm = $rwy.'/'.$autre_bout;
                        my $rlat1 = $donnee[9];  # $of_lat1
                        my $rlon1 = $donnee[10]; # $of_lon1
                        my $rlat2 = $donnee[18]; # $of_lat2
                        my $rlon2 = $donnee[19]; # $of_lon2
                        my $rlat = ($rlat1 + $rlat2) / 2;
                        my $rlon = ($rlon1 + $rlon2) / 2;
                        my $dist = distance_( [$rlat1, $rlon1, $rlat2, $rlon2] );
                        $dist = int( $dist * $KM2FEET );    # runway length, in feet
                        my $az1  = llll2dir_( [$rlat1, $rlon1, $rlat2, $rlon2] );
                        $az1 = round( $az1 * 100 ) / 100;
                        my @a = ();
                        #push(@a,[10, $rlat, $rlon, $rwynm, $az1, $dist, 0.0, 0.0, 75, 161161, 1, 0, 0, 0.25, 0, 0]); 
                        @a = (10, $rlat, $rlon, $rwynm, $az1, $dist, 0.0, 0.0, 75, 161161, 1, 0, 0, 0.25, 0, 0);
                        prt("Runway: 10, $rlat, $rlon, $rwynm, $az1, $dist, ...\n") if (!$quiet);
                        push (@donnees_aeroport, \@a);
                    }
                    # on garde aussi les fréquences COM...
                    push (@donnees_aeroport, \@donnee) if (($rtype >= 50)&&($rtype <= 56));
                }
            }
        }
        close (APT);
        prt("Closed $APTFILE...\n") if (!$quiet);
        
        # on récupère ses coordonnées de la première piste listée... pour l'instant
        my $lcnt = scalar @donnees_aeroport;
        if ($lcnt > 0) {
            my ($i,$ra,$alat,$alon);
            for ($i = 0; $i < $lcnt; $i++) {
                $ra = $donnees_aeroport[$i];
                $rtype = ${$ra}[0];
                ### prt("$i: rtype $rtype\n");
                if ($rtype == 10) {
                    $alat = ${$ra}[1];
                    $alon = ${$ra}[2];
                    $extremite_ok = 1;
                    last;
                }
            }
            if ($extremite_ok) {
                prt("Success: lat/lon $alat,$alon, $lcnt records...\n") if (!$quiet);
                # return @{$donnees_aeroport[1]}[1], @{$donnees_aeroport[1]}[2], \@donnees_aeroport;
                return $alat,$alon,\@donnees_aeroport;
            }
            $erreur = 'No runways found for '.$_[0]->[1]."...";
        } else {
            # ces lignes ne atteintes que si aucun aéroport a été trouvé dans la base
            $erreur = $_[0]->[1]." n'a pas été trouvé dans la base de données aéroports...";
        }
    };

    # to UPPER case
    $extremite->[1]   =~ tr/a-z/A-Z/;

    if ($extremite->[1] =~ /^TELNET:(\d+)/) {       # position actuelle de l'appareil, connue par telnet
        $extremite->[1] = "ici";
        ($extremite->[2], $extremite->[3]) = getPositionParTelnet ($1);
        $extremite->[4] = [[0, undef, undef, undef, undef, "position au ".`date`]];
        ($extremite->[0], $$proc, $$procx) = (undef, undef, undef);
    }
    elsif ($extremite->[1] =~ /^\[(.+),(.+)\]$/) {      # position exprimée en coordonnées cartésiennes
        $extremite->[1] = "pos";
        ($extremite->[2], $extremite->[3]) = ($1, $2);
        $extremite->[4] = [[0, undef, undef, undef,undef, $1.", ".$2]];
        if (abs($extremite->[2])<=90 && abs($extremite->[3])<=180) {
            $extremite_ok = 1;
        } else {
            $erreur = "format de coordonnées inconnu...: ".$extremite->[2]." ".$extremite->[3];
        }
        ($extremite->[0], $$proc, $$procx) = (undef, undef, undef);
    }
    else {                          # position par nom de l'aéroport
        # set positions and runway array
        ($extremite->[2], $extremite->[3], $extremite->[4]) = &$sous_fonction ($extremite);
    }

    # on ferme la connexion avec fgfs
    close ($fgfs) if $fgfs;

    # on retourne le résultat de nos recherches
    return $extremite_ok;
}

# NAV_TO_RAM
############
sub nav_to_ram ($$$) {
    my ($fichier, $phrase, $decale) = @_;
    
    prt("Loading file ${$fichier}, p=$phrase d=$decale\n") if (!$quiet);
    
    my @selection; # tableau qui va contenir les aides à la navigation utiles
    my $marge = 2;

    my $lat_sup = (($depart[2] >= $arrivee[2])? $depart[2]:$arrivee[2]) + $marge;
    my $lat_inf = (($depart[2] <= $arrivee[2])? $depart[2]:$arrivee[2]) - $marge;

    my $long_sup = (($depart[3] >= $arrivee[3])? $depart[3]:$arrivee[3]) + $marge;
    my $long_inf = (($depart[3] <= $arrivee[3])? $depart[3]:$arrivee[3]) - $marge;

    prt("For lat/lon $lat_sup,$long_sup $lat_inf,$long_inf...\n") if (!$quiet);
    
    if ( -e $$fichier ) {
        $$fichier =~ /.+\.(.+)$/;
        my $fichier_traite = ($1 eq 'gz')? 'gzip -d -c '.$$fichier.'|' : $$fichier;
        open (NAV, $fichier_traite) or die "je ne peux pas ouvrir $$fichier\n" ;
    } else {
        die "fichier $$fichier introuvable\n";
    }

    # on détermine la version du fichier nav.dat
    if ($$fichier eq $NAVFILE) {
        while (<NAV>) {
            if (/^(\d+) Version/) {
                $version = $1;
                last;
            }
        }
        # si la version est supérieure à 6.00 on incrémente de 1 les index des tableaux
        $version = ($version > 600)? 1 : 0;
    }

    my $ils = ($version)? '^(4|5)\s+\S+\s+\S+\s+\S+\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\S+)\s+(...)\s*'
                : '^(4|5)\s+\S+\s+\S+\s+\S+\s+(\S+)\s+\S+\s+\S+\s+(\S+)\s+(...)\s*';

    # on parcourt le fichier pour ne conserver que les balises intéressantes
    while (<NAV>) {
        chomp;
        if (/$phrase/) {
            push @selection, $_ if ($decale
                        &&  $2 <= $lat_sup
                        &&  $2 >= $lat_inf
                        &&  $3 <= $long_sup
                        &&  $3 >= $long_inf);
            push @selection, $_ if (!$decale
                        &&  $1 <= $lat_sup
                        &&  $1 >= $lat_inf
                        &&  $2 <= $long_sup
                        &&  $2 >= $long_inf);
            next;
        }
        # si par hasard on trouve des infos sur les balises ILS de notre aéroport d'arrivée, pourquoi se gêner?
        if (/$ils/ && $3 eq $arrivee[1]) { push (@{$arrivee[4]}, [$1, $4, $2/100]); }
    }
    close (NAV) or die "je ne peux pas fermer $$fichier";
    prt("Close ${$fichier}, returing ".scalar @selection." items.\n") if (!$quiet);
    
    return @selection;
}

# FONCTIONS DE CALCUL DU TRAJET (HORS SID/STAR)
###############################################

sub getNavAidNearestMidPoint ($$$) {
    my $leg         = $_[0];
    my $milieu      = $_[1];

    my @ref_dist    = (undef, undef, $_[2], $_[2]);
    my @ref_navaid  = (undef, undef, undef, undef);

    my $heading_from = llll2dir_ ( [$leg->[0], $leg->[1], $milieu->[0], $milieu->[1]] );
    my $heading_to   = llll2dir_ ( [$milieu->[0], $milieu->[1], $leg->[2], $leg->[3]] );

    #RECHERCHE DE LA BALISE LA PLUS PROCHE
    prt("Searching ".scalar @$navaid."... hdgs from $heading_from to $heading_to...\n") if (!$quiet);
    
    for (my $index = 0; $index < @$navaid; $index++) {
        # on récupère le type et les coordonnées
        # $1: type de balise
        # $2: latitude
        # $3: longitude
        $navaid->[$index] =~ /^(.)\s+(\S+)\s+(\S+)\s/;

        # on saute à la prochaine itération si la balise testée est celle d'une des
        # extrémités du segment
        next if ( ($2 == $leg->[0] && $3 == $leg->[1]) ||
              ($2 == $leg->[2] && $3 == $leg->[3]) );

        # on calcule l'écart de route en degrés
        my $deviation_to   = abs(llll2dir_ ([$leg->[0], $leg->[1], $2, $3]) - $heading_from);
        my $deviation_from = abs(llll2dir_ ([$2, $3, $leg->[2], $leg->[3]]) - $heading_to);

        # on saute à la prochaine itération si l'écart est supérieur à l'écart autorisé
        next if ($deviation_to > $deviation_max && $deviation_from > $deviation_max);

        # on calcule les distances...
        my $navaid_dist = distance_( [$milieu->[0], $milieu->[1], $2, $3] );
        my $dist_to     = distance_( [$leg->[0], $leg->[1], $2, $3] );
        my $dist_from   = distance_( [$2, $3, $leg->[2], $leg->[3]] );

        # si c'est la plus proche et si la distance est suffisante
        if ( $navaid_dist < $ref_dist[$1] &&
             $dist_to     > $dist_min     &&
             $dist_from   > $dist_min     ) {
            # on retient cette option et on sauve la nouvelle distance de référence
            $ref_navaid[$1] = $index;
            $ref_dist[$1]   = $navaid_dist;
        }
    }

    #RETOUR EN FONCTION DES CHOIX
    SWITCH : {
        #SI ON NE VEUT QUE DU VOR
        if ($vor_a_vor) { return $ref_navaid[$VOR]; last SWITCH; }

        #SI ON PREFERE LES VOR AUX NDB
        if ($vor_preferes && $ref_navaid[$NDB]) { return ($ref_navaid[$VOR])? $ref_navaid[$VOR] : $ref_navaid[$NDB]; last SWITCH; }

        #SI ON EST INDIFFERENT
        if ($ref_navaid[$VOR] && $ref_navaid[$NDB]) { return ($ref_dist[$VOR] < $ref_dist[$NDB])? $ref_navaid[$VOR] : $ref_navaid[$NDB]; last SWITCH; }

        #SI PAS DE VOR
        if (!$ref_navaid[$VOR] && $ref_navaid[$NDB]) { return $ref_navaid[$NDB]; last SWITCH; }

        #SI PAS DE NDB
        if ($ref_navaid[$VOR] && !$ref_navaid[$NDB]) { return $ref_navaid[$VOR]; }
        else    { return $ref_navaid[0]; }
    }
}

sub construction_route ($$$$) {
    # on récupère les arguments de la fonction
    my ($depuis, $vers, $plan, $lev) = @_;

    my $lat1 = $depuis->[0];
    my $lon1 = $depuis->[1];
    my $lat2 = $vers->[0];
    my $lon2 = $vers->[1];

    # les coordonnées du segments [depuis-vers]
    # my $coord_leg = [$depuis->[0], $depuis->[1], $vers->[0], $vers->[1]];
    my $coord_leg = [$lat1, $lon1, $lat2, $lon2];

    # on calcule les coordonnées du milieu du segment [depuis-vers]
    # par une méthode peu orthodoxe...
    my $mi_trajet = [ $depuis->[0]+(($vers->[0]-$depuis->[0])/2),
              $depuis->[1]+(($vers->[1]-$depuis->[1])/2) ];

    # on cherche la balise la plus proche du milieu du segment [depuis-vers]
    my $dist = distance_ ($coord_leg);
    
    #prt("Distance of this leg $dist...\n") if (!$quiet);
    prt("Leg: $lat1,$lon1 - $lat2,$lon2, dist ".(round($dist*100) / 100)." km\n") if (!$quiet);
    
    my $indexPlusProcheNavAid = getNavAidNearestMidPoint ($coord_leg, $mi_trajet, $dist/2);

    # si on en trouve une
    if ($indexPlusProcheNavAid) {
        # on récupère les coordonnées
        # $1 = latitude
        # $2 = longitude
        $navaid->[$indexPlusProcheNavAid] =~ /^.\s+(\S+)\s+(\S+)\s/;

        # on la nomme "waypoint"
        prt("waypoint $1,$2\n") if (!$quiet);
        my $waypoint =  [$1,$2];
        
        # on construit la route entre "depuis" et "waypoint"
        construction_route ($depuis, $waypoint, $plan, $lev + 1);

        # on sauve la balise la plus proche du milieu
        my @a = split /\s+/, $navaid->[$indexPlusProcheNavAid], 8 + $version;
        prt("Added: ".join(" ",@a)." to plan\n") if (!$quiet);
        push @$plan, \@a;

        # on construit la route entre "waypoint" et "vers"
        construction_route ($waypoint, $vers, $plan, $lev + 1);
    } else {
        prt("Failed to get any navaids on route!\n") if (($lev == 0) && !$quiet);
    }
}

# GESTION DES PROCÉDURES SID/STAR
#################################
sub teste_existence_procedure ($$$) {
    # on récupère les arguments de la fonction
    my ($sidstar, $fichier, $marqueur) = @_;
    my @trouvailles;

    # si le fichier sid.dat ou star.dat n'existe pas on abandonne la procédure
    if (! -e $$fichier) {
        prt(sprintf( "le fichier %s n'existe pas, la procédure %s est abandonnée", $$fichier, ($marqueur == 60)? 'SID' : 'STAR'));
        return 0;
    }

    # on ouvre le fichier
    $$fichier =~ /.+\.(.+)$/;
    my $fichier_traite = ($1 eq 'gz')? 'gzip -d -c '.$$fichier.'|' : $$fichier;
    open (FICHIER, $fichier_traite) or die "impossible d'ouvrir le fichier $$fichier!!!";

    # on parcourt le fichier à la recherche des procédures
    while (<FICHIER>) {
        chomp;
        if (/^$marqueur\s+(\S+)\s+(.+)/ && $1 eq $sidstar->[1]) {   # ouverture de procédure qui correspond
            my @procedure;
            push @procedure, $2;
            while (<FICHIER>) {
                chomp;
                last if (/^\s*$/);  # une ligne vide on arrête la recherche pour cette procédure
                push @procedure, $_;    # tant qu'il y a des données on prend
            }

            # on place la procédure entière dans @trouvailles
            push @trouvailles, \@procedure;
        }
    }

    # on ferme le fichier
    close (FICHIER);

    # @trouvaille contient toutes les procédures connues pour notre aéroport
    # on sauve les résultats là où qu'il faut
    $sidstar->[0] = \@trouvailles;

    # on renvoie la taille du tableau @trouvailles (0 = rien trouvé)
    my $taille = @trouvailles;
    return $taille;
}

sub mise_en_forme_procedure ($$) {
    my ($procedure, $extremite) = @_;

    my @procedure_exploitable;  # tableau contenant les étapes exploitables de la procédure entière
    my $nombre_d_entrees = 0;   # pour contrôler le nombre d'entrées de la procédure rééellement exploitables
                    # si il est nul, alors on abandonne

    # table de hachage utilisée par $sous_fonction
    my %type = ('F' => [$fix, '^\s*\S+\s+\S+\s+(\S+)\s*$'],
            'V' => [$navaid, ($version)? '^3\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)'
                           : '^3\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)'      ],
            'N' => [$navaid, ($version)? '^2\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)'
                           : '^2\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)'      ]);

    # retourne la ligne d'un fix ou d'une aide à la navigation depuis les bases de données correspondantes
    $sous_fonction = sub {
        my ($test, $nom) = @_;
        foreach my $element (@{$type{$test}->[0]}) {
            return $element if ($element =~ /$type{$test}->[1]/ && $1 eq $nom);
        }
    };

    # pour vérifier si la procédure sera modifiée
    my $modifie = @{$procedure};

    # on arrange chaque élément des procédures sid et star pour les incorporer à la route
    for (my $index = 1; $index < @{$procedure}; $index++) {
        $procedure->[$index] =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/;
        my $point_de_passage = $1;

        # si le point de passage est un fix, un vor ou un ndb...
        if ($point_de_passage == 65) {
            # on arrête si c'est l'arrivée (code A de la procédure star)
            # pour plus tard, les étapes de l'approche manquée seront sauvées quelque part pour traitement...
            last if ($2 eq 'A');

            # on récupère toute l'info le concernant
            $procedure->[$index] = &$sous_fonction ($2, $3);

            # on continue pour le prochain point de passage si on ne trouve rien
            next if !$procedure->[$index];

            # si c'est un vor ou un ndb on rajoute l'altitude minimale au dessus du point
            if ($2 eq 'V' || $2 eq 'N') {
                $procedure->[$index] .= " $4";
            }

            # s'il s'agit d'un fix, on reformate pour que la taille corresponde avec les autres point du plan de vol
            else {
                my $altitude_mini = $4;
                $procedure->[$index] =~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s*$/;
                $procedure->[$index] = ($version)? "65 $1 $2 fix fix fix fix $3 $altitude_mini"
                                 : "65 $1 $2 fix fix fix $3 $altitude_mini";
            }
        }

        # ...ou si le point de passage est un gps, on reformate comme pour les fix
        elsif ($point_de_passage == 66) {
            my ($lat, $lon) = ($3/1000000, $4/1000000);
            $procedure->[$index] = ($version)? "66 $lat $lon gps gps gps gps gps $2"
                             : "66 $lat $lon gps gps gps gps $2";
        }

        # ...ou si c'est un trajet d'attente, on l'abandonne (pour l'instant)
        elsif ($point_de_passage == 64) {
            next;
        }

        # on découpe les points de passage
        my @etape = split (/\s+/, $procedure->[$index]);
        $nombre_d_entrees++;
        push @procedure_exploitable, \@etape;
    }

    # on ne retient dans $depart[0]/$arrivee[0] que le nom de la procédure
    # et on indique si elle a été modifiée
    my $a_ete_modifie = ($nombre_d_entrees != $modifie)? ' (modifiée)' : undef;
    $extremite->[0] = ($nombre_d_entrees)? @{$procedure}[0].$a_ete_modifie : undef;

    # on retourne la procédure exploitable
    return \@procedure_exploitable;
}

sub sid_star ($$$$$$) {
    # on récupère les paramètres
    my ($proc, $procx, $extremite, $fichier, $marqueur, $autre_extremite) = @_;

    my $ref_dist = 99999;   # distance de référence pour comparer
    my $ref_index;      # index de référence pour se souvenir
    my $dist;       # la distance entre les deux extrémités
    my @retenues;       # contiendra la liste des procédures correspondant à la demande d'une piste particulière
    my $phrase_a_matcher;   # faute de meilleur nom...

    # table de hachage utilisée par $sous_fonction
    my %type = ('F' => [$fix, '^\s*(\S+)\s+(\S+)\s+(\S+)\s*$'],
            'V' => [$navaid, ($version)? '^3\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)'
                           : '^3\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\S+)'      ],
            'N' => [$navaid, ($version)? '^2\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)'
                           : '^2\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\S+)'      ]);

    # retourne les coordonnées d'un fix ou d'une aide à la navigation
    $sous_fonction = sub {
        my ($test, $nom) = @_;
        foreach my $element (@{$type{$test}->[0]}) {
            return ($1, $2) if ($element =~ /$type{$test}->[1]/ && $3 eq $nom);
        }
    };

    # si on trouve au moins une procédure sid/star:
    # elle(s) est(sont) placées dans $depart[0]/$arrivee[0]
    # et on charge les données nav et fix.
    if (teste_existence_procedure ($extremite, $fichier, $marqueur)) {
        @$fix    = nav_to_ram (\$FIXFILE, '^\s*(\S+)\s+(\S+)\s+\S+\s*$', 0) if (@{$fix} == 0);
        @$navaid = nav_to_ram (\$NAVFILE, '^(2|3)\s+(\S+)\s+(\S+)\s', 1)    if (@{$navaid} == 0);
    }
    # sinon on annule la demande de procédure sid/star
    # et on sort de la fonction
    else {
        ($extremite->[0], $$proc, $$procx) = (undef, undef, undef);
        prt(sprintf( "Aucune procédure %s n'a été trouvée pour %s\n", ($marqueur == 60)? 'SID':'STAR', $extremite->[1]));
        return;
    }


    # on commence par chercher la ou les procédures voulues
    if ($$proc) {
        foreach my $procedure (@{$extremite->[0]}) {
            push @retenues, $procedure if ($procedure->[0] =~ /\[RW$$proc.\s*/);
        }
        # si on a trouvé les pistes correspondantes à la demande
        if (@retenues != 0) {
            $extremite->[0] = \@retenues;
        }
        # sinon on annule la demande --sid/--star qui devient une demande --sidx/--starx
        else {
            prt(sprintf( "Aucune procédure %s n'a été trouvée pour la piste $$proc sur $extremite->[1]\n", ($marqueur == 60)? 'SID':'STAR'));
            $$proc  = undef;
            $$procx = 1;
        }
    }

    # on choisit la meilleure procédure,
    # pour l'instant le choix se fait par le calcul de la plus petite distance

    # pour chaque procédure connue
    for (my $index = @{$extremite->[0]}; $index--; ) {
        my $entree = 1;

        # $1 contient l'info du type de dernier(sid)/premier(star) point de passage de la procédure:
        #   - 4, ou 7: trajet d'attente (uniquement pour star)
        #   - 5: vor, ndb ou fix
        #   - 6: coordonnées gps
        POINT_DE_PASSAGE : {

        # on atteint le dernier élément de la procédure sid numéro $index
        # ou le premier de la procédure star numéro $index
        $phrase_a_matcher = ($marqueur == 60)? $extremite->[0]->[$index]->[@{$extremite->[0]->[$index]} - $entree]
                             : $extremite->[0]->[$index]->[$entree];
        $phrase_a_matcher =~ /^6(.)\s+/;

            if ($1 == 4 || $1 == 7) {   # c'est un trajet d'attente (holding pattern)
                # ben ça attendra encore un peu ... on lit l'étape suivante
                $entree++;
                next POINT_DE_PASSAGE;  # je ne suis pas tout à fait certain de l'orthodoxie de cette syntaxe...
            }
            if ($1 == 5) {          # c'est un fix ou un vor, ou un ndb...
                            # ou un point d'arrivée (code A) de procédure star mais je considère cette possibilité comme nulle
                # on cherche le type et le nom
                $phrase_a_matcher =~ /^65\s+(\S)\s+(\S+)/;

                # on cherche ses coordonnées
                my ($lat, $lon) = &$sous_fonction ($1, $2);

                # on passe au point suivant si le point recherché n'est pas connu
                if (!$lat) {
                    $entree++;
                    next POINT_DE_PASSAGE;  # je ne suis pas tout à fait certain de l'orthodoxie de cette syntaxe...
                }

                # on calcule la distance entre les deux extrémités
                $dist = distance_ ( [$lat, $lon, $autre_extremite->[1], $autre_extremite->[2]] );

                # on retient la solution si la distance est inférieure à la distance de référence
                ($ref_dist, $ref_index) = ($dist, $index) if ($dist < $ref_dist);

                # on sort
                last POINT_DE_PASSAGE;
            }
            if ($1 == 6) {          # c'est un point gps
                # on lit les coordonnées du point
                $phrase_a_matcher =~ /^66\s+\S+\s+(\S+)\s+(\S+)/;

                # on calcule la distance entre les deux extrémités
                $dist = distance_ ([$1/100000, $2/100000, $autre_extremite->[2], $autre_extremite->[3]]);

                # on retient la solution si la distance est inférieure à la distance de référence
                ($ref_dist, $ref_index) = ($dist, $index) if ($dist < $ref_dist);

                # on sort
                last POINT_DE_PASSAGE;  # inutile mais c'est pour faire joli
            }
        } # POINT_DE_PASSAGE
    } # for (my $index = @{$extremite->[0]}; $index--; )

    # on met en forme la procédure trouvée
    my $procedure_finale = mise_en_forme_procedure ($extremite->[0]->[$ref_index], $extremite);

    # on enregistre les coordonnées de l'extrémité sid/star, si trouvées
    $extremite->[2] = @{$procedure_finale->[@{$procedure_finale} - 1]}[1] if @{$procedure_finale->[@{$procedure_finale} - 1]}[1];
    $extremite->[3] = @{$procedure_finale->[@{$procedure_finale} - 1]}[2] if @{$procedure_finale->[@{$procedure_finale} - 1]}[2];

    # on retourne la procédure sid/star
    return $procedure_finale;
}

## PLAN DE VOL
##############
sub plan_de_vol {
    # les aides à la navigation
    my @NDBVOR;
    $navaid = \@NDBVOR;

    # les fix
    my @FIX;
    $fix = \@FIX;

    # l'aéroport de départ est le premier point
    push @route, ($version)? [1, $depart[2], $depart[3], @{$depart[4]->[0]}[1], $depart[4], 'apt', 'apt', $depart[1], @{$depart[4]->[0]}[5]] :
                 [1, $depart[2], $depart[3], @{$depart[4]->[0]}[1], $depart[4], 'apt', $depart[1], @{$depart[4]->[0]}[5]];

    # on trouve les coordonnées de sortie de la procedure sid, qui deviendront $depart[2] et  $depart[3]
    # le trajet sera contenu dans $depart[0]
    my $procedure_sid = sid_star (\$sid, \$sidx, \@depart, \$SIDFILE, 60, \@arrivee) if ($sid || $sidx);

    # on trouve les coordonnées d'entrée de la procédure star, qui deviendront $arrivee[2] et  $arrivee[3]
    # le trajet sera contenu dans $arrivee[0]
    my $procedure_star = sid_star (\$star, \$starx, \@arrivee, \$STARFILE, 61, \@depart) if ($star || $starx);

    # si ce n'est déjà fait on place les données nécessaires en mémoire pour aller plus vite
    # (@FIX seulement dans le cadre des procédures sid/star, pour le moment...)
    @FIX    = nav_to_ram (\$FIXFILE, '^\s*(\S+)\s+(\S+)\s+\S+\s*$', 0) if (($sid || $sidx || $star || $starx) && (@{$fix} == 0));
    my ($type_navaid, $decale) = ($vor_a_vor && !($sid || $sidx || $star || $starx))? ('^3', 0) : ('^(2|3)', 1);
    @NDBVOR =  nav_to_ram (\$NAVFILE, $type_navaid.'\s+(\S+)\s+(\S+)\s', $decale) if (@{$navaid} == 0);

    # on ajoute tous ses points de passage sid dans le plan de vol
    push @route, @{$procedure_sid} if $depart[0];

    # on construit la route entre les deux extrémités
    construction_route (    [$depart[2],  $depart[3]],
                [$arrivee[2], $arrivee[3]],
                \@route, 0);

    # on ajoute tous les points de passage star dans le plan de vol
    push @route, @{$procedure_star} if $arrivee[0];

    # on prend les coordonnées de la piste qui sera utilisée
    $sous_fonction = sub {
        my $extremite = shift;
        if ($extremite->[0] =~ /\[RW(...)\s*/) {
            my $piste = $1;
            foreach (@{$extremite->[4]}) { ($extremite->[2], $extremite->[3]) = ($_->[1], $_->[2]) if ($_->[3] =~ /$piste/) }
        }
    };
    &$sous_fonction (\@depart);
    &$sous_fonction (\@arrivee);

    # IL RESTE À TROUVER LA DERNIÈRE BALISE (ÉVENTUELLE) SITUÉE SUR L'AÉROPORT D'ARRIVÉE
    # si aucune procédure star n'est demandée (ou valable)

    # l'aéroport d'arrivée est le dernier point
    push @route, ($version)? [1, $arrivee[2], $arrivee[3], @{$arrivee[4]->[0]}[1], $arrivee[4], 'apt', 'apt', $arrivee[1], @{$arrivee[4]->[0]}[5]] :
                 [1, $arrivee[2], $arrivee[3], @{$arrivee[4]->[0]}[1], $arrivee[4], 'apt', $arrivee[1], @{$arrivee[4]->[0]}[5]];

    # on détruit les listes des aides à la navigation désormais inutiles
    $navaid = undef;
    $fix    = undef;
}

# FONCTIONS DE SORTIE DU RESULTAT
#################################

sub fichier_csv () {
    $sous_fonction = sub {
        my $i = $_[0].$_[3].$_[1].$_[3].$_[2];
        $i =~ s/\./$_[4]/g;
        return $i;
    };

    # ouverture du fichier
    if (!open (CSV, ">$CSVFILE")) {
        prt("Error: Failed to open $CSVFILE!\n");
        return;
    }

    # on configure les séparateurs
    my ($separateur, $decimal);
    if ($csv_conf =~ /^(.)(.)$/) {
        $separateur = $1;
        $decimal    = $2;
    }

    # on écrit le contenu du fichier
    for (my $index = 0; $index < @route; $index++) {
        printf CSV "%s\n", &$sous_fonction ($route[$index]->[6 + $version], $route[$index]->[1], $route[$index]->[2], $separateur, $decimal);
    }

    # on ferme le fichier
    close (CSV);
    prt("CSV output written to $CSVFILE.\n");
}


sub fichier_xg () {

    my $div = 1.852;
    my ($name,$lat,$lon);

    # ouverture du fichier
    if (!open (XG, ">$XGFILE")) {
        prt("Error: Failed to open $XGFILE!\n");
        return;
    }

    my ($max_lat,$max_lon,$min_lat,$min_lon);
    my ($waypointtype,$wpinfo,$max,$index);
    my ($leg,$heading,$distance,$distance_totale);


    $distance_totale = 0;
    $max_lat = -400;
    $max_lon = -400;
    $min_lat = 400;
    $min_lon = 400;

    # on écrit le contenu du fichier
    $max = scalar @route;
    print XG "# plan de vol dep ".$depart[1]." to ".$arrivee[1]." in $max legs\n";

    for ($index = 0; $index < $max; $index++)
    {
        if ($index > 0) {
            $leg      = [@{$route[$index-1]}[1],@{$route[$index-1]}[2],@{$route[$index]}[1],@{$route[$index]}[2]];
            $heading  = round (llll2dir_ ($leg));
            $distance = distance_ ($leg) / $div;
            $distance_totale += $distance;
            $distance = round ($distance);
        } else {
            $heading = '';
            $distance = '';
        }

        $lat = $route[$index]->[1];
        $lon = $route[$index]->[2];
        $name = $route[$index]->[6 + $version].' '.$route[$index]->[7 + $version];
	    $waypointtype = "APT";
	    $wpinfo = "none";
	    if ($route[$index]->[0] == 2) {
	        $wpinfo = "FREQ: ".$route[$index]->[4];
	        $waypointtype = "NDB";
	    }
	    elsif ($route[$index]->[0] == 3) { 
	        $wpinfo = "FREQ: ".$route[$index]->[4];
	        $waypointtype = "VOR"; 
	    }
	    elsif ($route[$index]->[0] == 65){ 
	        $waypointtype = "FIX"; 
	    }
	    elsif ($route[$index]->[0] == 66){ 
	        $waypointtype = "GPS"; 
	    }
        if (length($heading)) {
            $wpinfo .= " b=$heading d=$distance nm.";
        }
        if ($index == 0) { $wpinfo .= " BEGIN OF ROUTE"; }
	    if ($index == @route - 1) { $wpinfo .= " END OF ROUTE"; }
	
        printf XG "anno %f %f %s\n", $lon, $lat, "$name $waypointtype $wpinfo";
        printf XG "%f %f\n", $lon, $lat;
        # get bounding box
        $max_lat = $lat if ($lat > $max_lat);
        $max_lon = $lon if ($lon > $max_lon);
        $min_lat = $lat if ($lat < $min_lat);
        $min_lon = $lon if ($lon < $min_lon);
    }

    print XG "NEXT\n";

    print XG "# bbox=$min_lon,$min_lat,$max_lon,$max_lat\n";

    # on ferme le fichier
    close (XG);
    prt("XG output written to $XGFILE.\n");
}


sub fichier_wp () {
    $WPFILE = $depart[1]."-".$arrivee[1];
    my $WPFILE_test = $WPFILE;
    my $count = 1;
    if (! -d "$FGROOT/Routes") {
        prt("Error: Directory $FGROOT/Routes does NOT exist! Create it first...\n");
    }
    while ( -e "$FGROOT/Routes/$WPFILE_test.xml" ) {
	    $WPFILE_test = $WPFILE;
	    $WPFILE_test .= $count;
	    $count++;
    }
    $WPFILE = $WPFILE_test;

    # ouverture du fichier
    if (!open (WP, ">$FGROOT/Routes/$WPFILE.xml")) {
        prt("Error: Failed to open $FGROOT/Routes/$WPFILE.xml!\n");
        return;
    }

    my $template = <<EOT;
 <Waypoint n="%s">
  <ID type="string">%s</ID>
  <name type="string">%s</name>
  <latitude-deg type="double">%s</latitude-deg>
  <longitude-deg type="double">%s</longitude-deg>
  <altitude-ft type="double">0</altitude-ft>
  <waypoint-type type="string">%s</waypoint-type>
  <desc type="string">%s</desc>
 </Waypoint>\n
EOT
    print WP "<PropertyList>\n";
    # on écrit le contenu
    for (my $index = 1; $index < @route; $index++) {
	my $waypointtype = "APT";
	my $wpinfo = "none";
	if ($route[$index]->[0] == 2) {
	    $wpinfo = "FREQ: ".$route[$index]->[4];
	    $waypointtype = "NDB";
	}
	elsif ($route[$index]->[0] == 3) { 
	    $wpinfo = "FREQ: ".$route[$index]->[4];
	    $waypointtype = "VOR"; 
	}
	elsif ($route[$index]->[0] == 65){ 
	    $waypointtype = "FIX"; 
	}
	elsif ($route[$index]->[0] == 66){ 
	    $waypointtype = "GPS"; 
	}

	if ($index == @route - 1) { $wpinfo .= " END OF ROUTE"; }
	
        printf WP $template, 
		    $index - 1,
		    $route[$index]->[6 + $version], 
		    $route[$index]->[7 + $version],
		    $route[$index]->[1], 
		    $route[$index]->[2],
		    $waypointtype,
		    $wpinfo;
    }
    print WP "</PropertyList>";

    # fermeture du fichier
    close (WP);
    prt("File $FGROOT/Routes/$WPFILE.xml written.\n");
}

sub sortie_standard () { # cette procédure est de la bouillie pour les chats et pour les chiens!
    my $div = ($km)?1:1.852;
    my ($leg, $distance, $distance_totale, $heading);

    $sous_fonction = sub {
        print "\033[30;1m" if $add_couleur;
        prt("$_[0]\n");
        print "\033[m" if $add_couleur;
    };

    if ($com_dep) {
        &$sous_fonction ("Fréquences utiles pour le départ");
        foreach (@{$depart[4]}) { printf ("$_->[@{$_}-1]: %s\n", $_->[1]/100) if ($_->[0] >= 50 && $_->[@{$_}-1] ne 'APP');}
    }

    prt( "procédure SID  : $depart[0]\n" ) if $depart[0];
    prt( "procédure STAR : $arrivee[0]\n" ) if $arrivee[0];

    &$sous_fonction ("\nCode - Nom complet");
    prt(sprintf( "\t| Frequences | Heading | Course/RNW | Distance en %s\n", ($km)? 'km':'nm'));

    &$sous_fonction ("$depart[4]->[0]->[4] - $depart[4]->[0]->[5]");
    prt(sprintf("%s", ($depart[0] =~ /\RW(...)\s+/)? "décollage piste $1\n" : ''));
    for (my $index = 1; $index < @route; $index++) {
        $leg      = [@{$route[$index-1]}[1],@{$route[$index-1]}[2],@{$route[$index]}[1],@{$route[$index]}[2]];
        $heading  = round (llll2dir_ ($leg));
        $distance = distance_ ($leg) / $div;
        $distance_totale += $distance;
        $distance = round ($distance);

        ETAPE : {
            if (@{$route[$index]}[0] == 2) {    # étape ndb
                if ($version
                &&  $distance * $div > @{$route[$index]}[5]
                &&  (@{$route[$index-1]}[0] == 2 || @{$route[$index-1]}[0] == 3)) {
                    $distance -= round (@{$route[$index]}[5] / $div);
                    prt(sprintf( "\t| ADF %-7s| %-6s  |     --     | $distance\n",
                        @{$route[$index-1]}[4], $heading ))
                        if @{$route[$index-1]}[0] == 2;
                    prt(sprintf( "\t| NAV %-7s| %-6s  | %-10s | $distance\n",
                        @{$route[$index-1]}[4], $heading, round ($heading - @{$route[$index-1]}[5+$version])))
                        if @{$route[$index-1]}[0] == 3;
                    $distance = round (@{$route[$index]}[5] / $div);
                }
                prt(sprintf( "\t| ADF %-7s| %-6s  |     --     | $distance\n",
                    @{$route[$index]}[4], $heading ));
                &$sous_fonction ("@{$route[$index]}[6 + $version] - @{$route[$index]}[7 + $version]");
                last ETAPE;
            }
            if (@{$route[$index]}[0] == 3) {    # étape vor
                @{$route[$index]}[4] /= 100;
                if ($version
                &&  $distance * $div> (@{$route[$index]}[5]-5)
                &&  (@{$route[$index-1]}[0] == 2 || @{$route[$index-1]}[0] == 3)) {
                    $distance -= round (@{$route[$index]}[5] / $div);
                    prt(sprintf( "\t| ADF %-7s| %-6s  |     --     | $distance\n",
                        @{$route[$index-1]}[4], $heading ))
                        if @{$route[$index-1]}[0] == 2;
                    prt(sprintf( "\t| NAV %-7s| %-6s  | %-10s | $distance\n",
                        @{$route[$index-1]}[4], $heading, round ($heading - @{$route[$index-1]}[5+$version])))
                        if @{$route[$index-1]}[0] == 3;
                    $distance = round (@{$route[$index]}[5] / $div);
                }
                prt(sprintf("\t| NAV %-7s| %-6s  | %-10s | $distance\n", @{$route[$index]}[4], $heading, round ($heading - @{$route[$index]}[5+$version])));
                &$sous_fonction ("@{$route[$index]}[6 + $version] - @{$route[$index]}[7 + $version]");
                last ETAPE;
            }
            if (@{$route[$index]}[0] == 65) {   # étape fix
                prt(sprintf( "\t| FIX        | %-6s  |     --     | $distance\n", $heading));
                &$sous_fonction ("@{$route[$index]}[6 + $version]");
                last ETAPE;
            }
            if (@{$route[$index]}[0] == 66) {   # étape gps
                prt(sprintf( "\t| GPS        | %-6s  |   --   | $distance\n", $heading ));
                &$sous_fonction ("GPS - [@{$route[$index]}[1] , @{$route[$index]}[2]]");
                last ETAPE;
            }
            if (@{$route[$index]}[0] == 1) {    # aéroport de d'arrivée
                my ($localizer, $piste);
                if ($arrivee[0] =~ /\[RW(...)\s*/) {
                    $piste = $1;
                    $localizer = "RW $piste";
                    foreach (@{$arrivee[4]}) {
                        $localizer = "ILS $_->[2]" if (($_->[0] == 4 || $_->[0] == 5) && $_->[1] eq $piste);
                    }
                    prt(sprintf( "\t| %-10s | %-6s  | %-10s | $distance\n", $localizer, $heading, "RW $piste"));
                } else {
                    foreach (@{$arrivee[4]}) {
                        if ($_->[0] == 10) {
                            $piste = "RW $_->[3]" ;
                            prt(sprintf( "\t| %-10s | %-6s  | %-10s | $distance\n", $piste, $heading, $piste));
                        }
                        elsif ($_->[0] == 4 || $_->[0] == 5) {
                            ($localizer, $piste) = ("ILS $_->[2]", "RW $_->[1]");
                            prt(sprintf( "\t| %-10s | %-6s  | %-10s | $distance\n", $localizer, $heading, $piste));
                        }
                    }
                }
                &$sous_fonction ("$arrivee[4]->[0]->[4] - $arrivee[4]->[0]->[5]");
                last ETAPE;
            }
        }
    }
    $leg = [$depart[2], $depart[3], $arrivee[2], $arrivee[3]];
    prt(sprintf( "\ndistance totale parcourue: %s %s (vol direct: %s)\n\n", round ($distance_totale), ($km)? 'km':'nm', round (distance_ ($leg) / $div)));

    if ($com_app) {
        &$sous_fonction ("Fréquences utiles pour l'approche");
        foreach (@{$arrivee[4]}) { prt(sprintf ("$_->[@{$_}-1]: %s\n", $_->[1]/100)) if ($_->[0] >= 50 && $_->[@{$_}-1] ne 'DEP'); }
    }
}

#######################
# FONCTION PRINCIPALE #
#######################
sub main () {
    # si aucune option n'est demandée ou si l'option -h est appelée, on affiche l'aide et on quitte
    if (!$options || $help) {
        prt( $texte_aide );
        exit;
    }

    prt("Plan de vol ".$depart[1]." to ".$arrivee[1]."... moment...\n");

    if ($wpt ) {
        if (! -d "$FGROOT/Routes") {
            prt("Error: Directory $FGROOT/Routes does NOT exist! Create it first...\n");
            exit 1;
        }
    }

    # si le départ et l'arrivée ont été trouvés on commence la construction
    # du plan de vol, sinon on affiche un message d'erreur.
    (configure_extremite (\@depart, \$sid, \$sidx ) &&
     configure_extremite (\@arrivee,\$star,\$starx)) ? plan_de_vol : prt(sprintf($erreur));

    # redirection du résultat selon les choix
    sortie_standard if (!$no_stdout );
    fichier_csv if ($CSVFILE    );
    fichier_wp  if ($wpt );
    fichier_xg  if ($XGFILE );
} main;

# eof
