package Perl::Lint::Policy::Subroutines::ProhibitUnusedPrivateSubroutines;
use strict;
use warnings;
use Compiler::Lexer;
use Perl::Lint::Constants::Type;
use parent "Perl::Lint::Policy";

use constant {
    DESC => 'Private subroutine/method "%s" declared but not used',
    EXPL => 'Eliminate dead code',
};

sub evaluate {
    my ($class, $file, $tokens, $src, $args) = @_;

    my %allow;
    if (my $allow = $args->{prohibit_unused_private_subroutines}->{allow}) {
        $allow{$_} = 1 for split / /, $allow;
    }
    my $allow_regex = $args->{prohibit_unused_private_subroutines}->{private_name_regex};

    my $lexer;
    my @violations;
    my @private_functions;
    my %ignores;
    my %called;
    for (my $i = 0; my $token = $tokens->[$i]; $i++) {
        my $token_type = $token->{type};
        my $token_data = $token->{data};

        if ($token_type == FUNCTION_DECL) {
            $token = $tokens->[++$i];
            $token_data = $token->{data};
            my $function_token = $token;
            if (substr($token_data, 0, 1) eq '_' && !$allow{$token_data}) {
                if (!$allow_regex || $token_data !~ /$allow_regex/) {
                    my $declared_private_function = '';
                    for (; $token = $tokens->[$i]; $i++) {
                        $token_type = $token->{type};
                        if ($token_type == NAMESPACE || $token_type == FUNCTION) {
                            $declared_private_function = $token->{data};
                        }
                        elsif ($token_type == NAMESPACE_RESOLVER) {
                            last;
                        }
                        elsif ($token_type == LEFT_BRACE) {
                            push @private_functions, $function_token;

                            my $left_brace_num = 1;
                            for ($i++; $token = $tokens->[$i]; $i++) {
                                $token_type = $token->{type};
                                if ($token_type == LEFT_BRACE) {
                                    $left_brace_num++;
                                }
                                elsif ($token_type == RIGHT_BRACE) {
                                    last if --$left_brace_num <= 0;
                                }
                                elsif ($token_type == CALL || $token_type == KEY || $token_type == METHOD) {
                                    $token_data = $token->{data};
                                    if ($token_data eq $declared_private_function) {
                                        next;
                                    }
                                    $called{$token_data} = 1;
                                }
                            }
                            last;
                        }
                        elsif ($token_type == SEMI_COLON) {
                            last;
                        }
                    }
                }
            }
        }
        elsif ($token_type == CALL || $token_type == KEY || $token_type == METHOD) {
            $called{$token_data} = 1;
        }
        elsif ($token_type == USED_NAME && $token_data eq 'overload') {
            my $is_value = 1;
            for ($i++; $token = $tokens->[$i]; $i++) {
                $token_type = $token->{type};
                my $next_token = $tokens->[$i+1];
                my $next_token_type = $next_token->{type};
                if ($token_type == ARROW) {
                    if ($is_value) {
                        for ($i++; $token = $tokens->[$i]; $i++) {
                            $token_type = $token->{type};
                            if (
                                $token_type == KEY    ||
                                $token_type == STRING ||
                                $token_type == RAW_STRING
                            ) {
                                $ignores{$token->{data}} = 1;
                            }
                            elsif ($token_type == SEMI_COLON) {
                                last; # fail safe
                            }
                        }
                    }
                    $is_value = !$is_value;
                }
                elsif ($token_type == SEMI_COLON) {
                    last;
                }
            }
        }
        elsif ($token_type == REG_REPLACE || $token_type == REG_MATCH) {
            for ($i++; $token = $tokens->[$i]; $i++) {
                $token_type = $token->{type};
                if ($token_type == REG_REPLACE_TO || $token_type == REG_EXP) {
                    $lexer ||= Compiler::Lexer->new($file);
                    my $replace_to_tokens = $lexer->tokenize($token->{data});

                    for (my $i = 0; $token = $replace_to_tokens->[$i]; $i++) {
                        my $token_type = $token->{type};
                        if ($token_type == CALL || $token_type == KEY || $token_type == METHOD) {
                            $called{$token->{data}} = 1;
                        }
                    }
                }
                elsif ($token_type == SEMI_COLON) {
                    last; # fail safe
                }
            }
        }
    }

    for my $private_function (@private_functions) {
        my $private_function_name = $private_function->{data};
        if ($ignores{$private_function_name}) {
            next;
        }

        unless ($called{$private_function_name}) {
            push @violations, {
                filename => $file,
                line     => $private_function->{line},
                description => sprintf(DESC, $private_function_name),
                explanation => EXPL,
                policy => __PACKAGE__,
            };
        }
    }

    return \@violations;
}

1;

