#!/usr/bin/perl 

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell

use strict;
use Carp;
use utf8;
use English;
use Getopt::Long;
use IO::File;
use Pod::Usage;

use List::MoreUtils qw(uniq);
use Perl6::Slurp;

use lib qw(/usr/share/ikiwiki-scripts);
use Basicos::Config;
use MyIkiWiki::Tools;
use MyIkiWiki::Pages qw(:all);

my  $VERSION    =   '0.4';

my  @operations =   ();

###    Parsing the command line options...
my %config = _configure(
        name        =>  'ikitag',
        file        =>  '.myikiwiki',
        env         =>  'MYIKIWIKI_CONF',
        merge       =>  1,
        getopts     =>  {
            help    =>  1,
            man     =>  1,
        },
        variables   =>  {
            del     =>  [ 'del=s@', undef, undef, [] ],
            add     =>  [ 'add=s@', undef, undef, [] ],
            set     =>  [ 'set=s@', undef, undef, [] ],
            list    =>  [ 'list',   undef, undef, 0 ],
            create  =>  [ 'create-tags', undef, undef, 1 ],
            no_svn  =>  [ 'no-repository', undef, undef, 0 ],
            quiet   =>  [ 'quiet',  undef, undef, 0 ],
        },
        defaults    =>  {
            ikiwiki_setup   =>  q(./ikiwiki.setup),
        },
    );

### extracting the operations on tags ...    
foreach my $op ( qw(add del set) ) {
    _save_op( $op, @{$config{$op}} );
}    

### Reading the pages: @ARGV
foreach my $page_name (@ARGV) {
    ### read the file ...
    my  $text           = _read_page( $page_name );
    my  @tags_in_file   = _parse_tags_from_text( $text );

    ### display the tags before the operations ...
    _display_tags($page_name, @tags_in_file);

    ### process operations on tags ...
    foreach my $op (@operations) {
        my  $name   =   $op->[0];
        my  @tags   =   @{ $op->[1] };
        ### operation: $name
        ### tags: @tags
        @tags_in_file = _modify_tags(
                                file        =>  _page_to_file($page_name), 
                                operation   =>  $name,
                                content     =>  $text,
                                tags        =>  \@tags_in_file,
                                newtags     =>  \@tags,
                                );
        _display_op( $name, \@tags, \@tags_in_file ); 
    }

    ### display the tags after the operations ...
    _display_tags($page_name, @tags_in_file);

    ### creating ikiwiki tags ...
    _update_tags_for_page( $page_name, @tags_in_file ) if $config{create};

}

exit(0);

sub _report {
    my  ($msg, @args) = @_;

    if (not $config{quiet}) {
        print sprintf($msg,@args);
    }
}

sub _create_tags {
    my  @tags   =   @_;

    foreach my $tag (@tags) {
        if (not _exists_tag( $tag )) {
            ### create tag: $tag
            _build_tag( $tag );

            if (not $config{no_svn}) {
                ### adding ${file} to the software repository
                _add_to_repository(_tag_to_file($tag));
            }
        }
    }        
     
    return;
}
    
sub _display_op {
    my  $opname     =   shift;
    my  $optags_ref =   shift;
    my  $tags_ref   =   shift;
    
    if ($opname eq 'add') {
        _report("\tadding tags to page: %s\n", _list_tags($optags_ref));
    }
    elsif ($opname eq 'del') {
        _report("\tdeleting tags from page: %s\n", _list_tags($optags_ref));
    }
    elsif ($opname eq 'set') {
        _report("\tsetting tags in page: %s\n", _list_tags($optags_ref));
    }

    return;
}    
 
sub _list_tags {
    my  @tags   =   ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_;

    return join(",", @tags);
}    

sub _display_tags {
    my  $page           =   shift;
    my  @tags_in_file   =   @_;

    _report("tags in page %s: %s\n", $page, _list_tags(@tags_in_file));

    return;
}

sub _save_op {
    my  ($op, @tags)   = @_;

    push(@operations, [ $op, [ @tags ] ]) if @tags;

    return;
}

