#!/usr/bin/env perl

use strict;
use warnings;
use File::Spec;
use Cwd 'abs_path';
use Path::Tiny qw(path);
use File::Find;
use JSON::MaybeXS;
use Config;
use Template;
use lib 'local/lib';
use Sideload::Build::FileLists qw(
    get_upb_c_files
    get_utf8_c_files
    get_generated_c_files
    get_xs_helper_c_files
);

my $perl_dir = abs_path(File::Spec->curdir());
my $project_root = abs_path(File::Spec->catdir($perl_dir, '..'));

my $is_google3 = $project_root =~ m{/google3(?:/|$)};
my $output_name = $is_google3 ? 'BUILD' : 'BUILD.bazel';
my $output_file = File::Spec->catfile($perl_dir, $output_name);

my $perl = $ENV{PERL} || $^X;

my @xs_helper_c_files = get_xs_helper_c_files();

my @common_c_files = (@xs_helper_c_files);
@common_c_files = map { s{^\Q$project_root/\E}{}; $_ } @common_c_files;

my $embed_cflags = `$perl -MExtUtils::Embed -e 'ccopts()'`;
chomp $embed_cflags;
my @perl_ccopts = split /\s+/, $embed_cflags;

my $embed_ldflags = `$perl -MExtUtils::Embed -e 'ldopts()'`;
chomp $embed_ldflags;
my @perl_linkopts = split /\s+/, $embed_ldflags;

my $config_file = File::Spec->catfile($perl_dir, 't', 'c', 'c_test_config.json');
my $json_text = path($config_file)->slurp_utf8;
my $c_test_config = decode_json($json_text);

my @c_tests;
my @c_test_targets;
foreach my $test_key ( sort keys %$c_test_config ) {
    next if $test_key =~ /eval_croak_test/;
    next if $test_key =~ /_coro$/ || $test_key =~ /coro/;
    my $target_name = 'c_test_' . $test_key;
    $target_name =~ s{/}{-}g;
    push @c_test_targets, ':' . $target_name;

    my $config = $c_test_config->{$test_key};
    my @srcs = ('t/c/' . $test_key . '.c');
    if (exists $config->{extra_src}) {
        my $extra_src = $config->{extra_src};
        if (ref $extra_src eq 'ARRAY') {
            push @srcs, @{$extra_src};
        } else {
            push @srcs, $extra_src;
        }
    }

    if (exists $config->{deps} && ref $config->{deps} eq 'ARRAY') {
        # Add local .h files from deps directly to srcs (exclude sibling/external ones starting with ../) so Bazel copies them to the sandbox!
        push @srcs, grep { $_ =~ /\.h$/ && $_ !~ /^\.\./ } @{$config->{deps}};
    }

    my $reflection_dep = $is_google3 ? '//third_party/upb/upb/reflection' : '//upb/reflection';
    my $pcre2_dep = $is_google3 ? '//third_party/pcre2:pcre2' : '';
    my @deps = (':libprotobufperl', ':upb_perl_test_lib', $reflection_dep);
    push @deps, $pcre2_dep if $pcre2_dep;
    if (exists $config->{deps} && ref $config->{deps} eq 'ARRAY') {
        push @deps, grep { $_ !~ /\.h$/ } map { my $d = $_; $d =~ s{^\.\./}{}; $d } @{$config->{deps}};
    }
    my %seen;
    @deps = grep { !$seen{$_}++ } @deps;

    push @c_tests, {
        target_name => $target_name,
        srcs => \@srcs,
        ccflags => $config->{ccflags} || '',
        extra_ccflags => $config->{extra_ccflags} || '',
        ldflags => $config->{ldflags} || '',
        deps => \@deps,
        core_path => File::Spec->catfile($Config{archlibexp}, 'CORE'),
        perl_ccopts => \@perl_ccopts,
        perl_linkopts => \@perl_linkopts,
    };
}

