use strict;
use vars qw($VERSION %IRSSI);

use Irssi;
use IPC::Open3;
use CPAN::Meta::YAML;
use Text::ParseWords;
use Text::Wrap;
use Time::HiRes;
use File::Glob qw/:bsd_glob/;

$VERSION = '0.2';
%IRSSI = (
    authors	=> 'bw1',
    contact	=> 'bw1@aol.at',
    name	=> 'gitscriptassist',
    description	=> 'script management with git',
    license	=> 'Public Domain',
    url		=> 'https://scripts.irssi.org/',
    changed	=> '2020-02-02',
    modules => 'IPC::Open3 CPAN::Meta::YAML Text::ParseWords '.
					'Text::Wrap Time::HiRes',
    commands=> "gitscriptassist"
);

my $help= << "END";
%9Name%9
  /gitscriptassist -  $IRSSI{description}

%9Version%9
  $VERSION

%9Description%9
  \$ mkdir ~/foo
  \$ cd ~/foo
  \$ git clone https://github.com/irssi/scripts.irssi.org.git
  \$ irssi
  [(status)] /script load ~/foo/scripts.irssi.org/scripts/gitscriptassist.pl
  [(status)] /set gitscriptassist_repo ~/foo/scripts.irssi.org
  [(status)] /gitscriptassist search script
  [(status)] /quit
  \$ echo "/script load ~/foo/scripts.irssi.org/scripts/gitscriptassist.pl" >> \\
  > ~/.irssi/startup
  \$ irssi

%9Settings%9
  %Ugitscriptassist_repo%U
    path to the git workingdir
  %Ugitscriptassist_path%U
    path for the tempory files of /gitscriptassist
  %Ugitscriptassist_startup%U
    load the scripts on startup
  %Ugitscriptassist_integrate%U
    integrate in the script command

%9Commands%9
END

my %scmds=(
	'fetch'=>{
		'short'=>"git fetch -all",
	},
	'gitload'=>{
		'short'=>"load a script from the repository",
		'usage'=>"/gitscriptassist gitload {filename[.pl]|hash:filename[.pl]}",
		'file'=>1,
	},
	'info'=>{
		'short'=>"view script info",
		'usage'=>"/gitscriptassist info <filename[.pl]>",
		'file'=>1,
	},
	'log'=>{
		'short'=>"git log",
		'usage'=>"/gitscriptassist log [filename[.pl]]",
		'file'=>1,
	},
	'pull'=>{
		'short'=>"git pull",
	},
	'search'=>{
		'short'=>"search for word in scripts.yaml",
		'usage'=>"/gitscriptassist search <word>",
	},
	'status'=>{
		'short'=>"git status",
	},
	'help'=>{
		'short'=>"show help",
	},
	'autoload'=>{
		'short'=>"manage autoload",
		'usage'=>"/gitscriptassist autoload <command>",
		'sub' => {
			'list' => {
				'short'=>"show the list for startup",
			},
			'add' => {
				'short'=>"add a list entry for /script load",
				'file'=>1,
			},
			'gitadd' => {
				'short'=>"add a list entry for /script load via git",
				'file'=>1,
			},
			'write' => {
				'short'=>"write list to file",
			},
			'load' => {
				'short'=>"load the list from file",
			},
			'startup' => {
				'short'=>"trigger the startup",
			},
			'remove' => {
				'short'=>"remove a list entry",
			},
			'move' => {
				'short'=>"move a list entry",
			},
		},
	},
	'new'=>{
		'short'=>"show last modified scripts",
		'usage'=>"/gitscriptassist new [max]",
	},
);

my ($repo, $path, $startup, $integrate);

my $subproc;
my @nproc;

my %scripts;
my %time_scr;
my @comp_start;
my @autoload;

my ($fh_in, $fh_out, $fh_err);

