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

# Cubesat Space Protocol - A small network-layer protocol designed for Cubesats
# Copyright (C) 2012 GomSpace ApS (http://www.gomspace.com)
# Copyright (C) 2012 AAUSAT3 Project (http://aausat3.space.aau.dk) 
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

import os

APPNAME = 'libcsp'
VERSION = '1.5'

top = '.'
out = 'build'

def options(ctx):
    # Load GCC options
    ctx.load('gcc')
    
    ctx.add_option('--toolchain', default=None, help='Set toolchain prefix')

    # Set libcsp options
    gr = ctx.add_option_group('libcsp options')
    gr.add_option('--includes', default='', help='Add additional include paths. Separate with comma')
    gr.add_option('--install-csp', action='store_true', help='Installs CSP headers and lib')

    gr.add_option('--disable-output', action='store_true', help='Disable CSP output')
    gr.add_option('--disable-stlib', action='store_true', help='Build objects only')
    gr.add_option('--enable-rdp', action='store_true', help='Enable RDP support')
    gr.add_option('--enable-qos', action='store_true', help='Enable Quality of Service support')
    gr.add_option('--enable-promisc', action='store_true', help='Enable promiscuous mode support')
    gr.add_option('--enable-crc32', action='store_true', help='Enable CRC32 support')
    gr.add_option('--enable-hmac', action='store_true', help='Enable HMAC-SHA1 support')
    gr.add_option('--enable-xtea', action='store_true', help='Enable XTEA support')
    gr.add_option('--enable-bindings', action='store_true', help='Enable Python bindings')
    gr.add_option('--enable-python3-bindings', action='store_true', help='Enable Python3 bindings')
    gr.add_option('--enable-examples', action='store_true', help='Enable examples')
    gr.add_option('--enable-dedup', action='store_true', help='Enable packet deduplicator')

    # Interfaces
    gr.add_option('--enable-if-i2c', action='store_true', help='Enable I2C interface')
    gr.add_option('--enable-if-kiss', action='store_true', help='Enable KISS/RS.232 interface')
    gr.add_option('--enable-if-can', action='store_true', help='Enable CAN interface')
    gr.add_option('--enable-if-zmqhub', action='store_true', help='Enable ZMQHUB interface')
    
    # Drivers
    gr.add_option('--enable-can-socketcan', action='store_true', help='Enable Linux socketcan driver')
    gr.add_option('--with-driver-usart', default=None, metavar='DRIVER', help='Build USART driver. [windows, linux, None]')

    # OS
    gr.add_option('--with-os', metavar='OS', default='posix', help='Set operating system. Must be either \'posix\', \'macosx\', \'windows\' or \'freertos\'')
    gr.add_option('--enable-init-shutdown', action='store_true', help='Use init system commands for shutdown/reboot')

    # Options
    gr.add_option('--with-rdp-max-window', metavar='SIZE', type=int, default=20, help='Set maximum window size for RDP')
    gr.add_option('--with-max-bind-port', metavar='PORT', type=int, default=31, help='Set maximum bindable port')
    gr.add_option('--with-max-connections', metavar='COUNT', type=int, default=10, help='Set maximum number of concurrent connections')
    gr.add_option('--with-conn-queue-length', metavar='SIZE', type=int, default=100, help='Set maximum number of packets in queue for a connection')
    gr.add_option('--with-router-queue-length', metavar='SIZE', type=int, default=10, help='Set maximum number of packets to be queued at the input of the router')
    gr.add_option('--with-padding', metavar='BYTES', type=int, default=8, help='Set padding bytes before packet length field')
    gr.add_option('--with-loglevel', metavar='LEVEL', default='debug', help='Set minimum compile time log level. Must be one of \'error\', \'warn\', \'info\' or \'debug\'')
    gr.add_option('--with-rtable', metavar='TABLE', default='static', help='Set routing table type')
    gr.add_option('--with-connection-so', metavar='CSP_SO', type=int, default='0x0000', help='Set outgoing connection socket options, see csp.h for valid values')
    gr.add_option('--with-bufalign', metavar='BYTES', type=int, help='Set buffer alignment')

