#!/usr/bin/env perl

# This chunk of stuff was generated by App::FatPacker. To find the original
# file's code, look for the end of this BEGIN block or the string 'FATPACK'
BEGIN {
my %fatpacked;

$fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DIFFHIGHLIGHT';
  package DiffHighlight;

  use 5.008;
  use warnings FATAL => 'all';
  use strict;
  use Encode;

  # Highlight by reversing foreground and background. You could do
  # other things like bold or underline if you prefer.
  our @OLD_HIGHLIGHT = (
  	color_config('color.diff-highlight.oldnormal',    "\e[1;31m"),
  	color_config('color.diff-highlight.oldhighlight', "\e[1;31;48;5;52m"),
  	"\x1b[27m",
  );
  our @NEW_HIGHLIGHT = (
  	color_config('color.diff-highlight.newnormal',    "\e[1;32m"),
  	color_config('color.diff-highlight.newhighlight', "\e[1;32;48;5;22m"),
  	$OLD_HIGHLIGHT[2],
  );

  my $RESET = "\x1b[m";
  my $COLOR = qr/\x1b\[[0-9;]*m/;
  my $BORING = qr/$COLOR|\s/;

  # The patch portion of git log -p --graph should only ever have preceding | and
  # not / or \ as merge history only shows up on the commit line.
  my $GRAPH = qr/$COLOR?\|$COLOR?\s+/;

  my @removed;
  my @added;
  my $in_hunk;

  our $line_cb = sub { print @_ };
  our $flush_cb = sub { local $| = 1 };

  sub handle_line {
  	local $_ = shift;

  	if (!$in_hunk) {
  		$line_cb->($_);
  		$in_hunk = /^$GRAPH*$COLOR*\@\@ /;
  	}
  	elsif (/^$GRAPH*$COLOR*-/) {
  		push @removed, $_;
  	}
  	elsif (/^$GRAPH*$COLOR*\+/) {
  		push @added, $_;
  	}
  	else {
  		show_hunk(\@removed, \@added);
  		@removed = ();
  		@added = ();

  		$line_cb->($_);
  		$in_hunk = /^$GRAPH*$COLOR*[\@ ]/;
  	}

  	# Most of the time there is enough output to keep things streaming,
  	# but for something like "git log -Sfoo", you can get one early
  	# commit and then many seconds of nothing. We want to show
  	# that one commit as soon as possible.
  	#
  	# Since we can receive arbitrary input, there's no optimal
  	# place to flush. Flushing on a blank line is a heuristic that
  	# happens to match git-log output.
  	if (!length) {
  		$flush_cb->();
  	}
  }

  sub flush {
  	# Flush any queued hunk (this can happen when there is no trailing
  	# context in the final diff of the input).
  	show_hunk(\@removed, \@added);
  }

  sub highlight_stdin {
  	while (<STDIN>) {
  		handle_line($_);
  	}
  	flush();
  }

  # Ideally we would feed the default as a human-readable color to
  # git-config as the fallback value. But diff-highlight does
  # not otherwise depend on git at all, and there are reports
  # of it being used in other settings. Let's handle our own
  # fallback, which means we will work even if git can't be run.
  sub color_config {
  	my ($key, $default) = @_;
  	my $s = `git config --get-color $key 2>/dev/null`;
  	return length($s) ? $s : $default;
  }

  sub show_hunk {
  	my ($a, $b) = @_;

  	# If one side is empty, then there is nothing to compare or highlight.
  	if (!@$a || !@$b) {
  		$line_cb->(@$a, @$b);
  		return;
  	}

  	# If we have mismatched numbers of lines on each side, we could try to
  	# be clever and match up similar lines. But for now we are simple and
  	# stupid, and only handle multi-line hunks that remove and add the same
  	# number of lines.
  	if (@$a != @$b) {
  		$line_cb->(@$a, @$b);
  		return;
  	}

  	my @queue;
  	for (my $i = 0; $i < @$a; $i++) {
  		my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]);
  		$line_cb->($rm);
  		push @queue, $add;
  	}
  	$line_cb->(@queue);
  }

  sub highlight_pair {
  	my @a = split_line(shift);
  	my @b = split_line(shift);
  	my $opts = shift();

  	# Find common prefix, taking care to skip any ansi
  	# color codes.
  	my $seen_plusminus;
  	my ($pa, $pb) = (0, 0);
  	while ($pa < @a && $pb < @b) {
  		if ($a[$pa] =~ /$COLOR/) {
  			$pa++;
  		}
  		elsif ($b[$pb] =~ /$COLOR/) {
  			$pb++;
  		}
  		elsif ($a[$pa] eq $b[$pb]) {
  			$pa++;
  			$pb++;
  		}
  		elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
  			$seen_plusminus = 1;
  			$pa++;
  			$pb++;
  		}
  		else {
  			last;
  		}
  	}

  	# Find common suffix, ignoring colors.
  	my ($sa, $sb) = ($#a, $#b);
  	while ($sa >= $pa && $sb >= $pb) {
  		if ($a[$sa] =~ /$COLOR/) {
  			$sa--;
  		}
  		elsif ($b[$sb] =~ /$COLOR/) {
  			$sb--;
  		}
  		elsif ($a[$sa] eq $b[$sb]) {
  			$sa--;
  			$sb--;
  		}
  		else {
  			last;
  		}
  	}

  	my @OLD_COLOR_SPEC = @OLD_HIGHLIGHT;
  	my @NEW_COLOR_SPEC = @NEW_HIGHLIGHT;

  	# If we're only highlight the differences temp disable the old/new normal colors
  	if ($opts->{'only_diff'}) {
  		$OLD_COLOR_SPEC[0] = '';
  		$NEW_COLOR_SPEC[0] = '';
  	}

  	if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) {
  		return highlight_line(\@a, $pa, $sa, \@OLD_COLOR_SPEC),
  		       highlight_line(\@b, $pb, $sb, \@NEW_COLOR_SPEC);
  	}
  	else {
  		return join('', @a),
  		       join('', @b);
  	}
  }

  # we split either by $COLOR or by character. This has the side effect of
  # leaving in graph cruft. It works because the graph cruft does not contain "-"
  # or "+"
  sub split_line {
  	local $_ = shift;
  	return eval { $_ = Encode::decode('UTF-8', $_, 1); 1 } ?
  		map { Encode::encode('UTF-8', $_) }
  			map { /$COLOR/ ? $_ : (split //) }
  			split /($COLOR+)/ :
  		map { /$COLOR/ ? $_ : (split //) }
  		split /($COLOR+)/;
  }

  sub highlight_line {
  	my ($line, $prefix, $suffix, $theme) = @_;

  	my $start = join('', @{$line}[0..($prefix-1)]);
  	my $mid = join('', @{$line}[$prefix..$suffix]);
  	my $end = join('', @{$line}[($suffix+1)..$#$line]);

  	# If we have a "normal" color specified, then take over the whole line.
  	# Otherwise, we try to just manipulate the highlighted bits.
  	if (defined $theme->[0]) {
  		s/$COLOR//g for ($start, $mid, $end);
  		chomp $end;
  		return join('',
  			$theme->[0], $start, $RESET,
  			$theme->[1], $mid, $RESET,
  			$theme->[0], $end, $RESET,
  			"\n"
  		);
  	} else {
  		return join('',
  			$start,
  			$theme->[1], $mid, $theme->[2],
  			$end
  		);
  	}
  }

  # Pairs are interesting to highlight only if we are going to end up
  # highlighting a subset (i.e., not the whole line). Otherwise, the highlighting
  # is just useless noise. We can detect this by finding either a matching prefix
  # or suffix (disregarding boring bits like whitespace and colorization).
  sub is_pair_interesting {
  	my ($a, $pa, $sa, $b, $pb, $sb) = @_;
  	my $prefix_a = join('', @$a[0..($pa-1)]);
  	my $prefix_b = join('', @$b[0..($pb-1)]);
  	my $suffix_a = join('', @$a[($sa+1)..$#$a]);
  	my $suffix_b = join('', @$b[($sb+1)..$#$b]);

  	return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ ||
  	       $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ ||
  	       $suffix_a !~ /^$BORING*$/ ||
  	       $suffix_b !~ /^$BORING*$/;
  }
DIFFHIGHLIGHT

s/^  //mg for values %fatpacked;

my $class = 'FatPacked::'.(0+\%fatpacked);
no strict 'refs';
*{"${class}::files"} = sub { keys %{$_[0]} };

if ($] < 5.008) {
  *{"${class}::INC"} = sub {
    if (my $fat = $_[0]{$_[1]}) {
      my $pos = 0;
      my $last = length $fat;
      return (sub {
        return 0 if $pos == $last;
        my $next = (1 + index $fat, "\n", $pos) || $last;
        $_ .= substr $fat, $pos, $next - $pos;
        $pos = $next;
        return 1;
      });
    }
  };
}

else {
  *{"${class}::INC"} = sub {
    if (my $fat = $_[0]{$_[1]}) {
      open my $fh, '<', \$fat
        or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
      return $fh;
    }
    return;
  };
}

unshift @INC, bless \%fatpacked, $class;
  } # END OF FATPACK CODE


my $VERSION = "1.2.5";

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

use File::Spec;                                             # For catdir
use File::Basename;                                         # For dirname
use Encode;                                                 # For handling UTF8 stuff
use Cwd qw(abs_path);                                       # For realpath()
use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC
use DiffHighlight;

use strict;
use warnings FATAL => 'all';

my $remove_file_add_header     = 1;
my $remove_file_delete_header  = 1;
my $clean_permission_changes   = 1;
my $manually_color_lines       = 0; # Usually git/hg colorizes the lines, but for raw patches we use this
my $change_hunk_indicators     = git_config_boolean("diff-so-fancy.changeHunkIndicators","true");
my $strip_leading_indicators   = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true");
my $mark_empty_lines           = git_config_boolean("diff-so-fancy.markEmptyLines","true");
my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true");
my $ruler_width                = git_config("diff-so-fancy.rulerWidth", undef);
my $git_strip_prefix           = git_config_boolean("diff.noprefix","false");
my $has_stdin                  = has_stdin();

my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK])?/;
my $reset_color      = color("reset");
my $bold             = color("bold");
my $meta_color       = "";

my ($file_1,$file_2);
my $args              = argv(); # Hashref of all the ARGV stuff
my $last_file_seen    = "";
my $last_file_mode    = "";
my $i                 = 0;
my $in_hunk           = 0;
my $columns_to_remove = 0;
my $is_mercurial      = 0;
my $color_forced      = 0; # Has the color been forced on/off

# We try and be smart about whether we need to do line coloring, but
# this is an option to force it on/off
if ($args->{color_on}) {
	$manually_color_lines = 1;
	$color_forced         = 1;
} elsif ($args->{color_off}) {
	$manually_color_lines = 0;
	$color_forced         = 1;
}

# We only process ARGV if we don't have STDIN
if (!$has_stdin) {
	if ($args->{v} || $args->{version}) {
		die(version());
	} elsif ($args->{'set-defaults'}) {
		my $ok = set_defaults();
	} elsif ($args->{colors}) {
		# We print this to STDOUT so we can redirect to bash to auto-set the colors
		print get_default_colors();
		exit;
	} elsif (!%$args || $args->{help} || $args->{h}) {
		my $first = check_first_run();

		if (!$first) {
			die(usage());
		}
	} else {
		die("Missing input on STDIN\n");
	}
} else {
	# Check to see if were using default settings
	check_first_run();

	my @lines;
	local $DiffHighlight::line_cb = sub {
		push(@lines,@_);

		my $last_line = $lines[-1];

		# Buffer X lines before we try and output anything
		# Also make sure we're sending enough data to d-s-f to do it's magic.
		# Certain things require a look-ahead line or two to function so
		# we make sure we don't break on those sections prematurely
		if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|index|old mode|similarity index|rename (from|to))/)) {
			do_dsf_stuff(\@lines);
			@lines = ();
		}
	};

	my $line_count = 0;
	while (my $line = <STDIN>) {
		# If the very first line of the diff doesn't start with ANSI color we're assuming
		# it's a raw patch file, and we have to color the added/removed lines ourself
		if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) {
			$manually_color_lines = 1;
		}

		my $ok = DiffHighlight::handle_line($line);
		$line_count++;
	}

	DiffHighlight::flush();
	do_dsf_stuff(\@lines);
}

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

sub do_dsf_stuff {
	my $input = shift();

	#print STDERR "START -------------------------------------------------\n";
	#print STDERR join("",@$input);
	#print STDERR "END ---------------------------------------------------\n";

	while (my $line = shift(@$input)) {
		######################################################
		# Pre-process the line before we do any other markup #
		######################################################

		# If the first line of the input is a blank line, skip that
		if ($i == 0 && $line =~ /^\s*$/) {
			next;
		}

		######################
		# End pre-processing #
		######################

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

		####################################################################
		# Look for git index and replace it horizontal line (header later) #
		####################################################################
		if ($line =~ /^${ansi_color_regex}index /) {
			# Print the line color and then the actual line
			$meta_color = $1 || get_config_color("meta");

			# Get the next line without incrementing counter while loop
			my $next = $input->[0] || "";
			my ($file_1,$file_2);

			# The line immediately after the "index" line should be the --- file line
			# If it's not it's an empty file add/delete
			if ($next !~ /^$ansi_color_regex(---|Binary files)/) {

				# We fake out the file names since it's a raw add/delete
				if ($last_file_mode eq "add") {
					$file_1 = "/dev/null";
					$file_2 = $last_file_seen;
				} elsif ($last_file_mode eq "delete") {
					$file_1 = $last_file_seen;
					$file_2 = "/dev/null";
				}
			}

			if ($file_1 && $file_2) {
				print horizontal_rule($meta_color);
				print $meta_color . file_change_string($file_1,$file_2) . "\n";
				print horizontal_rule($meta_color);
			}
		#########################
		# Look for the filename #
		#########################
		#                                            $4              $5
		} elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.+?)(\s|\e|$)/) {

			# Mercurial looks like: diff -r 82e55d328c8c hello.c
			if ($4 eq "-r") {
				$is_mercurial = 1;
				$meta_color ||= get_config_color("meta");
			# Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy
			} else {
				$last_file_seen = $5;
			}

			$last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix).
			$in_hunk = 0;
		########################################
		# Find the first file: --- a/README.md #
		########################################
		} elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) {
			$meta_color ||= get_config_color("meta");

			if ($git_strip_prefix) {
				my $file_dir = $4 || "";
				$file_1 = $file_dir . $5;
			} else {
				$file_1 = $5;
			}

			# Find the second file on the next line: +++ b/README.md
			my $next = shift(@$input);
			$next    =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/;
			if ($1) {
				print $1; # Print out whatever color we're using
			}
			if ($git_strip_prefix) {
				my $file_dir = $4 || "";
				$file_2 = $file_dir . $5;
			} else {
				$file_2 = $5;
			}

			if ($file_2 ne "/dev/null") {
				$last_file_seen = $file_2;
			}

			# Print out the top horizontal line of the header
			print $reset_color;
			print horizontal_rule($meta_color);

			# Mercurial coloring is slightly different so we need to hard reset colors
			if ($is_mercurial) {
				print $reset_color;
			}

			print $meta_color;
			print file_change_string($file_1,$file_2) . "\n";

			# Print out the bottom horizontal line of the header
			print horizontal_rule($meta_color);
		########################################
		# Check for "@@ -3,41 +3,63 @@" syntax #
		########################################
		} elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) {
			$in_hunk        = 1;
			my $hunk_header = $4;
			my $remain      = bleach_text($5);

			# The number of colums to remove (1 or 2) is based on how many commas in the hunk header
			$columns_to_remove   = (char_count(",",$hunk_header)) - 1;
			# On single line removes there is NO comma in the hunk so we force one
			if ($columns_to_remove <= 0) {
				$columns_to_remove = 1;
			}

			if ($1) {
				print $1; # Print out whatever color we're using
			}

			my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header);
			#$last_file_seen = basename($last_file_seen);

			# Figure out the start line
			my $start_line = start_line_calc($new_offset,$new_count);

			# Last function has it's own color
			my $last_function_color = get_config_color("last_function");
			print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n";
		###################################
		# Remove any new file permissions #
		###################################
		} elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) {
			# Don't print the line (i.e. remove it from the output);
			$last_file_mode = "add";
		######################################
		# Remove any delete file permissions #
		######################################
		} elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) {
			# Don't print the line (i.e. remove it from the output);
			$last_file_mode = "delete";
		################################
		# Look for binary file changes #
		################################
		} elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) {
			my $change = file_change_string($2,$4);
			print horizontal_rule($meta_color);
			print "$meta_color$change (binary)\n";
			print horizontal_rule($meta_color);
		#####################################################
		# Check if we're changing the permissions of a file #
		#####################################################
		} elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) {
			my ($old_mode) = $4;
			my $next = shift(@$input);

			if ($1) {
				print $1; # Print out whatever color we're using
			}

			my ($new_mode) = $next =~ m/new mode (\d+)/;
			print "$last_file_seen changed file mode from $old_mode to $new_mode\n";

		###############
		# File rename #
		###############
		} elsif ($line =~ /^${ansi_color_regex}similarity index (\d+)%/) {
			my $simil = $4;

			# If it's a move with content change we ignore this and the next two lines
			if ($simil != 100) {
				shift(@$input);
				shift(@$input);
				next;
			}

			my $next    = shift(@$input);
			my ($file1) = $next =~ /rename from (.+)/;

			$next       = shift(@$input);
			my ($file2) = $next =~ /rename to (.+)/;

			if ($file1 && $file2) {
				# We may not have extracted this yet, so we pull from the config if not
				$meta_color ||= get_config_color("meta");

				my $change = file_change_string($file1,$file2);

				print horizontal_rule($meta_color);
				print $meta_color . $change . "\n";
				print horizontal_rule($meta_color);
			}

			$i += 3; # We've consumed three lines
			next;
		#####################################
		# Just a regular line, print it out #
		#####################################
		} else {
			# Mark empty line with a red/green box indicating addition/removal
			if ($mark_empty_lines) {
				$line = mark_empty_line($line);
			}

			# Remove the correct number of leading " " or "+" or "-"
			if ($strip_leading_indicators) {
				$line = strip_leading_indicators($line,$columns_to_remove);
			}
			print $line;
		}

		$i++;
	}
}

