#!perl

# -*- mode: perl -*-
# vi: set ft=perl :

use warnings;
use strict;
use utf8;
use File::Basename;
use File::Path;
use Getopt::Long;
use Term::ANSIColor 5.01;
use Term::ProgressBar 2.22;
use IO::Interactive 1.023 qw(is_interactive);
use FileHandle;

use Cwd;
use Pod::Usage qw(pod2usage);
use feature 'say';

use Jenkins::i18n (
    'remove_unused', 'find_files', 'load_properties', 'load_jelly',
    'find_langs',    'all_data',   'dump_keys',       'merge_data',
    'find_missing'
);
use Jenkins::i18n::Stats;
use Jenkins::i18n::Warnings;
use Jenkins::i18n::ProcOpts;
use Jenkins::i18n::License;
use Jenkins::i18n::Assertions qw(can_ignore has_hudson);

our $VERSION = '0.09';
my $current_dir = getcwd;
my (
    $lang, $dir,   $add, $remove, $counter, $target,
    $help, $debug, $man, $search, $version, $all_langs
) = ( undef, $current_dir, 0, 0, 0, $current_dir, 0, 0, 0, undef, 0, 0 );

GetOptions(
    'help'     => \$help,
    'version'  => \$version,
    'all'      => \$all_langs,
    'lang=s'   => \$lang,
    'dir=s'    => \$dir,
    'add'      => \$add,
    'remove'   => \$remove,
    'counter'  => \$counter,
    'target=s' => \$target,
    'debug'    => \$debug,
    'man'      => \$man,
    'search=s' => \$search
) or pod2usage( -exitval => 1 );

if ($version) {
    print "jtt version $VERSION\n";
    exit(0);
}

pod2usage( -exitval => 0, -verbose => 0 ) if ($help);
pod2usage( -exitval => 0, -verbose => 2 ) if ($man);

if ($all_langs) {
    my ( $files_ref, $all_langs );
    binmode( STDOUT, ':encoding(UTF-8)' );

    {
        local $| = 1;
        $ENV{ANSI_COLORS_DISABLED}++ unless is_interactive(*STDOUT);

        my $done = 'Done! ' . colored( '✓', 'green' );
        print 'Hang on, this might take a while.', "\n",
            'Searching for all available languages... ';
        $all_langs = find_langs($dir);
        say $done;
        print 'Searching for all translatable files... ';
        $files_ref = find_files( $dir, $all_langs );
        say $done, "\n", 'Now verifying each language translated status...';
    }

    my $warnings = Jenkins::i18n::Warnings->new(1);
    my %results;

    my $max      = $all_langs->size;
    my $progress = Term::ProgressBar->new(
        { name => 'Languages', count => $max, remove => 1 } );
    $progress->minor(0);
    my $next_update = 0;
    my $counter     = 0;

    foreach my $lang ( $all_langs->members ) {
        my $processor
            = Jenkins::i18n::ProcOpts->new( $dir, $target, 0, 0, 0, $debug,
            $lang, undef );
        my $stats = Jenkins::i18n::Stats->new;

        # process each file
        foreach my $file ( @{$files_ref} ) {
            $stats->inc('files');
            process_file( $file, $stats, $warnings, $processor );
            $warnings->reset;
        }

        $results{ $stats->perc_done } = $lang;
        $counter++;
        $next_update = $progress->update($counter)
            if $counter >= $next_update;
    }

    $progress->update($max) if $max >= $next_update;
    my @sorted = sort { $b <=> $a } keys(%results);
    my ( $lang, $percent );

    format LANGS_TOP =

         Translation Status

    Language code        %
    ------------------------
.

    format LANGS =
    @<<<<<             @<<<<
    $lang, $percent
.

    select(STDOUT);
    format_name STDOUT 'LANGS';
    format_top_name STDOUT 'LANGS_TOP';

    foreach my $val (@sorted) {
        $lang    = $results{$val};
        $percent = $val;
        write;
    }

    print "\n\n";
    exit(0);
}

# language parameter is mandatory and shouldn't be 'en'
unless ( $lang and ( $lang ne 'en' ) ) {
    pod2usage( -exitval => 1, -verbose => 1 );
}

print_summary( evaluate_translation($debug) );

# SUBS
# TODO: maintain here only the subs that handle interaction with the end user

