#!/usr/bin/perl

# PODNAME: tanker_merge

=encoding UTF-8
=cut

=head1 SYNOPSIS

    tanker_merge --project=project_id --token=12345 --source=feature_x --dest=master

 Obligatory options:

      --project=project_id  # project id in the tanker
      --token=12345         # project secret token
      --source=feature_x    # source branch name
      --dest=master         # destination branch
      --use_references_from_dest  # Script will automatically "solve" merge
                                  # conflicts with the problem 'references'.
      --auto_resolve_simple_case  # Script will automatically solve merge
                                  # conflicts in case where the translation
                                  # was added

 Optional options:

      --help                    # show this message and exit
      --use_test_tanker         # work with tanker-test.yandex-team.ru

=cut

# common modules
use strict;
use warnings FATAL => 'all';
use 5.008;
use Carp;
use Getopt::Long;
use File::Slurp;
use Term::ANSIColor qw(:constants);
use Pod::Usage;
use Data::Dumper;

use Yandex::Tanker;
use Yandex::Tanker::JSON;

my $true = 1;
my $false = '';

# subs
sub get_correct_opts {

    my $help;
    my $project;
    my $token;
    my $use_test_tanker;
    my $source;
    my $dest;
    my $use_references_from_dest;
    my $auto_resolve_simple_case;

    GetOptions(
        'help' => \$help,
        'project=s' => \$project,
        'token=s' => \$token,
        'use_test_tanker' => \$use_test_tanker,
        'source=s' => \$source,
        'dest=s' => \$dest,
        'use_references_from_dest' => \$use_references_from_dest,
        'auto_resolve_simple_case' => \$auto_resolve_simple_case,
    );

    if ($help) {
        pod2usage({-message => ''});
        exit;
    } elsif (not defined $project) {
        Yandex::Tanker::_print_error_and_exit("'--project' is missing");
    }

    my $opts = {
        project => $project,
        token => $token,
        use_test_tanker => $use_test_tanker,
        source => $source,
        dest => $dest,
        use_references_from_dest => $use_references_from_dest,
        auto_resolve_simple_case => $auto_resolve_simple_case,
    };

    if (not defined $opts->{token}) {
        Yandex::Tanker::_print_error_and_exit("'--token' is missing");
    };

    if (not defined $opts->{source}) {
        Yandex::Tanker::_print_error_and_exit("'--source' is missing");
    };

    if (not defined $opts->{dest}) {
        Yandex::Tanker::_print_error_and_exit("'--dest' is missing");
    };

    return $opts;
}

=head1 get_conflict_types

Input sample:

    {
        form   => {
            a        => "Log in",
            b        => "",
            resolved => undef,
        },
        hash   => "c304be53f2066ee550e9d64951605bc069e7121101f7c059fb23eadd802087854bac254fcbf379b6",
        status => {
            a        => "approved",
            b        => "requires_translation",
            resolved => undef,
        },
    }

One more input sample ('Form2 conflict is conflict in msgid[2]'):

    {
        'hash' => '1eae1211f5a15821917614c00038057e0106091abc442f575e4a95efe1f0a0342663ce73d80c2665',
        'form2' => {
            'a' => 'Unknown options in filter \\"%s\\"',
            'b' => 'Unknown options in filter  \\"%s\\"',
            'resolved' => undef,
        },
    }


=cut


sub get_conflict_types {
    my ($conflict_ref) = @_;

    my @keys = keys %{$conflict_ref};

    my @types;

    if (exists $conflict_ref->{a}) {

        @types = ( 'special_conflict' );

    } else {



        my @known_confict_types = (
            'form',
            'references',
            'status',
            'translator_comment',

            # `form[1-4]` conflicts are for ngettext strings
            'form1',  # form1 == msgstr[0]
            'form2',  # form2 == msgstr[1]
            'form3',
            'form4',
        );

        foreach my $type (@keys) {
            next if $type eq 'hash';

            if (not grep {$_ eq $type} @known_confict_types) {
                croak
                    "Can't find out the type of conflict. Got '$type', but this is not what we expect.\n"
                    . Dumper($conflict_ref)
                    ;

            }

            push @types, $type;
        }

        @types = sort {
            ( ($b eq 'form') cmp ($a eq 'form') )
        } @types;

    }

    return @types;
}

=head1 simple_case_status

It can return 'a', 'b' or undef.

It it returns undef then it is not simple case conflict. The return value 'a'
or 'b' describes what answer is the solution for the merge conflict.

Here is the sample of $conflict_ref with the simple conflict. Only one
translation was added and the status was chaned from 'approved' to
'requires_translation'.

    {
        form   => {
            a        => "Log in",
            b        => "",
            resolved => undef,
        },
        hash   => "c304be53f2066ee550e9d64951605bc069e7121101f7c059fb23eadd802087854bac254fcbf379b6",
        status => {
            a        => "approved",
            b        => "requires_translation",
            resolved => undef,
        },
    }

=cut

