#!/usr/bin/env python
# encoding: utf-8

"""
Waf tool for PX4 build
"""

from waflib import Logs, Task, Utils
from waflib.TaskGen import after_method, before_method, feature

import os
import sys

_dynamic_env_data = {}
def _load_dynamic_env_data(bld):
    bldnode = bld.bldnode.make_node('modules/PX4Firmware')
    for name in ('cxx_flags', 'include_dirs', 'definitions'):
        _dynamic_env_data[name] = bldnode.find_node(name).read().split(';')

@feature('px4_ap_stlib', 'px4_ap_program')
@before_method('process_source')
def px4_dynamic_env(self):
    # The generated files from configuration possibly don't exist if it's just
    # a list command (TODO: figure out a better way to address that).
    if self.bld.cmd == 'list':
        return

    if not _dynamic_env_data:
        _load_dynamic_env_data(self.bld)

    self.env.append_value('INCLUDES', _dynamic_env_data['include_dirs'])
    self.env.prepend_value('CXXFLAGS', _dynamic_env_data['cxx_flags'])
    self.env.prepend_value('CXXFLAGS', _dynamic_env_data['definitions'])

# Single static library
# NOTE: This only works only for local static libraries dependencies - fake
# libraries aren't supported yet
@feature('px4_ap_program')
@after_method('apply_link')
@before_method('process_use')
def px4_import_objects_from_use(self):
    queue = Utils.to_list(getattr(self, 'use', []))
    names = set()

    while queue:
        name = queue.pop(0)
        if name in names:
            continue
        names.add(name)

        try:
            tg = self.bld.get_tgen_by_name(name)
        except Errors.WafError:
            continue

        tg.post()
        for t in getattr(tg, 'compiled_tasks', []):
            self.link_task.set_inputs(t.outputs)

        queue.extend(Utils.to_list(getattr(tg, 'use', [])))

class px4_copy(Task.Task):
    run_str = '${CP} ${SRC} ${TGT}'
    color = 'CYAN'

    def keyword(self):
        return "PX4: Copying %s to" % self.inputs[0].name

    def __str__(self):
        return self.outputs[0].path_from(self.generator.bld.bldnode)

class px4_add_git_hashes(Task.Task):
    run_str = '${PYTHON} ${PX4_ADD_GIT_HASHES} --ardupilot ${PX4_APM_ROOT} --px4 ${PX4_ROOT} --nuttx ${PX4_NUTTX_ROOT} --uavcan ${PX4_UAVCAN_ROOT} ${SRC} ${TGT}'
    color = 'CYAN'

    def keyword(self):
        return "PX4: Copying firmware and adding git hashes"

    def __str__(self):
        return self.outputs[0].path_from(self.outputs[0].ctx.launch_node())

def _update_firmware_sig(fw_task, firmware, elf):
    original_post_run = fw_task.post_run
    def post_run():
        original_post_run()
        firmware.sig = firmware.cache_sig = Utils.h_file(firmware.abspath())
        elf.sig = elf.cache_sig = Utils.h_file(elf.abspath())
    fw_task.post_run = post_run

_cp_px4io = None
_firmware_semaphorish_tasks = []
_upload_task = []

