#!/usr/bin/perl

##########################
# TODO
# CHANGE VERSION
##########################

use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;
use JSON;
# use Data::Dumper; # TODO remove for prod

# Sample data
my %sample;
$sample{efl_command} = <<'END';
# For efl_command bundle, header must match new json bundle requirements
#  context     ;; command                            ;; useshell       ;; module ;; ifelapsed ;; promisee
  am_policy_hub ;; ${sys.workdir}/modules/cf-manifest ;; noshell      ;; false  ;;         1 ;; efl_update for cfengine
  backup_host ;; /usr/local/bin/backup              ;; nohell         ;; false  ;;         1 ;; my backups
END

my %headers = (
   'efl_main'              => [ qw/ class promiser bundle ifelapsed parameter promisee / ],
   'efl_command'           => [ qw/ class command useshell module ifelapsed promisee / ],
   'efl_server'            => [ qw/ class path admit promisee / ],
   'efl_class_returnszero' => [ qw/ class class_to_set command shell zero promisee / ],
   'efl_test_simple'       => [ qw/ class class_to_test is_or_isnt name / ],
   'efl_global_slists'     => [ qw/ class name contents shuffle promisee / ],
   'efl_dump_strings'      => [ qw/ class name promisee / ],
   'efl_global_strings'    => [ qw/ class name value promisee / ],
   'efl_class_cmd_regcmp'  => [ qw/ class class_to_set expression command useshell regex promisee / ],
   'efl_class_expression'  => [ qw/ class_to_set expression promisee / ],
   'efl_class_classmatch'  => [ qw/ class_to_set regex promisee / ],
   'efl_class_iprange'     => [ qw/ class_to_set ip_range promisee / ],
   'efl_sysctl_live'       => [ qw/ class variable value promisee / ],
   'efl_sysctl_conf_file'  => [ qw/ class variable value promisee / ],
   'efl_link'              => [ qw/ class link_name target link_type promisee / ],
   'efl_delete_files'      => [ qw/ class file_promiser recurse_level leaf_regex negate_match file_age promisee / ],
   'efl_copy_files'        => [ qw/ class file_promiser leaf_regex file_source server encrypt mode owner group promisee / ],
   'efl_packages_via_cmd'  => [ qw/ class package_policy package_name package_version architecture promisee / ],
   'efl_packages'          => [ qw/ class package_policy package_name package_version architecture promisee / ],
   'efl_start_service'     => [ qw/ class process_regex restart_cmd promisee / ],
   'efl_service_recurse'   => [ qw/ class process_regex config_dir config_dir_src server encrypt mode owner group restart_cmd promisee / ],
   'efl_service'           => [ qw/ class process_regex config_file config_file_src server template encrypt mode owner group restart_cmd promisee / ],
   'efl_edit_template'     => [ qw/ class promiser_file template server mode owner group promisee / ],
   'efl_enable_service'    => [ qw/ class service promisee / ],
   'efl_disable_service'   => [ qw/ class service promisee / ],
   'efl_file_perms'        => [ qw/ class file_promiser recurse_level leaf_regex negate mode owner group promisee / ],
   'efl_rcs_pull'          => [ qw/ class update_cmd checkout_cmd dest_dir mode owner group promisee / ],
);

# Get command options and switches
my %opt;
$opt{delimiter} = ';;';
$opt{array_delimiter} = ',';
GetOptions (
   \%opt,
   'help|?',
   'version',
   'delimiter=s',
   'array_delimiter=s',
   'sample=s',
   'bundle=s',
   );

# Prints usage and exits
sub usage {
   my %args = (
      exit => 0,
      @_,
   );

   pod2usage(
      -verbose  => 99,
      -exitval  => $args{exit},
      -sections =>"SYNOPSIS",
      -msg      => $args{msg},
   );
}

##########################
# Usage
 if ( $opt{help} ) { usage() }

##########################
# Version
if ( $opt{version} ) { pod2usage(-verbose=>99, -sections=>"VERSION" ) }

