#!/usr/bin/perl
#     (c)2011 Broadcom Corporation
#
#  This program is the proprietary software of Broadcom Corporation and/or its licensors,
#  and may only be used, duplicated, modified or distributed pursuant to the terms and
#  conditions of a separate, written license agreement executed between you and Broadcom
#  (an "Authorized License").  Except as set forth in an Authorized License, Broadcom grants
#  no license (express or implied), right to use, or waiver of any kind with respect to the
#  Software, and Broadcom expressly reserves all rights in and to the Software and all
#  intellectual property rights therein.  IF YOU HAVE NO AUTHORIZED LICENSE, THEN YOU
#  HAVE NO RIGHT TO USE THIS SOFTWARE IN ANY WAY, AND SHOULD IMMEDIATELY
#  NOTIFY BROADCOM AND DISCONTINUE ALL USE OF THE SOFTWARE.
#
#  Except as expressly set forth in the Authorized License,
#
#  1.     This program, including its structure, sequence and organization, constitutes the valuable trade
#  secrets of Broadcom, and you shall use all reasonable efforts to protect the confidentiality thereof,
#  and to use this information only in connection with your use of Broadcom integrated circuit products.
#
#  2.     TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS"
#  AND WITH ALL FAULTS AND BROADCOM MAKES NO PROMISES, REPRESENTATIONS OR
#  WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO
#  THE SOFTWARE.  BROADCOM SPECIFICALLY DISCLAIMS ANY AND ALL IMPLIED WARRANTIES
#  OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE,
#  LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION
#  OR CORRESPONDENCE TO DESCRIPTION. YOU ASSUME THE ENTIRE RISK ARISING OUT OF
#  USE OR PERFORMANCE OF THE SOFTWARE.
#
#  3.     TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL BROADCOM OR ITS
#  LICENSORS BE LIABLE FOR (i) CONSEQUENTIAL, INCIDENTAL, SPECIAL, INDIRECT, OR
#  EXEMPLARY DAMAGES WHATSOEVER ARISING OUT OF OR IN ANY WAY RELATING TO YOUR
#  USE OF OR INABILITY TO USE THE SOFTWARE EVEN IF BROADCOM HAS BEEN ADVISED OF
#  THE POSSIBILITY OF SUCH DAMAGES; OR (ii) ANY AMOUNT IN EXCESS OF THE AMOUNT
#  ACTUALLY PAID FOR THE SOFTWARE ITSELF OR U.S. $1, WHICHEVER IS GREATER. THESE
#  LIMITATIONS SHALL APPLY NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF
#  ANY LIMITED REMEDY.
#
# $brcm_Workfile: bipc_build.pl $
# $brcm_Revision: 2 $
# $brcm_Date: 10/28/11 2:04p $
#
# File Description:
#
# Revision History:
#
# $brcm_Log: /nexus/lib/ipc/build/bipc_build.pl $
# 
# 2   10/28/11 2:04p erickson
# SW7425-1364: add typecasts for C++ compilers
# 
# 1   10/4/11 5:44p vsilyaev
# SW7425-1364: Reference applicaion IPC and reference server
# 
#############################################################################
use strict;

use bapi_parse_c;
my $file;
my @funcs;
my %structs;

if ($#ARGV == -1) {
    print "Usage: perl bipc_build.pl file1.h file2.h ...\n";
    exit -1;
}



sub is_handle 
{
    my $type = shift;
    ($type =~ /_Handle$/ || $type =~ /_t$/);
}