@feature('px4_ap_program')
@after_method('process_source')
def px4_firmware(self):
    global _cp_px4io, _firmware_semaphorish_tasks, _upload_task
    version = self.env.get_flat('PX4_VERSION')

    px4 = self.bld.cmake('px4')
    px4.vars['APM_PROGRAM_LIB'] = self.link_task.outputs[0].abspath()

    if self.env.PX4_USE_PX4IO and not _cp_px4io:
        px4io_task = self.create_cmake_build_task('px4', 'fw_io')
        px4io = px4io_task.cmake.bldnode.make_node(
            'src/modules/px4iofirmware/px4io-v%s.bin' % version,
        )
        px4io_elf = px4.bldnode.make_node(
            'src/modules/px4iofirmware/px4io-v%s' % version
        )
        px4io_task.set_outputs([px4io, px4io_elf])

        romfs = self.bld.bldnode.make_node(self.env.PX4_ROMFS_BLD)
        romfs_px4io = romfs.make_node('px4io/px4io.bin')
        romfs_px4io.parent.mkdir()
        _cp_px4io = self.create_task('px4_copy', px4io, romfs_px4io)
        _cp_px4io.keyword = lambda: 'PX4: Copying PX4IO to ROMFS'

        px4io_elf_dest = self.bld.bldnode.make_node(self.env.PX4IO_ELF_DEST)
        cp_px4io_elf = self.create_task('px4_copy', px4io_elf, px4io_elf_dest)

    fw_task = self.create_cmake_build_task(
        'px4',
        'build_firmware_px4fmu-v%s' % version,
    )

    # we need to synchronize in order to avoid the output expected by the
    # previous ap_program being overwritten before used
    for t in _firmware_semaphorish_tasks:
        fw_task.set_run_after(t)
    _firmware_semaphorish_tasks = []

    if self.env.PX4_USE_PX4IO and _cp_px4io.generator is self:
        fw_task.set_run_after(_cp_px4io)

    firmware = px4.bldnode.make_node(
        'src/firmware/nuttx/nuttx-px4fmu-v%s-apm.px4' % version,
    )
    fw_elf = px4.bldnode.make_node(
        'src/firmware/nuttx/firmware_nuttx',
    )
    _update_firmware_sig(fw_task, firmware, fw_elf)

    fw_dest = self.bld.bldnode.make_node(
        os.path.join(self.program_dir, '%s.px4' % self.program_name)
    )
    git_hashes = self.create_task('px4_add_git_hashes', firmware, fw_dest)
    git_hashes.set_run_after(fw_task)
    _firmware_semaphorish_tasks.append(git_hashes)

    fw_elf_dest = self.bld.bldnode.make_node(
        os.path.join(self.program_dir, self.program_name)
    )
    cp_elf = self.create_task('px4_copy', fw_elf, fw_elf_dest)
    cp_elf.set_run_after(fw_task)
    _firmware_semaphorish_tasks.append(cp_elf)

    self.build_summary = dict(
        target=self.name,
        binary=fw_elf_dest.path_from(self.bld.bldnode),
    )

    if self.bld.options.upload:
        if _upload_task:
            Logs.warn('PX4: upload for %s ignored' % self.name)
            return
        _upload_task = self.create_cmake_build_task('px4', 'upload')
        _upload_task.set_run_after(fw_task)
        _firmware_semaphorish_tasks.append(_upload_task)

def _px4_taskgen(bld, **kw):
    if 'cls_keyword' in kw and not callable(kw['cls_keyword']):
        cls_keyword = str(kw['cls_keyword'])
        kw['cls_keyword'] = lambda tsk: 'PX4: ' + cls_keyword

    if 'cls_str' in kw and not callable(kw['cls_str']):
        cls_str = str(kw['cls_str'])
        kw['cls_str'] = lambda tsk: cls_str

    kw['color'] = 'CYAN'

    return bld(**kw)

@feature('_px4_romfs')
def _process_romfs(self):
    bld = self.bld
    file_list = (
        'firmware/oreoled.bin',
        'init.d/rc.APM',
        'init.d/rc.error',
        'init.d/rcS',
        (bld.env.PX4_BOOTLOADER, 'bootloader/fmu_bl.bin'),
    )

    romfs_src = bld.srcnode.find_dir(bld.env.PX4_ROMFS_SRC)
    romfs_bld = bld.bldnode.make_node(bld.env.PX4_ROMFS_BLD)

    for item in file_list:
        if isinstance(item, str):
            src = romfs_src.make_node(item)
            dst = romfs_bld.make_node(item)
        else:
            src = bld.srcnode.make_node(item[0])
            dst = romfs_bld.make_node(item[1])

        dst.parent.mkdir()
        self.create_task('px4_copy', src, dst)