def configure(ctx):
    # Validate OS
    if not ctx.options.with_os in ('posix', 'windows', 'freertos', 'macosx'):
        ctx.fatal('--with-os must be either \'posix\', \'windows\', \'macosx\' or \'freertos\'')

    # Validate USART drivers
    if not ctx.options.with_driver_usart in (None, 'windows', 'linux'):
        ctx.fatal('--with-driver-usart must be either \'windows\' or \'linux\'')

    if not ctx.options.with_loglevel in ('error', 'warn', 'info', 'debug'):
        ctx.fatal('--with-loglevel must be either \'error\', \'warn\', \'info\' or \'debug\'')

    # Setup and validate toolchain
    if (len(ctx.stack_path) <= 1) and ctx.options.toolchain:
        ctx.env.CC = ctx.options.toolchain + 'gcc'
        ctx.env.AR = ctx.options.toolchain + 'ar'

    ctx.load('gcc')

    # Set git revision define
    git_rev = os.popen('git describe --always 2> /dev/null || echo unknown').read().strip()

    # Setup DEFINES
    ctx.define('GIT_REV', git_rev)

    # Set build output format
    ctx.env.FEATURES = ['c']
    if not ctx.options.disable_stlib:
        ctx.env.FEATURES += ['cstlib']

    # Setup CFLAGS
    if (len(ctx.stack_path) <= 1) and (len(ctx.env.CFLAGS) == 0):
        ctx.env.prepend_value('CFLAGS', ["-std=gnu99", "-g", "-Os", "-Wall", "-Wextra", "-Wshadow", "-Wcast-align", "-Wwrite-strings", "-Wno-unused-parameter"])

    # Setup extra includes
    ctx.env.append_unique('INCLUDES_CSP', ['include'] + ctx.options.includes.split(','))

    # Add default files
    ctx.env.append_unique('FILES_CSP', ['src/*.c','src/interfaces/csp_if_lo.c','src/transport/csp_udp.c','src/arch/{0}/**/*.c'.format(ctx.options.with_os)])
    
    # Store OS as env variable
    ctx.env.append_unique('OS', ctx.options.with_os)

    # Libs
    if 'posix' in ctx.env.OS:
        ctx.env.append_unique('LIBS', ['rt', 'pthread', 'util'])
    elif 'macosx' in ctx.env.OS:
        ctx.env.append_unique('LIBS', ['pthread'])

    # Check for recursion
    if ctx.path == ctx.srcnode:
        ctx.options.install_csp = True
    
    # Windows build flags
    if ctx.options.with_os == 'windows':
        ctx.env.append_unique('CFLAGS', ['-D_WIN32_WINNT=0x0600'])

    ctx.define_cond('CSP_FREERTOS', ctx.options.with_os == 'freertos')
    ctx.define_cond('CSP_POSIX', ctx.options.with_os == 'posix')
    ctx.define_cond('CSP_WINDOWS', ctx.options.with_os == 'windows')
    ctx.define_cond('CSP_MACOSX', ctx.options.with_os == 'macosx')
        
    # Add CAN driver
    if ctx.options.enable_can_socketcan:
        ctx.env.append_unique('FILES_CSP', 'src/drivers/can/can_socketcan.c')

    # Add USART driver
    if ctx.options.with_driver_usart != None:
        ctx.env.append_unique('FILES_CSP', 'src/drivers/usart/usart_{0}.c'.format(ctx.options.with_driver_usart))
        
    # Interfaces
    if ctx.options.enable_if_can:
        ctx.env.append_unique('FILES_CSP', 'src/interfaces/csp_if_can.c')
        ctx.env.append_unique('FILES_CSP', 'src/interfaces/csp_if_can_pbuf.c')
    if ctx.options.enable_if_i2c:
        ctx.env.append_unique('FILES_CSP', 'src/interfaces/csp_if_i2c.c')
    if ctx.options.enable_if_kiss:
        ctx.env.append_unique('FILES_CSP', 'src/interfaces/csp_if_kiss.c')
    if ctx.options.enable_if_zmqhub:
        ctx.env.append_unique('FILES_CSP', 'src/interfaces/csp_if_zmqhub.c')
        ctx.check_cfg(package='libzmq', args='--cflags --libs')
        ctx.env.append_unique('LIBS', ctx.env.LIB_LIBZMQ)

    # Store configuration options
    ctx.env.ENABLE_BINDINGS = ctx.options.enable_bindings
    ctx.env.ENABLE_EXAMPLES = ctx.options.enable_examples

    # Check for python development
    if ctx.options.enable_bindings:
        ctx.env.LIBCSP_PYTHON2 = ctx.check_cfg(package='python2', args='--cflags --libs', atleast_version='2.7', mandatory=False)
        if ctx.options.enable_python3_bindings:
            ctx.env.LIBCSP_PYTHON3 = ctx.check_cfg(package='python3', args='--cflags --libs', atleast_version='3.5', mandatory=False)

    # Create config file
    if not ctx.options.disable_output:
        ctx.env.append_unique('FILES_CSP', 'src/csp_debug.c')
    else:
        ctx.env.append_unique('EXCL_CSP', 'src/csp_debug.c')

    if ctx.options.enable_rdp:
        ctx.env.append_unique('FILES_CSP', 'src/transport/csp_rdp.c')

    if ctx.options.enable_crc32:
        ctx.env.append_unique('FILES_CSP', 'src/csp_crc32.c')
    else:
        ctx.env.append_unique('EXCL_CSP', 'src/csp_crc32.c')

    if not ctx.options.enable_dedup:
        ctx.env.append_unique('EXCL_CSP', 'src/csp_dedup.c')

    if ctx.options.enable_hmac:
        ctx.env.append_unique('FILES_CSP', 'src/crypto/csp_hmac.c')
        ctx.env.append_unique('FILES_CSP', 'src/crypto/csp_sha1.c')

    if ctx.options.enable_xtea:
        ctx.env.append_unique('FILES_CSP', 'src/crypto/csp_xtea.c')
        ctx.env.append_unique('FILES_CSP', 'src/crypto/csp_sha1.c')
        
    ctx.env.append_unique('FILES_CSP', 'src/rtable/csp_rtable_' + ctx.options.with_rtable  + '.c')

    ctx.define_cond('CSP_DEBUG', not ctx.options.disable_output)
    ctx.define_cond('CSP_USE_RDP', ctx.options.enable_rdp)
    ctx.define_cond('CSP_USE_CRC32', ctx.options.enable_crc32)
    ctx.define_cond('CSP_USE_HMAC', ctx.options.enable_hmac)
    ctx.define_cond('CSP_USE_XTEA', ctx.options.enable_xtea)
    ctx.define_cond('CSP_USE_PROMISC', ctx.options.enable_promisc)
    ctx.define_cond('CSP_USE_QOS', ctx.options.enable_qos)
    ctx.define_cond('CSP_USE_DEDUP', ctx.options.enable_dedup)
    ctx.define_cond('CSP_USE_INIT_SHUTDOWN', ctx.options.enable_init_shutdown)
    ctx.define_cond('CSP_USE_CAN', ctx.options.enable_if_can)
    ctx.define_cond('CSP_USE_I2C', ctx.options.enable_if_i2c)
    ctx.define_cond('CSP_USE_KISS', ctx.options.enable_if_kiss)
    ctx.define_cond('CSP_USE_ZMQHUB', ctx.options.enable_if_zmqhub)
    ctx.define('CSP_CONN_MAX', ctx.options.with_max_connections)
    ctx.define('CSP_CONN_QUEUE_LENGTH', ctx.options.with_conn_queue_length)
    ctx.define('CSP_FIFO_INPUT', ctx.options.with_router_queue_length)
    ctx.define('CSP_MAX_BIND_PORT', ctx.options.with_max_bind_port)
    ctx.define('CSP_RDP_MAX_WINDOW', ctx.options.with_rdp_max_window)
    ctx.define('CSP_PADDING_BYTES', ctx.options.with_padding)
    ctx.define('CSP_CONNECTION_SO', ctx.options.with_connection_so)
    
    if ctx.options.with_bufalign != None:
        ctx.define('CSP_BUFFER_ALIGN', ctx.options.with_bufalign)

    # Set logging level
    ctx.define_cond('CSP_LOG_LEVEL_DEBUG', ctx.options.with_loglevel in ('debug'))
    ctx.define_cond('CSP_LOG_LEVEL_INFO', ctx.options.with_loglevel in ('debug', 'info'))
    ctx.define_cond('CSP_LOG_LEVEL_WARN', ctx.options.with_loglevel in ('debug', 'info', 'warn'))
    ctx.define_cond('CSP_LOG_LEVEL_ERROR', ctx.options.with_loglevel in ('debug', 'info', 'warn', 'error'))

    # Check compiler endianness
    endianness = ctx.check_endianness()
    ctx.define_cond('CSP_LITTLE_ENDIAN', endianness == 'little')
    ctx.define_cond('CSP_BIG_ENDIAN', endianness == 'big')

    # Check for stdbool.h
    ctx.check_cc(header_name='stdbool.h', mandatory=False, define_name='CSP_HAVE_STDBOOL_H', type='cstlib')

    # Check for libsocketcan.h
    if ctx.options.enable_if_can and ctx.options.enable_can_socketcan:
        have_socketcan = ctx.check_cc(lib='socketcan', mandatory=False, define_name='CSP_HAVE_LIBSOCKETCAN')
        if have_socketcan:
            ctx.env.append_unique('LIBS', ['socketcan'])

    ctx.define('LIBCSP_VERSION', VERSION)

    ctx.write_config_header('include/csp/csp_autoconfig.h')
    
