package App::bif::sync;
use strict;
use warnings;
use AnyEvent;
use Bif::Sync::Client;
use Bif::Mo;
use Coro;
use DBIx::ThinSQL qw/qv/;

our $VERSION = '0.1.5_5';
extends 'App::bif';

sub run {
    my $self = shift;
    my $opts = $self->opts;
    my $dbw  = $self->dbw;

    # Consider upping PRAGMA cache_size? Or handle that in Bif::Role::Sync?
    my @hubs = $dbw->xhashrefs(
        select     => [ 'h.id', 'n.name', 'h.location', 'n.uuid' ],
        from       => 'hubs h',
        inner_join => 'nodes n',
        on         => 'n.id = h.id',
        $opts->{hub} ? ( where => { 'n.name' => $opts->{hub} } ) : (),
        order_by => 'n.name',
    );

    return $self->err( 'SyncNone', 'no (matching) hubs found' )
      unless @hubs;

    $|++;    # no buffering

    foreach my $hub (@hubs) {
        my $error;
        my $cv = AE::cv;

        my $client = Bif::Sync::Client->new(
            name          => $hub->{name},
            db            => $dbw,
            location      => $hub->{location},
            debug         => $opts->{debug},
            debug_bifsync => $opts->{debug_bifsync},
            on_update     => sub {
                my $client = shift;
                $self->lprint( $client->name . ': ' . $_[0] );
            },
            on_error => sub {
                $error = shift;
                $cv->send;
            },
        );

        my $fh   = select;
        my $coro = async {
            select $fh;

            eval {
                $dbw->txn(
                    sub {
                        $self->start_work(
                            node_id       => $hub->{id},
                            start         => time,
                            start_comment => "sync",
                            billable      => 1,
                            save          => 1,
                        );

                        my @status = $client->sync_hub( $hub->{id} );

                        unless ( $status[0] eq 'HubMatch'
                            or $status[0] eq 'HubSync' )
                        {
                            $dbw->rollback;
                            $error =
"unexpected HubSync/Match status received: @status";
                            return;

                        }

                        if ( $status[0] eq 'HubSync' ) {
                            @status = $client->transfer_hub_changes;
                            if ( $status[0] ne 'TransferHubChanges' ) {
                                $dbw->rollback;
                                $error =
"unexpected TransferHubChanges status received: @status";
                                return;
                            }
                        }

                        print "\n";
                        $self->lprint('');
                        my $hub_id   = $client->hub_id;
                        my @projects = $dbw->xhashrefs(
                            select_distinct =>
                              [ 'p2.id AS id', 'n.path AS path' ],
                            from       => 'projects p',
                            inner_join => 'nodes_tree nt',
                            on         => 'nt.parent = p.id',
                            inner_join => 'projects p2',
                            on         => 'p2.id = nt.child AND p2.local = 1',
                            inner_join => 'nodes n',
                            on         => 'n.id = p2.id',
                            where      => {
                                'p.default_hub_id' => $hub_id,
                            },
                        );

                        foreach my $project (@projects) {
                            $client->name("$hub->{name}\[$project->{path}\]");
                            my $status =
                              $client->sync_project( $project->{id} );
                            if ( $status eq 'ProjectSync' ) {
                                @status =
                                  $client->transfer_project_related_changes;

                                if ( $status[0] ne
                                    'TransferProjectRelatedChanges' )
                                {
                                    $dbw->rollback;
                                    $error =
"unexpected TransferProjectRelatedChanges status received: @status";
                                    return;
                                }
                            }
                            elsif ( $status ne 'ProjectMatch' ) {
                                $dbw->rollback;
                                $error =
"unexpected sync_project status received: $status";
                                return $status;
                            }

                            print "\n";
                            $self->lprint('');
                        }

                        $dbw->xdo(
                            insert_or_replace_into =>
                              [ 'bifkv', qw/key change_id change_id2/ ],
                            select =>
                              [ qv('last_sync'), 'MAX(c.id)', 'MAX(c.id)', ],
                            from => 'changes c',
                        );

                        $self->stop_work(
                            stop    => time,
                            restore => 1,
                        );

                        return;
                    }
                );
            };

            if ($@) {
                $error = $@;
                print "\n";
            }

            return $cv->send;
        };

        $cv->recv;
        $client->disconnect;

        return $self->err( 'Unknown', $error ) if $error;

    }

    # TODO make this dependent on how many changes received/made since the last
    # analyze
    $dbw->do('ANALYZE');

    return $self->ok('Sync');
}

1;

__END__

=head1 NAME

=for bif-doc #sync

bif-sync -  exchange changes with hubs

=head1 VERSION

0.1.5_5 (2015-08-13)

=head1 SYNOPSIS

    bif sync [OPTIONS...]

=head1 DESCRIPTION

The C<bif sync> command connects to all remote repositories registered
as hubs in the local repository and exchanges changes.

=head1 ARGUMENTS & OPTIONS

=over

=item --hub, -H HUB

Limit the hubs to sync with. This option can be used multiple times.

=item --path, -p PATH

Limit the projects to sync. This option can be used multiple times.

=back

=head1 SEE ALSO

L<bif>(1)

=head1 AUTHOR

Mark Lawrence E<lt>nomad@null.netE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2013-2015 Mark Lawrence <nomad@null.net>

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

