#!/usr/bin/env perl

##
## watchdiff: watch difference
##
## Copyright 2014- Kazumasa Utashiro
##
## Original version on Feb 15 2014
##

use v5.14;
use warnings;

use open ":std" => ":encoding(utf8)";
use Fcntl;
use Pod::Usage;
use Data::Dumper;

use List::Util qw(pairmap);

use App::sdif;
my $version = $App::sdif::VERSION;

my $opt_d;
my $opt_unit = '';
my $opt_diff;
my $opt_date = 1;
my $opt_refresh = 1;
my $opt_interval = 2;
my $opt_count = 1000;
my $opt_clear = 1;
my $opt_silent = 0;
my $opt_newline = 1;
my $opt_mark = 0;
my $opt_old = 0;
my @opt_exec;
my @opt_colormap;

my @optargs = (
    "d" => \$opt_d,
    "unit=s" => \$opt_unit,
    "diff=s" => \$opt_diff,
    "e|exec=s" => \@opt_exec,
    "r|refresh:1" => \$opt_refresh,
    "i|interval=i" => \$opt_interval,
    "c|count=i" => \$opt_count,
    "clear!" => \$opt_clear,
    "s|silent!" => \$opt_silent,
    "M|mark!" => \$opt_mark,
    "O|old!" => \$opt_old,
    "D|date!" => \$opt_date,
    "N|newline!" => \$opt_newline,
    "colormap|cm=s" => \@opt_colormap,
    "p|plain" => sub { $opt_date = $opt_newline = 0 },
    "h|help" => sub { usage() },
    "H|man" => sub { pod2usage( {-verbose => 2} ) },
    );

use Getopt::EX::Long;
Getopt::Long::Configure(qw(bundling require_order));
GetOptions(@optargs) || usage({status => 1});

my %colormap = qw(
    APPEND	K/544
    DELETE	K/544
    OCHANGE	K/445
    NCHANGE	K/445
    OTEXT	K/455E
    NTEXT	K/554E
    );

use Getopt::EX::Colormap;
my $cm = Getopt::EX::Colormap
    ->new(HASH => \%colormap)
    ->load_params(@opt_colormap);

my @default_diff = (
    qw(cdif --no-command --no-unknown -U100),
    map { ('--cm', "$_=$colormap{$_}") } sort keys %colormap
    );

if (@ARGV) {
    push @opt_exec, [ @ARGV ];
} else {
    @opt_exec or pod2usage();
}

sub flush {
    use IO::Handle;
    state $stdout = IO::Handle->new->fdopen(fileno(STDOUT), "w") or die;
    $stdout->printflush(@_);
}

use App::cdif::Command;
my $old = App::cdif::Command->new(@opt_exec);
my $new = App::cdif::Command->new(@opt_exec);

my @diffcmd = do {
    if ($opt_diff) {
	use Text::ParseWords;
	shellwords $opt_diff;
    } else {
	( @default_diff,
	  map  { $_->[1] }
	  grep { $_->[0] }
	  [ $opt_unit   => "--unit=$opt_unit" ],
	  [ ! $opt_mark => '--no-mark' ],
	  [ ! $opt_old  => '--no-old' ],
	);
    }
};

use Getopt::EX::Colormap qw(ansi_code);
my %termcap = pairmap { $a => ansi_code($b) } qw(
    home	{CUP}
    clear	{CUP}{ED2}
    el		{EL}
    ed		{ED}
    );

sub run {
    use IO::File;
    my $pid = (my $fh = IO::File->new)->open('-|') // die "open: $@\n";
    if ($pid == 0) {
	open STDERR, ">&STDOUT" or die "dup: $!";
	close STDIN;
	exec @_ or warn "$_[0]: $!\n";
	exit 3;
    }
    binmode $fh, ':encoding(utf8)';
    my $result = do { local $/; <$fh> };
    for my $child (wait) {
	$child != $pid and die "child = $child, pid = $pid";
    }
    ($? >> 8) == 3 ? undef : $result;
}

print $termcap{clear} if $opt_refresh;
my $count = 0;
my $refresh_count = 0;
while (1) {
    $old->rewind;
    $new->update;
    my $data = run(@diffcmd, $old->path, $new->path) // die "diff: $!\n";
    if ($data eq '') {
	if ($opt_silent) {
	    flush $new->date, "\r";
	    next;
	}
	$data = $new->data;
	$data =~ s/^/ /mg if $opt_mark;
    }
    $data .= "\n" if $opt_newline;
    if ($opt_refresh) {
	$data =~ s/^/$termcap{el}/mg;
	if ($refresh_count++ % $opt_refresh == 0) {
	    print $termcap{clear};
	}
    }
    print $new->date, "\n\n" if $opt_date;
    print $data;
    if ($opt_refresh and $opt_clear) {
	flush $termcap{ed};
    }
} continue {
    last if ++$count == $opt_count;
    ($old, $new) = ($new, $old);
    sleep $opt_interval;
}

flush $termcap{el} if $opt_refresh;

exit;

######################################################################

=pod

=head1 NAME

watchdiff - repeat command and watch a difference

=head1 SYNOPSIS

watchdiff option -- command

Options:

	-r, --refresh:1     refresh screen count (default 1)
	-i, --interval=i    interval time in second (default 2)
	-c, --count=i       command repeat count (default 1000)
	-e, --exec=s        set executing commands
	-s, --silent        do not show same result
	-p, --plain         shortcut for --nodate --nonewline
	--[no]date          show date at the beginning (default on)
	--[no]newline       print newline result (default on)
	--[no]clear         clear screen after output (default on)
	--diff=command      diff command used to compare result
	--unit=unit         comparison unit (word/char/mecab)

=head1 EXAMPLES

	watchdiff ifconfig -a

	watchdiff df

	watchdiff --silent df

	watchdiff --refresh 5 --noclear df

	watchdiff -sri1 -- netstat -sp icmp

	watchdiff -e uptime -e iostat -e df

	watchdiff -ps --diff 'sdif --no-command -U-1' netstat -S -I en0

	watchdiff -pc18i10r0 date; say tea is ready


=head1 DESCRIPTION

Use C<^C> to terminate.


=head1 AUTHOR

Kazumasa Utashiro

L<https://github.com/kaz-utashiro/sdif-tools>


=head1 LICENSE

Copyright 2014- Kazumasa Utashiro

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.


=head1 SEE ALSO

L<diff(1)>, L<cdif(1)>, L<sdif(1)>


=cut
