#!/usr/bin/env python
# Licensed under the GNU GPL v3 or later, see COPYING file for details.
# Copyright 2008-2013 David Robillard
# Copyright 2008 Nedko Arnaudov

import os

from waflib import Build, Logs, Options, Utils
from waflib.extras import autowaf

# Version of this package (even if built as a child)
PATCHAGE_VERSION = '1.0.3'

# Variables for 'waf dist'
APPNAME = 'patchage'
VERSION = PATCHAGE_VERSION

# Mandatory variables
top = '.'
out = 'build'

# Release variables
uri          = 'http://drobilla.net/sw/patchage'
dist_pattern = 'http://download.drobilla.net/patchage-%d.%d.%d.tar.bz2'
post_tags    = ['Hacking', 'LAD', 'Patchage']


def options(ctx):
    ctx.load('compiler_cxx')

    opt = ctx.configuration_options()
    opt.add_option('--patchage-install-name', type='string',
                   default=APPNAME,
                   help='patchage install name')
    opt.add_option('--patchage-human-name', type='string',
                   default='Patchage',
                   help='patchage human name')

    ctx.add_flags(
        opt,
        {'jack-dbus':   'use Jack via D-Bus',
         'no-alsa':     'do not build Alsa Sequencer support',
         'no-binloc':   'do not find files from executable location',
         'light-theme': 'use light coloured theme'})