sub evaluate_translation {
    my $debug = shift;
    print "Searching for files ...\n";

    my $all_known    = find_langs($dir);
    my $find_results = find_files( $dir, $all_known );

    if ($debug) {
        warn 'Total languages identified: ' . $all_known->size . "\n";
        warn 'Total translation files identified: '
            . $find_results->size . "\n";
        my $next = $find_results->warnings;

        while ( my $warning = $next->() ) {
            warn "$warning\n";
        }
    }

    my $stats     = Jenkins::i18n::Stats->new;
    my $warnings  = Jenkins::i18n::Warnings->new;
    my $processor = Jenkins::i18n::ProcOpts->new(
        {
            source_dir  => $dir,
            target_dir  => $target,
            use_counter => $counter,
            is_remove   => $remove,
            is_add      => $add,
            is_debug    => $debug,
            lang        => $lang,
            search      => $search
        }
    );

    my $license          = Jenkins::i18n::License->new;
    my $next_file        = $find_results->files;
    my $processed_source = Set::Tiny->new;

    # process each file
    while ( my $file = $next_file->() ) {
        $stats->inc_files;
        process_file( $file, $stats, $warnings, $processor, $license );
        $warnings->reset;
    }

    return $stats;
}

# This is the main method with is run for each file
sub process_file {
    my ( $file, $stats, $warnings, $processor, $license ) = @_;
    my ( $jelly_entries_ref, $lang_entries_ref, $english_entries_ref )
        = all_data( $file, $processor );

    # TODO: invoke merge_data() from all_data(), since only merged keys
    # will be in use?
    my $merged_ref = merge_data( $jelly_entries_ref, $english_entries_ref );
    find_missing( $merged_ref, $lang_entries_ref, $stats, $warnings );

    # TODO: create separated function
    foreach my $entry ( keys %{$lang_entries_ref} ) {
        unless (( exists( $merged_ref->{$entry} ) )
            and ( defined $merged_ref->{$entry} ) )
        {
            $stats->inc_unused;
            $warnings->add( 'unused', $entry );
        }
    }

    # TODO: create separated function
    foreach my $entry ( keys %{$lang_entries_ref} ) {
        if (   $lang_entries_ref->{$entry}
            && $english_entries_ref->{$entry}
            && $lang_entries_ref->{$entry} eq $english_entries_ref->{$entry} )
        {
            unless ( can_ignore( $lang_entries_ref->{$entry} ) ) {
                $stats->inc_same;
                $warnings->add( 'same', $entry );
            }
            else {
                $warnings->add( 'ignored', $entry );
            }
        }
    }

    # TODO: create separated function
    foreach my $entry ( keys %{$lang_entries_ref} ) {
        if ( $lang_entries_ref->{$entry}
            && has_hudson( $lang_entries_ref->{$entry} ) )
        {
            $warnings->add( 'non_jenkins',
                ( "$entry -> " . $lang_entries_ref->{$entry} ) );
            $stats->inc_no_jenkins;
        }
    }

    if ( $processor->is_to_search ) {
        my $term = $processor->search_term;

        foreach my $entry ( keys %{$lang_entries_ref} ) {
            if (   $lang_entries_ref->{$entry}
                && $lang_entries_ref->{$entry} =~ $term )
            {
                $warnings->add( 'search_found',
                    ( "$entry -> " . $lang_entries_ref->{$entry} ) );
            }
        }

    }

    # TODO: avoid calling define_files() twice, create an attribute
    # to store such state
    my $curr_lang_file = ( $processor->define_files($file) )[0];
    $warnings->summary($curr_lang_file);

    # write new keys in our file adding the English translation as a reference
    if ( $processor->is_add and $warnings->has_missing ) {
        $license->print($curr_lang_file)
            unless ( -f $curr_lang_file );
        open( my $out, '>>', $curr_lang_file )
            or die "Cannot write to $curr_lang_file: $!\n";

        foreach my $entry ( keys %{$merged_ref} ) {
            unless ( exists( $lang_entries_ref->{$entry} ) ) {

                unless ( $lang_entries_ref->{$entry} ) {
                    my @todo = ( $entry, '=---TranslateMe ' );
                    push( @todo, $processor->get_counter )
                        if ( $processor->use_counter );
                    push( @todo, '--- ' );

                 # TODO: use merged_ref, testing if the key has a value or not
                    if ( $english_entries_ref->{$entry} ) {
                        push( @todo, $english_entries_ref->{$entry} );
                    }
                    else {
                        push( @todo, $entry );
                    }

                    print $out ( join( '', @todo ) ), "\n";
                    $processor->inc if ( $processor->use_counter );
                }
            }
        }
        close($out);
    }

    # write new keys in our file adding the English translation as a reference
    if ( $processor->is_remove and $warnings->has_unused ) {
        my $removed
            = remove_unused( $curr_lang_file,
            Set::Tiny->new( keys( %{$merged_ref} ) ),
            $license->read );
        print "Removed $removed keys\n";
    }

}

