package SimplyGit::Git;
use strict;
use warnings;
use Log::Log4perl qw(:easy);
use lib "/usr/local/lib/";
use Shellex::Shellex qw(shellex findBin);
use Exporter qw(import);
our @EXPORT_OK = qw(
readConfig getStatus returnState addFiles
commitChanges pushChanges stashAndReset resetFromUpstream
updateGitIgnore appendRepoUserConfig parseSGConfig
warnOnUser basicClone basicPull knocker
);

# TODO: Add info/debug logging for all subroutines

sub knocker($$$) {

my $target = shift;
my $portRef = shift;
my $logger = shift;
my $nmapCmd = findBin("nmap",$logger);
foreach my $port (@$portRef) {
print "Knocking at $port\r";
shellex("$nmapCmd -Pn --host_timeout 201 --max-retries 0 -p $port $target > /dev/null",$logger);
}

# So we don't have random chars potentially on the line after using \r
print "\n";

}

sub checkPath($$) {

my $path = shift;
my $logger = shift;
if ( ! -d $path ) {
$logger->error("$path doesn't look like a dir, exiting...");
exit 1;
}

}

sub warnOnUser($$$) {

my $user = shift;
my $email = shift;
my $logger = shift;

my $gitCmd = findBin("git",$logger);
my $configuredUser = shellex("$gitCmd config --get user.name",$logger);
my $configuredEmail = shellex("$gitCmd config --get user.email",$logger);

if ( $configuredUser ne $user || $configuredEmail ne $email ) {
print "***************\n";
print "Your configured user/email don't match what you declared in the config file!\n";
print "Desired User: $user\nConfigured User: $configuredUser\nDesired Email: $email\nConfigured Email: $configuredEmail\n";
print "***************\n";
}

}

# https://perlmaven.com/trim
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };

sub parseSGConfig($$) {

my $config = shift;
my $logger = shift;
if ( ! -e $config ) {
$logger->error("$config doesn't look like a regular file, exiting...");
exit 1;
}
my $catCmd = findBin("cat",$logger);
my @configLines = split("\n",shellex("$catCmd $config",$logger));
my %configHash;
foreach my $line ( @configLines ) {
chomp $line;
if ( $line =~ m/^(.*)\ =\ "(.*)"$/ ) {
$configHash{$1} = $2;
}

if ( $line =~ m/^(.*)\ =\ \[(.*)\]/ ) {
my @trimmedPorts;
my @ports = split(",",$2);
foreach my $port (@ports) {
$port =~ /(\d{1,5})/;
push(@trimmedPorts,trim($1));
}
$configHash{$1} = \@trimmedPorts;
}
}

return %configHash;

}

sub returnConfigPath($$) {

my $path = shift;
my $logger = shift;
checkPath($path,$logger);

my $gitConfigPath = $path . "/" . ".git/config";
return $gitConfigPath;

}


sub readConfig($$) {

# This sub is probably not really needed for what I'm trying to do
# git itself already parses this config...but an interesting exercise non the less
# and may be useful later
my $path = shift;
my $logger = shift;
my $gitConfigPath = returnConfigPath($path,$logger);
my $catCmd = findBin("cat",$logger);
my @configLines = split("\n",shellex("$catCmd $gitConfigPath",$logger));
# Key is config header, value is hash ref containing config values
my %gitConfig;
my @valueLines;
my $lineCounter = 0;
foreach my $line ( @configLines ) {
$lineCounter++;
#if ( $line =~ m/\[(.*)\]/ ) {
if ( $line =~ m/\[(.*)\]/ ) {
#$valueLine =~ /\t(.*)\ =\ (.*)$/;
$gitConfig{$1} = "";
}

}

# Tag each line with it's heading
# Only way I could think of that worked to solve how this
# There are almost certainly better ways
my @taggedLines;
my $tag = "NULLTAG";
foreach my $line ( @configLines ) {
if ( $line =~ m/\[(.*)\]/ ) {
$tag = $1;
} else {
my $newLine = $tag . $line;
push(@taggedLines,$newLine);
}
}

# Get all of the tagged lines into a hash structure.
foreach my $key ( keys %gitConfig ) {
my %stash;
foreach my $tl ( @taggedLines ) {
if ( $tl =~ m/^($key)/ ) {
$tl =~ s/^($key)//g;
$tl =~ m/^\t(.*)\ \=\ (.*)$/;
my $confKey = $1;
my $confVal = $2;
$stash{$confKey} = $confVal;
}
}
$gitConfig{$key} = \%stash;
}

return %gitConfig;

}

sub getStatus($) {

my $logger = shift;
my $gitCmd = findBin("git",$logger);
my $status = shellex("$gitCmd status -uall --porcelain",$logger);
chomp $status;
return $status;

}

