#!/usr/bin/perl

use strict;
use warnings;

use FindBin qw($RealBin);

use lib "$RealBin/../lib";

use Config::General;
use File::Path;
use Getopt::Long;
use JSON;
use Log::Log4perl qw(:easy);

use perfSONAR_PS::MeshConfig::Config::Mesh;

Log::Log4perl->easy_init($DEBUG);

my ( $status, $res );

my $INPUT = '-';
my $OUTPUT = '-';
my $LOGGER_CONF;
my $DEBUGFLAG;
my $HELP;

$status = GetOptions(
    'input=s'  => \$INPUT,
    'output=s' => \$OUTPUT,
    'logger=s'  => \$LOGGER_CONF,
    'verbose'   => \$DEBUGFLAG,
    'help'      => \$HELP
);

my $logger;
unless ( $LOGGER_CONF ) {
    use Log::Log4perl qw(:easy);

    my $output_level = $INFO;
    if ( $DEBUGFLAG ) {
        $output_level = $DEBUG;
    }

    my %logger_opts = (
        level  => $output_level,
        layout => '%d (%P) %p> %F{1}:%L %M - %m%n',
    );

    Log::Log4perl->easy_init( \%logger_opts );
}
else {
    use Log::Log4perl qw(get_logger :levels);

    Log::Log4perl->init( $LOGGER_CONF );
}

$logger = get_logger( "perfSONAR_PS" );
$logger->level( $DEBUG ) if $DEBUGFLAG;

my %configuration;

if ($INPUT eq "-") {
    my $config = "";
    while(<>) {
        $config .= $_;
    }
    %configuration = Config::General->new(-String => $config)->getall;
}
else {
    %configuration = Config::General->new($INPUT)->getall;
}

# We maintain a mapping of the array variables to their 'real' name so we know
# what to rename them to.
my %array_variables = (
                    "administrator" => "administrators",
                    "test" => "tests",
                    "measurement_archive" => "measurement_archives",
                    "address" => "addresses",
                    "member" => "members",
                    "a_member" => "members",
                    "b_member" => "members",
                    "site" => "sites",
                    "organization" => "organizations",
                    "host" => "hosts",
                    "no_agent" => "no_agents",
                );

# Parse everything except the 'test', 'group' and 'test_spec' elements.
my %json_configuration = %{ __parse_hash(\%configuration, [ "test", "group", "test_spec" ]) };

# Go through and parse all the test specs, keeping track of their id.
my %test_specs = ();
foreach my $test_spec_id (keys %{ $configuration{"test_spec"} }) {
    my $desc = $configuration{"test_spec"}->{$test_spec_id};

    $test_specs{$test_spec_id} = __parse_hash($desc);
}

# Go through and parse all the groups, keeping track of their id.
my %groups = ();
foreach my $group_id (keys %{ $configuration{"group"} }) {
    my $desc = $configuration{"group"}->{$group_id};

    $groups{$group_id} = __parse_hash($desc);
}

my @tests = ();

$configuration{"test"} = [] unless $configuration{"test"};

if (ref($configuration{"test"}) ne "ARRAY") {
    $configuration{"test"} = [ $configuration{"test"} ];
}

# Parse all the tests, and merge their group and test_spec so that the test is
# of the appropriate json format.
foreach my $test (@{ $configuration{"test"} }) {
    $test = __parse_hash($test);
    my $group = $test->{group};
    my $test_spec = $test->{test_spec};

    delete($test->{group});
    delete($test->{test_spec});

    unless ($test_specs{$test_spec}) {
       die("missing test spec: ".$test_spec);
    }

    unless ($groups{$group}) {
       die("missing group: ".$group);
    }

    my %test_spec = %{ $test_specs{$test_spec} };
    my %group = %{ $groups{$group} };

    $test->{parameters} = \%test_spec;
    $test->{members}    = \%group;

    # In the .json, no_agents are specified at the group level.
    if ($test->{no_agents}) {
        $test->{members}->{no_agents} = $test->{no_agents};
        delete($test->{no_agents});
    }

    push @tests, $test;
}

$json_configuration{tests} = \@tests;

# Parse the resulting hash to  make sure it's correct. We use strict checking
# to ensure we're not generating junk.
my $mesh = perfSONAR_PS::MeshConfig::Config::Mesh->parse(\%json_configuration, 1);

$mesh->validate_mesh();

if ($OUTPUT eq "-") {
    print JSON->new->pretty(1)->encode(\%json_configuration);
}
else {
    open(OUTPUT, ">$OUTPUT");
    print OUTPUT JSON->new->pretty(1)->encode(\%json_configuration);
    close(OUTPUT);
}

exit 0;

# Go through the hash, and convert any 'array' variables into an array, and
# rename their 'key'.
sub __parse_hash {
    my ($hash, $skip) = @_;

    my %skip_map = map { $_ => 1 } @$skip;

    my %new_hash = ();

    foreach my $key  (keys %$hash) {
        my $value = $hash->{$key};

        next if ($skip_map{$key});

        if ($array_variables{$key}) {
            $key = $array_variables{$key};

            $value = [ $value ] unless (ref($value) eq "ARRAY");
        }

        if (ref($value) eq "ARRAY") {
            my @new_value = ();

            foreach my $element (@$value) {
                if (ref($element) eq "HASH") {
                    push @new_value, __parse_hash($element);
                }
                else {
                    push @new_value, $element;
                }
            }

            $value = \@new_value;
        }
        elsif (ref($value) eq "HASH") {
            $value = __parse_hash($value);
        }

        $new_hash{$key} = $value;
    }

    return \%new_hash;
}
