#!/usr/bin/env python
import os, sys, getopt, re, pickle

copyright = """/*
 * Copyright (C) 2016 BlueKitchen GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * 4. Any redistribution, use, or modification is done solely for
 *    personal benefit and not for any commercial purpose or for
 *    monetary gain.
 *
 * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
 * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Please inquire about commercial licensing options at 
 * contact@bluekitchen-gmbh.com
 *
 */
"""

single_hfile_header_begin = """

/*
 *  btstack_rtos.h
 *
 *  @brief BTstack Wrapper for use with Real-Time OS
 *         Wraps each public BTstack function into a thread-safe version
 * 
 *  @note  Don't edit - generated by tool/btstack_rtos_generator.py
 *
 */

#ifndef __BTSTACK_RTOS_H
#define __BTSTACK_RTOS_H

#if defined __cplusplus
extern "C" {
#endif

#include "btstack_config.h"

#ifndef BTSTACK_RTOS_ENTER
#error Please define BTSTACK_RTOS_ENTER that locks a recursive mutex when using the RTOS wrapper btstack_rtos.h
#endif

#ifndef BTSTACK_RTOS_EXIT
#error Please define BTSTACK_RTOS_EXIT that releases a recursive mutex when using the RTOS wrapper btstack_rtos.h
#endif

/* API_START */


"""

single_hfile_api_header = """
#include "API_NAME"
"""

single_hfile_header_end = """

/* API_END */

#if defined __cplusplus
}
#endif

#endif // __BTSTACK_RTOS_H
"""

multiple_header_begin = """

/*
 *  FILENAME
 *
 *  @brief BTstack Wrapper for use with Real-Time OS
 *         Wraps each public BTstack function into a thread-safe version
 * 
 *  @note  Don't edit - generated by tool/btstack_rtos_generator.py
 *
 */

#ifndef GUARD
#define GUARD

#if defined __cplusplus
extern "C" {
#endif

#include "btstack_config.h"
#include "HEADER"

#ifndef BTSTACK_RTOS_ENTER
#error Please define BTSTACK_RTOS_ENTER that locks a recursive mutex when using the RTOS wrapper btstack_rtos.h
#endif

#ifndef BTSTACK_RTOS_EXIT
#error Please define BTSTACK_RTOS_EXIT that releases a recursive mutex when using the RTOS wrapper btstack_rtos.h
#endif

/* API_START */


"""

multiple_header_end = """

/* API_END */

#if defined __cplusplus
}
#endif

#endif // GUARD
"""

class State:
    SearchStartAPI = 0
    SearchEndAPI = 1
    DoneAPI = 2


num_functions = 0

# [file_name, api_title, api_label]
apis = [ 
    ["src/ble/ancs_client.h", "BLE ANCS Client", "ancsClient", True],
    ["src/ble/att_db_util.h", "BLE ATT Database", "attDb", True],
    ["src/ble/att_server.h", "BLE ATT Server", "attServer", True],
    ["src/ble/gatt_client.h", "BLE GATT Client", "gattClient", True],
    ["src/ble/le_device_db.h", "BLE Device Database", "leDeviceDb", True],
    ["src/ble/sm.h", "BLE Security Manager", "sm", True],

    ["src/classic/bnep.h", "BNEP", "bnep", True],
    ["src/classic/btstack_link_key_db.h","Link Key DB","lkDb", True],
    ["src/classic/hsp_hs.h","HSP Headset","hspHS", True],
    ["src/classic/hsp_ag.h","HSP Audio Gateway","hspAG", True],  
    ["src/classic/hfp_hf.h","HFP Hands-Free","hfpHF", True],
    ["src/classic/hfp_ag.h","HFP Audio Gateway","hfpAG", True],
    ["src/classic/pan.h", "PAN", "pan", True],
    ["src/classic/rfcomm.h", "RFCOMM", "rfcomm", True],
    ["src/classic/sdp_client.h", "SDP Client", "sdpClient", True],
    ["src/classic/sdp_client_rfcomm.h", "SDP RFCOMM Query", "sdpQueries", True],
    ["src/classic/sdp_server.h", "SDP Server", "sdpSrv", True],
    ["src/classic/sdp_util.h","SDP Utils", "sdpUtil", True],

    ["src/ad_parser.h", "BLE Advertisements Parser", "advParser", False],
    # ["src/btstack_chipset.h","BTstack Chipset","btMemory", True],
    # ["src/btstack_control.h","BTstack Hardware Control","btControl", True],
    ["src/btstack_event.h","HCI Event Getter","btEvent", False],
    # ["src/btstack_memory.h","BTstack Memory Management","btMemory", True],
    ["src/btstack_linked_list.h","BTstack Linked List","btList", False],
    ["src/btstack_run_loop.h", "Run Loop", "runLoop", True],
    ["src/btstack_util.h", "Common Utils", "btUtil", False],
    ["src/gap.h", "GAP", "gap", True],
    ["src/hci.h", "HCI", "hci", True],
    ["src/hci_dump.h","HCI Logging","hciTrace", True],
    # ["src/hci_transport.h","HCI Transport","hciTransport", True],
    ["src/l2cap.h", "L2CAP", "l2cap", True],
]