sub returnState($) {

my $logger = shift;
my $gitCmd = findBin("git",$logger);
my $currentStatus = getStatus($logger);
my @statusLines = split("\n", $currentStatus);
my @untracked;
my @modified;
my @added;
my @deleted;
foreach my $file ( @statusLines ) {
$file =~ m/^\ {0,1}([A-Z?]{1,2})\ {1,2}(.*)/;
my $fileAttrs = $1;
my $filename = $2;
my @attrs = split("",$fileAttrs);
foreach my $attr ( @attrs ) {

if ( $attr =~ m/\?/ ) {
push(@untracked, $filename) unless grep $_ eq $filename, @untracked;
}

if ( $attr =~ m/[M]/ ) {
push(@modified, $filename) unless grep $_ eq $filename, @modified;
}

if ( $attr =~ m/[A]/ ) {
push(@added, $filename) unless grep $_ eq $filename, @added;
}

if ( $attr =~ m/[D]/ ) {
push(@deleted, $filename) unless grep $_ eq $filename, @deleted;
}

}
}

return ( \@untracked, \@modified, \@added, \@deleted );

}

sub addFiles($$) {

my $filesToAddRef = shift;
my $logger = shift;
my $gitCmd = findBin("git",$logger);
foreach my $file ( @$filesToAddRef ) {
shellex("$gitCmd add $file",$logger);
}

}

sub commitChanges($$) {

my $commitMsg = shift;
chomp $commitMsg;
my $logger = shift;
my $gitCmd = findBin("git",$logger);
shellex("$gitCmd commit -m \"$commitMsg\"",$logger);

}

sub pushChanges($) {

my $logger = shift;
my $gitCmd = findBin("git",$logger);
my $output = shellex("$gitCmd push",$logger);

}

sub dropStash($) {

my $logger = shift;
my $gitCmd = findBin("git",$logger);
my @stashList = split("\n", shellex("$gitCmd stash list",$logger));
my $stashCount = scalar @stashList;
# TODO: Don't need $stashCount, should just be able to iterate over @stashList
if ( scalar @stashList == 0 ) {
print "Stash is empty so not dropping\n";
} else {
foreach my $stashNum ( 1..$stashCount ) {
shellex("$gitCmd stash drop 0",$logger);
}
}

}

sub stashAndReset($) {

my $logger = shift;
my $gitCmd = findBin("git",$logger);
shellex("$gitCmd stash",$logger);
dropStash($logger);
shellex("$gitCmd rebase",$logger);
}

sub resetFromUpstream($) {

# git stash and git reset --hard and git pull ? I think
# git reset upstream/master; git stash
my $logger = shift;
my $gitCmd = findBin("git",$logger);
my $upstream = shellex("$gitCmd config --get remote.upstream.url",$logger);
if ( $upstream eq "" || ! defined $upstream ) {
print "Upstream not configured, exiting\n";
exit 1;
}

shellex("$gitCmd reset upstream/master",$logger);
shellex("$gitCmd stash",$logger);
dropStash($logger);
print "Successful reset from upstream\n";
print "Changes have not been pushed, run \'$gitCmd pull\' to revert\n";

}

sub updateGitIgnore($$$) {

my $path = shift;
# Maybe better to accept an array of values
my $ignoreValue = shift;
my $logger = shift;
checkPath($path,$logger);
my $filename = $path . "/" . ".gitignore";
# Make sure we're not appending/writing if entry already exists in gitignore
if ( -f $filename ) {
my $catCmd = findBin("cat",$logger);
my @ignoreLines = split("\n",shellex("$catCmd $filename",$logger));
if ( ! grep( /^$ignoreValue$/, @ignoreLines ) ) {
open(my $fh, ">>", $filename) or die $logger->error("Couldn't open $filename, exiting...");
chomp $ignoreValue;
print $fh "$ignoreValue\n";
close $fh;
}
} else {
open(my $fh, ">", $filename) or die $logger->error("Couldn't open $filename, exiting...");
chomp $ignoreValue;
print $fh "$ignoreValue\n";
close $fh;
}

}

sub appendRepoUserConfig($$$) {

my $desiredName = shift;
my $desiredEmail = shift;
my $logger = shift;

my $gitCmd = findBin("git",$logger);
my $currentName = shellex("$gitCmd config --get user.name",$logger);
chomp $currentName;
my $currentEmail = shellex("$gitCmd config --get user.email",$logger);
chomp $currentEmail;

if ( $currentName eq $desiredName ) {
print "Already have $desiredName configured\n";
} else {
shellex("$gitCmd config user.name \"$desiredName\"",$logger);
print "Set $desiredName successfully\n";
}

if ( $currentEmail eq $desiredEmail ) {
print "Already have $desiredEmail configured\n";
} else {
shellex("$gitCmd config user.email \"$desiredEmail\"",$logger);
print "Set $desiredEmail successfully\n";
}

}

sub basicClone($$) {

my $cloneTarget = shift;
my $logger = shift;
my $gitCmd = findBin("git",$logger);
shellex("$gitCmd clone $cloneTarget",$logger);
print "Successfully cloned $cloneTarget\n";

}

sub basicPull($) {

my $logger = shift;
my $gitCmd = findBin("git",$logger);
my $gitPullReturn = shellex("$gitCmd pull",$logger);
print "git pull returned:\n$gitPullReturn\n";

}