sub load_autoload {
	my $fh;
	my $fn = $path.'/autoload.yaml';
	if (-e $fn) {
		open $fh, "<:utf8", $fn;
		my $yt = do { local $/; <$fh> };
		my $yml= CPAN::Meta::YAML->read_string($yt);
		if (defined $yml->[0]) {
			@autoload =@{$yml->[0]};
		}
		close $fh;
		if ($startup) {
			ascmd_startup();
		}
	}
}

sub write_autoload {
	my $fh;
	my $fn = $path.'/autoload.yaml';
	if (scalar(@autoload) >0) {
		open $fh, ">:utf8", $fn;
		my $yml =CPAN::Meta::YAML->new(\@autoload);
		print $fh $yml->write_string;
		close $fh;
	}
}

sub load_scripts {
	my $fh;
	my $f  =$repo.'/_data/scripts.yaml';
	my $fn = bsd_glob $f, GLOB_TILDE;
	if (-e $fn) {
		%time_scr= ();
		%scripts= ();
		open $fh, "<:utf8", $fn;
		my $yt = do { local $/; <$fh> };
		my $yml= CPAN::Meta::YAML->read_string($yt);
		my @l =@{$yml->[0]};
		foreach my $s (@l) {
			$scripts{$s->{filename}}=$s;
		}
		foreach my $s (@l) {
			if (!exists $time_scr{$s->{modified}}) {
				$time_scr{$s->{modified}} =[];
			}
			push @{$time_scr{$s->{modified}}}, $s;
		}
		close $fh;
	}
}

sub run {
	my (%arg) =@_;
	if (!defined $subproc) {
		$subproc={%arg};
		use Symbol 'gensym'; $fh_err = gensym;
		my $pid = open3 ($fh_in, $fh_out, $fh_err, $subproc->{cmd});
		if (defined $pid) {
			$subproc->{pid}=$pid;
			Irssi::pidwait_add($pid);
		}
	} else {
		push @nproc, {%arg}
	}
}

sub sig_run_end {
	my ($pid, $status) = @_;

	if (defined $subproc) {
		my $old = select $fh_out;
		{
			local $/;
			$subproc->{out} = <$fh_out>;
			$subproc->{out} =~ s/\n$//;
			select $old;
		}

		{
			select $fh_err;
			local $/;
			$subproc->{err} = <$fh_err>;
			$subproc->{err} =~ s/\n$//;
			select $old;
		}

		if (exists $subproc->{next}) {
			if (ref ($subproc->{next}) eq 'CODE') {
				&{$subproc->{next}}();
			} elsif (ref ($subproc->{next}) eq 'ARRAY') {
				foreach my $p (@{$subproc->{next}}) {
					if (ref ($p) eq 'CODE') {
						&{$p}();
					}
				}
			}
		}
		$subproc = undef;
		if (scalar(@nproc) >0 ){
			my %arg = %{shift @nproc};
			run(%arg);
		}
	}
}

sub draw_box {
    my ($title, $text, $footer, $colour) = @_;
    my $box = '';
    $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
    foreach (split(/\n/, $text)) {
        $box .= '%R|%n '.$_."\n";
    }
    $box .= '%R`--<%n'.$footer.'%R>->%n';
    $box =~ s/%.//g unless $colour;
    return $box;
}

sub print_msg {
	my ( @te );
	if ($subproc->{out} ne '') {
		push @te, $subproc->{out};
	}
	if ($subproc->{err} ne '') {
		push @te,'E:'.$subproc->{cmd};
		push @te,'E:'.$subproc->{err};
	}
	if (defined $subproc->{label} &&
			($subproc->{out} ne '' ||
				$subproc->{err} ne '' )) {
		Irssi::print(
			draw_box($IRSSI{name}, join( "\n",@te) ,$subproc->{label}, 1),
				, MSGLEVEL_CLIENTCRAP);
	}
}

sub next_gitload {
	if ($subproc->{err} eq '') {
		Irssi::command("script load $path/$subproc->{filename}");
	}
}

