#!/usr/bin/env perl
use v5.10;
use strict;
use warnings;
use App::Rad;
use Capture::Tiny qw( capture );
use Git::Repository;
use Path::Class qw( file dir );

# ABSTRACT: Simple. Git-based. Notes.

sub setup {
    my ( $c ) = @_;
    $c->register_commands({
        add     => 'add a new note, and edit it',
        append  => 'append to a note',
        delete  => 'delete the note',
        edit    => 'edit a note',
        init    => 'Initiliazie notes (optionally from remote repo)',
        list    => 'lists id and subject of all notes',
        replace => 'replace the contents of the note ( from STDIN )',
        show    => 'show the contents of the note',
        sync    => 'Sync notes with remote (pull + push)',
    });
}

sub pre_process {
    my ( $c ) = @_;
    my $cmd = $c->cmd;

    # If we aren't initializing, check to make sure our notes directory exists
    if( $cmd ne 'init' ) {
        if( -d notes_repo() ) {
            $c->stash->{git} = Git::Repository->new( git_dir => notes_repo() );
        } else {
            # We are not initialized
            die "Notes Directory has not been initialized!\n" .
                "Run init [remote git repo] to initialize.\n";
        }
    }

    return if not $c->is_command($cmd) or $cmd ~~ [qw( help init sync )];
    sync( $c, pull_only => 1 ) if auto_sync();
}

sub post_process {
    my ( $c ) = @_;
    my $cmd = $c->cmd;
    if($c->is_command($cmd) and not $cmd ~~ [qw( help init list show sync )]) {
        sync( $c, push_only => 1 ) if auto_sync();
    }
    say $c->output if $c->output;
}

sub invalid {
    my ( $c, $cmd ) = @_;
    $cmd //= $c->cmd;

    unless ($c->is_command($cmd)) {
        my @cmds = grep { /^$cmd/ } $c->commands;
        $cmd = $cmds[0] if (@cmds == 1);
    }

    $c->execute( $cmd ) and return if $c->is_command( $cmd );
    $c->execute( 'help' ) and return;
}

App::Rad->run;

# helpers ---------------------------------------------------------------------