sub simple_case_status {
    my ($conflict_ref) = @_;

    croak "Internal error. Stopped" if ref $conflict_ref ne "HASH";
    my @keys = sort keys %{$conflict_ref};

    return undef if scalar @keys != 3;
    return undef if $keys[0] ne "form";
    return undef if $keys[1] ne "hash";
    return undef if $keys[2] ne "status";

    if (
        length($conflict_ref->{form}->{a}) > 0
        and length($conflict_ref->{form}->{b}) == 0
        and (
            (not defined $conflict_ref->{form}->{resolved})

            # second json file upload
            or ($conflict_ref->{form}->{resolved} eq $conflict_ref->{form}->{a})
        )
        and $conflict_ref->{status}->{a} eq 'approved'
        and $conflict_ref->{status}->{b} eq 'requires_translation'
        and (
            (not defined $conflict_ref->{status}->{resolved})

            # second json file upload
            or ($conflict_ref->{status}->{resolved} eq 'approved')
        )
    ) {
        return 'a',
    } elsif (
        length($conflict_ref->{form}->{a}) == 0
        and length($conflict_ref->{form}->{b}) > 0
        and (
            (not defined $conflict_ref->{form}->{resolved})

            # second json file upload
            or ($conflict_ref->{form}->{resolved} eq $conflict_ref->{form}->{b})
        )
        and $conflict_ref->{status}->{a} eq 'requires_translation'
        and $conflict_ref->{status}->{b} eq 'approved'
        and (
            (not defined $conflict_ref->{status}->{resolved})

            # second json file upload
            or ($conflict_ref->{status}->{resolved} eq 'approved')
        )
    ) {
        return 'b',
    } else {
        return undef;
    }
}

# main
sub main {
    my $opts = get_correct_opts();

    my $tanker_url;
    if ($opts->{use_test_tanker}) {
        $tanker_url = $Yandex::Tanker::TEST_URL;
    } else {
        $tanker_url = $Yandex::Tanker::PROD_URL;
    }

    my $tanker = Yandex::Tanker->new({
        url     => $tanker_url,
        project => $opts->{project},
        token   => $opts->{token},
    });

    my $solution_data = {};

    SOLVE_CONFLICT:
    while(1) {
        my ($status, $conflict_json) = $tanker->merge(
            source => $opts->{source},
            dest => $opts->{dest},
            solution => to_json($solution_data),
        );

        if ($status) {
            print "Merge completed successfully.\n";
            last SOLVE_CONFLICT;
        } else {
            print "There was merge conflicts you need to solve.\n\n";
        }

        my $conflicts_data = from_json($conflict_json);
        $solution_data = $conflicts_data;

        my $i = 1;
        my $total_conflicts = scalar keys %{$conflicts_data};

        foreach my $conflict_name (sort keys %{$conflicts_data}) {
            print "## Conflict #$i/$total_conflicts - $conflict_name\n\n";

            my @conflicts_types = get_conflict_types($conflicts_data->{$conflict_name});

            my $simple_case_status = simple_case_status($conflicts_data->{$conflict_name});

            if ($opts->{auto_resolve_simple_case} and $simple_case_status) {

                $solution_data->{$conflict_name}->{form}->{resolved} =
                    $solution_data->{$conflict_name}->{form}->{$simple_case_status};
                $solution_data->{$conflict_name}->{status}->{resolved} = "approved";

                print "Solving simple conflict automagically.\n\n";

            } else {

                print "List of problems in this conflict:\n";
                print " * $_\n" foreach @conflicts_types;
                print "\n";

                foreach my $type (@conflicts_types) {
                    print "Problem: '$type'\n";

                    my $value_in_dest;
                    my $value_in_source;

                    if ($type eq "special_conflict") {

                        $value_in_dest = $conflicts_data->{$conflict_name}->{a};
                        $value_in_source = $conflicts_data->{$conflict_name}->{b};

                    } else {

                        $value_in_dest = $conflicts_data->{$conflict_name}->{$type}->{a};
                        $value_in_source = $conflicts_data->{$conflict_name}->{$type}->{b};

                    }

                    print "a (" .  $opts->{dest} . "): ", YELLOW, $value_in_dest, RESET, "\n";
                    print "b (" .  $opts->{source} . "): ", YELLOW, $value_in_source, RESET, "\n";

                    my $answer;

                    if ($type eq 'references' and $opts->{use_references_from_dest}) {

                        # Deciding how to solve merge conflict automaticly
                        $answer = "a";
                        print "\nSolving conflict automaticly. Using data from branch '" . $opts->{dest} . "' (answer '$answer').\n";

                    } else {
                        # Asking user to solve merge confclict

                        INPUT:
                        while(1) {
                            print BOLD, BLUE, "Your answer [a,b]? ", RESET;
                            my $input = <>;
                            chomp $input;
                            if ($input eq "a" or $input eq "b") {
                                $answer = $input;
                                last INPUT;
                            }
                        }
                    }

                    if ($type eq "special_conflict") {
                        $solution_data->{$conflict_name}->{resolved} = $answer;
                    } else {
                        $solution_data->{$conflict_name}->{$type}->{resolved} = $conflicts_data->{$conflict_name}->{$type}->{$answer};
                    }

                    print "\n";
                }
            }

            $i++;
        }
    }

}

main();

__END__