sub scmd_script_info {
	my ($server, $witem, @args) =@_;
	my @te;
	my $s = $scripts{$args[0]};
	if (!defined $s) {
		$s = $scripts{$args[0].'.pl'};
	}
	if (defined $s) {
		push @te, "name:        $s->{name}";
		push @te, "authors:     $s->{authors}";
		push @te, "description:";
		my $d;
		{
			local $Text::Wrap::columns = 60;
			local $Text::Wrap::unexpand= 0;
			$d =wrap('   ','   ',$s->{description});
		}
		push @te, $d;
		push @te, "filename:    $s->{filename}";
		push @te, "version:     $s->{version}";
		push @te, "modified:    $s->{modified}";
		Irssi::print(
			draw_box($IRSSI{name}, join( "\n",@te) ,'info' , 1),
				, MSGLEVEL_CLIENTCRAP);
	}
}

sub scmd_script_search {
	my ($server, $witem, @args) =@_;
	my @te;
	my @scrs;
	my $ml=0;
	my $w=$args[0];
	foreach my $fn (sort keys %scripts) {
		my $s=$scripts{$fn};
		if (
				$s->{name} =~ m/$w/i ||
				$s->{authors} =~ m/$w/i ||
				$s->{description} =~ m/$w/i ||
				$s->{filename} =~ m/$w/i ) {
			push @scrs, $s;
			my $l=length($s->{filename});
			$ml=$l if ( $ml < $l);
		}
	}

	foreach my $s (@scrs) {
		my $i = sprintf "%-*s ", $ml, $s->{filename};
		my $dt=$s->{description};
		$dt=~ s/\n/ /g;
		$dt=~ s/\s+/ /g;
		my $d;
		{
			local $Text::Wrap::columns = 60;
			local $Text::Wrap::unexpand= 0;
			$d =wrap($i, ' 'x($ml+1), $dt);
		}
		push @te, $d;
	}
	Irssi::print(
		draw_box($IRSSI{name}, join( "\n",@te) ,'search' , 1),
			, MSGLEVEL_CLIENTCRAP);
}

sub scmd_gitload {
	my ($server, $witem, @args) =@_;
	my ($po, $fn);
	if ($args[0] =~ m/^(.*):(.*)$/) {
		$po=$1;
		$fn=$2;
	} else {
		$po='master';
		$fn=$args[0];
	}
	$fn .= '.pl' if ($fn !~ m/\.pl$/);
	run(
		'cmd' => "git -C $repo show $po:scripts/$fn >$path/$fn",
		'label'=> 'gitload',
		'filename'=>$fn,
		'point'=>$po,
		'next' => [\&next_gitload,\&print_msg]);
}

sub scmd_help {
	my ($server, $witem, @args) =@_;
	my @te;
	if (scalar(@args) ==0 ) {
		chomp $help;
		push @te, $help;
		foreach my $c (sort keys %scmds) {
			if (exists $scmds{$c}->{short}) {
				push @te, sprintf("  %%9%-10s%%9 %s", $c, $scmds{$c}->{short});
			}
			if (scalar(keys %{$scmds{$c}->{sub}}) ) {
				push @te, '    '.join ' ',sort keys %{$scmds{$c}->{sub}};
			}
		}
		Irssi::print(
			draw_box($IRSSI{name}, join( "\n",@te) ,'help' , 1),
				, MSGLEVEL_CLIENTCRAP);
	} elsif ( exists $scmds{$args[0]} ) {
		my $sa = $args[0];
		push @te, "%9/$IRSSI{name} $sa%9";
		if (exists $scmds{$sa}->{short}) {
			push @te, "  $scmds{$sa}->{short}";
		}
		if (exists $scmds{$sa}->{usage}) {
			push @te, "%9Usage:%9";
			push @te, "  $scmds{$sa}->{usage}";
		}
		if (scalar(keys %{$scmds{$sa}->{sub}}) >0) {
			push @te, "%9Commands:%9";
			foreach my $su (sort keys %{$scmds{$sa}->{sub}}) {
				if (exists $scmds{$sa}->{sub}->{$su}->{short}) {
					push @te, sprintf("  %-10s %s", $su, $scmds{$sa}->{sub}->{$su}->{short});
				}
			}
		}
		Irssi::print(
			draw_box($IRSSI{name}, join( "\n",@te) ,'help' , 1),
				, MSGLEVEL_CLIENTCRAP);
	}
}

