#!/usr/bin/env perl

# Copyright (c) 2015-2021 Christian Jaeger, copying@christianjaeger.ch
# This is free software. See the file COPYING.md that came bundled
# with this file.

use strict;
use warnings;
use warnings FATAL => 'uninitialized';
use experimental "signatures";

# find modules from functional-perl working directory (not installed)
use Cwd 'abs_path';
our ($mydir, $myname);

BEGIN {
    my $location = (-l $0) ? abs_path($0) : $0;
    $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
    ($mydir, $myname) = ($1, $2);
}
use lib "$mydir/../lib";
use lib "$mydir/../meta";

use Getopt::Long;
use Perl::Critic;
use FunctionalPerl::ModuleList;
use Chj::Backtrace;
use FP::Array ":all";
use FP::Stream ":all";
use FP::PureArray;
use Chj::xperlfunc qw(xprintln);
use FP::Ops "the_method";
use Chj::ruse;
use FP::Repl;

my %ignore_file = map { $_ => 1 } qw(
    lib/Chj/HTTP/Daemon.pm
    lib/Chj/Class/Array.pm
);

# Would you prefer to ignore =~ /Using \$a or \$b outside sort/ ?
my %ignore_policy = map { $_ => 1 } qw(
    Perl::Critic::Policy::Freenode::DollarAB
    Perl::Critic::Policy::Subroutines::ProhibitAmpersandSigils
);

sub usage {
    print "usage: $myname

  Criticizes the functional-perl code base, while refraining from
  critiques which are not appropriate.

  Configuration: .perlcriticrc

  Options:
    --repl
";
    exit 1;
}

our $verbose = 0;
our $opt_repl;
GetOptions(
    "verbose" => \$verbose,
    "help"    => sub {usage},
    "repl"    => \$opt_repl,
) or exit 1;
usage if @ARGV;

{

    package PFLANZE::CriticResult;
    use FP::Struct ["path"] => ("FP::Struct::Show");

    sub prefix($self) {
        $self->path . "\t"
    }
    _END_;
}

{

    package PFLANZE::CriticErr;
    use FP::Struct ["message"] => "PFLANZE::CriticResult";

    sub has_critique($self) {
        1    # sort of? some errors
    }

    sub string($self) {
        $self->prefix . "Exception: " . $self->message
    }
    _END_;
}
PFLANZE::CriticErr::constructors->import;

{

    package PFLANZE::CriticOk;
    use FP::Struct ["all_critiques"] => "PFLANZE::CriticResult";

    sub critiques($self) {
        $self->{_cache_critiques} //= do {
            $self->all_critiques->filter(
                sub ($c) {
                    not $ignore_policy{ $c->{_policy} }
                }
            )
        }
    }

    sub has_critique($self) {
        @{ $self->critiques } > 0
    }

    sub string($self) {
        my $p = $self->prefix;
        $self->critiques->map (
            sub($c) {
                "$p$c"
            }
        )->strings_join("");
    }
    _END_;
}
PFLANZE::CriticOk::constructors->import;

sub critique($path) {
    my $critic = Perl::Critic->new();
    eval { CriticOk($path, purearray($critic->critique($path))) }
        // CriticErr($path, "$@")
}

my $files = array_to_stream(modulepathlist)->filter(
    sub($file) {
        not $ignore_file{$file}
    }
);

my $critiques = $files->map(\&critique);
my $relevant  = $critiques->filter(the_method "has_critique");
if ($opt_repl) {
    repl;
} else {
    $|++;
    $relevant->for_each(
        sub($c) {
            xprintln $c->string
        }
    );
}