######################################################################################################
# End regular code, begin functions
######################################################################################################

# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805
sub parse_hunk_header {
	my ($line) = @_;
	my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\@\@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? \@\@+/;
	$o_cnt = 1 unless defined $o_cnt;
	$n_cnt = 1 unless defined $n_cnt;
	return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
}

# Mark the first char of an empty line
sub mark_empty_line {
	my $line = shift();

	my $reset_color  = "\e\\[0?m";
	my $reset_escape = "\e\[m";
	my $invert_color = "\e\[7m";

	$line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/;

	return $line;
}

# String to boolean
sub boolean {
	my $str = shift();
	$str    = trim($str);

	if ($str eq "" || $str =~ /^(no|false|0)$/i) {
		return 0;
	} else {
		return 1;
	}
}

# Memoize getting the git config
{
	my $static_config;

	sub git_config_raw {
		if ($static_config) {
			# If we already have the config return that
			return $static_config;
		}

		my $cmd = "git config --list";
		my @out = `$cmd`;

		$static_config = \@out;

		return \@out;
	}
}

# Fetch a textual item from the git config
sub git_config {
	my $search_key    = lc($_[0] || "");
	my $default_value = lc($_[1] || "");

	my $out = git_config_raw();

	# If we're in a unit test, use the default (don't read the users config)
	if (in_unit_test()) {
		return $default_value;
	}

	my $raw = {};
	foreach my $line (@$out) {
		if ($line =~ /=/) {
			my ($key,$value) = split("=",$line,2);
			$value =~ s/\s+$//;
			$raw->{$key} = $value;
		}
	}

	# If we're given a search key return that, else return the hash
	if ($search_key) {
		return $raw->{$search_key} || $default_value;
	} else {
		return $raw;
	}
}

