package Filters;

use strict;
use warnings;
use diagnostics;

use FindBin;
use lib $FindBin::Bin;
use Util;

my %CONFIGS = (
    no_aot_instr => {
        label   => "Exclude WAMR AOT array (instr)",
        regions => [
            [
                '_wamr_aot_start', '_wamr_aot_end',
                'p.injection_instr_absolute'
            ]
        ],
    },
    no_aot_data => {
        label   => "Exclude WAMR AOT array (data)",
        regions =>
          [ [ '_wamr_aot_start', '_wamr_aot_end', 'g.data_physical_address' ] ],
    },
    no_mmap_instr => {
        label   => "Exclude WAMR mmap (instr)",
        regions => [
            [
                '_wamr_mmap_start', '_wamr_mmap_end',
                'p.injection_instr_absolute'
            ]
        ],
    },
    no_mmap_data => {
        label   => "Exclude WAMR mmap (data)",
        regions => [
            [ '_wamr_mmap_start', '_wamr_mmap_end', 'g.data_physical_address' ]
        ],
    },
    no_runtime_pool_instr => {
        label   => "Exclude WAMR runtime pool (instr)",
        regions => [
            [
                '_wamr_runtime_pool_start', '_wamr_runtime_pool_end',
                'p.injection_instr_absolute'
            ]
        ],
    },
    no_runtime_pool_data => {
        label   => "Exclude WAMR runtime pool (data)",
        regions => [
            [
                '_wamr_runtime_pool_start', '_wamr_runtime_pool_end',
                'g.data_physical_address'
            ]
        ],
    },
    no_linear_pool_instr => {
        label   => "Exclude WAMR linear pool (instr)",
        regions => [
            [
                '_wamr_linear_pool_start', '_wamr_linear_pool_end',
                'p.injection_instr_absolute'
            ]
        ],
    },
    no_linear_pool_data => {
        label   => "Exclude WAMR linear pool (data)",
        regions => [
            [
                '_wamr_linear_pool_start', '_wamr_linear_pool_end',
                'g.data_physical_address'
            ]
        ],
    },
    no_global_heap_instr => {
        label   => "Exclude WAMR global heap (instr)",
        regions => [
            [
                '_wamr_global_heap_start', '_wamr_global_heap_end',
                'p.injection_instr_absolute'
            ]
        ],
    },
    no_global_heap_data => {
        label   => "Exclude WAMR global heap (data)",
        regions => [
            [
                '_wamr_global_heap_start', '_wamr_global_heap_end',
                'g.data_physical_address'
            ]
        ],
    },
);

# All entries are functions part of the native call chain
my @NATIVE_CALL_FUNCS = (
    'aot_invoke_native',               # imported-function dispatch (AOT)
    'aot_call_indirect',               # call_indirect dispatch (AOT)
    'wasm_interp_call_func_native',    # native dispatch (Interp)
    'wasm_runtime_invoke_native',      # common
    'invokeNative',                    # common

    # Imported functions:
    'host_fail_start_trace',
    'host_fail_stop_trace',
    'host_fail_marker_positive',
    'host_fail_marker_negative',
    'host_fail_marker_detected',
    'host_print',
);

$CONFIGS{no_native_call_instr} = {
    label   => "Exclude WAMR native call chain (instr)",
    regions => [
        map { { func => $_, col => 'p.injection_instr_absolute' } }
          @NATIVE_CALL_FUNCS
    ],
};

$CONFIGS{no_native_call_data} = {
    label   => "Exclude WAMR native call chain (data)",
    regions => [
        map { { func => $_, col => 'g.data_physical_address' } }
          @NATIVE_CALL_FUNCS
    ],
};

# Those will be executed automatically by runner.pl (+ no filter at all)
# my @DEFAULT_CONFIGS = ('no_aot_data');
my @DEFAULT_CONFIGS = ();

sub get_configs {
    return \%CONFIGS;
}

sub get_default_configs {
    return @DEFAULT_CONFIGS;
}

sub build_filter_clause {
    my ( $experiment_dir, $config_name ) = @_;

    return "" unless defined $config_name && length($config_name) > 0;

    my $regions = $CONFIGS{$config_name}{regions};
    return "" unless defined $regions;

    return ""
      unless defined $experiment_dir && -f "$experiment_dir/system.elf";

    my $elf = "$experiment_dir/system.elf";

    my @filters;
    for my $region (@$regions) {
        my ( $start, $end, $col );

        if ( ref($region) eq 'HASH' ) {

            # Single-function form: resolve [start, end) via symbol + size.
            $col = $region->{col};
            ( $start, $end ) = Util::elf_sym_range( $elf, $region->{func} );
        }
        else {
            # Linker-range form: [start_sym, end_sym, col].
            my ( $start_sym, $end_sym );
            ( $start_sym, $end_sym, $col ) = @$region;
            $start = Util::elf_sym_addr( $elf, $start_sym );
            $end   = Util::elf_sym_addr( $elf, $end_sym );
        }

        next unless defined $start && defined $end && $end > $start;
        push @filters, "$col NOT BETWEEN $start AND @{[$end - 1]}";
    }

    return "" unless @filters;
    return "\nAND " . join( "\nAND ", @filters );
}

1;
