The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package VIM::Packager::Installer;
use warnings;
use strict;
use File::Spec;
use File::Path;
use File::Copy;
use Exporter::Lite;
use YAML;
use VIM::Packager::Utils qw(vim_rtp_home vim_inst_record_dir findbin);
use LWP::UserAgent;
use VIM::Packager::Record;
use VIM::Packager::MetaReader;
use Carp;
use FileHandle;

our @EXPORT = ();
our @EXPORT_OK = qw(
        install_deps 
        install 
        install_deps_remote 
        install_deps_from_git 
        uninstall
        bump_version
        );


# FIXME:  install deps from vim script archive network.


=head2 install_deps_from_git

    install dependencies from repository. e.g. repositories on github.com 

=cut

sub install_deps_from_git {
    my $clone_path = shift @ARGV;
    my $version_required = shift @ARGV;

    require VIM::Packager::MakeMaker;
    require VIM::Packager::Git;
    my $g = VIM::Packager::Git->new;
    $g->clone( $clone_path );

    # convert meta to makefile
    my $maker = VIM::Packager::MakeMaker->new;

    my $system = "make";
    my $stderr = $^O eq "MSWin32" ? "" : " 2>&1 ";

    my $pipe = FileHandle->new("$system install $stderr |") 
        || Carp::croak("Can't execute $system: $!");

    my $makeout = "";
    while (<$pipe>) {
        print $_; # intentionally NOT use Frontend->myprint because it
                  # looks irritating when we markup in color what we
                  # just pass through from an external program
        $makeout .= $_;
    }  
    $pipe->close;

    $g->cleanup();
}

=head2 install_deps

    install dependencies from VSAN.

    XXX: implement me.

=cut

sub install_deps {
    my $deps = shift @ARGV;
    my @pkgs = split /,/,$deps;
    # use Data::Dumper;warn Dumper( \@pkgs );
    die 'please implement me!!!';

    # * foreach dependency

    # * retreive vimscript tarball

    # * untar to build directory

    # * change directory to build directory

    # * read package meta file

    # * check dependency

    # * install dependencies

    # * call VIM::Pacakger::Installer to install files

}

our $VERBOSE = $ENV{VERBOSE} ? 1 : 0;

=head2 install_deps_remote


=cut

# XXX: give all dependency pkgnames in one time
sub install_deps_remote {
    my $pkgname = shift @ARGV;
    my %install = @ARGV;

    print sprintf( "Installing dependencies: %s\n",  $pkgname);

    $|++;
    while( my ($target,$from) = each %install ) {

        # XXX: we might need to expand Makefile macro to support such things like:
        #    $(VIM_BASEDIR)/path/to/
        # see VIM::Packager::MakeMaker
        # XXX: we should compare the installed file and the downloaded file.
        $target = File::Spec->join( vim_rtp_home() , $target );

        print "Downloading $from " ;
        print " to " . $target if $VERBOSE;
        print "...";

        {
            my ($v,$dir,$file) = File::Spec->splitpath( $target );
            File::Path::mkpath [ $dir ] unless -e $dir;
        }

        my $ua = LWP::UserAgent->new;
        $ua->timeout( 10 );
        $ua->env_proxy();

        my $content;
        my $response = $ua->get( $from );
        if( $response->is_success ) {
            $content = $response->decoded_content;


            print "[ OK ]\n";
        }
        else {
            print "[ FAIL ]\n";
            print $response->status_line;
        }

        # XXX: try to get the last modified time

        # if target exists , then we should do a diff
        if ( $content and -e $target ) {
            my @src = split /\n/,$content;

            open FH_T , "<", $target;
            my @target = <FH_T>;
            close FH_T;

            chomp @target;
            chomp @src;

            my $diff = diff_base_install( \@src , \@target );
            if ( $diff ) {
                my $ans = prompt_for_different( $target );
                while( $ans =~ /d/i ) {
                    print "Diff:\n";
                    print $diff;
                    $ans = prompt_for_different( $target );
                }
                if( $ans =~ /r/i ) {
                    # do replace
                    open RH,">",$target;
                    print RH join("\n",@src);
                    close RH;
                    print "$target replaced\n";
                }
                elsif ( $ans =~ /s/i ) {
                    # do nothing
                    print "Skipped\n";
                }
            }
        }
        elsif ( $content and ! -e $target ) {
            open RH,">",$target;
            print RH $content;
            close RH;
            print "$target installed\n";
        }


    }
}


=head2 prompt_for_different

=cut

sub prompt_for_different {
    my $target = shift;
    print "Installed script version not found. instead , we found the installed script file.\n";
    print "The installed vim script file is different from which you just downloaded.\n";
    print "Which is: $target.\n";
    print "(Replace(r) / Diff(d) / Merge(m) / Skip(s) ) it with the remote one ? ";
    my $ans = <STDIN>;
    chomp $ans;
    return $ans;
}


=head2 diff_base_install ArrayRef:From , ArrayRef:To

diff text

=cut