# Fetch a boolean item from the git config
sub git_config_boolean {
	my $search_key    = lc($_[0] || "");
	my $default_value = lc($_[1] || 0); # Default to false

	# If we're in a unit test, use the default (don't read the users config)
	if (in_unit_test()) {
		return boolean($default_value);
	}

	my $result = git_config($search_key,$default_value);
	my $ret    = boolean($result);

	return $ret;
}

# Check if we're inside of BATS
sub in_unit_test {
	if ($ENV{BATS_CWD}) {
		return 1;
	} else {
		return 0;
	}
}

sub get_less_charset {
	my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG");
	foreach (@less_char_vars) {
		return $ENV{$_} if defined $ENV{$_};
	}

	return "";
}

sub should_print_unicode {
	if (-t STDOUT) {
		# Always print unicode chars if we're not piping stuff, e.g. to less(1)
		return 1;
	}

	# Otherwise, assume we're piping to less(1)
	my $less_charset = get_less_charset();
	if ($less_charset =~ /utf-?8/i) {
		return 1;
	}

	return 0;
}

# Try and be smart about what line the diff hunk starts on
sub start_line_calc {
	my ($line_num,$diff_context) = @_;
	my $ret;

	if ($line_num == 0 && $diff_context == 0) {
		return 1;
	}

	# Git defaults to three lines of context
	my $default_context_lines = 3;
	# Three lines on either side, and the line itself = 7
	my $expected_context      = ($default_context_lines * 2 + 1);

	# The first three lines
	if ($line_num == 1 && $diff_context < $expected_context) {
		$ret = $diff_context - $default_context_lines;
	} else {
		$ret = $line_num + $default_context_lines;
	}

	if ($ret < 1) {
		$ret = 1;
	}

	return $ret;
}

