#!/usr/bin/perl

use warnings;
use strict;
use Getopt::Std;
use vars qw/ $opt_c $opt_n $opt_f $opt_e $opt_l $opt_d $opt_h $opt_t $opt_v $opt_x $opt_z /;
use Config::Tiny;
use File::Spec;

use FTN::Database qw(&open_ftndb &close_ftndb);
use FTN::Database::Nodelist qw(&create_ftnnode_index &drop_ftnnode_index);
use FTN::Log qw(&logging);

$Getopt::Std::STANDARD_HELP_VERSION = 1;

=head1 NAME

nl2ftndb - Load information into a Nodelist table in an Fidonet/FTN SQL database.

=head1 VERSION

Version 0.16

=cut

our $VERSION = 0.16;

=head1 DESCRIPTION

Initial load of a particular Fidonet/FTN St. Louis Format Nodelist into an SQL Database
for Fidonet/FTN processing. The SQL Database engine is one for which a DBD module exists,
defaulting to SQLite.

=head1 SYNOPSIS

C<nl2ftndb -c config_file -n nodelist_directory [-t tablename] [-f nodelist_file] [-l log_file] [-d domain] [-z zone] [-e] [-v] [-x] load>

C<nl2ftndb [-h | --help]>

C<nl2ftndb --version>

=cut

getopts('c:n:f:l:d:t:ehvxz:');

if ($opt_h) {
    HELP_MESSAGE(*STDOUT);    #printing usage/help message
    exit 0;
}

my (
    $nodelist_directory, $nodelist_file, $db_handle, $sql_statement, $domain, $type,
    $number,   $name,   $location,  $log_file, $command,
    $sysop, $phone,  $bps, $flags,    $table_name, $db_name,
    $db_user, $db_password, $db_type, $config_file
);

=head1 OPTIONS

=over

=item -c

This is the filename and path of a configuration file, with the
default being ftnpldb.cfg in the current directory.

=cut

if ($opt_c) {
    $config_file = $opt_c;
    undef $opt_c;
}
else {
    my $config_dir = File::Spec->curdir();
    $config_file = File::Spec->join($config_dir, 'ftnpldb.cfg');
}

#  Get configuration from file
my $ftndb_config = Config::Tiny->new();
$ftndb_config = Config::Tiny->read($config_file)
    or die "Could not open configuration file:  $config_file";

=item -l

This would be the filename and path of a log file, with the default
being ftnpldb.log in the current directory.

=cut

if ($opt_l) {
    $log_file = $opt_l;
    undef $opt_l;
}
else {
    $log_file = "ftnpldb.log";
}

my $log_id = "nl2db";

logging($log_file, $log_id, "Starting nl2ftndb... ");

=item -v

Verbose option.

=cut

if ($opt_v) {
    logging($log_file, $log_id, "Verbose flag is set");
}

=item -x

Debug option.

=cut

if ($opt_x) {
    logging($log_file, $log_id, "Debug flag is set");
}

# Get the definition of the database type
$db_type = $ftndb_config->{Database}->{Type};	

if ($opt_v) { logging($log_file, $log_id, "Database type being used is $db_type") };

# Get the definition of the database name
$db_name = $ftndb_config->{Database}->{Name};

if ($opt_v) { logging($log_file, $log_id, "Database name being used is $db_name") };

# Get the definition of the database user
if ($db_type eq "SQLite") {

    $db_user = "";

} else {
    $db_user = $ftndb_config->{Database}->{User};

    if ($opt_v) { logging($log_file, $log_id, "Database user being used is $db_user") };

}

# Get the definition of the database password
if ($db_type eq "SQLite") {

    $db_password = ""; 

} else {

    $db_password = $ftndb_config->{Database}->{Password};
    if ($opt_v) { logging($log_file, $log_id, "Database password being used is $db_password") };

}

=item -n

The nodelist directory being used.
This is required, so if it is not set the program will exit displaying
a help message.

=cut