sub print_summary {
    my $stats = shift;
    die "Must receive a Jenkins::i18n::Stats instance as parameter"
        unless ( ref($stats) eq 'Jenkins::i18n::Stats' );
    my $summary_ref = $stats->summary;
    warn "Not a single translation key was identified"
        if ( scalar( keys( %{$summary_ref} ) ) == 0 );

    my ( $item, $total, $percent );

    format SUMMARY_TOP =

         Translation Status

    Status         Total      %
    -----------------------------
.

    format SUMMARY =
    @<<<<<<<<<<    @<<<<    @<<<<
    $item, $total, $percent
.

    my @metrics = qw(done missing unused empty same no_jenkins);
    select(STDOUT);
    format_name STDOUT 'SUMMARY';
    format_top_name STDOUT 'SUMMARY_TOP';

    foreach my $metric (@metrics) {
        $item    = ucfirst($metric);
        $total   = $summary_ref->{$metric};
        $percent = $summary_ref->{"p$metric"};
        write;
    }

    say "\n", 'Total of files: ', $stats->files;
}

=pod

=head1 NAME

jtt - CLI to help internationalization for Jenkins

=head1 USAGE

  jtt --lang=xx

  options:
    --dir=directory    -> source folder for searching files, optional
    --help             -> print this help message and exits
    --man              -> provides this CLI manpage and exits
    --version          -> prints the CLI version and exits
    --all              -> prints all identified language codes and their respective
                          percentage of translation
    --lang=xx          -> language code to use
    --add              -> optional, generate new files and add new keys to existing
                          files if present
    --remove           -> optional, remove unused key/value pair for existing files
                          if present
    --counter          -> optional, to each translated key, unique value is added
                          to easily identify match missing translation with value
                          in source code if present
    --target=directory -> optional, target directory for writing files
    --search=regex     -> optional, search for a given regular expression in the
                          translation content
    --debug            -> optional, print debugging messages to STDOUT when they
                          are available

=head1 OPTIONS

C<--lang> is mandatory (except for C<--all>) and it has to be different to
English. If it is the only option provided, all files in the provided language
code will be analyzed and a report will be provided at the end of it, without
modifying a single translation file.

C<--dir> and C<--target> are optional and the default values for each one is
the current directory. This probably the best configuration if you're going
to use a Git repository to support the changes.

Some examples:

=over

=item *

Look for Spanish files with incomplete keys in the current directory:

  jtt --lang=es

=item *

Remove all orphaned keys from German files which are in the current directory:

  jtt --lang=de --remove .

=back

=head1 DESCRIPTION

=head2 Reporting overall status

The C<--all> option will run through all translation files to:

=over

=item 1.

Load all the original English messages.

=item 2.

Identify all available translation, extracting the related language code.

=item 3.

For each language, prints it's code and the respective translation percentage.

=back

Any other command line options are ignored if used with C<--all>.

=head2 Translation changing helpers

To generate missing translation keys and missing properties files and to
remove unused keys, the CLI uses this process:

=over

=item 1.

It recursively looks for files in a folder, and analyzes them to extract the
keys being used in the application. In order to do that, the option C<--lang>
is required and the value must be different of C<english>.

=item 2.

If C<--add> is in use, it generates the appropriate file for the desired
language and adds these keys to it, adding the English text as a reference.
If the properties file already exists the CLI updates it with the new keys.

=item 3.

When C<--remove> is in use and there are unused keys in our file, the CLI
removes them.

=item 4.

The C<--search> option can be used instead of the two describe above in order
to just search for a particular value in all files and print to STDOUT their
location. No changes are made to any file.

=back

The C<--counter> can be used with both options, as well C<--debug>.

The C<--dir> and C<--target> options can also be used, but you probably just
want to move inside to the path where the Jenkins Git repository is and run
from there instead. These options are kept for compatibility with the original
tool and might be removed in the future.

=head1 REFERENCES

Make sure to also check the proposed translation
L<workflow|https://github.com/glasswalk3r/jenkins-translation-tool/blob/main/Workflow.md>
documentation.

=head1 AUTHOR

=over

=item *

Original C<translation-tool.pl>: Manuel Carrasco.

=item *

Fork to C<jtt>: Alceu Rodrigues de Freitas Junior.

=back

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2022 of Alceu Rodrigues de Freitas Junior,
E<lt>arfreitas@cpan.orgE<gt>.

This file is part of Jenkins Translation Tool project.

Jenkins Translation Tool 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.

Jenkins Translation Tool 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
Jenkins Translation Tool. If not, see (http://www.gnu.org/licenses/).

The original C<translation-tool.pl> script was licensed through the MIT License,
copyright (c) 2004, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number
of other of contributors. Translations files generated by the Jenkins
Translation Tool CLI are distributed with the same MIT License.

=cut