sub diff_base_install {
    my ($src_lines,$to_lines) = @_;
    require Algorithm::Diff;

    my $diff = Algorithm::Diff->new( $src_lines , $to_lines );
    $diff->Base(1);
    
    my $result = "";
    while(  $diff->Next()  ) {
        next   if  $diff->Same();

        my $sep = '';

        if(  ! $diff->Items(2)  ) {
            $result .= sprintf "%d,%dd%d\n", $diff->Get(qw( Min1 Max1 Max2 ));
        } 
        elsif(  ! $diff->Items(1)  ) {
            $result .= sprintf "%da%d,%d\n", $diff->Get(qw( Max1 Min2 Max2 ));
        } 
        else {
            $sep = "---\n";
            $result .= sprintf "%d,%dc%d,%d\n", $diff->Get(qw( Min1 Max1 Min2 Max2 ));
        }  
        $result .= "< $_\n"   for  $diff->Items(1);
        $result .= $sep;
        $result .= "> $_\n"   for  $diff->Items(2);
    }

    return $result ? $result : undef;
}



=head2 install $pkgname %install_files

install package vimlib files

%install_file is a hash, which key is source file , value is target path of installation.

=cut

sub install {
    my $pkgname = shift @ARGV;
    my %install_to = @ARGV;

    my $meta = VIM::Packager::MetaReader->new->read_metafile();

    # we should check more details on those files which are going to be
    #      installed.
    my $found_record = VIM::Packager::Record::find( $pkgname );
    if( $found_record and -e $found_record ) {
        my $r = VIM::Packager::Record::read( $found_record );
        my $version = $r->{meta}->{version};
        printf "Found installed package: %s v%s\n" , $pkgname , $version ;

        # uninstall older version
        if( $version < $meta->{version} ) {
            print "We require version up to " . $meta->{version} . "\n";
            print "Uninstalling $pkgname v$version\n";
            for my $f ( @{ $r->{files} } ) {
                if( -e $f ) {
                    print "Removing $f\n";
                    unlink $f;
                }
                else {
                    print "Warning: Can not found file $f\n";
                }
            }
        }
        else {
            print "Package $pkgname has been installed. Skipped.\n";
            print "run \$ make uninstall before make install if you need to reinstall it\n";
            return;
        }
    }

    print "Installing $pkgname " . $meta->{version} . "\n";
    while( my ($from,$to) = each %install_to ){
        my ( $v, $dir, $file ) = File::Spec->splitpath($to);

        print("$from doesnt exist.\n"),next unless -e $from;

        File::Path::mkpath [ $dir ] unless -e $dir ;

        if( -e $to ) {
            my $mtime_to = (stat($to))[9];
            my $mtime_from = (stat($from))[9];

            if ( $mtime_from > $mtime_to ) {
                File::Copy::copy( $from , $to );
                print STDOUT "Installing $from => $to \n";
            }
            else {
                print STDOUT "Skip $from\n";
            }
        }
        else {
            File::Copy::copy( $from , $to );
            print STDOUT "Installing $from => $to \n";
        }
    }

    my @files = values %install_to;

    print STDERR "Making checksum...\n";
    my @e = Vimana::Record->mk_file_digests( @files );
    use Vimana::Record;
    Vimana::Record->add( {
            version => 0.2,    # record spec version
            generated_by => 'VIM-Packager' . $Vimana::VERSION,
            install_type => 'meta',    # auto , make , rake ... etc
            package => $pkgname,
            files => \@e,
            meta => $meta,
    } );

    print "Updating doc tags\n";
    system(qq|vim -c ':helptags \$VIMRUNTIME/doc'  -c q |);
    print "Done\n";
}


=head2 uninstall [pkgname]

=cut

sub uninstall {
    my $pkgname = shift @ARGV;
    my $f = VIM::Packager::Record::find( $pkgname );

    unless( $f and -e $f ) {
        print "Can not found record of $pkgname\n";
        return ;
    }

    my $r = YAML::LoadFile( $f );
    my @files = @{ $r->{files} };

    for ( @files ) {
        print "Removing $_\n";
        if( ! -e $_ ) {
            print "Warning: can not found file $_.\n";
            next;
        }
        unlink $_;
    }

    print "Removing record $pkgname\n";
    unlink $f;
}


=head2 bump_version

you can export VIMPACKAGE_AUTO_COMMIT to do auto commit after version bumpped.

    export VIMPACKAGE_AUTO_COMMIT=1

=cut

sub bump_version {
    my $meta = VIM::Packager::MetaReader->new->read_metafile();
    my $previous_ver = $meta->{version};
    my $version = $previous_ver + 0.01;

    my $file = $meta->{version_from};
    if( -e $file ) {
        open FH , "<" , $file;
        my @lines = <FH>;
        close FH;

        my $found;
        for ( @lines ) {
            if( /^"=?\s*Version:?\s+$previous_ver/i ) {
                $found++;
                print "Version tag found: $_";
                $_ = qq{" Version: $version\n};
            }
        }

        open FH , ">" , $file;
        print FH @lines;
        close FH;

        if( $ENV{VIMPACKAGE_AUTO_COMMIT} and -e '.git' and $found ) {
            # found git repository
            print "Found .git directory\n";
            print "Do auto-commit\n";
            my $git = qx{which git};
            chomp $git;
            qx{$git commit $file -m"Bump version to $version."};
        }

        print "Version bumped to $version\n";
        print "Done.\n";
    }
}

1;