if ($opt_n) {

    $nodelist_directory = $opt_n;

=item -f

The nodelist file.
If the -e option is also set, then this is an exact file name.  If -e is
not set, then this is the basename of the nodelist files.

=cut

    if ($opt_f) {

=item -e

If set, then the -f option must be exact file name.  If not set, then
the -f option is the basename of a nodelist file.

=cut

        if ($opt_e) {
            logging($log_file, $log_id, "Using exact file name $opt_f.");
            $nodelist_file = $opt_f;
        }
        else {
            $nodelist_file = get_nodelist_filename($opt_f);
        }

    }
    else { 
        $nodelist_file = get_nodelist_filename("nodelist");
    }

}
else {
    print "\nThe Nodelist directory variable must be set... \n";
    HELP_MESSAGE(*STDERR);
    exit(1);
}

=item -t

The nodelist table name to be used.
Note that if there is a period in the name, that period will be changed
to an underscore.

=cut

if ($opt_t) {
    if ( $opt_t =~ /\./ ) {    # period in proposed table name?
        logging($log_file, $log_id, "sqlite does not allow periods in table names.");
        $opt_t =~ tr/\./_/;    # change period to underscore
        $table_name = $opt_t;     #
        logging($log_file, $log_id, "Changed table name to $table_name.");
    }
    else {                     # no period in name
        $table_name = $opt_t;     #  just assign to variable
    }

}
else {
    $table_name = "Nodelist";     # default table name
}


my $zone_only = 0;     # Set default as zone 0, which is not used for zone numbers.

=item -z

If defined;  is the only zone to be loaded

=cut

if ($opt_z) {
    $zone_only = $opt_z;    # 
    logging($log_file, $log_id, "Only loading Zone: $zone_only");
    undef $opt_z;
}

=item -d

If defined, this is the domain of the nodelist being loaded.  Defaults
to fidonet.

=back

=cut

if ($opt_d) {
    $domain = $opt_d;
}
else {
    $domain = 'fidonet'; 
}

if ($opt_v) {                  # log domain name
    logging($log_file, $log_id, "Domain: '$domain'");
}
if ($opt_x) {
    logging($log_file, $log_id, "Debug mode is set");
}

=head1 COMMANDS

=over

=item load 

This will load an FTN Nodelist database table in an SQL database
server being used for Fidonet/FTN processing. 

=back

=cut

# Parse commands
#   if a command is found, then execute & exit
#   if not;  display error message, then help message, then exit
$command = shift;
if (lc($command) eq "load") {

    load_ftn_nodelist();

} else {
    print STDERR "Unrecognized command.\n";
    HELP_MESSAGE(*STDERR);
    exit 1;

}	# End of command parsing.

exit();

############################################
# subroutines
############################################

# Display of help message
############################################
sub HELP_MESSAGE {

    my $fh = shift;
    
    print $fh "\n\tSynopsis:\n";
    print $fh "nl2ftndb -n nodelist_directory [-c config_file] [options] load\n";
    print $fh "nl2ftndb -h | --help\t= Display this help message.\n";
    print $fh "nl2ftndb --version\t= Display version message\n";

    print $fh "\tOptions.\n";
    print $fh "-n nodelist_directory\t= Nodelist directory\n";
    print $fh "[-c config_file]\t= Name and path for configuration file.\n";
    print $fh "[-t tablename]\t\t= Nodelist table name;  defaults to 'Nodelist'.\n";
    print $fh "[-f nodelist_file]\t= Nodelist filename, defaults to 'nodelist'.\n";
    print $fh "[-l log_file]\t\t= Log filename;  defaults to ftnpldb.log in current dir)\n";
    print $fh "[-d domain]\t\t= Nodelist domain;  defaults to 'fidonet'.\n";
    print $fh "[-z zone_number]\t= If present, then only the defined zone 'zone_number' is loaded.\n";
    print $fh "[-e]\t\t\t= If present, then nodelist_file is an exact filename\n";
    print $fh "[-v]\t\t\t= Verbose Mode\n";
    print $fh "[-x]\t\t\t= Debug Mode\n";

    return();

}

