#!/usr/bin/perl -w
#
# Copyright 2025 Henri Verbeet
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

use strict;
use warnings;
use JSON;
use open ':utf8';
binmode STDOUT, ':utf8';

sub method_name($)
{
    shift->{selector} =~ s/(:.*)//r;
}

sub method_parameters($$)
{
    my ($method, $method_type) = @_;

    my $parameters = join ", ", $method_type eq "class" ? () : "id self",
            map {"$_->{type} $_->{name}"} @{$method->{parameters}};

    length $parameters ? $parameters : "void";
}

sub send_function($)
{
    shift->{'return-float'} ? "vkd3d_objc_msgSend_fpret" : "objc_msgSend";
}

sub invocation_type($$)
{
    my ($method, $method_type) = @_;

    "$method->{'return-type'} (*)(" . join(", ", $method_type eq "class" ? "Class" : "id", "SEL",
            map {$_->{type}} @{$method->{parameters}}) . ")";
}

sub invocation_parameters($$$)
{
    my ($method, $interface_name, $method_type) = @_;

    join ", ", $method_type eq "class" ? "objc_getClass(\"$interface_name\")" : "self",
            "sel_registerName(\"$method->{selector}\")", map {$_->{name}} @{$method->{parameters}};
}

sub invocation($$$)
{
    my ($method, $interface_name, $method_type) = @_;

    ($method->{'return-type'} eq "void" ? "" : "return ")
            . "((${\invocation_type $method, $method_type})f)"
            . "(${\invocation_parameters $method, $interface_name, $method_type});";
}

sub print_method($$$)
{
    my ($method, $interface_name, $method_type) = @_;

    print "static inline $method->{'return-type'} "
            . "${interface_name}_${\method_name $method}(${\method_parameters $method, $method_type})\n";
    print "{\n";
    print "    void *f = ${\send_function $method};\n";
    print "    ${\invocation $method, $interface_name, $method_type}\n";
    print "}\n\n";
}

sub print_property($$)
{
    my ($property, $interface_name) = @_;

    my $method =
    {
        'return-type' => $property->{type},
        'return-float' => $property->{float},
        selector => $property->{getter} // $property->{name},
    };
    my $method_type = $property->{class} ? "class" : "instance";

    print_method $method, $interface_name, $method_type;
    if (!$property->{readonly})
    {
        $method->{'return-type'} = "void";
        $method->{'return-float'} = 0;
        $method->{selector} = "set${\ucfirst $property->{name}}:";
        $method->{parameters} = [$property];
        print_method $method, $interface_name, $method_type;
    }
}

sub print_interface(_)
{
    my ($interface) = @_;

    print_method $_, $interface->{name}, "class" foreach (@{$interface->{'class-methods'}});
    print_method $_, $interface->{name}, "instance" foreach (@{$interface->{'instance-methods'}});
    print_property $_, $interface->{name} foreach (@{$interface->{properties}});
}

sub print_header($)
{
    my ($grammar) = @_;

    my $guard = "__VKD3D_${\uc $grammar->{name}}_H__";

    print "/*\n";
    print " * This file is automatically generated.\n";
    print " * The original source is covered by the following license:\n";
    print " *\n";
    print map {" * $_" =~ s/ +$//r . "\n"} @{$grammar->{copyright}};
    print " */\n\n";

    print "#ifndef $guard\n";
    print "#define $guard\n\n";

    print "#include <objc/objc-runtime.h>\n\n";

    print "#ifdef __arm64__\n";
    print "# define vkd3d_objc_msgSend_fpret objc_msgSend\n";
    print "#else\n";
    print "# define vkd3d_objc_msgSend_fpret objc_msgSend_fpret\n";
    print "#endif /* __arm64__ */\n\n";

    print_interface foreach (@{$grammar->{interfaces}});

    print "#undef vkd3d_objc_msgSend_fpret\n\n";

    print "#endif /* $guard */\n";
}

die "No input file specified.\n" unless @ARGV;
print_header do
{
    local $/;
    open my $fh, '<', $ARGV[0] or die $!;
    decode_json <$fh>;
};