sub ascmd_startup {
	my ($server, $witem, @args) =@_;
	foreach my $s (@autoload) {
		if (exists $s->{load}) {
			Irssi::command("script load $s->{load}");
		} elsif (exists $s->{gitload}) {
			scmd_gitload($server, $witem, $s->{gitload});
		}
	}
}

sub ascmd_list {
	my ($server, $witem, @args) =@_;
	my @te;
	my $co=0;
	foreach (@autoload){
		my ($k, $f);
		($k) = keys %$_;
		$f   = $_->{$k};
		push @te,sprintf("%4d %-10s %s", $co, $k, $f);
		$co++;
	}
	Irssi::print(
		draw_box($IRSSI{name}, join( "\n",@te) ,'autoload list' , 1),
			, MSGLEVEL_CLIENTCRAP);
}

sub scmd_autoload {
	my ($server, $witem, @args) =@_;
	my $c = shift @args;
	if ($c eq 'list') {
		ascmd_list($server, $witem, @args);
	} elsif ( $c eq 'add') {
		push @autoload, { load=>$args[0]};
	} elsif ( $c eq 'gitadd') {
		push @autoload, { gitload=>$args[0]};
	} elsif ( $c eq 'remove') {
		splice @autoload,$args[0],1;
	} elsif ( $c eq 'move') {
		my $b =splice @autoload,$args[0],1;
		my @ab =splice @autoload,$args[1];
		push @autoload, $b;
		push @autoload, @ab;
	} elsif ( $c eq 'write') {
		write_autoload();
	} elsif ( $c eq 'load') {
		load_autoload();
	} elsif ( $c eq 'startup') {
		ascmd_startup($server, $witem, @args);
	}
}

sub scmd_new {
	my ($server, $witem, @args) =@_;
	my @te;
	my $co=1;
	my $max=5;
	if (defined $args[0]) {
		$max= $args[0];
	}
	foreach my $t (sort { $b cmp $a } keys %time_scr) {
		foreach my $s ( @{$time_scr{$t}}) {
			push @te,"$t  $s->{filename}";
			$co++;
		}
		last if ($co > $max);
	}
	Irssi::print(
		draw_box($IRSSI{name}, join( "\n",@te) ,'new' , 1),
			, MSGLEVEL_CLIENTCRAP);
}

sub cmd {
	my ($args, $server, $witem)=@_;
	my @args = grep { $_ ne ''}  quotewords('\s+', 0, $args);
	my $c =shift @args;

	if ($c eq 'gitload') {
		scmd_gitload($server, $witem, @args);

	} elsif ($c eq 'status') {
		run(
			'cmd' => "git -C $repo status -sbuno",
			'label'=> 'status',
			'next' => [\&print_msg, \&load_scripts]);

	} elsif ($c eq 'pull') {
		run(
			'cmd' => "git -C $repo pull",
			'label'=> 'pull',
			'next' => [\&print_msg, \&load_scripts]);

	} elsif ($c eq 'fetch') {
		run(
			'cmd' => "git -C $repo fetch --all",
			'label'=> 'fetch',
			'next' => \&print_msg);

	} elsif ($c eq 'log') {
		my $s;
		if (defined $args[0]) {
			$s = "scripts/$args[0]";
			if ($s !~ m/\.pl$/) {
				$s .=".pl";
			}
		}
		run(
			'cmd' => "git -C $repo log master -n 10 ".
							"--invert-grep --grep='automatic scripts database update' ".
							"--no-decorate --no-merges ".
							"--date=short ".
							"--pretty='format:%cd %h %s' ".
							"$s",
			'label'=> 'log',
			'next' => \&print_msg);

	} elsif ($c eq 'info') {
		scmd_script_info($server, $witem, @args);

	} elsif ($c eq 'search') {
		scmd_script_search($server, $witem, @args);

	} elsif ($c eq 'help') {
		scmd_help($server, $witem, @args);

	} elsif ($c eq 'new') {
		scmd_new($server, $witem, @args);

	} elsif ($c eq 'autoload') {
		scmd_autoload($server, $witem, @args);
	}
}