# Remove + or - at the beginning of the lines
sub strip_leading_indicators {
	my $line              = shift(); # Array passed in by reference
	my $columns_to_remove = shift(); # Don't remove any lines by default

	if ($columns_to_remove == 0) {
		return $line; # Nothing to do
	}

	$line =~ s/^(${ansi_color_regex})([ +-]){${columns_to_remove}}/$1/;

	if ($manually_color_lines) {
		if (defined($5) && $5 eq "+") {
			my $add_line_color = get_config_color("add_line");
			$line              = $add_line_color . $line . $reset_color;
		} elsif (defined($5) && $5 eq "-") {
			my $remove_line_color = get_config_color("remove_line");
			$line                 = $remove_line_color . $line . $reset_color;
		}
	}

	return $line;
}

# Count the number of a given char in a string
sub char_count {
	my ($needle,$str) = @_;
	my $len = length($str);
	my $ret = 0;

	for (my $i = 0; $i < $len; $i++) {
		my $found = substr($str,$i,1);

		if ($needle eq $found) { $ret++; }
	}

	return $ret;
}

# Remove all ANSI codes from a string
sub bleach_text {
	my $str = shift();
	$str    =~ s/\e\[\d*(;\d+)*m//mg;

	return $str;
}

# Remove all trailing and leading spaces
sub trim {
	my $s = shift();
	if (!$s) { return ""; }
	$s =~ s/^\s*|\s*$//g;

	return $s;
}

# Print a line of em-dash or line-drawing chars the full width of the screen
sub horizontal_rule {
	my $color = $_[0] || "";
	my $width = $ruler_width || `tput cols`;

	if (is_windows()) {
		$width--;
	}

	# em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm
	#my $dash = "\x{2014}";
	# BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm
	my $dash;
	if ($use_unicode_dash_for_ruler && should_print_unicode()) {
		$dash = Encode::encode('UTF-8', "\x{2500}");
	} else {
		$dash = "-";
	}

	# Draw the line
	my $ret = $color . ($dash x $width) . "\n";

	return $ret;
}

sub file_change_string {
	my $file_1 = shift();
	my $file_2 = shift();

	# If they're the same it's a modify
	if ($file_1 eq $file_2) {
		return "modified: $file_1";
	# If the first is /dev/null it's a new file
	} elsif ($file_1 eq "/dev/null") {
		my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1];
		return "added: $add_color$file_2$reset_color";
	# If the second is /dev/null it's a deletion
	} elsif ($file_2 eq "/dev/null") {
		my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1];
		return "deleted: $del_color$file_1$reset_color";
	# If the files aren't the same it's a rename
	} elsif ($file_1 ne $file_2) {
		my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1});
		$old = trim($old);
		$new = trim($new);

		# highlight_pair resets the colors, but we want it to be the meta color
		$old =~ s/(\e0?\[m)/$1$meta_color/g;
		$new =~ s/(\e0?\[m)/$1$meta_color/g;

		return "renamed: $old to $new";
	# Something we haven't thought of yet
	} else {
		return "$file_1 -> $file_2";
	}
}

