#!/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(';')

    _dynamic_env_data['DEFINES'] = [
        'NUTTX_GIT_VERSION="%s"' % bld.git_submodule_head_hash('PX4NuttX')[:8],
        'PX4_GIT_VERSION="%s"' % bld.git_submodule_head_hash('PX4Firmware')[:8],
    ]

@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'])
    self.env.append_value('DEFINES', _dynamic_env_data['DEFINES'])

# 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_lib(Task.Task):
    run_str = '${CP} ${SRC} ${PX4_AP_PROGRAM_LIB}'
    color = 'CYAN'

    def runnable_status(self):
        status = super(px4_copy_lib, self).runnable_status()
        if status != Task.SKIP_ME:
            return status

        pseudo_target = self.get_pseudo_target()
        try:
            if pseudo_target.sig != self.inputs[0].sig:
                status = Task.RUN_ME
        except AttributeError:
            status = Task.RUN_ME

        return status

    def get_pseudo_target(self):
        if hasattr(self, 'pseudo_target'):
            return self.pseudo_target

        bldnode = self.generator.bld.bldnode
        self.pseudo_target = bldnode.make_node(self.env.PX4_AP_PROGRAM_LIB)
        return self.pseudo_target

    def keyword(self):
        return 'PX4: Copying program library'

    def post_run(self):
        super(px4_copy_lib, self).post_run()
        pseudo_target = self.get_pseudo_target()
        pseudo_target.sig = pseudo_target.cache_sig = self.inputs[0].sig

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):
    original_post_run = fw_task.post_run
    def post_run():
        original_post_run()
        firmware.sig = firmware.cache_sig = Utils.h_file(firmware.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')

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

        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'

    cp_lib = self.create_task('px4_copy_lib', self.link_task.outputs)
    # we need to synchronize because the path PX4_AP_PROGRAM_LIB is used by all
    # ap_programs
    if _firmware_semaphorish_tasks:
        for t in _firmware_semaphorish_tasks:
            cp_lib.set_run_after(t)
    _firmware_semaphorish_tasks = []

    fw_task = self.create_cmake_build_task(
        'px4',
        'build_firmware_px4fmu-v%s' % version,
    )
    fw_task.set_run_after(cp_lib)
    if self.env.PX4_USE_PX4IO and _cp_px4io.generator is self:
        fw_task.set_run_after(_cp_px4io)

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

    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)

    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.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

    program_lib_name = cfg.env.cxxstlib_PATTERN % 'ap_program'
    env.PX4_AP_PROGRAM_LIB = os.path.join('px4-extra-files', program_lib_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')

    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),
        APM_PROGRAM_LIB=bldpath(env.PX4_AP_PROGRAM_LIB),
        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',
            '-I%s' % bldpath('libraries/GCS_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(
        features='cmake_configure',
        name='px4',
        cmake_src=bld.srcnode.find_dir('modules/PX4Firmware'),
        cmake_vars=bld.env.PX4_CMAKE_VARS,
        group='dynamic_sources',
    )

    px4.cmake_build(
        'msg_gen',
        group='dynamic_sources',
        cmake_output_patterns='src/modules/uORB/topics/*.h',
    )
    px4.cmake_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',
    )