def configure(cfg):
    cfg.env.CMAKE_MIN_VERSION = '3.2'
    cfg.load('cmake')
    cfg.find_program('cp')

    bldnode = cfg.bldnode.make_node(cfg.variant)
    env = cfg.env

    env.AP_PROGRAM_FEATURES += ['px4_ap_program']
    env.AP_STLIB_FEATURES += ['px4_ap_stlib']

    def srcpath(path):
        return cfg.srcnode.make_node(path).abspath()

    def bldpath(path):
        return bldnode.make_node(path).abspath()

    if env.PX4_VERSION == '1':
        bootloader_name = 'px4fmu_bl.bin'
    else:
        bootloader_name = 'px4fmuv%s_bl.bin' % env.get_flat('PX4_VERSION')

    # TODO: we should move stuff from mk/PX4 to Tools/ardupilotwaf/px4 after
    # stop using the make-based build system
    env.PX4_ROMFS_SRC = 'mk/PX4/ROMFS'
    env.PX4_ROMFS_BLD = 'px4-extra-files/ROMFS'
    env.PX4_BOOTLOADER = 'mk/PX4/bootloader/%s' % bootloader_name

    env.PX4_ADD_GIT_HASHES = srcpath('Tools/scripts/add_git_hashes.py')
    env.PX4_APM_ROOT = srcpath('')
    env.PX4_ROOT = srcpath('modules/PX4Firmware')
    env.PX4_NUTTX_ROOT = srcpath('modules/PX4NuttX')
    env.PX4_UAVCAN_ROOT = srcpath('modules/uavcan')

    if env.PX4_USE_PX4IO:
        env.PX4IO_ELF_DEST = 'px4-extra-files/px4io'

    env.PX4_CMAKE_VARS = dict(
        CONFIG='nuttx_px4fmu-v%s_apm' % env.get_flat('PX4_VERSION'),
        CMAKE_MODULE_PATH=srcpath('Tools/ardupilotwaf/px4/cmake'),
        UAVCAN_LIBUAVCAN_PATH=env.PX4_UAVCAN_ROOT,
        NUTTX_SRC=env.PX4_NUTTX_ROOT,
        PX4_NUTTX_ROMFS=bldpath(env.PX4_ROMFS_BLD),
        ARDUPILOT_BUILD='YES',
        EXTRA_CXX_FLAGS=' '.join((
            # NOTE: these "-Wno-error=*" flags should be removed as we update
            # the submodule
            '-Wno-error=double-promotion',
            '-Wno-error=reorder',
            # NOTE: *Temporarily* using this definition so that both
            # PX4Firmware build systems (cmake and legacy make-based) can live
            # together
            '-DCMAKE_BUILD',
            '-DARDUPILOT_BUILD',
            '-I%s' % bldpath('libraries/GCS_MAVLink'),
            '-I%s' % bldpath('libraries/GCS_MAVLink/include/mavlink'),
            '-Wl,--gc-sections',
        )),
        EXTRA_C_FLAGS=' '.join((
            # NOTE: *Temporarily* using this definition so that both
            # PX4Firmware build systems (cmake and legacy make-based) can live
            # together
            '-DCMAKE_BUILD',
        )),
    )

def build(bld):
    version = bld.env.get_flat('PX4_VERSION')
    px4 = bld.cmake(
        name='px4',
        cmake_src=bld.srcnode.find_dir('modules/PX4Firmware'),
        cmake_vars=bld.env.PX4_CMAKE_VARS,
    )

    px4.build(
        'msg_gen',
        group='dynamic_sources',
        cmake_output_patterns='src/modules/uORB/topics/*.h',
    )
    px4.build(
        'prebuild_targets',
        group='dynamic_sources',
        cmake_output_patterns='px4fmu-v%s/NuttX/nuttx-export/**/*.h' % version,
    )

    bld(
        name='px4_romfs_static_files',
        group='dynamic_sources',
        features='_px4_romfs',
    )

    bld.extra_build_summary = _extra_build_summary

def _extra_build_summary(bld, build_summary):
    build_summary.text('')
    build_summary.text('PX4')
    build_summary.text('', '''
The ELF files are pointed by the path in the "%s" column. The .px4 files are in
the same directory of their corresponding ELF files.
''' % build_summary.header_text['target'])

    if not bld.options.upload:
        build_summary.text('')
        build_summary.text('', '''
You can use the option --upload to upload the firmware to the PX4 board if you
have one connected.''')

    if bld.env.PX4_USE_PX4IO:
        build_summary.text('')
        build_summary.text('PX4IO')
        summary_data_list = bld.size_summary([bld.env.PX4IO_ELF_DEST])
        header = bld.env.BUILD_SUMMARY_HEADER[:]
        try:
            header.remove('target')
        except ValueError:
            pass
        header.insert(0, 'binary_path')
        build_summary.print_table(summary_data_list, header)