sub trim_whitespace
{
   my @fields = @_;

   @fields = map { s/\s+$//; s/^\s+//; $_ } @fields;

   return @fields;
}

##########################
# Validate
if ( $opt{sample} && ! $sample{$opt{sample}} )
{
   usage( msg => "Wrong sample arg: $opt{sample}", exit => 2 );
}

if ( $opt{bundle} && $opt{bundle} eq 'all' )
{
   print "$_\n" for keys %headers;
   exit;
}

for my $bundle ( qw/ enable disable/ )
{
   if ( $opt{bundle} eq "efl_chkconfig_${bundle}_service" )
   {
      warn "Bundle $opt{bundle} has been replaceed with efl_${bundle}_service";
      exit;
   }
}

if ( $opt{bundle}  && ! grep m/^$opt{bundle}$/, keys %headers )
{
   usage( msg => "Wrong or missing bundle name: $opt{bundle}", exit => 3 );
}

##########################
# Main matter here

if ( $opt{sample} )
{
   print $sample{$opt{sample}};
   exit;
}

my ( @fields, $pre_json, @pre_json, $fh );
my @headers = @{ $headers{$opt{bundle}} };
my $rows = 0;

while (<STDIN>)
{
  chomp;

  # Remove in line comments.
  s/\s#.*?$//g;

  # Remove new line, carrage returns in case it is a windows file, or any
  # trailing white space
  s/[\n\r\s]$//;

  # Skip comment lines.
  next if m/^#/;

  # Skip whitespace lines.
  next if m/^\s$/;

  if ( @fields = split m/$opt{delimiter}/ )
  {
     @fields = trim_whitespace( @fields );

     for ( my $f = 0; $f <= $#fields; $f++ )
     {
        if ( $fields[$f] =~ m/$opt{array_delimiter}/ )
        {
           my @array = split m/$opt{array_delimiter}/, $fields[$f];
           @array = trim_whitespace( @array );
           push @{ $pre_json->{$headers[$f]} }, @array;
        }
        else
        {
           $pre_json->{$headers[$f]} = $fields[$f];
        }
     }
     push @pre_json, $pre_json;
     undef $pre_json;
     $rows++;
  }
}

my $json = JSON->new->pretty;
my $pretty_json = $json->encode( \@pre_json );
print $pretty_json;

##########################
# POD

=head1 SYNOPSIS

Convert CSV file to JSON file as a filter.
csvtojson < input.csv > output.json
[ -h | --help | -? ] help
[ -v | -- version ] version
[ -s | --sample] <commands|server> pring sample data
[ -d | --delimitier] '<delimiter regex>' defaults to ';;'
[ -a | --array_delimitier] '<delimiter regex>' defaults to ','
[ -b | --bundle] <bundle name> convert file for this EFL bundle

Use perldoc to see full documentation.

=head1 DESCRIPTION

Any commas found in a field and program will treat field as an array.

=head1 EXAMPLES

  # context     ;; command                            ;; useshell       ;; module ;; ifelapsed ;; promisee
  am_policy_hub ;; ${sys.workdir}/modules/cf-manifest ;; noshell      ;; false  ;;         1 ;; efl_update for cfengine
  backup_host ;; /usr/local/bin/backup              ;; nohell         ;; false  ;;         1 ;; my backups

  csvtojson -b efl_command < efl_command.txt > efl_command.json

  cat efl_command.json
  [
   {
      "context"   : "am_policy_hub",
      "command"   : "${sys.workdir}/modules/cf-manifest",
      "useshell"  : "noshell",
      "module"    : "false",
      "ifelapsed" : "1",
      "promisee"  : "efl_update for cfengine"
   },
   {
      "context"   : "backup_host",
      "command"   : "usr/local/bin/backup",
      "useshell"  : "noshell",
      "module"    : "false",
      "ifelapsed" : "1",
      "promisee"  : "my  backups"
   }
   ]

   Show bundles currently support. If yours is missing assume it's WIP.

   csvtojson -b all

=head2 Using sample data

Show sample data for efl_command bundle.
   csvtojson -s efl_command

=head1 VERSION

1.0

=head1 SEE ALSO

=head1 AUTHOR

Neil H. Watson, nwatson@evolvethinking.com

=head1 COPYRIGHT

Copyright 2014 Evolve Thinking Limited

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 3 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
<http://www.gnu.org/licenses/>.

=cut
