
package App::Vimpl;
our $VERSION = '0.05';

=head1 NAME

App::Vimpl - Helps manage your vim plugins with Git

=head1 VERSION

Version 0.05

=head1 SYNOPSIS

Use Vimpl to manage your Vim scripts with Git and mirrors listed on Vim-scripts.org

	vimpl <command> [<args>]

=head1 OPTIONS
	
	init - Initializes a git repo in your $HOME/.vim folder and installs 
		Pathogen.vim as a git submodule.

	restore <gitrepo> - Clones the provided git repository into $HOME/.vim
		Initializes all git submodules and updates all submodules.

	search <vim plugins>... - Provides a list of matching plugins found
		on vim-scripts.org for each name provided.

	install [<vim plugin name>|<vim script number>|<git repo>]...- Installs
		provided Vim plugin names/numbers as git submodules from their
		respective vim-scripts.org mirrors.  Also will install a provided
		git repo as a submodule in .vim/bundle.

	list - Lists the installed Vim plugins (omits Pathogen.vim from
		the list)

	remove <vim plugins>... - Removes provided Vim plugins.
		Must match the names in 'list'.

	pull [config|plugin|all] - Does a 'git pull origin master' for either
		the remote for the $HOME/.vim repo , git submodules or both.
		If not specified, plugins only.

	update - Updates the local list of plugin mirrors from vim-scripts.org.

	backup - If the $HOME/.vim repo already has a remote origin,
		'backup' will push to it.

		backup github <name> [public|private] - Vimpl will attempt to
			create a repo for you on GitHub <name> using credentials in your
			$HOME/.gitconfig .  Vimpl will then push your .vim config to
			the fresh repo.

		backup <gitrepo> - Vimpl will attempt to set the provided repo as
			the remote origin in $HOME/.vim and push to it.

=head1 USAGE EXAMPLES

	$ vimpl init
	
	$ vimpl install SelectBuf SuperTab The-NERD-tree Wombat
	
	$ vimpl pull plugins


=head1 AUTHOR

Colin Kennedy, C<< <moshen at cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests through the web interface at 
L<https://github.com/moshen/Vimpl/Issues>.  I will be notified, and 
then you'll automatically be notified of progress on your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc App::Vimpl


You can also look for information at:

=over 4

=item * Vimpl's GitHub page

L<https://github.com/moshen/Vimpl>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/App-Vimpl>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/App-Vimpl>

=item * Search CPAN

L<http://search.cpan.org/dist/App-Vimpl/>

=back


=head1 ACKNOWLEDGEMENTS

The inspiring GitHub script mirrors at L<http://vim-scripts.org>.


=head1 LICENSE AND COPYRIGHT

Copyright 2011 Colin Kennedy.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.


=cut



use v5.10;

use Web::Scraper;
use LWP::UserAgent;
use Data::Dumper;
use JSON::XS;
use File::Path;
use File::Copy;
use feature "switch";
use Config::IniFiles;
use MIME::Base64;


use warnings;
use strict;

my $json = JSON::XS->new->ascii->pretty;
my $agent = LWP::UserAgent->new();


my $vim_dir = $ENV{HOME} . '/.vim/';

