import sys
from subprocess import Popen, PIPE

import waftools.objcopy
import waftools.insert_text_section_crc

from resources.types.resource_definition import ResourceDefinition
from resources.types.resource_object import ResourceObject

CUSTOM_CONFIG_H_PATH = 'config/custom_config_main.h'


def _export_dialog_fw_includes(bld):
    fw_includes = ['include', '../common/include', '../../include', 'config']
    bld(export_includes=fw_includes, name='dialog_fw_includes')


def build(bld):
    bld.env.append_value('DEFINES', ['BLE_MAIN_FW=1'])

    sys.path.append(bld.path.find_node('../..').abspath())
    from dialog_waf import get_sdk_node, collect_sdk_sources, generate_mem_ld

    mem_ld_node = generate_mem_ld(bld, CUSTOM_CONFIG_H_PATH)

    # Collect source files:
    source_dirs = ['src', '../../common']
    sources = sum([bld.path.find_node(d).ant_glob('**/*.c')
                   for d in source_dirs], [])
    sdk_sources = [
        'sdk/bsp/startup/config.c',
        'sdk/bsp/startup/startup_ARMCM0.S',
        'sdk/bsp/startup/system_ARMCM0.c',
        'sdk/bsp/startup/vector_table.S',

        # FreeRTOS:
        'sdk/bsp/free_rtos/event_groups.c',
        'sdk/bsp/free_rtos/light_mutex.c',
        'sdk/bsp/free_rtos/list.c',
        'sdk/bsp/free_rtos/portable/GCC/ARM_CM0/port.c',
        'sdk/bsp/free_rtos/queue.c',
        'sdk/bsp/free_rtos/tasks.c',
        'sdk/bsp/free_rtos/timers.c',

        # Core/Peripheral APIs:
        'sdk/bsp/adapters/src/ad_gpadc.c',
        'sdk/bsp/adapters/src/ad_rf.c',
        'sdk/bsp/adapters/src/ad_spi.c',
        'sdk/bsp/adapters/src/ad_temp_sens.c',
        'sdk/bsp/osal/resmgmt.c',
        'sdk/bsp/peripherals/src/hw_aes_hash.c',
        'sdk/bsp/peripherals/src/hw_cpm.c',
        'sdk/bsp/peripherals/src/hw_dma.c',
        'sdk/bsp/peripherals/src/hw_gpadc.c',
        'sdk/bsp/peripherals/src/hw_gpio.c',
        'sdk/bsp/peripherals/src/hw_otpc.c',
        'sdk/bsp/peripherals/src/hw_qspi.c',  # optimization opportunity
        'sdk/bsp/peripherals/src/hw_rf.c',
        'sdk/bsp/peripherals/src/hw_spi.c',
        'sdk/bsp/peripherals/src/hw_tempsens.c',
        'sdk/bsp/peripherals/src/hw_timer0.c',
        'sdk/bsp/peripherals/src/hw_timer1.c',
        'sdk/bsp/peripherals/src/hw_trng.c',
        'sdk/bsp/peripherals/src/hw_uart.c',
        'sdk/bsp/peripherals/src/hw_usb_charger.c',  # optimization opportunity
        'sdk/bsp/peripherals/src/hw_watchdog.c',
        'sdk/bsp/peripherals/src/hw_wkup.c',
        'sdk/bsp/peripherals/src/sys_tcs.c',
        'sdk/bsp/system/sys_man/sys_clock_mgr.c',
        'sdk/bsp/system/sys_man/sys_power_mgr.c',
        'sdk/bsp/system/sys_man/sys_rtc.c',
        'sdk/bsp/system/sys_man/sys_watchdog.c',

        # BLE Services:
        'sdk/interfaces/ble_services/src/ble_service.c',
        'sdk/interfaces/ble_services/src/dis.c',
        'sdk/interfaces/ble_services/src/hrs.c',
    ]
    sources.extend(collect_sdk_sources(bld, sdk_sources))

    # BLE API sources:
    ble_sources = get_sdk_node(bld).ant_glob(
        'sdk/interfaces/ble/**/*.c',
        # This file is deprecated and also drags in Dialog's list.c, which
        # conflicts with libutil's list.c, so skip them:
        excl='sdk/interfaces/ble/src/ble_attribdb.c '
             'sdk/interfaces/ble/src/util/list.c')

    sources.extend(ble_sources)

    def add_wrap(linkflags, func_name):
        linkflags += ['-Wl,--wrap=%s' % func_name,
                      '-Wl,--undefined=__wrap_%s' % func_name]

    # LDFLAGS:
    linkflags = ['-Wl,-Map,bt_da14681_main.map',
                 '-Wl,--undefined=uxTopUsedPriority',
                 '-Wl,--build-id=sha1',
                 ]

    # Wrappers (Jay-Z, Tupac, etc):
    add_wrap(linkflags, 'ble_gap_address_set')
    add_wrap(linkflags, 'ble_gap_device_name_set')
    add_wrap(linkflags, 'ble_gap_appearance_set')
    add_wrap(linkflags, 'ble_gap_per_pref_conn_params_set')
    add_wrap(linkflags, 'ble_gap_role_set')
    add_wrap(linkflags, 'ble_gap_mtu_size_set')
    add_wrap(linkflags, 'patch_rom_functions')

    # Dialog main FW Includes (used by libOS):
    _export_dialog_fw_includes(bld)

    variant = None
    libble_stack_name = None
    rom_symbols_path = None
    if bld.env.bt_controller == 'da14681-00':
        variant = 'DA14681-00-Debug'
        libble_stack_name = 'ble_stack_da14681_00'
        rom_symbols_path = 'sdk/bsp/misc/da14681_00_rom.symbols'
    elif bld.env.bt_controller == 'da14681-01':
        variant = 'DA14681-01-Debug'
        libble_stack_name = 'ble_stack_da14681_01'
        rom_symbols_path = 'sdk/bsp/misc/da14681_01_rom.symbols'
        # Sadly, it appears not all remote devices support packet length extension. We ran into a
        # pretty serious bug with the iPhone 7 (PBL-42011) that would prevent the watch from being
        # able to pair/communicate with the phone. Hopefully, in the future we can turn this back
        # on to harness the throughput improvements gained.
        bld.env.append_value('DEFINES', ['SUPPORTS_PACKET_LENGTH_EXTENSION=0'])
    else:
        raise Exception("Unknown bt_controller '%s'" % bld.env.bt_controller)

    # Link libble_stack.a:
    libble_stack_node = get_sdk_node(bld).find_node(
        'sdk/interfaces/ble_stack/%s' % variant)
    bld.read_stlib(libble_stack_name, paths=[libble_stack_node])

    # Link ROM symbols:
    # waf doesn't recognize the .symbols file, so just pass it
    # as a linker 'flag':
    rom_symbols_path = get_sdk_node(bld).find_node(rom_symbols_path).abspath()
    linkflags.append(rom_symbols_path)

    # Link to a temporary elf (no_crc)
    no_crc_node = bld.path.get_bld().make_node('bt_da14681_main.no_crc')
    bld.program(features='c asm cprogram',
                source=sources,
                target=no_crc_node,
                lib=['gcc'],
                linkflags=linkflags,
                ldscript=mem_ld_node,
                inject_include_files=[CUSTOM_CONFIG_H_PATH],
                # Note: Dialog SDK also includes a "util/list.h" header.
                # Put 'libutil_includes' before 'dialog_sdk_includes' to
                # make sure libutil's "util/list.h" gets picked first:
                use=['dialog_fw_includes',
                     'libbtutil_includes',
                     'libbtutil-cm0',
                     'libutil_includes',
                     'libos_includes',
                     'dialog_board_main',
                     'dialog_sdk_includes',
                     libble_stack_name,
                     'pblibc-cm0',
                     'libos-dialog',
                     'libutil-cm0',
                     'root_includes'],
                # Hack to prevent waf from compiling the shared sdk files twice
                # into the same place. I think it's a waf bug but one of the
                # maintainers disagrees with me:
                # https://github.com/waf-project/waf/issues/1661
                idx=3000)

    bld.add_manual_dependency(no_crc_node, mem_ld_node)

    # Update the .text_crc32 section with the correct CRC32.
    elf_node = no_crc_node.change_ext('.elf')
    bld(rule=waftools.insert_text_section_crc.wafrule, source=no_crc_node, target=elf_node)
    bld.add_manual_dependency(elf_node, no_crc_node)

    # Create the log_strings .elf and check the format specifier rules
    if 'PBL_LOGS_HASHED' in bld.env.DEFINES:
        bt_loghash_node = bld.path.get_bld().make_node('bt_da14681_loghash_dict.json')

        # Create the log_strings .elf and check the format specifier rules
        bld(rule=waftools.generate_log_strings_json.wafrule,
            source=elf_node, target=bt_loghash_node)

        bld.LOGHASH_DICTS.append(bt_loghash_node)

    # Note: We play a couple tricks here. When the main image is loaded onto
    # the BLE chip, we have a bootloader running in RAM. We place the main
    # image heap in this region so that nothing needs to be copied over to the
    # region code is executing from. However, the vector table must always
    # reside at the beginning of RAM. We relocate the vector table using
    # objcopy such that it falls right after the .infoblob section. The main MCU
    # firmware uses this to figure out where to load things to. This makes all the
    # sections contiguous and prevents objcopy from creating a larger bin
    # padded with a bunch of extra 0s. The main firmware has a special command
    # to deal with loading the vector table into the correct location
    bin_node = elf_node.change_ext('.bin')

    def _generate_bt_patch_rule(task):
        # Get the address of the region we should move the vector table to
        # TODO: Extend nm_generator to easily parse for stuff like this
        readelf_proc = Popen(['arm-none-eabi-readelf', '-S', task.inputs[0].abspath()], stdout=PIPE)
        readelf_output = readelf_proc.communicate()[0]
        for line in readelf_output.splitlines():
            if 'vt_stash_region' not in line:
                continue
            line = line[6:]
            addr = int(line.split()[2], 16)
            break

        waftools.objcopy.objcopy(task, 'binary', extra_args=('--change-section-address .vt=0x%x' % addr))

    bld(rule=_generate_bt_patch_rule, source=elf_node, target=bin_node)

    storage = bld.get_bluetooth_fw_storage()

    def _create_bt_patch_resource(task):
        bin_data = task.inputs[0].read('rb')
        reso = ResourceObject(ResourceDefinition('raw', 'BT_FW_IMAGE', None,
                              storage=storage), bin_data)
        reso.dump(task.outputs[0])

    reso_node = bin_node.change_ext('.bin.reso')
    bld(rule=_create_bt_patch_resource, source=bin_node, target=reso_node)

    bld.DYNAMIC_RESOURCES.append(reso_node)


def configure(conf):
    args = []

    if conf.options.save_temps:
        args += ['-save-temps=obj']

    conf.env.append_value('CFLAGS', args)

# vim:filetype=python