sub _modify_tags {
    my  %params     =   @_;

    ### found tags: @tags_in_file 
    my @tags_in_file = @{$params{tags}};

    if ($params{operation} eq 'add') {
        ### add tags ...
        @tags_in_file = uniq( @tags_in_file, @{ $params{newtags} } );
    }
    elsif ($params{operation} eq 'del') {
        ### delete tags ...
        @tags_in_file = _delete_tags( \@tags_in_file, $params{newtags} );
    }
    elsif ($params{operation} eq 'set') {
        ### setting tags ...
        @tags_in_file = @{ $params{newtags} };
    }

    ### rewrite the tag line...
    my $text = _rewrite_tag_line( $params{content}, @tags_in_file);
    
    ### update the file: $file
    ### new tags: @tags_in_file
    _write_page( $params{file}, $text );

    return @tags_in_file;
}

sub _delete_tags {
    my  $source_tags    =   shift;
    my  $deleted_tags   =   shift;
    my  %tags       = map { $_ => 1 } @{ $source_tags };

    foreach my $key ( @{ $deleted_tags} ) {
        if (exists($tags{$key})) {
            delete $tags{$key};
        }
    }

    return keys(%tags);
}    

sub _rewrite_tag_line {
    my  $text       =   shift;
    my  $tags       =   join(" ", @_);

    # is there a tag line ? 
    if ($text =~ m{^\[\[tag\s}xms) {
        # yes, search and replace it content 
        $text =~ s{^\[\[tag\s+([^\]]+)\]\]}{[[tag ${tags}]]}xms;
    }
    else {
        # no, insert the line before the text
        $text = sprintf("[[tag %s]]\n", $tags) . $text;
    }

    return $text;
}    

__END__
=head1 NAME

ikitag - update tags in ikiwiki source pages

=head1 USAGE

    ikitag [operation labels] files

=head1 OPTIONS

=over 

=item * [ add | del | set ] tag [ ... tag ]

These are the three operations on tag. Add one or more tags, delete one or
more tags and set one or more tags. 

=item * create_tags

This parameter enables the create tag mode. After set the tags on a file,
updating it tag line, the program check for the existence of the tags in the
wiki source tree, and create the markdown pages for each. It's enabled by
default.

=item * no-repository

If this parameter is present the program add the new tag files to the software
repository.

=item * help | man 

Show the short version (help) or long version (man) of the documentation.

=item * quiet

Be quiet.

=back 

=head2 Software repository

ikitag try to read the configuration file of ikiwiki, searching a file named
F<ikiwiki.setup> in the current directory (it should be the root wiki
directory) and load its contents. 

The path to the tag file is build through the next expresión (%config contains
the setup):

    $config{srcdir} . "/" . $config{tagbase} . "/" . $tag_name . ".mdwn"
    
If the tag don't exists the program create a empty file and use the value of the
C<rcs> field in the config to determine the appropiate method for add to the repository:

=over 

=item svn: C<svn add $tag_file>

=item git: <git add $tag_file>

=back 

=head1 DESCRIPTION

A full description of the application and its features.
May include numerous subsections (i.e., =head2, =head3, etc.).

=head1 CONFIGURATION AND ENVIRONMENT

A full explanation of any configuration system(s) used by the application,
including the names and locations of any configuration files, and the meaning
of any environment variables or properties that can be set. These descriptions
must also include details of any configuration language used. 

=head1 DEPENDENCIES

List of perl modules:

=over 

=item * L<MyIkiWiki::Tools>

=item * L<List::MoreUtils>

=item * L<Perl6::Slurp>

=back

=head1 BUGS AND LIMITATIONS

There are no known bugs in this module.

Please report problems to the author.

Patches are welcome.

=head1 AUTHOR

Víctor Moral <victor@taquiones.net>

=head1 LICENCE AND COPYRIGHT

Copyright (c) 2007 "Víctor Moral" <victor@taquiones.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 2 of the License, or
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