def build(ctx):

    # Set install path for header files
    install_path = False
    if ctx.options.install_csp:
        install_path = '${PREFIX}/lib'
        ctx.install_files('${PREFIX}/include/csp', ctx.path.ant_glob('include/csp/*.h'))
        ctx.install_files('${PREFIX}/include/csp/interfaces', 'include/csp/interfaces/csp_if_lo.h')

        if 'src/interfaces/csp_if_can.c' in ctx.env.FILES_CSP:
            ctx.install_files('${PREFIX}/include/csp/interfaces', 'include/csp/interfaces/csp_if_can.h')
        if 'src/interfaces/csp_if_i2c.c' in ctx.env.FILES_CSP:
            ctx.install_files('${PREFIX}/include/csp/interfaces', 'include/csp/interfaces/csp_if_i2c.h')
        if 'src/interfaces/csp_if_kiss.c' in ctx.env.FILES_CSP:
            ctx.install_files('${PREFIX}/include/csp/interfaces', 'include/csp/interfaces/csp_if_kiss.h')
        if 'src/interfaces/csp_if_zmqhub.c' in ctx.env.FILES_CSP:
            ctx.install_files('${PREFIX}/include/csp/interfaces', 'include/csp/interfaces/csp_if_zmqhub.h')
        if 'src/drivers/usart/usart_{0}.c'.format(ctx.options.with_driver_usart) in ctx.env.FILES_CSP:
            ctx.install_as('${PREFIX}/include/csp/drivers/usart.h', 'include/csp/drivers/usart.h')            
        if 'src/drivers/can/can_socketcan.c' in ctx.env.FILES_CSP:
            ctx.install_as('${PREFIX}/include/csp/drivers/can_socketcan.h', 'include/csp/drivers/can_socketcan.h')

        ctx.install_files('${PREFIX}/include/csp', 'include/csp/csp_autoconfig.h', cwd=ctx.bldnode)

    ctx(export_includes='include', name='csp_h')

    ctx(features=ctx.env.FEATURES,
        source=ctx.path.ant_glob(ctx.env.FILES_CSP, excl=ctx.env.EXCL_CSP),
        target = 'csp',
        includes= ctx.env.INCLUDES_CSP,
        export_includes = ctx.env.INCLUDES_CSP,
        use = 'include freertos_h',
        install_path = install_path,
    )

    # Build shared library for Python bindings
    if ctx.env.ENABLE_BINDINGS:
        ctx.shlib(source = ctx.path.ant_glob(ctx.env.FILES_CSP, excl=ctx.env.EXCL_CSP),
                  name = 'csp_shlib',
                  target = 'csp',
                  includes = ctx.env.INCLUDES_CSP,
                  export_includes = 'include',
                  use = ['include'],
                  lib = ctx.env.LIBS)

        # python3 bindings
        if ctx.env.LIBCSP_PYTHON3:
            ctx.shlib(source = ['src/bindings/python/pycsp.c'],
                      target = 'csp_py3',
                      includes = ctx.env.INCLUDES_CSP + ctx.env.INCLUDES_PYTHON3,
                      export_includes = 'include',
                      use = ['csp_shlib', 'include'],
                      lib = ctx.env.LIBS)

        # python2 bindings
        if ctx.env.LIBCSP_PYTHON2:
            ctx.shlib(source = ['src/bindings/python/pycsp.c'],
                      target = 'csp_py2',
                      includes = ctx.env.INCLUDES_CSP + ctx.env.INCLUDES_PYTHON2,
                      export_includes = 'include',
                      use = ['csp_shlib', 'include'],
                      lib = ctx.env.LIBS)

    if ctx.env.ENABLE_EXAMPLES:
        ctx.program(source = ctx.path.ant_glob('examples/simple.c'),
            target = 'simple',
            includes = ctx.env.INCLUDES_CSP,
            lib = ctx.env.LIBS,
            use = 'csp')

        if ctx.options.enable_if_kiss:
            ctx.program(source = 'examples/kiss.c',
                target = 'kiss',
                includes = ctx.env.INCLUDES_CSP,
                lib = ctx.env.LIBS,
                use = 'csp')

        if ctx.options.enable_if_zmqhub:
            ctx.program(source = 'examples/zmqproxy.c',
                        target = 'zmqproxy',
                        includes = ctx.env.INCLUDES_CSP,
                        lib = ctx.env.LIBS,
                        use = 'csp')

        if 'posix' in ctx.env.OS:
            ctx.program(source = 'examples/csp_if_fifo.c',
                target = 'fifo',
                includes = ctx.env.INCLUDES_CSP,
                lib = ctx.env.LIBS,
                use = 'csp')

        if 'windows' in ctx.env.OS:
            ctx.program(source = ctx.path.ant_glob('examples/csp_if_fifo_windows.c'),
                target = 'csp_if_fifo',
                includes = ctx.env.INCLUDES_CSP,
                use = 'csp')

def dist(ctx):
    ctx.excl = 'build/* **/.* **/*.pyc **/*.o **/*~ *.tar.gz'