def configure(conf):
    conf.load('compiler_cxx', cache=True)
    conf.load('autowaf', cache=True)
    autowaf.set_cxx_lang(conf, 'c++11')

    if Options.options.strict:
        # Check for programs used by lint target
        conf.find_program("flake8", var="FLAKE8", mandatory=False)
        conf.find_program("clang-tidy", var="CLANG_TIDY", mandatory=False)
        conf.find_program("iwyu_tool", var="IWYU_TOOL", mandatory=False)

    if Options.options.ultra_strict:
        autowaf.add_compiler_flags(conf.env, '*', {
            'clang': [
                '-Wno-alloca',
                '-Wno-cast-qual',
                '-Wno-double-promotion',
                '-Wno-float-conversion',
                '-Wno-float-equal',
                '-Wno-implicit-float-conversion',
                '-Wno-padded',
                '-Wno-pedantic',
                '-Wno-shorten-64-to-32',
                '-Wno-sign-conversion',
            ],
            'gcc': [
                '-Wno-alloca',
                '-Wno-conversion',
                '-Wno-float-equal',
                '-Wno-padded',
                '-Wno-pedantic',
                '-Wno-stack-protector',
                '-Wno-suggest-attribute=noreturn',
            ],
        })

        autowaf.add_compiler_flags(conf.env, 'c', {
            'clang': [
                '-Wno-bad-function-cast',
                '-Wno-missing-noreturn',
            ],
            'gcc': [
                '-Wno-bad-function-cast',
            ],
        })

        autowaf.add_compiler_flags(conf.env, 'cxx', {
            'clang': [
                '-Wno-weak-vtables',
            ],
            'gcc': [
                '-Wno-conditionally-supported',
                '-Wno-effc++',
            ],
        })

    conf.check_pkg('dbus-1',
                   uselib_store='DBUS',
                   system=True,
                   mandatory=False)

    conf.check_pkg('dbus-glib-1',
                   uselib_store='DBUS_GLIB',
                   system=True,
                   mandatory=False)

    conf.check_pkg('gthread-2.0 >= 2.14.0',
                   system=True,
                   uselib_store='GTHREAD')

    conf.check_pkg('glibmm-2.4 >= 2.14.0',
                   system=True,
                   uselib_store='GLIBMM')

    conf.check_pkg('gtkmm-2.4 >= 2.12.0',
                   system=True,
                   uselib_store='GTKMM')

    conf.check_pkg('ganv-1 >= 1.5.2', uselib_store='GANV')

    if conf.env.DEST_OS == 'darwin':
        conf.check_pkg('gtk-mac-integration',
                       uselib_store='GTK_OSX',
                       system=True,
                       mandatory=False)
        if conf.env.HAVE_GTK_OSX:
            conf.define('PATCHAGE_GTK_OSX', 1)

    # Check for dladdr
    conf.check_function('cxx', 'dladdr',
                        header_name = 'dlfcn.h',
                        defines     = ['_GNU_SOURCE'],
                        lib         = ['dl'],
                        define_name = 'HAVE_DLADDR',
                        return_type = 'int',
                        arg_types   = 'const void*,Dl_info*',
                        mandatory   = False)

    # Use Jack D-Bus if requested (only one jack driver is allowed)
    use_jack_dbus = (Options.options.jack_dbus and
                     conf.env.HAVE_DBUS and
                     conf.env.HAVE_DBUS_GLIB)

    if use_jack_dbus:
        conf.define('HAVE_JACK_DBUS', 1)
    else:
        conf.check_pkg('jack >= 0.120.0',
                       uselib_store='JACK',
                       system=True,
                       mandatory=False)

        if conf.env.HAVE_JACK:
            conf.define('PATCHAGE_LIBJACK', 1)

            conf.check_function('cxx', 'jack_get_property',
                                header_name = 'jack/metadata.h',
                                define_name = 'HAVE_JACK_METADATA',
                                uselib      = 'JACK',
                                return_type = 'int',
                                arg_types   = '''jack_uuid_t,
                                                     const char*,
                                                     char**,
                                                     char**''',
                                mandatory   = False)

    # Use Alsa if present unless --no-alsa
    if not Options.options.no_alsa:
        conf.check_pkg('alsa',
                       uselib_store='ALSA',
                       system=True,
                       mandatory=False)

    # Find files at binary location if we have dladdr unless --no-binloc
    if not Options.options.no_binloc and conf.is_defined('HAVE_DLADDR'):
        conf.define('PATCHAGE_BINLOC', 1)

    if Options.options.light_theme:
        conf.define('PATCHAGE_USE_LIGHT_THEME', 1)

    # Boost headers
    conf.check_cxx(header_name='boost/optional/optional.hpp')
    conf.check_cxx(header_name='boost/variant.hpp')

    # Check for system provided fmt
    conf.check_pkg('fmt',
                   uselib_store='FMT',
                   system=True,
                   mandatory=False)

    if not conf.env.HAVE_FMT:
        # Fall back to minimal bundled version if not found
        Logs.warn("System fmt library not found, using bundled copy")
        include_dir = conf.path.find_node('subprojects/fmt/include')
        conf.define('FMT_HEADER_ONLY', 1)
        conf.env.LIB_FMT = []
        conf.env.INCLUDES_FMT = [include_dir.abspath()]

    conf.env.PATCHAGE_VERSION = PATCHAGE_VERSION

    conf.env.APP_INSTALL_NAME = Options.options.patchage_install_name
    conf.env.APP_HUMAN_NAME = Options.options.patchage_human_name
    conf.define('PATCHAGE_DATA_DIR', os.path.join(
        conf.env.DATADIR, conf.env.APP_INSTALL_NAME))

    conf.define('PATCHAGE_VERSION', PATCHAGE_VERSION)
    conf.write_config_header('patchage_config.h', remove=False)

    autowaf.display_summary(
        conf,
        {'Install name':            conf.env.APP_INSTALL_NAME,
         'App human name':          conf.env.APP_HUMAN_NAME,
         'Jack (D-Bus)':            conf.is_defined('HAVE_JACK_DBUS'),
         'Jack (libjack)':          conf.is_defined('PATCHAGE_LIBJACK'),
         'Jack Metadata':           conf.is_defined('HAVE_JACK_METADATA'),
         'Alsa Sequencer':          bool(conf.env.HAVE_ALSA)})

    if conf.env.DEST_OS == 'darwin':
        autowaf.display_msg(conf, "Mac Integration",
                            bool(conf.env.HAVE_GTK_OSX))