def split_arguments(args_string):
    args = []
    brace_level = 0
    arg = ''
    for c in args_string:
        if c == '(':
            brace_level += 1
        if c == ')':
            brace_level -= 1
        if c == ',' and brace_level == 0:
            args.append(arg)
            arg = ''
            continue
        arg = arg + c
    if len(arg):
        args.append(arg)
    return args

def argument_name(parameter):
    function_pointer = re.match('[\w\s\*]*\(\s*\*(\w*)\s*\)\(.*\)', parameter)
    if function_pointer:
        return function_pointer.group(1)
    parts = parameter.split(' ')
    filtered_parts = [part for part in parts if part not in ['']]
    arg = filtered_parts[len(filtered_parts)-1].replace('*','').replace('[]','')
    # le_device_db_encryption_set(index, ediv, rand[8], ltk, key_size, authenticated, authorized); 
    if arg == 'rand[8]':
        arg = 'rand'
    return arg

def create_wrapper(fout, type_and_name, arg_string, need_lock):
    global num_functions

    parts = type_and_name.split(' ')
    filtered_parts = [part for part in parts if part not in ['static','inline','']]
    name = filtered_parts[len(filtered_parts)-1]
    return_type = ' '.join(filtered_parts[:-1])
    # handle *function_name
    if name.startswith('*'):
        name = name[1:]
        return_type = return_type + ' *'
    rtos_name = "rtos_" + name
    is_void_function = len(filtered_parts) == 2 and filtered_parts[0] == "void"
    args = split_arguments(arg_string)
    call = []
    is_ellipse_function = False
    if len(args)!= 1 or args[0] != 'void':
        for arg in args:
            call_arg = argument_name(arg)
            if call_arg == '...':
                is_ellipse_function = True
                call.append('argptr')
                name += '_va_arg'
            else:
                call.append(argument_name(arg))
    call_args = ', '.join(call)
    fout.write('static inline ' + return_type + ' ' + rtos_name + '(' + ", ".join(args) + '){\n')
    orig_call = name + '(' + call_args + ')'
    if need_lock:
        fout.write('    BTSTACK_RTOS_ENTER();\n')
        if is_ellipse_function:
            fout.write('    va_list argptr;\n')
            fout.write('    va_start(argptr, %s);\n' % call[-2])
        if is_void_function:
            fout.write('    ' + orig_call+';\n')
        else:
            fout.write('    ' + return_type + ' res = ' + orig_call + ';\n')
        if is_ellipse_function:
            fout.write('    va_end(argptr);\n')
        fout.write('    BTSTACK_RTOS_EXIT();\n')
        if not is_void_function:
            fout.write('    return res;\n')
    else:
        if is_void_function:
            fout.write('    ' + orig_call+';\n')
        else:
            fout.write('    return ' + orig_call + ';\n')
    fout.write('}\n')
    fout.write('\n')
    num_functions += 1