sub init {
	if(! -d $vim_dir){
		mkdir($vim_dir) or die "\nCan't create .vim in " . $ENV{HOME} . " are you sure everything's okay?\n";
	}

	chdir($vim_dir);

	if(! -d "bundle"){ mkdir("bundle"); }
	if(! -d "autoload"){ mkdir("autoload"); }

	if( scalar(grep($_ =~ /^\s?([AM]|\?\?\s+bundle)/, `git status --porcelain`)) > 0){
		print "\nAn exsisting and unclean git repository was found in $vim_dir." .
			"\n\tPlease commit or remove the pending changes before proceeding.\n";
		exit 1;
	}
	if($? == 0){
		print "\nAn exsisting git repository was found in $vim_dir." .
			"\n\tI will try to use it, but I thought I would warn you.\n";
	} else {

		if( system("git init") != 0 ){
			die "\nThere was a problem creating a git repository in $vim_dir.\n";
		}
	}

	if(-f "autoload/pathogen.vim"){ unlink("autoload/pathogen.vim"); }

	if(system("git submodule add git://github.com/vim-scripts/pathogen.vim.git pathogen.vim") == 0){
		symlink("../pathogen.vim/plugin/pathogen.vim", "autoload/pathogen.vim") or
			die "\nCan't symlink pathogen.vim into .vim/autoload.\n";
	} else {
		die "\nFailed to add pathogen as a submodule.";
	}

	
	# Check and include the .vimrc file in $ENV{HOME}/.vim/
	my @vimrc = (
		"filetype off\n",
		"call pathogen#helptags()\n",
		"call pathogen#runtime_append_all_bundles()\n"
	);
  if( -f $ENV{HOME} . "/.vimrc" ){
		open(VIMRC, "<" . $ENV{HOME} . "/.vimrc");
		my @vimrc_old = <VIMRC>;
		close(VIMRC);

		if( scalar(grep($_ =~ /call\s+pathogen/i, @vimrc_old)) > 1 ){
			print "\n\tFound pathogen.vim config in .vimrc\n";
			@vimrc = @vimrc_old;
		} else {
			print "\n\tDid not find pathogen.vim config in .vimrc\n";
			push(@vimrc , @vimrc_old);
		}

		my $vimrc_backup = $ENV{HOME} . "/.vimrc.backup" . time();
		move($ENV{HOME} . "/.vimrc" , $vimrc_backup);

		print "\n\tBacking up your .vimrc file to " . $vimrc_backup . "\n";
	}

	if( -f "$vim_dir.vimrc" ){
		move( "$vim_dir.vimrc", "$vim_dir.vimrc.backup" . time());
	}
	open(VIMRC, ">$vim_dir" . ".vimrc");
	foreach my $line (@vimrc){
		print VIMRC $line;
	}
	close(VIMRC);

	symlink(".vim/.vimrc", "../.vimrc");


	print "\n";

	print "\n\tGetting the latest list of Vim scripts mirrors from vim-scripts.org\n";
	update_vim_scripts();

	# TODO : write out a default .gitignore file (if there isn't one) and add it to the repo as well.
	if(system("git add autoload .vimrc") == 0 ){
		if( system("git commit -m 'Initialized a new vimpl config, installed pathogen.vim'") == 0 ){
			print "\n\tSuccessfully initialized a new vimpl repo.\n\tTry installing some vim scripts.\n\n";
		}else{
			die "\nProblem committing new changes while initializing a vimpl git repo.\n";
		}
	}else{
		die "\nProblem committing new changes while initializing a vimpl git repo.\n";
	}

}

sub backup {
	my $upper_args = $_[1];
	my $has_origin = 0;

	my @git_remote_list = grep($_ =~ /^origin\s+.+\.git\s+\(push\)$/, `git remote -v`);
	if( scalar(@git_remote_list) > 0 ){
		$has_origin = 1;

		print "\tBacking up to existing origin:\n\t $git_remote_list[0]\n";
	}

	if( defined( $upper_args->[0] ) && $has_origin == 0 ){
		
		# GitHub repo check and creation
		#   This is reaaaaaally ugly, (un)fortunately it works
		given($upper_args->[0]){
			when ('github') {
				unless( defined( $upper_args->[1] )){
					print "\n\tPlease provide a repo name.\n";
					exit 1;
				}
				if( -f $ENV{HOME} . "/.gitconfig" ){
					my $gitconfig = Config::IniFiles->new( -file => $ENV{HOME} . "/.gitconfig" );
					if( $gitconfig->val('github', 'user') && $gitconfig->val('github', 'token') ){
						my $user = $gitconfig->val('github', 'user');
						my $token = $gitconfig->val('github', 'token');
						print "\tUsing GitHub user: $user to create a repository.\n";
		
						$agent->default_header("Authorization" => "Basic " . encode_base64("$user/token:$token"));
	
						my $repolist = decode_json($agent->get("https://github.com/api/v2/json/repos/show/$user")->content()) or
							die "\nCouldn't fetch repo list for $user from GitHub.\n";
	
						if( $repolist->{repositories} ){
							if( scalar( grep( $_->{name} eq $upper_args->[1], @{$repolist->{repositories}} )) > 0 ){
								print "\tThere is an exsisting repository on GitHub called: " . $upper_args->[1] .
									"\n\tPlease use another name.\n";
								exit 1;
							} else {
								my $public;
								if( defined($upper_args->[2]) ){
									$public = $upper_args->[2] eq "public" ? 1 : 0;
								} else {
									$public = 1;
								}
								my $create_response = decode_json($agent->post("https://github.com/api/v2/json/repos/create", {
																									"name" => $upper_args->[1], 
																									"description" => "My Vim config",
																									"public" => $public } )->content()) or
									die "\nError creating repo " . $upper_args->[1] . " on GitHub\n";
	
								if( $create_response->{repository} ){
									print "\tSuccessfully created repository: " . $create_response->{repository}->{name} . " on GitHub.\n";
									if( system( "git remote add origin git\@github.com:$user/" . $create_response->{repository}->{name} . ".git" ) == 0){
										print "\tSuccessfully added your GitHub repo as the remote origin.\n\n";
									} else {
										print "\n\tThere was a problem adding your GitHub repository as the repo origin\n";
										exit 1;
									}
								} else {
									print "\n\tFailed to create a GitHub repo for you. Are you sure your credentials are correct in .gitconfig?\n";
									exit 1;
								}
								
									
							}
						}
	
			
						$has_origin = 1;
					} else {
						print "\tNo .gitconfig file found in " . $ENV{HOME} . "\n" . 
							"\tVimpl reads your stored github user and token from your .gitconfig file.\n" .
							"\tIf you're an avid git user you should probably have one.\n\n";
						exit 1;
					}
				}
			}
			when (/[a-zA-Z0-9_\-@.\/:]+\.git/) {
				if( system("git remote add origin " . $upper_args->[0] ) == 0 ){
					print "\tAdded remote origin successfully\n";
					$has_origin = 1;
				} else {
					die "\nProblem adding remote origin: " . $upper_args->[0] . "\n";
				}
			}
		}
	}

	if( $has_origin ){
		if( system("git push origin master") == 0 ){
			print "\n\tVim configuration backed up successfully.\n";
		} else {
			die "\nProblem pushing to remote origin\n";
		}
	}
}

