package Analizo::Batch::Job::Git;

use parent 'Analizo::Batch::Job';
use Cwd;
use Cwd 'abs_path';
use File::Spec;
use Digest::SHA qw/ sha1_hex /;
use File::Copy::Recursive qw(dircopy);
use File::Path qw(remove_tree);

sub new {
  my ($class, $directory, $id, $data) = @_;
  $class->SUPER::new(directory => $directory, actual_directory => $directory, id => $id, data => $data);
}

sub batch($$) {
  my ($self, $batch) = @_;
  if ($batch) {
    $self->{finder} = sub { $batch->find($_[0]); };
    $batch->share_filters_with($self);
  }
  return undef;
}

sub parallel_prepare {
  my ($self) = @_;
  $self->{actual_directory} = _create_work_directory($self->directory);
}

sub parallel_cleanup {
  my ($self) = @_;
  my $actual_directory = _create_work_directory($self->directory);
  remove_tree($actual_directory);
}

sub _create_work_directory {
  my ($original_dir) = @_;
  my $basename = 'analizo.' . $$ . '.' . sha1_hex(abs_path($original_dir));
  my $newdir = File::Spec->catfile(File::Spec->tmpdir(), $basename);
  if (! -d $newdir) {
    # Assume that the same directory may have been created before by the same
    # process.
    dircopy($original_dir, $newdir);
  }
  return $newdir;
}

sub prepare {
  my ($self) = @_;
  # change directory
  $self->{oldcwd} = getcwd();
  chdir($self->{actual_directory});
  # checkout
  $self->{old_branch} = git_current_branch();
  $self->git_checkout($self->id);
}

sub cleanup {
  my ($self) = @_;
  # undo checkout
  $self->git_checkout($self->{old_branch});
  delete($self->{old_branch});
  # undo directory change
  chdir($self->{oldcwd});
  delete($self->{oldcwd});
}

sub relevant {
  my ($self) = @_;
  for my $file (keys(%{$self->changed_files})) {
    if ($self->filename_matches_filters($file)) {
      return 1;
    }
  }
  return 0;
}

sub previous_wanted {
  my ($self) = @_;
  if ($self->is_merge) {
    return undef;
  } else {
    return $self->previous_relevant;
  }
}

sub previous_relevant {
  my ($self) = @_;
  if (exists($self->{previous_relevant})) {
    return $self->{previous_relevant};
  }
  my $previous_relevant = $self->_calculate_previous_relevant();
  $self->{previous_relevant} = $previous_relevant;
  return $self->{previous_relevant};
}

sub _calculate_previous_relevant {
  my ($self) = @_;
  my $finder = $self->{finder};
  if ($self->is_first_commit) {
    return undef;
  } elsif ($self->is_merge) {
    my @parents = map { &$finder($_) } @{$self->data->{parents}};
    my %grandparents = map { $_ => 1 } (grep { $_} (map { $_->previous_relevant } @parents));
    my @grandparents = keys(%grandparents);
    if (scalar(@grandparents) == 1) {
      return $grandparents[0];
    } else {
      return undef;
    }
  } else {
    my $parent = &$finder($self->data->{parents}->[0]);
    if ($parent->relevant) {
      return $parent->id;
    } else {
      return $parent->previous_relevant();
    }
  }
}

sub is_first_commit {
  my ($self) = @_;
  return scalar(@{$self->data->{parents}}) == 0;
}

sub is_merge {
  my ($self) = @_;
  my $ret = scalar(@{$self->data->{parents}}) > 1;
  return $ret;
}

sub changed_files {
  my ($self) = @_;
  return $self->data->{changed_files};
}

sub data {
  my ($self) = @_;
  return $self->{data};
}

sub metadata {
  my ($self) = @_;
  my $data = $self->data;
  return [
    ['previous_commit_id', $self->previous_wanted()],
    ['author_date', $data->{author_date}],
    ['author_name', $data->{author_name}],
    ['author_email', $data->{author_email}],
    ['changed_files', $data->{changed_files}],
    ['files', $self->files()],
  ];
}

sub git_checkout {
  my ($self, $commit) = @_;
  system("git checkout --quiet $commit");
}

sub git_current_branch {
  my @branches = `git branch`;
  chomp(@branches);
  my @current = grep { /^\*/ } @branches;
  my $current = $current[0];
  $current =~ s/^\*\s*//;
  return $current;
}

sub files {
  my ($self) = @_;
  if (defined($self->{files})) {
    return $self->{files};
  }
  my @files = `cd $self->{directory} && git ls-tree -r $self->{id}`;
  my %files = ();
  foreach my $line (@files) {
    my ($mode, $type, $sha1, $file) = split(/\s+/,$line);
    if ($self->filename_matches_filters($file)) {
      $files{$file} = $sha1;
    }
  }
  $self->{files} = \%files;
  return $self->{files};
}

1;