def write_wrappers_for_file(fout, file, header_name, need_lock):
    with open(file, 'rb') as fin:
        typedefFound = 0
        multiline_function_def = 0
        multiline = ''
        multiline_comment = 0
        inline_function = 0
        state = State.SearchStartAPI

        for line in fin:
            if state == State.DoneAPI:
                continue
            
            if state == State.SearchStartAPI:
                parts = re.match('.*API_START.*',line)
                if parts:
                    state = State.SearchEndAPI
                continue
            
            if state == State.SearchEndAPI:
                parts = re.match('.*API_END.*',line)
                if parts:
                    state = State.DoneAPI
                    continue

            if inline_function:
                function_end = re.match('.*}.*', line)
                if function_end:
                    inline_function = 0
                continue

            if multiline_function_def:
                multiline += line
                function_end = re.match('.*\)', line)
                if function_end:
                    multiline_function_def = 0
                    function = re.match('([\w\s\*]*)\(([\w\s,\*]*)\).*', multiline)
                    if function:
                        type_and_name = function.group(1)
                        arg_string    = function.group(2)
                        create_wrapper(fout, type_and_name, arg_string, need_lock)
                continue

            if multiline_comment:
                comment_end = re.match('.*\*/.*', line)
                if comment_end:
                    multiline_comment = 0
                fout.write(line)
                continue

            # search typedef struct end
            if typedefFound:
                typedef = re.match('}\s*(.*);\n', line)
                if typedef:
                    typedefFound = 0
                continue

            # search comment line
            comment = re.match(".*/\*.*\*/.*", line)
            if comment:
                fout.write(line)
                continue

            # search start of multi line comment
            comment = re.match(".*/\*", line)
            if comment:
                fout.write(line)
                multiline_comment = 1
                continue
            
            # ignore __attribute__ for hci_dump_log in src/hci_dump.h
            param = re.match(".*__attribute__", line)
            if param:
                continue

            # search typedef struct begin
            typedef =  re.match('.*typedef\s+struct.*', line)
            if typedef:
                typedefFound = 1
            
            # complete function declaration
            function = re.match('([\w\s\*]*)\((.*)\).*', line)
            if function:
                if "return" in line:
                    continue
                type_and_name = function.group(1)
                arg_string    = function.group(2)
                create_wrapper(fout, type_and_name, arg_string, need_lock)
                inline_function = 'inline' in line;
                continue

            # multi-line function declaration
            function = re.match('([\w\s\*]*)\((.*).*', line)
            if function:
                multiline = line
                multiline_function_def = 1
                continue

    # fout.write(single_hfile_header_begin)


def create_wrapper_file(btstack_root, apis, wrapper_file):
    with open(wrapper_file, 'w') as fout:
        fout.write(copyright)
        fout.write(single_hfile_header_begin)

        for api_tuple in apis:
            api_filename = btstack_root + "/" + api_tuple[0]
            need_lock = api_tuple[3]
            header_file = api_tuple[0].replace('src/','')
            fout.write(single_hfile_api_header.replace("API_NAME", header_file))
            write_wrappers_for_file(fout, api_filename, header_file, need_lock)
            # fout.write(single_hfile_header_begin)
        fout.write(single_hfile_header_end)

def create_wrapper_files(btstack_root, rtos_folder, apis):
    for api_tuple in apis:
        api_filename = btstack_root + "/" + api_tuple[0]
        need_lock = api_tuple[3]
        header_file = api_tuple[0].replace('src/','')
        path_parts = header_file.split('/')
        path_parts[-1] = 'rtos_' + path_parts[-1]
        rtos_file = '/'.join(path_parts)
        wrapper_file = rtos_folder + '/' + rtos_file
        # print('- %s' % wrapper_file)
        with open(wrapper_file, 'w') as fout:
            guard = '__' + rtos_file.replace('.','_').upper()
            fout.write(copyright)
            fout.write(multiple_header_begin.replace('FILENAME',rtos_file).replace('GUARD',guard).replace('HEADER',header_file))
            write_wrappers_for_file(fout, api_filename, header_file, need_lock)
            fout.write(multiple_header_end.replace('GUARD',guard))

def assert_dir_exists(path):
    if not os.path.exists(path):
        os.makedirs(path)    

def main(argv):
    btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
    print ('BTstack folder is: %s' % btstack_root)

    # single file
    # gen_path = btstack_root + '/src/btstack_rtos.h'
    # print ('Generating RTOS wrapper %s' % gen_path)
    # create_wrapper_file(btstack_root, apis, gen_path)

    # individual files in platform/rtos
    print ('Generating RTOS wrappers...')
    rtos_folder = btstack_root + '/platform/rtos'
    assert_dir_exists(rtos_folder)
    assert_dir_exists(rtos_folder+'/ble')
    assert_dir_exists(rtos_folder+'/classic')
    create_wrapper_files(btstack_root, rtos_folder, apis)   

    # summary
    print ('Number wrapped headers: %u' % len(apis))
    print ('Number wrapped functions: %u' % num_functions)

if __name__ == "__main__":
   main(sys.argv[1:])