########################################################################
# Load an FTN Nodelist table in an FTN database
########################################################################
sub load_ftn_nodelist {

    #  set defaults
    my $zone   = 1;
    my $net    = 0;
    my $node   = 0;
    my $point  = 0;
    my $region = 0;

    logging($log_file, $log_id, "Nodelist directory: '$nodelist_directory'");
    logging($log_file, $log_id, "Nodelist file: '$nodelist_file'");

    open( NODELIST, "$nodelist_directory/$nodelist_file" )
	or die logging($log_file, $log_id, "Cannot open $nodelist_directory/$nodelist_file");

    # connect to database
    $db_handle = FTN::Database::open_ftndb($db_type, $db_name, $db_user, $db_password);

    #
    if ($opt_v) {
        logging($log_file, $log_id, "Deleteing old entries for '$domain'");
    }

    #   remove any and all entries where domain = $domain when doing an insert to the table
    FTN::Database::Nodelist::remove_ftn_domain($db_handle, $table_name, $domain);

    # drop the old nodelist table index, if it exists.
    logging($log_file, $log_id, "Dropping existing nodelist table index if it already exists.");
    FTN::Database::Nodelist::drop_ftnnode_index($db_handle);

    if ($opt_v) {
	logging($log_file, $log_id, "Loading database from nodelist $nodelist_file");
    }

    while (<NODELIST>) {
	if ( /^;/ || /^\cZ/ ) {

	    #	print;
	    next;
	}

	( $type, $number, $name, $location, $sysop, $phone, $bps, $flags ) =
	    split( ',', $_, 8 );

	# originally took care of these by deleteing them
	$name =~ tr/\'//d;    #  take care of single quotes in system name fields

	$location =~ tr/\'//d;     #  take care of single quotes in location fields

	$sysop =~ tr/\'//d;   # take care of single quotes in sysop name fields

	# if $flags is undefined (i.e., nothing after the baud rate)
	if ( !defined $flags ) {
	    $flags = " ";
	} 
	else {
	    $flags =~ s/\r?\n$//;	# else remove EOL (removes \r\n or \n but not \r) from $flags
	}

	if ( $type eq "Zone" ) {    # Zone line
	    $zone = $number;
	    $net  = $number;
	    $node = 0;
	}    #
	elsif ( $type eq "Region" ) {    # Region line
	    $region = $number;
	    $net    = $number;
	    $node   = 0;
	}
	elsif ( $type eq "Host" ) {      # Host line
	    $net  = $number;
	    $node = 0;
	}
	else {
	    $node = $number;
	}

	# display where in the nodelist we are if debug flag is set
	if ($opt_x) {
	    print "$type,";
	    printf "%-16s", "$zone:$net/$node";
	    print "$sysop\n";
	}

	# If zone_only is defined, then go to the next line if the zone number is not the same as zone_only 
	if ($zone_only > 0) {
	    if ($zone != $zone_only) {
		next;
	    }
	}
    
	#	Build Insert Statement
	$sql_statement = "INSERT INTO $table_name ";

	$sql_statement .= "(type,zone,net,node,point,region,name,";
	$sql_statement .= "location,sysop,phone,baud,flags,domain,source) ";
	$sql_statement .= "VALUES (";

	$sql_statement .= "'$type', ";
	$sql_statement .= "'$zone', ";
	$sql_statement .= "'$net', ";
	$sql_statement .= "'$node', ";
	$sql_statement .= "'$point', ";
	$sql_statement .= "'$region', ";
	$sql_statement .= "'$name', ";
	$sql_statement .= "'$location', ";
	$sql_statement .= "'$sysop', ";
	$sql_statement .= "'$phone', ";
	$sql_statement .= "'$bps', ";
	$sql_statement .= "'$flags', ";
	$sql_statement .= "'$domain', ";
	$sql_statement .= "'$nodelist_file') ";

	#	Execute the insert SQL statment
	$db_handle->do("$sql_statement ")
	    or die logging($log_file, $log_id, $DBI::errstr);

    }

    if ($opt_v) { logging($log_file, $log_id, "Create ftnnode index"); }
    # Recreate ftnnode Index
    FTN::Database::Nodelist::create_ftnnode_index($db_handle, $table_name);

    if ($opt_v) { logging($log_file, $log_id, "Closing database"); }
    # disconnect from database
    FTN::Database::close_ftndb($db_handle);

    close NODELIST;

    logging($log_file, $log_id, "Nodelist table loaded... ");

    return();
}