# Check to see if STDIN is connected to an interactive terminal
sub has_stdin {
	my $i   = -t STDIN;
	my $ret = int(!$i);

	return $ret;
}

# We use this instead of Getopt::Long because it's faster and we're not parsing any
# crazy arguments
# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html
sub argv {
	my $ret = {};

	for (my $i = 0; $i < scalar(@ARGV); $i++) {

		# If the item starts with "-" it's a key
		if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_-]*\w)$/) && ($ARGV[$i] !~ /^-\w\w/)) {
			# If the next item does not start with "--" it's the value for this item
			if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) {
				$ret->{$key} = $ARGV[$i + 1];
			# Bareword like --verbose with no options
			} else {
				$ret->{$key}++;
			}
		}
	}

	# We're looking for a certain item
	if ($_[0]) { return $ret->{$_[0]}; }

	return $ret;
}

# Output the command line usage for d-s-f
sub usage {
	my $out = color("white_bold") . version() . color("reset") . "\n";

	$out .= "Usage:

git diff --color | diff-so-fancy # Use d-s-f on one diff
diff-so-fancy --colors           # View the commands to set the recommended colors
diff-so-fancy --set-defaults     # Configure git-diff to use diff-so-fancy and suggested colors

# Configure git to use d-s-f for *all* diff operations
git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n";

	return $out;
}