sub parse_func
{
    my $prototype = shift;
    my %func;
    my $more;
    my @params; # this is a list of a param hash

    # comment out the attr hint int the actual prototype
    my $actual_prototype = $prototype;
    $actual_prototype =~ s/(attr{.+?})/\/* $1 *\//sg;
    $func{PROTOTYPE} = $actual_prototype;
#    print "'$actual_prototype'\n";


    ($func{RETTYPE}, $more) = $prototype =~ /(.*?)\s*([\s*\*])\w+\s*\(/;
    if ($more eq "*") {
        $func{RETTYPE} .= $more;
    }
    $func{RETTYPE_ISHANDLE} = is_handle $func{RETTYPE};
    ($func{FUNCNAME}) = $prototype =~ /(\w+)\s*\(/;

    # get the params into a raw list
    $prototype =~ /\(\s*(attr{(.+?)})?(.*?)\)$/s;
    my $params=$3;
    if(defined $2) {
        $func{ATTR} = parse_attr $2;
    }
    if ($params eq "void") {
        $params = undef;
    }
    my @rawparams = split /,/, $params;
    my $p;

    for $p (@rawparams) {
        my %paramhash;

        # See if we have a attr hint and grab it now
        # This also removes that hint from the variable
        if ($p =~ s/attr{(.+?)}//) {
#            print "$func{FUNCNAME} attr = $1\n";
            $paramhash{ATTR} = parse_attr $1;
        }

        # parse type and name
        my ($type, $name) = $p =~ /(\w[\w\s]*[\s\*]+)(\w+)/;
        # strip leading and trailing whitespace
        $type =~ s/^\s*(.*?)\s*$/$1/;

        $paramhash{NAME} = $name;
        $paramhash{TYPE} = $type;
        # a pointer in the type means the data is passed by reference
        $paramhash{ISREF} = ($type =~ /\*\s*$/);

        if ($paramhash{ISREF}) {
            # a const pointer is assumed to be an input parameter,
            # note that "const void **" is parsed as a non-const pointer to "const void *", so it's out
            # nexus does not parse [out]
            $paramhash{INPARAM} = ($type =~ /^\s*const/) && !($type =~ /\*\s*\*/);
            if ($paramhash{INPARAM}) {
                ($paramhash{BASETYPE}) = ($type =~ /^\s*const\s*(.*?)\s*\*\s*$/);
            }
            else {
                ($paramhash{BASETYPE}) = ($type =~ /^\s*(.*?)\s*\*\s*$/);

                # non-const void* params w/ attr{nelem_out} are out params, otherwise they are actually in params
                if ($paramhash{BASETYPE} eq 'void' && !defined $paramhash{ATTR}->{'nelem_out'}) {
                    $paramhash{INPARAM} = 1;
                }
            }
        }
        else {
            # if not by-reference, then by-value, which is always in in param
            $paramhash{INPARAM} = 1;
        }
        push @params, \%paramhash;
    }
    $func{PARAMS} = \@params;

    # Return a reference to the func hash
    return \%func;
}

sub parse_funcs
{
    my $func;
    my @funcrefs;
    for $func (@_) {
        my $funcref = parse_func $func;

        push @funcrefs, $funcref;
    }
    @funcrefs;
}

#use Data::Dumper;

sub group_api 
{
    my $func;
    my %classes;

    for $func (@_) {
        my $name = $func->{FUNCNAME};
        if( ($name =~ /^(\w+)_create$/) || ($name =~ /^(\w+)_Create$/) || ($name =~ /^(\w+)_open$/) || ($name =~ /^(\w+)_Open$/)) {
            my %class;
            my $name = $1;
            if($func->{RETTYPE_ISHANDLE} && ($func->{RETTYPE} =~ /^(\w+)_Handle/ || $func->{RETTYPE} =~ /^(\w+)_t/)) {
                $class{NAME} = $name;
                $class{TYPE} = $1;
                $class{CONSTRUCTOR} = $func;
                $classes{$name} = \%class;
            } else {
                die "Invalid constructor $func->{PROTOTYPE}";
            }
        }
    }
    for $func (@_) {
        my $name = $func->{FUNCNAME};
        my $classname;
        for $classname (keys %classes) {
            if($name =~ /^${classname}_(\w+)$/) {
                my $method = lc $1;
                my $class = $classes{$classname};
                next if $name eq $class->{CONSTRUCTOR}->{FUNCNAME};
                if($method eq 'close' || $method eq 'destroy') {
                    $class->{DESTRUCTOR} = $func;
                } else {
                    my $methods = $class->{METHODS};
                    push @$methods, $func;
                    $class->{METHODS} = $methods;
                }
            }
        }
    }
#    print Dumper(\%classes);
    return \%classes;
}

sub append_code {
    my $dest = shift;
    my $code = shift;
    my $ident = shift;

    for(@$code) {
        push @$dest, "$ident$_";
    }
}