################################################
# get nodelist filename, given path & base name
################################################
sub get_nodelist_filename {

    # Find the most recent version (by day number) when given a base name & dir
    # of the nodelist;  once this is implemented, this will be the default.
    # Note that if there is more than one file with the same base name, it will
    # use the first one found.

    my ( $i, @files );

    my ($basename) = @_;

    if ($opt_v) { logging($log_file, $log_id, "Searching for $basename files.") }

    opendir( DIR, $nodelist_directory );
    @files = sort {$b cmp $a} (grep( /$basename\.[0-9][0-9][0-9]$/i, readdir(DIR) ));
    closedir(DIR);

    if ( $#files == -1 ) {
        logging($log_file, $log_id, "Nodelist files $basename not found");
        print("\nNodelist files $basename not found.\n");
        HELP_MESSAGE();
        exit();
    }
    else {
        if ($opt_v) {
            for ( $i = 0 ; $i < @files ; $i++ ) {
                logging($log_file, $log_id, "Nodelist file $i found: $files[$i]");
            }
        }
    }

    if ( $#files > 1 ) {
        logging($log_file, $log_id, "More than one '$basename' found, using first.");
    }

    return ( $files[0] );    # return filename

}

########################################################################
# Display Version message
########################################################################
sub VERSION_MESSAGE {

    my $fh = shift;
    
    print $fh "nl2ftndb version $VERSION\n";

    return();

}

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

=head1 CONFIGURATION

The B<Database> section in the configuration file has the following
keywords:

=over 4

=item Type

Database type.
This needs to be a database type for which a DBD module exists, the type
being the name as used in the DBD module.  The default type is SQLite.

=item Name

Database name.
For an SQLite database; this needs to be at least the filename and can
also include a path.

=item User

Database user.
For an SQLite database, this defaults to an empty string as it is not
needed for that type of database.

=item Password

Database password.
For an SQLite database, this defaults to an empty string as it is not
needed for that type of database.

=back

This is an example of the contents of an ftnpldb.cfg file:

    [Database]
    Type=mysql
    Name=ftndbtst
    User=sysop
    Password=ftndbtst

=head1 EXAMPLES

Given that $NLDIR is the directory where the nodelist files can be found
and that NODELIST is the base nodelist filename and that $CFGFILE is an
existing configuration file, the following command line can be used to
load an FTN nodelist from a set of nodelist files all with the same
basename of NODELIST:

C<nl2ftndb -c $CFGFILE -n $NLDIR -f NODELIST -d fidonet -v load>

Given that $NLDIR is the directory where the nodelist files can be
found and that $CFGFILE is an existing configuration file, the
following command line can be used to load a specified zone of the
specified domain from a specified nodelist:

C<nl2ftndb -c $CFGFILE -n $NLDIR -f nodelist.197 -d fidonet -z 1 -e -v load>

=head1 AUTHOR

Robert James Clay, C<< <jame at rocasa.us> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-ftn-database at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=FTN-Database>.  I will
be notified, and then you'll automatically be notified of progress on your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc nl2ftndb


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=FTN-Database>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/FTN-Database>

=item * Search CPAN

L<http://search.cpan.org/dist/FTN-Database>

=back

=head1 SEE ALSO

 L<FTN::Database>, L<FTN::Database::Nodelist>, L<ftndbadm>,
 L<ftndbadm>, and L<listftndb>

=head1 COPYRIGHT & LICENSE

Copyright 2010 Robert James Clay, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

