#!/usr/bin/perl

=head1 NAME

beacon - beacon command line client

=cut

use strict;

our $VERSION = '0.22';

use Getopt::Long;
use Pod::Usage;
use Data::Beacon;

my ($help, $man, $configfile, $file, $name, $testmode, $quiet, $linksmode,
    $premeta);

GetOptions(
    "config:s" => \$configfile,
    "help|?" => \$help,
    "man" => \$man,
    "pre:s" => \$premeta,
    "file:s" => \$file,
    "name:s" => \$name,
    "test" => \$testmode,
    "quiet" => \$quiet,
    "links" => \$linksmode,
    # -time : use #TIMESTAMP from input file
) or pod2usage(2);
pod2usage(1) if $help or (@ARGV and $ARGV[0] eq 'help');
pod2usage(-verbose => 2) if $man;

my $beaconstore; # TODO: parse configfile and initialize BeaconStore, if required

my $beaconfile;
my %handlers = ( 
    error => sub {
        my ($msg, $lineno, $line) = @_;
        print STDERR "$msg at line $lineno\n";
    }
);

$handlers{error} = undef if $quiet;

# additional sophisticated command line parsing follows

my $cmd = shift @ARGV;

if (defined $cmd) {
    if ($cmd eq 'links') {
        $cmd = 'dump'; 
        $linksmode = 1;
    }
    if (!($cmd =~ /^(dump|expand|about|parse|list|init|insert|update|delete|query)$/)) {
        if (defined $file) {
            $name = $cmd;
            $cmd = shift @ARGV;
        } elsif (defined $name) {
            $file = $cmd;
            $cmd = shift @ARGV;
        } else {
            $file = $cmd;
            $cmd = "parse";
            # misspelled command?
            failed("File '$file' not found. Use -h for help")
                unless ($file eq '-' or -f $file);
        }
    }
}
unless ($cmd) {
    if (defined $file or defined $name) {
        $cmd = 'about';
    } else {
        failed("Please specify at least a command, name, file, or -h for help\n");
    }
}

# parse the metafile first
if (defined $premeta) {
    my $m = beacon($premeta);
    failed( $m->lasterror ) if $m->errorcount;
    $handlers{pre} = { $m->meta() };
}

sub requirefile {
    $file = shift @ARGV unless defined $file;
    failed("Please specifiy a file to parse") unless defined $file;
    $beaconfile = beacon( $file, %handlers );
}

sub requirename {
    $name = shift @ARGV unless defined $name;
    failed("Please specifiy a beacon name!") unless defined $name;
    $name = lc($name);
    failed("Not a valid beacon name: $name") unless $name =~ /^[a-z][a-z0-9_.-]+$/;
}

sub requirestore {
    failed("Command $cmd requires a beacon store. Please provide a config file")
        unless $beaconstore;
}


if ($cmd eq 'list' or $cmd eq 'init') {
    requirestore();
} elsif($cmd eq 'parse') {
    requirefile();
} else {
    if ($cmd =~ /^(expand|dump|about|query)$/) {
        $beaconstore ? requirename() : requirefile();
    } else { # delete|insert|update : require store and name
        requirestore();
        requirename();
        requirefile() if $cmd =~ /^(insert|update)$/;
    }
}

# end of additional command line parsing

if ($testmode) {
    print "Running in test mode with the following arguments:\n";
    print "  command: $cmd\n";
    print "  file:    $file\n";
    print "  name:    $name\n";
    print "  config:  $configfile\n";
    print "  pre:     $premeta\n" if defined $premeta;
    exit; # TODO: we could perform some more action but read-only
}

# Now finally do something

if ($cmd eq 'parse') { # parse a file
    $beaconfile->parse();
    print $beaconfile->metafields(); # extended metafields
    # TODO: show whether there have been errors
} elsif($cmd eq 'about') { # show info about a file or stored beacon
    # TODO: beaconstore
    print $beaconfile->metafields(); 
} elsif($cmd eq 'dump') { # dump a full, parsed beacon file
    print $beaconfile->metafields() unless $linksmode;
    $beaconfile->parse(
        'link' => sub { print getbeaconlink(@_) . "\n"; }
    );
    # TODO: we may warn on errors, wrong count etc.
} elsif($cmd eq 'expand') { # dump a full, parsed beacon file
    $beaconfile->parse(
        'link' => sub {  # TODO: put this into the package and test it
            my ($id, $label, $description, $to, $fullid, $fulluri) = @_;
            my @link = $fullid;
            push @link, $label if $label ne '' or $description ne '';
            push @link, $description if $description ne '';
            push @link, $fulluri;
            print join('|', @link) . "\n";
        }
    );
} # TODO: implement other commands


sub failed { # error handler
    my $msg = shift;
    $msg =~ s/\n$//g;
    $msg =~ s/ at .+ line \d+//;
    print STDERR "$msg!\n";
    exit(1);
}

=head1 SYNOPSIS

beacon [ <options> ] [ <command> ] [ <file> ]

=head1 ARGUMENTS

 -file <name>   specify a file (use '-' for stdin)

 -links       only print links, without meta fields
 -quiet       suppress all error messages
 -test        enable test mode (no stored beacon is modified)
 -help        brief help message
 -pre <file>  start with meta fields from a given beacon file
 -man         full documentation with examples

 about <name>    show meta information about a file or stored beacon
 parse <file>    parse a full beacon file and print meta information
 dump <name>     parse a beacon file or dump a stored beacon
 links <name>    same as dump, but omit meta fields
 expand <name>   parse and expand a beacon file or dump a stored beacon

The following options and commands are not fully implemented yet, but 
planned to support storing and modifying beacons in a database or other store.

 -config <file> specify config file
 -name <name>   specify a name (for stored beacons)

 init           initialize beacon store
 list           list names of all stored beacons
 insert <name> <file>  insert a new beacon from file to a store
 update <name> <file>  replace a beacon from file to a store
 delete <name>         remove a beacon from a store

 query <name> <id>     query a beacon (stored of file) for an id

=head1 DESCRIPTION

This command line script manages beacon files. You can use it for parsing,
validating, expanding, and storing beacon files in a database (the latter
not implemented yet).

The first command line argument is treated as command
or as [file] name. To simply show the meta fields of a beacon file (command
'about', you can use any one of the following:

  beacon myfile
  beacon -file myfile
  beacon -file myfile about
  beacon about myfile



=head1 NOTE

The command name 'beacon' clashes with a tool of same name from the
ax25-tools package (L<http://www.linux-ax25.org/>. If you need to use
beacon together with hamradio, you need to rename one of the two scripts.

=head1 AUTHOR

Jakob Voss C<< <jakob.voss@gbv.de> >>

=head1 LICENSE

Copyright (C) 2007-2010 by Verbundzentrale Goettingen (VZG) and Jakob Voss

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself, either Perl version 5.8.8 or, at
your option, any later version of Perl 5 you may have available.