sub print_code
{
    my $file = shift;
    my $code = shift;
    my $ident = shift;

    if(defined $code) {
        for(@$code) {
            print $file "$ident$_\n";
        }
    }
}
sub append_structure  {
    my $dest = shift;
    my $name = shift;
    my $code = shift;

    if(scalar @$code) {
        push @$dest, "struct {";
        append_code $dest,$code,"    ";
        push @$dest, "} $name;";
    }
}

my $ipc_field = "_b_ipc_id";
my $ipc_data = "_b_ipc_data";
my $ipc_result = "_b_ipc_result";

sub process_function_args {
    my $ipc = shift;
    my $class = shift;
    my $func = shift;
    my $type = shift;
    my @data_in;
    my @data_out;
    my @server_args;
    my @client_in;
    my @client_out;
    my $params = $func->{PARAMS};
    my $param;
    my $data = "$ipc_data -> $func->{FUNCNAME}";
    my $cnt = 0;
    for $param (@$params) {
        if($cnt == 0) {
            push @server_args, "* $ipc_field";
            if( $type eq 'CONSTRUCTOR') {
               die "Invalid constructor $func->{PROTOTYPE}" unless $param->{TYPE} eq 'bipc_t';
               push @data_in, "bipc_interface_descriptor $ipc_field;";
               push @data_out, "unsigned $ipc_result;";
               push @client_in, "$data . in. $ipc_field = bipc_${class}_descriptor;";
               push @client_out, "$ipc_result -> id = $data . out. $ipc_result;";
           } else {
               die "Invalid function $func->{PROTOTYPE}" unless is_handle $param->{TYPE};
           }
        }  elsif ($param->{ISREF}) { 
            if ($param->{INPARAM}) {
                push @server_args, "& $data .in.  $param->{NAME}";
                push @data_in, "$param->{BASETYPE} $param->{NAME};";
                push @client_in, "$data .in. $param->{NAME} = * $param->{NAME};"
            } else {
                push @server_args, "& $data .out.  $param->{NAME}";
                push @data_out, "$param->{BASETYPE} $param->{NAME};";
                push @client_out, "* $param->{NAME} = $data . out. $param->{NAME};";
            }
        } else {
            push @server_args, "$data .in. $param->{NAME}";
            push @data_in, "$param->{TYPE} $param->{NAME};";
            push @client_in, "$data .in. $param->{NAME} = $param->{NAME};"
        }
        $cnt++;
    }
    if($func->{RETTYPE} ne 'void' && $type ne 'CONSTRUCTOR') {
        push @data_out, "$func->{RETTYPE} $ipc_result;";
        push @client_out, "$ipc_result = $data . out. $ipc_result;";
    }
    if(0 == scalar @data_in) {
        push @data_in, "unsigned _b_ipc_unused;";
    }
    $ipc->{$func->{FUNCNAME}} = {
        'SERVER_ARGS' => \@server_args,
        'DATA_IN' => \@data_in,
        'DATA_OUT' => \@data_out,
        'CLIENT_IN' => \@client_in,
        'CLIENT_OUT' => \@client_out,
        'TYPE' => $type,
        'FUNC' => $func
    }
}

sub make_class_ipc_data
{
    my %ipc;
    my $class = shift;
    my $name = $class->{NAME};
    process_function_args \%ipc, $name, $class->{CONSTRUCTOR}, 'CONSTRUCTOR';
    process_function_args \%ipc, $name, $class->{DESTRUCTOR}, 'DESTRUCTOR';
    for (@{$class->{METHODS}}) {
        process_function_args \%ipc, $name, $_;
    }
#    print Dumper(\%ipc);
    return \%ipc;
}



# Print out the perl datastructure
#bapi_parse_c::print_api @funcrefs;
#bapi_parse_c::print_struct \%structs;



sub send_offset_and_size {
   my $name = shift;
   my $func_ipc = shift;
   my $offset='0';
   my $size='0';

    if(scalar @{$func_ipc->{CLIENT_OUT}}) { 
        $size = " sizeof($ipc_data ->$func_ipc->{FUNC}->{FUNCNAME} . out)";
        $offset = " offsetof(union b_ipc_${name}_data, $func_ipc->{FUNC}->{FUNCNAME} . out )  - offsetof(union b_ipc_${name}_data, $func_ipc->{FUNC}->{FUNCNAME})";
    }
    ($offset, $size);
}