sub restore {
	my $upper_args = $_[1]; # Hashref, probably should check
	
	if( -d $vim_dir ){
		print "\n\t$vim_dir already exists!\n";
		exit 1;
	}

	if( system( "git clone " . $upper_args->[0] . " $vim_dir" ) == 0 ){
		chdir($vim_dir);
		if( system( "git submodule init" ) == 0 ){
			if( system( "git submodule update" ) == 0 ){
				print "\n\tVim configuration successfully restored.\n";
			} else {
				die "\nFailed to update git submodules in $vim_dir.\n";
			}
		} else {
			die "\nFailed to initialize git submodules in $vim_dir.\n";
		}
	} else {
		die "\nFailed to clone your backup repo into $vim_dir.\n";
	}
				
}

sub install_git_submodule {
	my $mods_to_install = $_[1]; # Should be an array ref
	my $modules = get_submodule_list();
	my $scripts = get_vim_scripts();

	my $filt = sub {
		my ($mod, $ins) = @_;
		
		if( $ins =~ /^\d+$/ ){
			if( $mod->{script_text}->[0] == $ins ){
				return 1;
			}
		}

		if($mod->{script_text}->[2] eq $ins){
			return 1;
		}
		
		if($mod->{script_repo} eq $ins){
			return 1;
		}else{
			return 0;
		}
	};

	my @to_be_installed;
	foreach my $mod (@{$mods_to_install}){
		#  Filter input to get a list of modules to install
		#    Detect Git repo and strip repo name
		if( $mod =~ /^(https?:\/\/|git:\/\/|.+\@.+:).+?\/?([^\/]+)\.git$/i ){
			push(@to_be_installed, { script_url  => $mod, script_repo => $2 });
		} else {
			push(@to_be_installed, grep($filt->($_, $mod), @{$scripts->{scripts}}));
		}
	}
	
	if(@to_be_installed){
		my @installed;
		my @problem_mods;
		foreach my $mod_install (@to_be_installed){

			if(scalar(grep( $_ eq $mod_install, @{$modules->{list}} )) > 0){
				print "\t$mod_install is already installed.\n";
				next;
			}

			print "\n";
			if( system("git submodule add " . $mod_install->{script_url} . " bundle/" . $mod_install->{script_repo}) == 0 ){
				push(@installed, $mod_install->{script_repo});
			}else{
				push(@problem_mods, $mod_install->{script_repo});
			}
		}
		print "\n";

		if( system("git commit -m 'Installed submodule(s) " . join(", ", @installed) . "'") == 0 ){
			print "\n\tSuccessfully installed " . join(", ", @installed) . "\n";
		}else{
			die "\nProblem committing new changes while installing submodules.\n";
		}

		if(@problem_mods){
			print "\n\tThere was a problem installing: " . join(", ", @problem_mods) . "\n";
		}
	}
}

sub pull_update {
	my $upper_args = $_[1]; # Hashref again

	unless(defined($upper_args->[0])){
		$upper_args->[0] = 'plugin';
	}

	given($upper_args->[0]){
		when (/config|remote/) {
			pull_config();
		}
		when (/plugin(s)?/) {
			pull_git_submodules();
		}
		when ('all') {
			pull_config();
			pull_git_submodules();
		}
	}
}

sub pull_config {
	# Need to make sure that there is a remote origin here
	if( scalar(grep($_ =~ /^origin\s+.+\.git\s+\(push\)$/, `git remote -v`)) > 0 ){
		if( system( "git pull origin master" ) > 0 ){
			die "\nFailed to pull from remote origin.\n";
		}
	}
}

sub pull_git_submodules {
	if( system("git submodule foreach 'git pull origin master'") > 0 ){
		die "\nThere was a problem pulling updates for your installed scripts\n";
	}
}

