package App::bif::log;
use strict;
use warnings;
use App::bif::Context;
use Text::Autoformat qw/autoformat/;
use locale;

our $VERSION = '0.1.0_25';

our $NOW;
our $bold;
our $yellow;
our $dark;
our $reset;
our $white;

sub init {
    $NOW = time;

    require POSIX;
    require Term::ANSIColor;
    require Time::Piece;
    require Time::Duration;

    $bold   = Term::ANSIColor::color('bold');
    $yellow = Term::ANSIColor::color('yellow');
    $dark   = Term::ANSIColor::color('dark');
    $reset  = Term::ANSIColor::color('reset');
    $white  = Term::ANSIColor::color('white');
}

sub run {
    my $ctx = App::bif::Context->new(shift);
    my $db  = $ctx->db();

    init();

    if ( $ctx->{id} ) {
        my $info = $ctx->get_topic( $ctx->{id} );

        my $func = __PACKAGE__->can( '_log_' . $info->{kind} )
          || return $ctx->err( 'LogUnimplemented',
            'cannnot log type: ' . $info->{kind} );

        return $func->( $ctx, $info );
    }

    return _log_hub( $ctx, $ctx->get_topic( $db->get_local_hub_id ) );

}

sub _header {
    return [
        ( $_[0] ? $_[0] . ':' : '' ) . $reset,
        $_[1] . ( defined $_[2] ? $dark . ' <' . $_[2] . '>' : '' ) . $reset
    ];
}

sub _new_ago {
    use locale;

    my $time    = shift;
    my $offset  = shift;
    my $hours   = POSIX::floor( $offset / 60 / 60 );
    my $minutes = ( abs($offset) - ( abs($hours) * 60 * 60 ) ) / 60;
    my $dt      = Time::Piece->strptime( $time + $offset, '%s' );

    my $local =
      sprintf( '%s %+.2d%.2d', $dt->strftime('%a %F %R'), $hours, $minutes );

    return ( Time::Duration::ago( $NOW - $time, 1 ), $local );
}

sub _reformat {
    my $text = shift;
    my $depth = shift || 0;

    $depth-- if $depth;

    my $left   = 1 + 4 * $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 $ctx  = shift;
    my $row  = shift;
    my $type = shift;

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

    ( my $id = $row->{update_id} ) =~ s/(.+)\./$yellow$1$dark\./;
    my @data = (
        _header( $yellow . $type, $id,            $row->{update_uuid} ),
        _header( 'From',          $row->{author}, $row->{email} ),
        _header( 'When', _new_ago( $row->{mtime}, $row->{mtimetz} ) ),
    );

    if ( $row->{status} ) {
        push(
            @data,
            _header(
                'Subject', "[$row->{path}][$row->{status}] $row->{title}"
            )
        );
    }
    else {
        push( @data, _header( 'Subject', "[$row->{path}] $row->{title}" ) );
    }

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

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

    return;
}

