#! /usr/bin/perl

use strict;
use Getopt::Long;
use Data::Dumper;

# There are various YAML implementations for perl. These are the main
# options:
#
# YAML:		has problems parsing its own data
# YAML::Tiny:	has huge problems parsing its own data
# YAML::Syck:	works, doesn't make a difference between '1' and 1
# YAML::XS:	works, makes strings utf8 when reading data
use YAML::XS;

use Bootloader::Tools;
# Explicitly load all loader specific modules as Bootloader::Library
# normally loads them depending on loader type but we don't know in advance
# what objects show up in our YAML input data.
#
eval "use Bootloader::Core::$_" for qw ( ELILO GRUB GRUB2 GRUB2EFI LILO NONE PowerLILO ZIPL );

sub Usage;
sub ReadYAML;
sub WriteYAML;

# we need something to log to until we have the real state setup
my $logger = Bootloader::Library->new();

# the current state
my $pbl_state;

my $opt_state_in;
my $opt_state_out;


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# parse arguments

Usage 0 if @ARGV == 0;

GetOptions(
  'state-in=s'   => \$opt_state_in,
  'state-out=s'  => \$opt_state_out,
  'state=s'      => sub { $opt_state_out = $opt_state_in = $_[1] },
  'version'      => sub { print "$Bootloader::Library::VERSION\n"; exit 0 },
  'help'         => sub { Usage 0 },
) || Usage 1;


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# read existing state, if any

if($opt_state_in) {
  $pbl_state = ReadYAML($opt_state_in, 1);
  if($pbl_state) {
    # increment session id
    $pbl_state->{session_id} =~ s/\.(\d+)$/.${\($1+1)}/;
    # restart log
    $pbl_state->StartLog();
  }
}

# if we didn't get a saved state, start with a fresh one
$pbl_state = $logger if !defined $pbl_state;

# use normal logging function
$logger = $pbl_state;


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# process all commands
# command format: 'foo=Func(bar)' (no spaces!)
# foo and bar are optional

for my $cmd (@ARGV) {
  if($cmd =~ /^(([^=]+)=)?([\w]+)\(([^()]*)\)$/) {
    my $res_file = $2;
    my $func = $3;
    my $arg_file = $4;
    my $multi = $arg_file =~ s/^@//;

    my @args;
    if($arg_file ne "") {
      my $args = ReadYAML($arg_file);
      if($multi && ref($args) eq 'ARRAY') {
        @args = @$args;
      }
      else {
        @args = ($args);
      }
    }

    my $res;

    if(exists $Bootloader::Tools::{$func}) {
      # InitLibrary() is a bit special, as it creates the state
      if($func eq 'InitLibrary') {
        $res = Bootloader::Tools::InitLibrary($pbl_state);
      }
      else {
        Bootloader::Tools::SetState($pbl_state);
        eval "\$res = Bootloader::Tools::$func(\@args)";
      }
    }
    elsif(exists $Bootloader::Library::{$func}) {
      $res = $pbl_state->$func(@args);
    }
    elsif(exists $Bootloader::FileIO::{$func}) {
      $res = $pbl_state->$func(@args);
    }
    elsif(exists $Bootloader::Path::{$func}) {
      eval "\$res = Bootloader::Path::$func(\@args)";
    }
    else {
      $logger->error("$func: no such function");
    }

    WriteYAML($res_file, $res) if $res_file ne "";
  }
  else {
    $logger->error("$cmd: invalid command format");
  }
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# store final state

WriteYAML($opt_state_out, $pbl_state) if $opt_state_out;


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub Usage
{
  my $err = $_[0];

  if($err) {
    print STDERR "Try 'pbl-yaml --help' for more information.\n";

    exit $err;
  }

  print <<"  usage";
Usage: pbl-yaml [OPTIONS] COMMANDS
perl-Bootloader library call wrapper using YAML files for arguments.

Options:
  --state-in FILE    Read perl-Bootloader state from FILE.
                     If it can't be read, a new state is created.
  --state-out FILE   Store final perl-Bootloader state in FILE.
  --state FILE       Use FILE for reading and storing the state.
  --version          Print perl-Bootloader version.
  --help             Write this help text.

Commands:

  COMMANDS have the form 'RESULT=FUNCTION(ARGUMENT) or RESULT=FUNCTION(\@ARGUMENTS)'.
  
  ARGUMENT is a YAML file containing a single argument. ARGUMENTS is a YAML
  file containing an array of arguments. The return value of FUNCTION will
  be stored in RESULT. RESULT and ARGUMENT are optional.

  FUNCTION is a function from either Bootloader::Tools or Bootloader::Library.

Examples:

  pbl-yaml --state foo1 'InitLibrary()' 'bar=ReadSettings()'
  pbl-yaml --state foo2 'x1=GetDeviceMapping()' 'x2=ReadMountPoints(x1)' 'x3=ReadPartitions(x1)'

Note:

  Logging is done via perl-Bootloader. That means it ends up in
  /var/log/pbl.log or on STDERR if that file is not writable.

  usage

  exit $err;
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Read data from file.
#
sub ReadYAML
{
  my $file = $_[0];
  my $warn_only = $_[1];

  my $x;

  if(open my $f, "<", $file) {
    local $/;
    $_ = <$f>;
    close $f;
    $x = YAML::XS::Load($_);
  }

  if(!defined $x) {
    if($warn_only) {
      $logger->milestone("$file: no YAML data");
    }
    else {
      $logger->error("$file: no YAML data");
    }
  }

  return $x;
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Write data to file.
#
sub WriteYAML
{
  my $file = $_[0];
  my $data = $_[1];

  my $x = YAML::XS::Dump($data);

  if(defined $x && open my $f, ">", $file) {
    print $f $x;
    close $f;
  }
  else {
    $logger->error("$file: failed to write YAML data");
  }
}