sub remove_git_submodule {
	my $mods_to_remove = $_[1]; # Should be an array ref
	my $modules = get_submodule_list();

	my $count = 0;
	my @removing;

	open(OUT, ">.gitmodules");
	while( $count < scalar(@{$modules->{text}}) ){
		my @found = grep( $modules->{text}->[$count] =~ /^\[submodule "bundle\/$_"\]/, @{$mods_to_remove});
		if(scalar(@found) > 0 ){
			$count += 3;
			print "\tRemoving " . $found[0] . " from .gitmodules\n";
			push(@removing, "bundle/" . $found[0]);
		} else {
			print OUT $modules->{text}->[$count];
			$count += 1;
		}
	}

	close(OUT);

	if( scalar(@removing) > 0 ){
		my @problem_mods = ();
		my @successful_mods = ();
		foreach my $rem_mod (@removing){
			if( system("git rm --cached $rem_mod") == 0 ){
				print "\tRemoved $rem_mod from git cache\n";
				rmtree($rem_mod);
				print "\tRemoved $rem_mod from the file system\n";

				$rem_mod =~ s/^bundle\///;
				push(@successful_mods, $rem_mod);
			} else {
				print "\tThere was a problem removing the git cache for $rem_mod\n" .
					"\tSkipping the removal process of the directory " . $vim_dir . $rem_mod . "\n";

				$rem_mod =~ s/^bundle\///;
				push(@problem_mods, $rem_mod);
			}
		}
	
			if(system("git add .gitmodules") == 0 ){
				if( system("git commit -m 'Removed submodule(s) " . join(", ", @successful_mods) . "'") == 0 ){
					print "\n\tSuccessfully removed " . join(", ", @successful_mods) . "\n";
				}
			}

			if( scalar(@problem_mods) > 0 ){
				print "\tThere were issues removing " . join(", ", @problem_mods) . "\n";
			}
	} else {
		print "\tNothing found to remove.\n";
	}
	
}

sub search_vim_scripts {
	my $mods_to_search = $_[1]; # Should be an array ref

	my $filt = sub {
		my ($mod_to_search, $script) = @_;

		if( $mod_to_search =~ /^\d+$/ ){
			if( $script->{script_text}->[0] == $mod_to_search ){
				return 1;
			}
		}

		if( $script->{script_text}->[2] =~ /$mod_to_search/ig ){
			return 1;
		}

		if( $script->{script_repo} =~ /$mod_to_search/ig ){
			return 1;
		}else{
			return 0;
		}
	};
	my $scripts = get_vim_scripts();

	foreach my $mod_to_search (@{$mods_to_search}){
		my @results = grep($filt->($mod_to_search, $_), @{$scripts->{scripts}});
		print "For query: $mod_to_search , found:\n";
		map { print "\t" . $_->{script_repo} . "\n"; } @results;
		print "\n";
	}
}

sub get_submodule_list {
	my $modules = {};
	open(MODULES, "<.gitmodules");
	@{$modules->{text}} = <MODULES>;
	close(MODULES);

	$modules->{list} = ();
	foreach my $line (@{$modules->{text}}){
		if( $line =~ /^\[submodule "bundle\/(.+)"\]/ ){
			push(@{$modules->{list}}, $1);
		}
	}

	return $modules;
}

sub update_vim_scripts {
	my $vim_scripts_scraper = scraper {
		process "tr", "scripts[]" => scraper {
			process "td", "script_text[]" => 'TEXT';
			process "a", "script_url" => sub { # Convert provided URLs to git:// urls
				my $url = $_->attr('href');
				$url =~ s/^http/git/;
				return $url . '.git';
			};
			# Grab the repo name from the provided URLs
			process "a", "script_repo" => sub { return ($_->attr('href') =~ /\/([^\/]+)$/)[0]; };
		};
	};

	my $scripts_page = $agent->get('http://vim-scripts.org/vim/scripts.html')->content();

	unless($scripts_page){
		die "Could not fetch scripts list from vim-scripts.org\n";
	}

	my $scripts = $vim_scripts_scraper->scrape($scripts_page);

	open OUT, ">vim-scripts.json"
		or die "Could not write out vim-scripts.json list in $vim_dir";
	print OUT $json->encode($scripts);
	close OUT;
}

sub get_vim_scripts {
	my $scripts = "";

	if(-f "vim-scripts.json"){
		open IN, "<vim-scripts.json";
		$scripts = decode_json(join("",<IN>)); # Probably shouldn't be sluuuurping here
		close IN;
	}else{
		die "\nvim-scripts.json not found in $vim_dir , try running vimpl update.\n";
	}

	return $scripts;
}


1;