if ($is_google3) {
    my @starlark_tests;
    foreach my $test (@c_tests) {
        my @extra_deps = grep {
            $_ ne ':libprotobufperl' &&
            $_ ne ':upb_perl_test_lib' &&
            $_ ne '//third_party/pcre2:pcre2' &&
            $_ ne '//third_party/upb/upb/reflection'
        } @{$test->{deps}};
        push @starlark_tests, {
            target_name => $test->{target_name},
            srcs => $test->{srcs},
            deps => \@extra_deps,
        };
    }
    my $starlark_content = "# Generated by gen_build_bazel.pl. DO NOT EDIT.\n\n";
    $starlark_content .= "C_TESTS = " . to_starlark(\@starlark_tests) . "\n";
    my $bzl_file = File::Spec->catfile($perl_dir, 'c_test_config.bzl');
    my $is_new = !-f $bzl_file;
    path($bzl_file)->spew_utf8($starlark_content);
    if ($is_new) {
        system('g4', 'add', $bzl_file);
    }
}

my @bench_sources = glob 't/c/benchmark/*.c';
my @c_benchmarks;
foreach my $src (@bench_sources) {
    my $target_name = $src;
    $target_name =~ s{t/c/benchmark/(.*)\.c$}{c_bench_$1};
    push @c_benchmarks, {
        target_name => $target_name,
        src => $src,
        core_path => File::Spec->catfile($Config{archlibexp}, 'CORE'),
        perl_linkopts => \@perl_linkopts,
    };
}

my $upb_prefix = $is_google3 ? '//third_party/upb/upb/' : '//upb/';
my $protobuf_prefix = $is_google3 ? '//third_party/protobuf:' : '//:';
my $abseil_prefix = $is_google3 ? '//third_party/absl/' : '@abseil-cpp//absl/';
my $descriptor_upb_target = $is_google3 ? '//net/proto2/proto:descriptor_upb_c_proto' : '//src/google/protobuf:descriptor_upb_c_proto';
my $plugin_upb_target = $is_google3 ? '//net/proto2/compiler:plugin_upb_c_proto' : '//src/google/protobuf/compiler:plugin_upb_c_proto';

my $vars = {
    project_root           => $project_root,
    common_c_files         => \@common_c_files,
    Config                 => \%Config,
    USE_ASAN               => $ENV{USE_ASAN},
    USE_UBSAN              => $ENV{USE_UBSAN},
    USE_MSAN               => $ENV{USE_MSAN},
    USE_TSAN               => $ENV{USE_TSAN},
    c_tests                => \@c_tests,
    c_benchmarks           => \@c_benchmarks,
    c_test_targets         => \@c_test_targets,
    is_google3             => $is_google3,
    upb_prefix             => $upb_prefix,
    protobuf_prefix        => $protobuf_prefix,
    abseil_prefix          => $abseil_prefix,
    descriptor_upb_target  => $descriptor_upb_target,
    plugin_upb_target      => $plugin_upb_target,
};

my $tt = Template->new({
    INCLUDE_PATH => [ File::Spec->catdir($perl_dir, 'inc', 'templates') ],
    PRE_CHOMP    => 0,
    POST_CHOMP   => 0,
    TRIM         => 0,
    TTC_DIR      => undef,
});

my $content = '';
if (!$tt->process('BUILD.bazel.tt', $vars, \$content)) {
    die 'Template processing failed: ' . $tt->error();
}

path($output_file)->spew_utf8($content);
print "Successfully wrote to $output_file$/";

if ($is_google3) {
    print "Running g4 fix --format=build on $output_file...\n";
    system('g4', 'fix', '--format=build', $output_file);
}

exit 0;

sub to_starlark {
    my ($val) = @_;
    if (ref $val eq 'ARRAY') {
        return '[' . join(', ', map { to_starlark($_) } @$val) . ']';
    } elsif (ref $val eq 'HASH') {
        return '{' . join(', ', map { '"' . $_ . '": ' . to_starlark($val->{$_}) } sort keys %$val) . '}';
    } else {
        return '"' . $val . '"';
    }
}