def build(bld):
    out_base = ''
    if bld.env.DEST_OS == 'darwin':
        out_base = 'Patchage.app/Contents/'

    # Program
    prog = bld(features     = 'cxx cxxprogram',
               includes     = ['.', 'src'],
               target       = out_base + bld.env.APP_INSTALL_NAME,
               uselib       = 'DBUS GANV DBUS_GLIB FMT GTKMM GTHREAD GTK_OSX',
               install_path = '${BINDIR}')
    prog.source = '''
            src/Canvas.cpp
            src/CanvasModule.cpp
            src/Configuration.cpp
            src/Connector.cpp
            src/Legend.cpp
            src/Metadata.cpp
            src/Patchage.cpp
            src/TextViewLog.cpp
            src/event_to_string.cpp
            src/handle_event.cpp
            src/main.cpp
    '''
    if bld.is_defined('HAVE_JACK_DBUS'):
        prog.source += ' src/JackDbusDriver.cpp '
    if bld.is_defined('PATCHAGE_LIBJACK'):
        prog.source += ' src/JackLibDriver.cpp '
        prog.uselib += ' JACK NEWJACK '
    if bld.env.HAVE_ALSA:
        prog.source += ' src/AlsaDriver.cpp '
        prog.uselib += ' ALSA '
    if bld.is_defined('PATCHAGE_BINLOC') and bld.is_defined('HAVE_DLADDR'):
        prog.lib = ['dl']

    # XML UI definition
    bld(features         = 'subst',
        source           = 'src/patchage.ui',
        target           = out_base + 'patchage.ui',
        install_path     = '${DATADIR}/' + bld.env.APP_INSTALL_NAME,
        chmod            = Utils.O644,
        PATCHAGE_VERSION = PATCHAGE_VERSION)

    # 'Desktop' file (menu entry, icon, etc)
    bld(features         = 'subst',
        source           = 'patchage.desktop.in',
        target           = 'patchage.desktop',
        install_path     = '${DATADIR}/applications',
        chmod            = Utils.O644,
        BINDIR           = os.path.normpath(bld.env.BINDIR),
        APP_INSTALL_NAME = bld.env.APP_INSTALL_NAME,
        APP_HUMAN_NAME   = bld.env.APP_HUMAN_NAME)

    if bld.env.DEST_OS == 'darwin':
        # Property list
        bld(features         = 'subst',
            source           = 'osx/Info.plist.in',
            target           = out_base + 'Info.plist',
            install_path     = '',
            chmod            = Utils.O644)

        # Icons
        bld(rule   = 'cp ${SRC} ${TGT}',
            source = 'osx/Patchage.icns',
            target = out_base + 'Resources/Patchage.icns')

        # Gtk/Pango/etc configuration files
        for i in ['pangorc', 'pango.modules', 'loaders.cache', 'gtkrc']:
            bld(rule   = 'cp ${SRC} ${TGT}',
                source = 'osx/' + i,
                target = out_base + 'Resources/' + i)

    # Icons
    # After installation, icon cache should be updated using:
    # gtk-update-icon-cache -f -t $(datadir)/icons/hicolor
    icon_sizes = [16, 22, 24, 32, 48, 128, 256]
    for s in icon_sizes:
        d = '%dx%d' % (s, s)
        bld.install_as(
            os.path.join(bld.env.DATADIR, 'icons', 'hicolor', d, 'apps',
                         bld.env.APP_INSTALL_NAME + '.png'),
            os.path.join('icons', d, 'patchage.png'))

    bld.install_as(
        os.path.join(bld.env.DATADIR, 'icons', 'hicolor', 'scalable', 'apps',
                     bld.env.APP_INSTALL_NAME + '.svg'),
        os.path.join('icons', 'scalable', 'patchage.svg'))

    bld.install_files('${MANDIR}/man1', bld.path.ant_glob('doc/*.1'))


class LintContext(Build.BuildContext):
    fun = cmd = 'lint'


def lint(ctx):
    "checks code for style issues"
    import subprocess
    import sys

    st = 0

    if "FLAKE8" in ctx.env:
        Logs.info("Running flake8")
        st = subprocess.call([ctx.env.FLAKE8[0],
                              "wscript",
                              "--ignore",
                              "E221,W504,E251,E241,E741"])
    else:
        Logs.warn("Not running flake8")

    if "IWYU_TOOL" in ctx.env:
        Logs.info("Running include-what-you-use")
        cmd = [ctx.env.IWYU_TOOL[0], "-o", "clang", "-p", "build"]
        output = subprocess.check_output(cmd).decode('utf-8')
        if 'error: ' in output:
            sys.stdout.write(output)
            st += 1
    else:
        Logs.warn("Not running include-what-you-use")

    if "CLANG_TIDY" in ctx.env and "clang" in ctx.env.CXX[0]:
        Logs.info("Running clang-tidy")

        import json

        with open('build/compile_commands.json', 'r') as db:
            commands = json.load(db)
            files = [c['file'] for c in commands]

        procs = []
        for f in files:
            cmd = [ctx.env.CLANG_TIDY[0], '--quiet', '-p=.', f]
            procs += [subprocess.Popen(cmd, cwd='build')]

        for proc in procs:
            proc.communicate()
            st += proc.returncode
    else:
        Logs.warn("Not running clang-tidy")

    if st != 0:
        sys.exit(st)