package App::bif::log;
use strict;
use warnings;
use feature 'state';
use locale;
use Bif::Mo;
use utf8;
use Text::Autoformat qw/autoformat/;
use Text::FormatTable;
use Time::Duration qw/ago/;
use Time::Piece;
use Time::Seconds;

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

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

    if ( $opts->{id} ) {
        my $info  = $self->get_topic( $opts->{id} );
        my $class = "App::bif::log::$info->{kind}";

        if ( eval "require $class" ) {
            $opts->{path} = delete $opts->{id}
              if ( $info->{kind} eq 'project' );

            return $class->new( db => $self->db, opts => $opts )->run;
        }
        die $@ if $@;
        return $self->err( 'LogUnimplemented',
            'cannnot log type: ' . $info->{kind} );
    }

    $opts->{format} = 'short' if delete $opts->{short};

    return $self->run_by_full  if $opts->{format} eq 'full';
    return $self->run_by_short if $opts->{format} eq 'short';
    return $self->err( 'FormatUnsupported',
        "unsupported format type: $opts->{format}" );
}

sub run_by_full {
    my $self = shift;
    my $opts = $self->opts;
    my $db   = $self->db();

    my @order;
    if ( $opts->{order} eq 'time' ) {
        @order = ( 'c.mtime DESC', 'c.id DESC' );
    }
    elsif ( $opts->{order} eq 'id' ) {
        @order = ( 'c.id DESC', 'c.mtime DESC' );
    }
    else {
        return $self->err( 'OrderUnsupported',
            "unsupported order: $opts->{order}" );
    }

    state $have_dbix = DBIx::ThinSQL->import(qw/ case concat coalesce sq qv/);

    my ( $yellow, $reset ) = $self->colours(qw/yellow reset/);
    my $now = $self->now;

    my $sth = $db->xprepare(
        with => 'b',
        as   => sq(
            select => [ 'b.change_id AS start', 'b.change_id2 AS stop' ],
            from   => 'bifkv b',
            where => { 'b.key' => 'last_sync' },
        ),
        select => [
            '"c" || c.id AS change_id',
            'p.path AS project',
            'COALESCE(c.author,e.name) AS author',
            "COALESCE(c.author_contact_method || ': ' || c.author_contact,"
              . "ecm.method || ': ' || ecm.mvalue) AS contact",
            'COALESCE(iss.title, t.title) AS title',
            'c.uuid AS change_uuid',
            'c.action',
            'c.mtime',
            'c.mtimetz',
            'c.mtimetzhm',
            qq{$now - strftime('%s', c.mtime, 'unixepoch') AS mtime_age},
            'c.message',
            '0 AS depth',
            concat(
                'c.action',
                case (
                    when => 'b.start',
                    then => qv( $yellow . ' [+]' . $reset ),
                    else => qv(''),
                )
            )->as('action'),
        ],
        from       => 'changes c',
        left_join  => 'change_deltas cd',
        on         => 'cd.change_id = c.id',
        inner_join => 'entities e',
        on         => 'e.id = c.identity_id',
        inner_join => 'entity_contact_methods ecm',
        on         => 'ecm.id = e.default_contact_method_id',
        inner_join => 'identities i',
        on         => 'i.id = c.identity_id',
        left_join  => 'b',
        on         => 'c.id BETWEEN b.start+1 AND b.stop',
        left_join  => 'tasks t',
        on         => 't.id = cd.action_topic_id_1',
        left_join  => 'task_status ts',
        on         => 'ts.id = t.task_status_id',
        left_join  => 'issues iss',
        on         => 'iss.id = cd.action_topic_id_1',
        left_join  => 'topics tp',
        on         => 'tp.id = t.id',
        left_join  => 'projects p',
        on         => 'p.id = ts.project_id OR p.id = cd.action_topic_id_1',
        left_join  => 'hubs h',
        on         => 'h.id = p.hub_id',
        order_by   => \@order,
    );

    $sth->execute;

    $self->start_pager;

    while ( my $row = $sth->hashref ) {
        $self->log_comment( $row, 1 );
    }

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

sub run_by_short {
    my $self = shift;
    return $self->run_by_time if $self->opts->{order} eq 'time';
    return $self->run_by_id   if $self->opts->{order} eq 'id';
}

sub run_by_time {
    my $self = shift;
    my $opts = $self->opts;
    my $now  = $self->now;
    my $db   = $self->db;
    my ( $dark, $reset, $yellow ) = $self->colours(qw/yellow reset yellow/);

    state $have_thinsql = DBIx::ThinSQL->import(qw/concat coalesce qv case sq/);

    my $sth = $db->xprepare(
        with => 'b',
        as   => sq(
            select => [ 'b.change_id AS start', 'b.change_id2 AS stop' ],
            from   => 'bifkv b',
            where => { 'b.key' => 'last_sync' },
        ),
        select => [
            q{strftime('%H:%M:%S',c.mtime,'unixepoch','localtime')},
            '"c" || c.id',
            'COALESCE(c.author_shortname,i.shortname,e.name) AS author',
            concat(
                'c.action',
                case (
                    when => 'b.start',
                    then => qv( $yellow . ' [+]' . $reset ),
                    else => qv(''),
                )
            ),
            q{strftime('%Y-%m-%d',c.mtime,'unixepoch','localtime') AS mdate},
            qq{$now - strftime('%s', c.mtime, 'unixepoch')},
        ],
        from       => 'changes c',
        left_join  => 'change_deltas cd',
        on         => 'cd.change_id = c.id',
        inner_join => 'entities e',
        on         => 'e.id = c.identity_id',
        inner_join => 'identities i',
        on         => 'i.id = c.identity_id',
        left_join  => 'b',
        on         => 'c.id BETWEEN b.start+1 AND b.stop',
        left_join  => 'tasks t',
        on         => 't.id = cd.action_topic_id_1',
        left_join  => 'task_status ts',
        on         => 'ts.id = t.task_status_id',
        order_by   => [ 'mdate DESC', 'c.mtime ASC', 'c.id DESC' ],
    );

    $sth->execute;

    $self->start_pager;

    my @days = (
        qw/Sunday Monday Tuesday Wednesday Thursday Friday
          Saturday/
    );

    my $localtime = localtime();
    my $today     = $localtime->strftime('%F');
    my $yesterday =
      ( $localtime - ONE_HOUR * ( $localtime->hour + 12 ) )->strftime('%F');

    my $mdate = '';
    my $table = Text::FormatTable->new(' l  l  l  l ');

    while ( my $n = $sth->arrayref ) {
        if ( $n->[4] ne $mdate ) {
            $table->rule(' ') if $mdate;
            $mdate = $n->[4];
            if ( $mdate eq $today ) {
                $table->head(
                    $dark . $n->[4],
                    'Who',
                    'Action [today]',
                    'CID' . $reset,
                );
            }
            elsif ( $mdate eq $yesterday ) {
                $table->head(
                    $dark . $n->[4],
                    'Who',
                    'Action [yesterday]',
                    'CID' . $reset
                );
            }
            else {
                $table->head(
                    $dark . $n->[4],
                    'Who',
                    'Action [' . ago( $n->[5], 1 ) . ']',
                    'CID' . $reset,
                );
            }
        }

        $table->row( $n->[0], $n->[2], $n->[3], $n->[1] );
    }

    print $table->render . "\n";

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

sub run_by_id {
    my $self = shift;
    my $opts = $self->opts;
    my $now  = $self->now;
    my $db   = $self->db;
    my ( $dark, $reset, $yellow ) = $self->colours(qw/yellow reset yellow/);

    state $have_thinsql = DBIx::ThinSQL->import(qw/concat coalesce qv case sq/);

    my $sth = $db->xprepare(
        with => 'b',
        as   => sq(
            select => [ 'b.change_id AS start', 'b.change_id2 AS stop' ],
            from   => 'bifkv b',
            where => { 'b.key' => 'last_sync' },
        ),
        select => [
            '"c" || c.id',
            q{strftime('%Y-%m-%d-%H:%M:%S',c.mtime,'unixepoch','localtime')},
            'COALESCE(c.author,i.shortname,e.name) AS author',
            concat(
                'c.action',
                case (
                    when => 'b.start',
                    then => qv( $yellow . ' [+]' . $reset ),
                    else => qv(''),
                )
            ),
        ],
        from       => 'changes c',
        left_join  => 'change_deltas cd',
        on         => 'cd.change_id = c.id',
        inner_join => 'entities e',
        on         => 'e.id = c.identity_id',
        inner_join => 'identities i',
        on         => 'i.id = c.identity_id',
        left_join  => 'b',
        on         => 'c.id BETWEEN b.start+1 AND b.stop',
        left_join  => 'tasks t',
        on         => 't.id = cd.action_topic_id_1',
        left_join  => 'task_status ts',
        on         => 'ts.id = t.task_status_id',
        left_join  => 'projects p',
        on         => 'p.id = ts.project_id OR p.id = cd.action_topic_id_1',
        left_join  => 'hubs h',
        on         => 'h.id = p.hub_id',
        order_by   => ['c.id DESC'],
    );

    $sth->execute;

    my $data = $sth->arrayrefs || return $self->ok('LogUid');
    $self->start_pager( scalar @$data );

    print $self->render_table( ' l  l  l  l ',
        [ 'CID', 'Date', 'Who', 'Action' ], $data );

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

sub reformat {
    my $self  = shift;
    my $text  = shift;
    my $depth = shift || 0;

    #    $depth-- if $depth;

    my $left   = 2 + 2 * $depth;
    my $indent = '    ' x $depth;

    my @result;

    foreach my $para ( split /\n\n/, $text ) {
        if ( $para =~ m/^[^\s]/ ) {
            push( @result, autoformat( $para, { left => $left } ) );
        }
        else {
            $para =~ s/^/$indent/gm;
            push( @result, $para, "\n\n" );
        }
    }

    return @result;
}

my $title;
my $path;

sub log_item {
    my $self = shift;
    my $row  = shift;
    my $type = shift;

    my $yellow = $self->colours('yellow');
    my $dark   = $self->colours('yellow');
    my $reset  = $self->colours('yellow');

    $title = $row->{title};
    $path  = $row->{path};

    ( my $id = $row->{change_id} ) =~ s/(.+)\./$yellow$1$dark\./;
    my @data = (
        $self->header(
            $dark . $yellow . 'change',
            $dark . $yellow . $row->{change_id},
            $row->{change_uuid}
        ),
    );

    push( @data, $self->header( 'Action', $row->{action} ) );
    push( @data, $self->header( 'From', $row->{author}, $row->{contact} ) );

    #    push( @data, $self->header( 'To', $row->{path} ) ) if $row->{path};
    push( @data, $self->header( 'When', $self->ctime_ago($row) ) );

    if ( $row->{title} and $row->{status} ) {
        push( @data, $self->header( 'Subject', $row->{title} ) );
    }
    elsif ( $row->{title} ) {
        push( @data,
            $self->header( 'Subject', "[$row->{path}] $row->{title}" ) );
    }

    foreach my $field (@_) {
        next unless defined $field->[1];
        push( @data, $self->header(@$field) );
    }

    print $self->render_table( ' l  l', undef, \@data ) . "\n";
    print $self->reformat( $row->{message} ), "\n";

    return;
}

my $last_change_id;
my $last_row;

sub log_start {
    my $self = shift;
    $last_change_id = 'c0';
    $last_row       = undef;
}

sub log_next {
    my $self = shift;
    my $row  = shift;

    state @data;
    state $yellow = $self->colours('yellow');
    state $dark   = $self->colours('dark');

    # Assume finish
    if ( !$row ) {
        print $self->render_table( 'l  l', undef, \@data,
            1 + 2 * ( $last_row->{depth} ) )
          . "\n";

        print $self->reformat( $last_row->{message}, $last_row->{depth} ), "\n";
        return;
    }

    if ( $row->{change_id} ne $last_change_id ) {
        if ($last_row) {
            print $self->render_table( 'l  l', undef, \@data,
                1 + 2 * ( $last_row->{depth} ) )
              . "\n";

            print $self->reformat( $last_row->{message}, $last_row->{depth} ),
              "\n";
        }

        @data = (
            $self->header(
                $dark . $yellow . 'change',
                $dark . $yellow . $row->{change_id},
                $row->{change_uuid},
            ),
            $self->header( 'Action', $row->{action} ),
            $self->header( 'From', $row->{author}, $row->{contact} ),
            $self->header( 'When', $self->mtime_ago($row) ),
        );
        push( @data,
            $self->header( $dark . 'Delta', $dark . $row->{delta_value} ) )
          if $row->{delta};
    }
    else {
        push( @data,
            $self->header( $dark . 'Delta', $dark . $row->{delta_value} ) );
    }

    $last_change_id = $row->{change_id};
    $last_row       = $row;
}

sub log_comment {
    my $self = shift;
    my $row  = shift;
    my $raw  = shift;

    my @data;

    state $yellow = $self->colours('yellow');
    state $dark   = $self->colours('yellow');

    push(
        @data,
        $self->header(
            $dark . $yellow . 'change',
            $dark . $yellow . $row->{change_id},
            $row->{change_uuid},
        ),
    );
    push( @data, $self->header( 'Action', $row->{action} ) );
    push( @data, $self->header( 'From', $row->{author}, $row->{contact} ) );

    $path = $row->{path} if $row->{path};
    push( @data, $self->header( 'To', $path ) ) if $path;
    push( @data, $self->header( 'When', $self->mtime_ago($row) ) );

    if ( $row->{title} ) {
        $title = $row->{title} if defined $row->{title};
        push( @data, $self->header( 'Subject', "$title" ) ) if $title;
    }
    elsif ( $row->{status} ) {
        push( @data, $self->header( 'Subject', "[$row->{status}] $title" ) )
          if $title;
    }
    elsif ( !$raw ) {
        push( @data, $self->header( 'Subject', "↪ $title" ) ) if $title;
    }

    foreach my $field (@_) {
        next unless defined $field->[1];
        push( @data, $self->header(@$field) );
    }

    print $self->render_table( 'l  l', undef, \@data,
        1 + 2 * ( $row->{depth} ) )
      . "\n";

    if ( $row->{push_to} ) {
        print "[Pushed to " . $row->{push_to} . "]\n\n\n";
    }
    else {
        print $self->reformat( $row->{message}, $row->{depth} ), "\n";
    }
}

1;
__END__

=head1 NAME

=for bif-doc #history

bif-log - review the repository or topic history

=head1 VERSION

0.1.5_3 (2015-06-30)

=head1 SYNOPSIS

    bif log [ITEM] [OPTIONS...]

=head1 DESCRIPTION

The C<bif log> command displays repository history. Without any
arguments it is equivalent to C<bif log repo --order time>, which
displays the history of changes in the current repository in reverse
chronological order.

=head1 ARGUMENTS & OPTIONS

=over

=item ITEM

A topic kind. As a shortcut, if ITEM is a topic ID or a project PATH
then the ITEM kind will be looked up and the appropriate sub-command
run.

=item --action, -a

Sort actions in the order in which they were added to the repository
instead of by time/thread.

=back

=head1 DEVELOPER METHODS

=over

=item log_comment($row, $raw)

Prints the details contained in the hash reference C<$row> as a
comment. If C<$raw> is true then the Subject line will not be copied
from the previous comment.

=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.

