#!/usr/bin/perl -w

use strict ;
use warnings ;
use Carp ;

=head1 NAME 

 $>hdr - hexdump range

=head1 USAGE

 $> hdr -r range_definitions file_to_dump
 
 $> hdr file_to_dump -r 'header,40, yellow:padding,24:magic,16,red:size,4:data,100:footer,16' -o ver

=head1 OPTIONS

 range_description|r              file name containing a description
                                  or a string description formated as:
			                   'color:name,size:name,size,...'
					    
 offset                           position in the data where to start dumping
 offset_start                     value added to the offset before display
 
 maximum_size                     amount of data to dump
 
 orientation|o                    'horizontal' or 'vertical'
 display_column_names|col         display columns names
 display_ruler|rul                display horizontal ruler
 format|f                         'ANSI' or 'ASCII' or 'HTML' 
 display_command_line             make the command line part of the output
 
 color                            'bw' or 'cycle',
 colors                           file containing custom colors
 start_color                      name of the first random color to use
 start_tag/end_tag                text that is output before and after the dump
                                       see L<hdr_examples.pod>
 
 data_width|w                     number of bytes per dump line
 
 offset_format                    'hex' or 'dec' 
 display_offset                   0 == no the offset display
 display_cumulative_offset        0 == no cumulative offset display
 display_zero_size_range          0 == no display of range with size 0
 display_zero_size_range_warning  0 == no warnings about ranges with size 0
 
 display_range_name               1 == display of the range name
 maximum_range_name_size          truncate range name if longer
 display_range_size               1 == prepend the range size to the name
 
 display_hex_dump                 1 == display hexadecimal dump column
 display_dec_dump                 1 == display decimal dump column
 display_ascii_dump               1 == display ASCII dump column
 display_user_information         1 == display user information columns
 
 display_bitfields                1 == display bitfields
 display_source                   1 == display source for bitfields 
 bit_zero_on_left                 1 == bit index zero is on the left
 
 h|help                           display this scripts help page
 generate_completion_script|bash  generates a completion script on STDOUT
 

=head1 EXIT STATUS

Non zero if an error occured.

=head1 AUTHOR

  Nadim ibn hamouda el Khemir
  CPAN ID: NKH
  mailto: nkh@cpan.org

=cut

#------------------------------------------------------------------------------------------------------------------------

use Getopt::Long ;
use English qw( -no_match_vars ) ;

use File::Slurp ;
use IO::Select ;

use Data::HexDump::Range qw() ;
use Term::Bash::Completion::Generator ;

our $VERSION = '0.04' ;

use Readonly ;
Readonly my $SIZE_IF_RANGE_ERROR=> 256 ;
Readonly my $DEFAULT_SIZE => 16 ;

#------------------------------------------------------------------------------------------------------------------------

my @options = 
	(
	'range_description|r=s' => \ my $range_description,
	'dump_range_description|d' =>\my $dump_range_description,

	'offset=i' =>  \my $offset,
	'offset_start=i' => \my $offset_start,
	
	'maximum_size=i' =>  \my $maximum_size,
	'orientation|o=s' => \my $orientation,
	'display_column_names|col' => \my $display_column_names,
	'display_ruler|rul' => \my $display_ruler,

	'format|f=s' => \my $format,
	'display_command_line' => \my $display_command_line,
	
	'color=s' => \my $color,
	'colors=s' => \my $color_file,
	'start_color=s' => \my $start_color,
	'start_tag=s' => \my $start_tag,
	'end_tag=s' => \my $end_tag,
	
	'data_width=i' =>  \my $data_width,

	'offset_format=s' => \my $offset_format,
	'display_offset=i' => \my $display_offset,
	'display_cumulative_offset=i' => \my $display_cumulative_offset,
	'display_zero_size_range=i' => \my $display_zero_size_range,
	'display_zero_size_range_warning=i' => \my $display_zero_size_range_warning,

	'display_range_name=i' => \my $display_range_name,
	'maximum_range_name_size=i' => \my$maximum_range_name_size,
	'display_range_size=i' => \my $display_range_size,

	'display_hex_dump=i' => \my $display_hex_dump,
	'display_dec_dump=i' => \my $display_dec_dump,
	'display_ascii_dump=i' => \my $display_ascii_dump,
	'display_user_information=i' => \my $display_user_information,

	'display_bitfields=i' => \my $display_bitfields,
	'display_bitfield_source=i' => \my $display_bitfield_source,
	'bit_zero_on_left' => \my $bit_zero_on_left,

	'h|help' => \&display_help, 
	'generate_completion_script|bash' => \my $generatebash_completion,
	) ;

my @ARGV_COPY = @ARGV ; # getopt removes elements

display_help() unless GetOptions(@options) ;

generate_completion_script(@options) if $generatebash_completion ;

print "\n$start_tag\n\n" if defined $start_tag ;