sub _log_comment {
    my $ctx = shift;
    my $row = shift;
    my @data;

    push(
        @data,
        _header(
            $dark . $yellow . ( $row->{depth} > 1 ? 'reply' : 'update' ),
            $dark . $yellow . $row->{update_id},
            $row->{update_uuid}
        ),
        _header( 'From', $row->{author},          $row->{email} ),
        _header( 'When', _new_ago( $row->{mtime}, $row->{mtimetz} ) ),
    );

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

    if ( $row->{title} ) {
        $title = $row->{title} if $row->{title};
        push( @data, _header( 'Subject', "[$path] $title" ) );
    }
    elsif ( $row->{status} ) {
        push( @data,
            _header( 'Subject', "[$path][$row->{status}] Re: $title" ) );
    }
    else {
        push( @data, _header( 'Subject', "[$path] Re: $title" ) );
    }

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

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

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

sub _log_hub {
    my $ctx  = shift;
    my $db   = $ctx->db;
    my $info = shift;

    my $sth = $db->xprepare(
        select => [
            q{strftime('%w',u.mtime,'unixepoch','localtime') AS weekday},
            q{strftime('%Y-%m-%d',u.mtime,'unixepoch','localtime') AS mdate},
            q{strftime('%H:%M:%S',u.mtime,'unixepoch','localtime') AS mtime},
            'u.message',
        ],
        from       => 'hub_deltas hd',
        inner_join => 'updates u',
        on         => 'u.id = hd.update_id',
        where      => { 'hd.hub_id' => $info->{id} },
        group_by   => [qw/weekday mdate mtime/],
        order_by   => 'u.id DESC',
    );

    $sth->execute;

    $ctx->start_pager;

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

    my $first   = $sth->array;
    my $weekday = $first->[0];

    print " $dark$first->[1] ($days[$weekday]) $reset \n";
    print '-' x 80, "\n";
    print " $first->[2]  $first->[3]\n";

    while ( my $n = $sth->array ) {
        if ( $n->[0] != $weekday ) {
            print "\n $dark$n->[1] ($days[ $n->[0] ])$reset\n";
            print '-' x 80, "\n";
        }

        print " $n->[2]  $n->[3]\n";
        $weekday = $n->[0];
    }

    $ctx->end_pager;
    return $ctx->ok('LogRepo');
}

sub _log_task {
    my $ctx  = shift;
    my $db   = $ctx->db;
    my $info = shift;

    my $sth = $db->xprepare(
        select => [
            'task_deltas.task_id AS id',
            "task_deltas.task_id ||'.' || task_deltas.update_id AS update_id",
            'SUBSTR(updates.uuid,1,8) AS update_uuid',
            'task_deltas.title',
            'updates.mtime',
            'updates.mtimetz',
            'updates.author',
            'updates.email',
            'task_status.status',
            'task_status.status',
            'projects.path',
            'projects.title AS project_title',
            'updates_tree.depth',
            'updates.message',
        ],
        from       => 'task_deltas',
        inner_join => 'updates',
        on         => 'updates.id = updates_tree.child',
        left_join  => 'task_status',
        on         => 'task_status.id = task_deltas.status_id',
        left_join  => 'projects',
        on         => 'projects.id = task_status.project_id',
        inner_join => 'updates_tree',
        on         => {
            'updates_tree.parent' => $info->{first_update_id},
            'updates_tree.child'  => \'task_deltas.update_id'
        },
        where    => { 'task_deltas.task_id' => $info->{id} },
        order_by => 'updates.path ASC',
    );

    $sth->execute;

    $ctx->start_pager;

    _log_item( $ctx, scalar $sth->hash, 'task' );
    _log_comment( $ctx, $_ ) for $sth->hashes;

    $ctx->end_pager;
    return $ctx->ok('LogTask');
}

sub _log_issue {
    my $ctx  = shift;
    my $db   = $ctx->db;
    my $info = shift;

    DBIx::ThinSQL->import(qw/concat case qv/);
    my $sth = $db->xprepare(
        select => [
            'project_issues.issue_id AS "id"',
            'updates.uuid',
            concat( 'project_issues.id', qv('.'), 'updates.id' )
              ->as('update_id'),
            'SUBSTR(updates.uuid,1,8) AS update_uuid',
            'updates.mtime',
            'updates.mtimetz',
            'updates.author',
            'updates.email',
            'updates.message',
            'updates.ucount',
            'issue_status.status',
            'issue_status.status',
            'issue_deltas.new',
            'issue_deltas.title',
            'projects.path',
            'updates_tree.depth',
        ],
        from       => 'issue_deltas',
        inner_join => 'updates',
        on         => 'updates.id = issue_deltas.update_id',
        inner_join => 'projects',
        on         => 'projects.id = issue_deltas.project_id',
        inner_join => 'project_issues',
        on         => {
            'project_issues.project_id' => \'issue_deltas.project_id',
            'project_issues.issue_id'   => \'issue_deltas.issue_id',
        },
        left_join  => 'issue_status',
        on         => 'issue_status.id = issue_deltas.status_id',
        inner_join => 'updates_tree',
        on         => {
            'updates_tree.child'  => \'updates.id',
            'updates_tree.parent' => $info->{first_update_id}
        },
        where    => { 'issue_deltas.issue_id' => $info->{id} },
        order_by => 'updates.path ASC',
    );

    $sth->execute;

    $ctx->start_pager;

    _log_item( $ctx, scalar $sth->hash, 'issue' );

    while ( my $row = $sth->hash ) {
        my @data;
        push(
            @data,
            _header(
                $dark . $yellow . ( $row->{depth} > 1 ? 'reply' : 'update' ),
                $dark . $yellow . $row->{update_id},
                $row->{update_uuid}
            ),
        );

        my @r = ($row);
        if ( $row->{ucount} > 2 ) {
            for my $i ( 1 .. ( $row->{ucount} - 2 ) ) {
                my $r = $sth->hash;
                push(
                    @data,
                    _header(
                        $dark
                          . $yellow
                          . ( $r->{depth} > 1 ? 'reply' : 'update' ),
                        $dark . $yellow . $r->{update_id},
                        $r->{update_uuid}
                    ),
                );
                push( @r, $r );
            }
        }

        push( @data,
            _header( 'From', $row->{author},          $row->{email} ),
            _header( 'When', _new_ago( $row->{mtime}, $row->{mtimetz} ) ),
        );

        my $i;
        foreach my $row (@r) {
            $path = $row->{path} if $row->{path};

            if ( $row->{title} ) {
                $title = $row->{title} if $row->{title};
                push( @data, _header( 'Subject', "[$path] $title" ) );
            }
            elsif ( $row->{status} ) {
                push( @data,
                    _header( 'Subject', "[$path][$row->{status}] Re: $title" )
                );
            }
            else {
                push( @data, _header( 'Subject', "[$path] Re: $title" ) );
            }

        }

        $row = pop @r;

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

        print _reformat( $row->{message}, $row->{depth} ), "\n";

    }

    $ctx->end_pager;
    return $ctx->ok('LogIssue');
}

sub _log_project {
    my $ctx  = shift;
    my $db   = $ctx->db;
    my $info = shift;

    my $sth = $db->xprepare(
        select => [
            'project_deltas.project_id AS id',
            "project_deltas.project_id ||'.' || updates.id AS update_id",
            'SUBSTR(updates.uuid,1,8) AS update_uuid',
            'project_deltas.title',
            'updates.mtime',
            'updates.mtimetz',
            'updates.author',
            'updates.email',
            'updates.message',
            'updates_tree.depth',
            'project_status.status',
            'project_status.status',
            'projects.path',
            'project_deltas.name',
        ],
        from       => 'project_deltas',
        inner_join => 'projects',
        on         => 'projects.id = project_deltas.project_id',
        inner_join => 'topics',
        on         => 'topics.id = projects.id',
        inner_join => 'updates_tree',
        on         => 'updates_tree.parent = topics.first_update_id AND
                       updates_tree.child = project_deltas.update_id',
        inner_join => 'updates',
        on         => 'updates.id = updates_tree.child',
        left_join  => 'project_status',
        on         => 'project_status.id = project_deltas.status_id',
        where      => {
            'project_deltas.project_id' => $info->{id},

            #            'project_deltas.new'        => undef,
        },
        order_by => 'updates.path asc',
    );

    $sth->execute;

    $ctx->start_pager;

    my $first = $sth->hash;
    _log_item( $ctx, $first, 'project', [ 'Phase', $first->{status} ] );
    _log_comment( $ctx, $_ ) for $sth->hashes;

    $ctx->end_pager;
    return $ctx->ok('LogProject');
}

1;
__END__

=head1 NAME

bif-log - review the repository or topic history

=head1 VERSION

0.1.0_25 (2014-06-14)

=head1 SYNOPSIS

    bif log [ID] [OPTIONS...]

=head1 DESCRIPTION

Display the history of changes in the repository in reverse
chronological order.

=head1 ARGUMENTS

=over

=item ID

A topic ID or a project PATH. If this argument is used then only the
history from that topic will be displayed, in hierarchical
(conversation topic) order.

=back

=head1 OPTIONS

=over

=item --filter TYPE

Only show entries of a specific TYPE:

=over

=item * new

Entries related to topic creation.

=item * status

Entries related to topic status changes. Note that this option will
include the entries from the 'new' filter.

=back

Can be used multiple times to filter on multiple types.

=item --format STYLE

[Not Implemented] Change the amount of detail displayed for each entry.
STYLE can be one of the following:

=over

=item * short

=item * normal

=item * full

=back

=item --group TIMESPAN

[Not Implemented] Group the entries together based on one of the
following TIMESPANs:

=over

=item * hour

=item * day

=item * week

=item * month

=item * logtime

Group the entries into blocks of activity that happened in the last
hour, today, yesterday, last week, etc.

=back

=back

=head1 SEE ALSO

L<bif>(1)

=head1 AUTHOR

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

=head1 COPYRIGHT AND LICENSE

Copyright 2013-2014 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.