sub get_default_colors {
	my $out  = "# Recommended default colors for diff-so-fancy\n";
	$out    .= "# --------------------------------------------\n";
	$out    .= 'git config --global color.ui true

git config --global color.diff-highlight.oldNormal    "red bold"
git config --global color.diff-highlight.oldHighlight "red bold 52"
git config --global color.diff-highlight.newNormal    "green bold"
git config --global color.diff-highlight.newHighlight "green bold 22"

git config --global color.diff.meta       "yellow"
git config --global color.diff.frag       "magenta bold"
git config --global color.diff.commit     "yellow bold"
git config --global color.diff.old        "red bold"
git config --global color.diff.new        "green bold"
git config --global color.diff.whitespace "red reverse"
';

	return $out;
}

# Output the current version string
sub version {
	my $ret  = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n";
	$ret    .= "Version      : $VERSION\n";

	return $ret;
}

sub is_windows {
	if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') {
		return 1;
	} else {
		return 0;
	}
}

# Return value is whether this is the first time they've run d-s-f
sub check_first_run {
	my $ret = 0;

	# If first-run is not set, or it's set to "true"
	my $first_run     = git_config_boolean('diff-so-fancy.first-run');
	# See if they're previously set SOME diff-highlight colors
	my $has_dh_colors = git_config_boolean('color.diff-highlight.oldnormal') || git_config_boolean('color.diff-highlight.newnormal');

	#$first_run = 1; $has_dh_colors = 0;

	if (!$first_run || $has_dh_colors) {
		return 0;
	} else {
		print "This appears to be the first time you've run diff-so-fancy, please note\n";
		print "that the default git colors are not ideal. Diff-so-fancy recommends the\n";
		print "following colors.\n\n";

		print get_default_colors();

		# Set the first run flag to false
		my $cmd = 'git config --global diff-so-fancy.first-run false';
		system($cmd);

		exit;
	}

	return 1;
}