if($display_command_line)
	{
	use Text::Colorizer ;
	my $c= Text::Colorizer->new(FORMAT => $format || 'ANSI', JOIN => q{ }) ;
	
	print $c->color_all('bright_white', 'hdr', grep{! /-output_command_line/xsm} @ARGV_COPY) ;
	}

my $data ;

my $io_select = IO::Select->new(\*STDIN) ;
if($io_select->can_read(0))
	{
	local $INPUT_RECORD_SEPARATOR = undef ;
	$data =  <STDIN> ; ## no critic (InputOutput::ProhibitExplicitStdin)
	}
else
	{
	display_help() unless @ARGV ;
	$data = read_file shift @ARGV ;
	}

$offset ||= 0 ;

my $range ;

if(defined $range_description )
	{
	if($range_description =~ /,/xsm)
		{
		$range = $range_description ;
		}
	else
		{
		# a file
		$range  = do $range_description || ['hdr: range error', $SIZE_IF_RANGE_ERROR ] ;
		}
	}
else
	{
	$range = ['no range definition', length($data) ] ;
	$display_range_name = 0 ;
	$display_bitfield_source = 0 ;
	}
	
	
my @color_file ;
@color_file = (COLOR_NAMES => $color_file) if(defined $color_file) ;

my $hdr = Data::HexDump::Range->new
			(
			INTERACTION => {WARN => sub {warn @_}}, ## no critic (ErrorHandling::RequireCarping)
			
			ORIENTATION => $orientation || 'horizontal',
			DISPLAY_COLUMN_NAMES => defined $display_column_names ? $display_column_names : 0,
			DISPLAY_RULER => defined $display_ruler ? $display_ruler : 0,
			
			FORMAT => $format || 'ANSI',
			COLOR => defined $color ? $color : 'cycle',
			START_COLOR => $start_color,
			
			OFFSET_FORMAT => $offset_format || 'hex',
			OFFSET_START => $offset_start || 0,
			
			DATA_WIDTH => $data_width || $DEFAULT_SIZE,
			
			DISPLAY_RANGE_NAME => defined $display_range_name ? $display_range_name : 1 ,
			DUMP_RANGE_DESCRIPTION => defined $dump_range_description ? $dump_range_description : 0 ,
			MAXIMUM_RANGE_NAME_SIZE => defined $maximum_range_name_size ? $maximum_range_name_size : $DEFAULT_SIZE,
			DISPLAY_RANGE_SIZE => defined $display_range_size ? $display_range_size : 0,
			
			DISPLAY_OFFSET  => defined $display_offset ? $display_offset : 1 ,
			DISPLAY_CUMULATIVE_OFFSET  => defined $display_cumulative_offset ? $display_cumulative_offset : 1 ,
			DISPLAY_HEX_DUMP => defined $display_hex_dump ? $display_hex_dump : 1,
			DISPLAY_DEC_DUMP => defined $display_dec_dump ? $display_dec_dump : 0,
			DISPLAY_ASCII_DUMP => defined $display_ascii_dump ? $display_ascii_dump :  1 ,
			DISPLAY_USER_INFORMATION => defined $display_user_information ? $display_user_information :  0 ,
			DISPLAY_ZERO_SIZE_RANGE => defined $display_zero_size_range ? $display_zero_size_range : 1,
			DISPLAY_ZERO_SIZE_RANGE_WARNING => defined  $display_zero_size_range_warning ? $display_zero_size_range_warning : 1,
			
			DISPLAY_BITFIELDS => $display_bitfields,
			DISPLAY_BITFIELD_SOURCE => defined $display_bitfield_source ? $display_bitfield_source : 1,
			BIT_ZERO_ON_LEFT =>  defined $bit_zero_on_left ? $bit_zero_on_left : 0,
			@color_file
			) ;

print $hdr->dump( $range, $data, $offset, $maximum_size) ;

print "\n$end_tag\n\n" if defined $end_tag ;

#------------------------------------------------------------------------------------------------------------------------

sub display_help
{

#~ =head2 display_help()

#~ I<Arguments> - None

#~ I<Returns> - Nothing

#~ I<Exceptions> - exits with status code B<1>

#~ =cut

my ($this_script) = ($PROGRAM_NAME =~m/(.*)/sxm ) ;

print {*STDERR} `perldoc $this_script`  or croak 'Error: Can\'t display help!' ; ## no critic (InputOutput::ProhibitBacktickOperators)
exit(1) ;
}

#------------------------------------------------------------------------------------------------------------------------

sub generate_completion_script
{
#~ =head2 generate_completion_script(@definitions)

#~ I<Arguments> - @definitions - getop options description

#~ I<Returns> - Nothing

#~ I<Exceptions> - exits with status code B<1> after emitting the completion script on stdout

#~ =cut

my (@definitions) = @_ ;

my $flip = 0 ;
my @options = grep {++$flip % 2} @definitions ;

print Term::Bash::Completion::Generator::generate_bash_completion_function('hdr', [@options], undef, 1) ;

exit(0) ;
}