sub editor { $ENV{EDITOR} || 'vim' }
sub notes_dir { dir( $ENV{APP_NOTES_DIR} ) || dir( $ENV{HOME}, '.notes' ) }
sub notes_repo { file( notes_dir, '.git' ) }
sub auto_sync {  $ENV{APP_NOTES_AUTOSYNC} // 1 }

sub find_notes {
    my ( $c, %args ) = @_;
    my $sort = $args{sort} // 1;

    opendir my $dh, notes_dir();
    my @notes = map {
        file( notes_dir(), $_ )
    } grep { !/^\./ } readdir( $dh );

    # Sort only if requested (default)
    @notes = sort { -M $a <=> -M $b } @notes if( $sort );
    # Filter if needed
    @notes = grep { /$args{search}/i } @notes if $args{search};

    # Return most recent match
    return \@notes;
}

sub read_stdin { local $/; <STDIN> }

sub check_stdin { ( -t STDIN ) ? 0 : read_stdin() }

sub get_title { join ' ', @ARGV; }

sub get_filename {
    return undef unless $_[0];
    ( my $r = $_[0] ) =~ s/ /-/g;
    return  $r;
}

sub edit_file {
    my ( $c, $file, %args ) = @_;

    $args{check_stdin} //= 1; # Default to check stdin
    my $verb = ( -e $file->stringify ) ? "Updated " : "Created ";

    my $stdin = $args{check_stdin} ? check_stdin() : 0;
    if( $stdin ) {
        open FILE, ( $args{append} ? '>>' : '>' ), $file;
        print FILE $stdin;
        close FILE;
    } else {
        my $cmd = [ editor(), $file ];
        # Let them edit the file
        system join( ' ', @$cmd );
    }

    # Commit their changes if they wrote the file
    if ( -e $file ) {
        my $output = capture {
            $c->stash->{git}->run( add => $file->stringify );
            $c->stash->{git}->run( commit => '-m', $verb . $file->basename );
        };
    }
}

# commands --------------------------------------------------------------------

sub add {
    my ( $c ) = @_;
    my $title = get_title();
    die "Need a title!" unless $title;

    my $file = file( notes_dir(), get_filename( $title ) );
    die "File already exists!" if -e $file;

    edit_file( $c, $file );
}

sub append {
    my ( $c ) = @_;
    my $title = get_title();
    my $notes = find_notes( $c, search => get_filename( $title ) );

    die "No matching notes found" unless @$notes > 0;

    my $file = $notes->[0];
    edit_file( $c, $file, append => 1 );
}

sub delete {
    my ( $c ) = @_;
    my $title = get_title();
    my $notes = find_notes( $c, search => get_filename( $title ) );

    die "No matching note found!" unless @$notes > 0;
    my $to_rm = $notes->[0];

    print "Delete \"" . $to_rm->basename . "\" ? ";
    my $res = <STDIN>;

    if( $res ~~ /^y(es)?$/i ) {
        my $output = capture {
            my $msg = "Removed \"". $to_rm->basename . "\".";
            $c->stash->{git}->run( rm => $to_rm->stringify );
            $c->stash->{git}->run( commit => -m => $msg );
        };
    } else {
        return "Not Removed.";
    }
}

sub edit {
    my ( $c ) = @_;
    my $title = get_title();
    my $notes = find_notes( $c, search => get_filename( $title ) );

    die "No matching notes found!" unless @$notes > 0;
    my $to_edit = $notes->[0];

    edit_file( $c, $to_edit, check_stdin => 0 );
}

sub init {
    my ( $c ) = @_;

    die "Notes dir already exists!" if -d notes_dir();

    my $dir = notes_dir();
    my $repo = $ARGV[0];
    my $output = capture {
        if( $repo ) {
            say "Initializing notes from $repo...";
            Git::Repository->run( clone => $repo, $dir->stringify );
        } else {
            say "Initializing notes ($dir)...";
            print Git::Repository->run( init => $dir->stringify );
        }
    }
}

sub list {
    my ( $c ) = @_;
    my $search = @ARGV > 0 ? join ' ', @ARGV : undef;
    my $notes = find_notes( $c, search => get_filename( $search ) );
    say $_->basename for @$notes;
    return;
}

sub replace {
    my ( $c ) = @_;
    my $title = get_title();
    my $notes = find_notes( $c, search => get_filename( $title ) );

    die "No matching notes found" unless @$notes > 0;

    my $file = $notes->[0];
    edit_file( $c, $file );
}

sub show {
    my ( $c ) = @_;
    my $title = get_title();
    my $notes = find_notes( $c, search => get_filename( $title ) );

    die "No matching notes found" unless @$notes > 0;

    system "cat $notes->[0]";
}

sub sync {
    my ( $c, %args ) = @_;
    my $output = capture {
        $c->stash->{git}->run( 'pull' ) unless $args{push_only};
        $c->stash->{git}->run( 'push' ) unless $args{pull_only};
    };
    return;
}

# PODNAME: notes


__END__
=pod

=head1 NAME

notes - Simple. Git-based. Notes.

=head1 VERSION

version 0.005

=head1 SYNOPSIS

    Usage: notes command [arguments]

    Available Commands:
        add     add a new note, and edit it
        append  append to a note ( from STDIN )
        delete  delete the note
        edit    edit a note
        help    show syntax and available commands
        init    Initiliazie notes (optionally from remote repo)
        list    lists id and subject of all notes
        replace replace the contents of the note ( from STDIN )
        show    show the contents of the note
        sync    Sync notes with remote (pull + push)

    # To get started
    $ notes init
    # Or, optionally, get started with an existing git repo
    $ notes init git@gist.github.com:12343.git

    # Create a note and edit it (with $EDITOR, or vim by default)
    # Note name will be Hello-World
    $ notes add Hello World
    # Add another (markdown) note via STDIN
    $ echo "# Title" | notes add TEST.md

    # List notes
    $ notes list
    TEST.md
    Hello-World

    # List notes w/filter (case-insensitive)
    $ notes list te
    TEST.md

    # Edit a note (finds the most recently edited match, case insensitive)
    # This will open up the Hello-World note created above
    $ notes edit hel

    # Will replace the contents of Hello-World with "Hello, World"
    $ echo "Hello, World" | notes replace hel

    # Will append "END" to Hello-World
    $ echo "END" | notes append he

    # Sync notes with remote (if your git repo has a remote)
    $ notes sync

=head1 DESCRIPTION

L<App::Notes> is a very simple command line tool that lets you creat, edit,
search, and manage simple text-based notes inside of a git repository.

This is very useful for keeping notes in a repository
(especially a C<gist> on L<GitHub|http://github.com>) that can be sync'ed
across machines, and also for keeping a history of all your notes.

Every time a note is created, modified or removed, L<App::Notes> will commit
the change to the git repo.  By default, It will C<pull> before each command
executes, and C<push> when its done (except on C<list> and C<show> commands).
To turn this behavior off, set C<APP_NOTES_AUTO_SYNC=0>, and then you can
manually pull/push with C<notes sync>.

=head1 AUTHORS

=over 4

=item *

William Wolf <throughnothing@gmail.com>

=item *

Naveed Massjouni <naveedm9@gmail.com>

=back

=head1 COPYRIGHT AND LICENSE


William Wolf has dedicated the work to the Commons by waiving all of his
or her rights to the work worldwide under copyright law and all related or
neighboring legal rights he or she had in the work, to the extent allowable by
law.

Works under CC0 do not require attribution. When citing the work, you should
not imply endorsement by the author.

=cut