sub sig_setup_changed {
	my $r = Irssi::settings_get_str('gitscriptassist_repo');
	if ($r ne $repo ) {
		$r =~ s#/$##;
		$repo= $r;
		%scripts=();
		load_scripts();
	}
	my $p = Irssi::settings_get_str('gitscriptassist_path');
	$p =~ s#/$##;
	if ($p !~ m#^[~/]#) {
		$path = Irssi::get_irssi_dir().'/'.$p;
	}
	if (! -e $path ) {
		Irssi::print('gitscriptassist: make working dir "'.$path.'"', MSGLEVEL_CLIENTCRAP);
		mkdir $path;
	}
	$startup  = Irssi::settings_get_bool('gitscriptassist_startup');
	my $bi= Irssi::settings_get_bool('gitscriptassist_integrate');
	if ($bi==1 && $integrate != $bi) {
		$integrate=$bi;
		bind_cmd('script');
	}
}

sub do_complete {
	my ($strings, $window, $word, $linestart, $want_space) = @_;
	my $ok;
	foreach (@comp_start) {
		$ok=1 if ($linestart =~ m/^$_/);
	}
	return unless $ok;

	if ($word =~ m/^(.*:)/) {
		@$strings = grep { m/^$word/} map {$1.$_} keys %scripts;
	} else {
		@$strings = grep { m/^$word/} keys %scripts;
	}
	$$want_space = 1;
	Irssi::signal_stop;
}

sub bind_cmd {
	my ($cm)=@_;
	Irssi::command_bind($cm ,\&cmd);
	foreach my $c (keys %scmds) {
		Irssi::command_bind($cm .' '.$c,\&cmd);
		foreach my $s (keys %{$scmds{$c}->{sub}}) {
			Irssi::command_bind($cm .' '.$c.' '.$s,\&cmd);
		}
	}
	foreach my $sc (keys %scmds) {
		if (exists $scmds{$sc}->{file}) {
			push @comp_start, "/$cm $sc";
		}
		foreach my $s (keys %{$scmds{$sc}->{sub}}) {
			if (exists $scmds{$sc}->{sub}->{$s}->{file}) {
				push @comp_start, "/$cm $sc $s";
			}
		}
	}
}

sub UNLOAD {
	write_autoload();
}

Irssi::command_bind('help', sub {
		my @args = grep { $_ ne '' } quotewords('\s+', 0, $_[0]);
		my $s = shift @args;
		if ($s eq $IRSSI{name} ) {
			scmd_help(undef, undef, @args);
			Irssi::signal_stop;
		}
	}
);


Irssi::signal_add_first('complete word',  \&do_complete);
Irssi::signal_add('pidwait', 'sig_run_end');
Irssi::signal_add('setup changed', 'sig_setup_changed');

Irssi::settings_add_str($IRSSI{name}, 'gitscriptassist_repo', '~/foo/script-irssi');
Irssi::settings_add_str($IRSSI{name}, 'gitscriptassist_path', 'gitscriptassist');
Irssi::settings_add_bool($IRSSI{name}, 'gitscriptassist_startup', 0);
Irssi::settings_add_bool($IRSSI{name}, 'gitscriptassist_integrate', 0);

bind_cmd($IRSSI{name});

sig_setup_changed();
load_autoload();