sub make_ipc 
{
    my $classes = shift;
    my $server = shift;
    my $file = shift;


    open FILE, ">${file}";
    print FILE "/*********************************\n";
    print FILE "*\n";
    print FILE "* This file is autogenerated by the IPC script .\n";
    print FILE "*\n";

    for (values %$classes) {
        my $f;
        my $ipc = make_class_ipc_data $_;
        my @methods = keys %$ipc;
        my @enums;
        my $name= $_->{NAME};
        my $constructor = $_->{CONSTRUCTOR}->{FUNCNAME};
        my $destructor = $_->{DESTRUCTOR}->{FUNCNAME};
        printf STDOUT "%s IPC $name\n", ($server?"server":"client");
        if($server) {
            print FILE "* This file contains function that accesse data over the IPC channel and routes execution to local implementation.\n";
        } else {
            print FILE "* This file contains stub for every API function that intercepts call and routes it the server via IPC channel.\n";
        }
        print FILE "*\n";
        print FILE "*********************************/\n";
        append_code \@enums, \@methods, "\tb_ipc_";
        print FILE "enum b_ipc_${name}_methods {\n";
        print FILE join(",\n",sort(@enums));
        print FILE "\n};\n";
        print FILE "union b_ipc_${name}_data {\n";
        for (keys %$ipc) {
            my @data;
            append_structure \@data, "in", $ipc->{$_}->{DATA_IN};
            append_structure \@data, "out", $ipc->{$_}->{DATA_OUT};
            print FILE "    struct {\n";
            print_code \*FILE, \@data, "        ";
            print FILE "    } $_;\n";
        }
        print FILE "};\n";
        if(not $server) {
            print FILE "const bipc_interface_descriptor bipc_${name}_descriptor = {\n";
            print FILE "    \"$name\",\n";
            print FILE "    \"$name\",\n";    
            print FILE "    sizeof(union b_ipc_${name}_data)\n";
            print FILE "};\n";
            print FILE "struct $_->{TYPE} {\n";
            print FILE "    bipc_t ipc;\n";
            print FILE "    unsigned id;\n";
            print FILE "};\n";
            print FILE "#include \"bipc_client_priv.h\"\n";
            print FILE "#include \"bkni.h\"\n";

            # Build client portion 
            for (keys %$ipc) {
                my $func_ipc = $ipc->{$_};
                print FILE "$func_ipc->{FUNC}->{PROTOTYPE}\n\{\n";
                my $params = $func_ipc->{FUNC}->{PARAMS};
                my $handle = @$params[0]->{NAME};
                print FILE "    union b_ipc_${name}_data * $ipc_data;\n";
                if($func_ipc->{FUNC}->{RETTYPE} ne 'void') {
                    print FILE "    $func_ipc->{FUNC}->{RETTYPE} $ipc_result;\n";
                    if($func_ipc->{FUNC}->{RETTYPE_ISHANDLE}) {
                        print FILE "    $ipc_result = NULL;\n";
                    } else {
                        print FILE "    $ipc_result = -1;\n";
                    }
                }
                my $ipc_arg;
                my $ipc_id;
                if($func_ipc->{TYPE} eq 'CONSTRUCTOR') {
                    $ipc_arg = $handle;
                    $ipc_id = "BIPC_INSTANCE_ID_NEW";
                    print FILE "    $ipc_data = (union b_ipc_${name}_data *)bipc_client_begin($ipc_arg, &bipc_${name}_descriptor);\n";
                } else {
                    $ipc_arg = "$handle -> ipc";
                    $ipc_id = "$handle -> id";
                    print FILE "    $ipc_data = (union b_ipc_${name}_data *)bipc_client_begin($ipc_arg, NULL);\n";
                }
                print_code \*FILE, $func_ipc->{CLIENT_IN}, "    ";
                my ($send_offset,$send_size) = send_offset_and_size $name, $func_ipc;
                print FILE "    if(bipc_client_send($ipc_arg, $ipc_id, b_ipc_$_ , sizeof( $ipc_data ->$func_ipc->{FUNC}->{FUNCNAME} . in ) , $send_offset, $send_size)==0) {\n";
                if($func_ipc->{TYPE} eq 'CONSTRUCTOR') {
                    print FILE "        if($ipc_data->$func_ipc->{FUNC}->{FUNCNAME} . out . $ipc_result == BIPC_INSTANCE_ID_NEW) { goto done;}\n";
                    print FILE "        $ipc_result = ($func_ipc->{FUNC}->{RETTYPE}) BKNI_Malloc(sizeof( * $ipc_result));\n";
                    print FILE "        $ipc_result->ipc = $handle;\n";
                }
                print_code \*FILE, $func_ipc->{CLIENT_OUT}, "        ";
                print FILE "    } else {\n";
                print FILE "    }\n";
                print FILE "    goto done;\n";
                print FILE "done:\n";
                print FILE "    bipc_client_end($ipc_arg);\n";
                if($func_ipc->{FUNC}->{RETTYPE} ne 'void') {
                    print FILE "    return $ipc_result;\n";
                }
                print FILE "}\n";
            }
        } else {
            # Build server portion 
            print FILE "#include \"bipc_server.h\"\n\n";


            print FILE "static int b_ipc_${name}_process( void **$ipc_field, unsigned entry, void *buf, size_t recv_size, size_t *send_offset, size_t *send_size)\n";
            print FILE "{\n";
            print FILE "    int rc=0;\n";
            print FILE "    union b_ipc_${name}_data * $ipc_data = buf;\n";
            print FILE "    switch(entry) {\n";
            for (keys %$ipc) {
                my $func_ipc = $ipc->{$_};
                print FILE "    case b_ipc_$_:\n";
                print FILE "        if(recv_size == sizeof( $ipc_data ->$func_ipc->{FUNC}->{FUNCNAME} . in )) {\n";
                my ($send_offset,$send_size) = send_offset_and_size $name, $func_ipc;
                if($func_ipc->{TYPE} eq 'CONSTRUCTOR') {
                    print FILE "            $func_ipc->{FUNC}->{RETTYPE}  $ipc_result = \n";
                } elsif($func_ipc->{FUNC}->{RETTYPE} ne 'void') {
                    print FILE "            $ipc_data -> $func_ipc->{FUNC}->{FUNCNAME} .out. $ipc_result = \n";
                }
                print FILE "            $_(\n";
                print FILE "                ";
                print FILE join(",\n                ",@{$func_ipc->{SERVER_ARGS}});
                print FILE "\n           );\n";
                if($func_ipc->{TYPE} eq 'CONSTRUCTOR') {
                    print FILE "            * $ipc_field = $ipc_result;\n";
                    print FILE "            $ipc_data -> $func_ipc->{FUNC}->{FUNCNAME} .out. $ipc_result = $ipc_result!=NULL ? *send_offset : BIPC_INSTANCE_ID_NEW;\n";
                }  
                print FILE "            *send_offset = $send_offset;\n";
                print FILE "            *send_size = $send_size;\n";
                print FILE "        } else { \n";
                print FILE "            rc = -1; \n";
                print FILE "        };\n";
                print FILE "        break;\n";
            }
            print FILE "    default:\n";
            print FILE "        rc = -1;\n";
            print FILE "        break;\n";
            print FILE "    }\n";
            print FILE "    return rc;\n";
            print FILE "}\n";

            print FILE "const bipc_server_descriptor bipc_${name}_server_descriptor = {\n";
            print FILE "    {\n";
            print FILE "        \"$name\",\n";
            print FILE "        \"$name\",\n";    
            print FILE "        sizeof(union b_ipc_${name}_data)\n";
            print FILE "    },\n";
            print FILE "    b_ipc_${constructor},\n";
            print FILE "    b_ipc_${destructor},\n";
            print FILE "    b_ipc_${name}_process\n";
            print FILE "};\n";
        }

    }
    close FILE;
}

my $server_file = shift @ARGV;
my $client_file = shift @ARGV;

for $file (@ARGV) {
    push @funcs, bapi_parse_c::get_func_prototypes $file;

    my $file_structs = bapi_parse_c::parse_struct $file;
    my $name;
    my $members;
    while (($name, $members) = each %$file_structs) {
        $structs{$name} = $members;
    }
}
my @funcrefs = parse_funcs @funcs;

my $classes = group_api @funcrefs;

make_ipc $classes,  1, $server_file;
make_ipc $classes,  0, $client_file;

