#| Utilities for interacting with idris and associated tooling
unit module IUtils;

need IUtils::IDEMode;

use IUtils::Regexes;

use paths;

#| Structure representing the tests in a module
class ModuleTests {
    #| The name of this module
    has Str:D $.name is required;
    #| The source file of this module
    has IO::Path:D $.source is required;
    #| A list of the associated tests this module has
    has Str:D @.tests is required;
}

#| Structure representing all of the runables assocated with a project
class PackageRunables {
    #| The ipkg for this project
    has IO::Path:D $.ipkg is required;
    # TODO: Add benchmarks
    #| A map from the name of the module to a list of tests
    has ModuleTests:D %.tests is required;
}

#| Structure representing the root of what idris considers a package directory,
#| with the associated ipkg and source files. These can and will overlap within
#| the same directory.
class PackageInfo {
    has IO::Path:D $.ipkg is required;
    has IO::Path:D $.root is required;
    has IO::Path:D @.sources is required;

    method runnables {
        # Locate the tests
        my %tests = Hash.new;
        for @.sources -> $source {
            my $contents = $source.slurp;
            if $contents ~~ &module-name {
                my $module-name = $<name>.Str;
                my @tests;
                for $contents.match(&flagged-expression, :g) -> $match {
                   @tests.push($match<test-name>.Str);
                }
                if @tests.elems > 0 {
                    %tests{$module-name} =
                        ModuleTests.new(name => $module-name,
                                        source => $source,
                                        tests => @tests);
                }
            }
        }
        # Build and return the runnables
        PackageRunables.new(ipkg => self.ipkg, tests => %tests)
    }
}

#| Scan a particular ipkg for its associated sources
sub scan-ipkg(IO::Path:D $ipkg --> PackageInfo:D) {
    my $contents = $ipkg.slurp;
    my $src-dir =
        ($contents ~~
         / 'sourcedir' \h* '=' \h*
           '"' $<value>=[<-["]>*] '"' /)<value>
            // "src";

    my IO::Path:D @sources =
        paths($ipkg.parent.add($src-dir), :file(*.ends-with(".idr"))).map(*.IO);
    PackageInfo.new(ipkg => $ipkg, root => $ipkg.parent, sources => @sources)
}

# TODO: Add some parsing of pack.toml to locate test packages and associate them
# with their source ipkg

#| Scan $*CWD to locate ipkgs and their associated sources
sub scan-packages(--> Array[PackageInfo:D]) is export {
    my PackageInfo:D @ipkgs =
        paths(:file(*.ends-with(".ipkg"))).map(*.IO.&scan-ipkg);
    return @ipkgs;
}