sub set_defaults {
	my $color_config = get_default_colors();
	my $git_config   = 'git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"';
	my $first_cmd    = 'git config --global diff-so-fancy.first-run false';

	my @cmds = split(/\n/,$color_config);
	push(@cmds,$git_config);
	push(@cmds,$first_cmd);

	# Remove all comments from the commands
	foreach my $x (@cmds) {
		$x =~ s/#.*//g;
	}

	# Remove any empty commands
	@cmds = grep($_,@cmds);

	foreach my $cmd (@cmds) {
		system($cmd);
		my $exit = ($? >> 8);

		if ($exit != 0) {
			die("Error running: '$cmd' (error #18941)\n");
		}
	}

	return 1;
}

# Borrowed from: https://www.perturb.org/display/1167_Perl_ANSI_colors.html
# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue'
sub color {
	my $str = shift();

	# No string sent in, so we just reset
	if (!length($str) || $str eq 'reset') { return "\e[0m"; }

	# Some predefined colors
	my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0);
	$str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg;

	# Get foreground/background and any commands
	my ($fc,$cmd) = $str =~ /(\d+)?_?(\w+)?/g;
	my ($bc)      = $str =~ /on_?(\d+)/g;

	# Some predefined commands
	my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7);
	my $cmd_num = $cmd_map{$cmd // 0};

	my $ret = '';
	if ($cmd_num)     { $ret .= "\e[${cmd_num}m"; }
	if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; }
	if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; }

	return $ret;
}

# Get colors used for various output sections (memoized)
{
	my $static_config;

	sub get_config_color {
		my $str = shift();

		my $ret = "";
		if ($static_config->{$str}) {
			return $static_config->{$str};
		}

		if ($str eq "meta") {
			# Default ANSI yellow
			$ret = DiffHighlight::color_config('color.diff.meta', color(11));
		} elsif ($str eq "reset") {
			$ret = color("reset");
		} elsif ($str eq "add_line") {
			# Default ANSI green
			$ret = DiffHighlight::color_config('color.diff.new', color('bold') . color(2));
		} elsif ($str eq "remove_line") {
			# Default ANSI red
			$ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1));
		} elsif ($str eq "last_function") {
			$ret = DiffHighlight::color_config('color.diff.func', color(146));
		}

		# Cache (memoize) the entry for later
		$static_config->{$str} = $ret;

		return $ret;
	}
}

sub starts_with_ansi {
	my $str = shift();

	if ($str =~ /^$ansi_color_regex/) {
		return 1;
	} else {
		return 0;
	}
}

# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4
