#!/usr/bin/env python

import os
import sys

from waflib import Logs, Options, TaskGen
from waflib.extras import autowaf

# Library and package version (UNIX style major, minor, micro)
# major increment <=> incompatible changes
# minor increment <=> compatible changes (additions)
# micro increment <=> no interface changes
PUGL_VERSION       = '0.2.0'
PUGL_MAJOR_VERSION = '0'

# Mandatory waf variables
APPNAME = 'pugl'        # Package name for waf dist
VERSION = PUGL_VERSION  # Package version for waf dist
top     = '.'           # Source directory
out     = 'build'       # Build directory


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

    opts = ctx.configuration_options()
    opts.add_option('--target', default=None, dest='target',
                    help='target platform (e.g. "win32" or "darwin")')

    ctx.add_flags(
        opts,
        {'all-headers': 'install complete header implementation',
         'no-gl':       'do not build OpenGL support',
         'no-cairo':    'do not build Cairo support',
         'no-static':   'do not build static library',
         'no-shared':   'do not build shared library',
         'log':         'print GL information to console',
         'grab-focus':  'work around keyboard issues by grabbing focus'})


def configure(conf):
    conf.load('compiler_c', cache=True)
    conf.load('autowaf', cache=True)
    autowaf.set_c_lang(conf, 'c99')

    conf.env.ALL_HEADERS     = Options.options.all_headers
    conf.env.TARGET_PLATFORM = Options.options.target or sys.platform
    platform                 = conf.env.TARGET_PLATFORM

    if platform == 'darwin':
        conf.env.append_unique('CFLAGS', ['-Wno-deprecated-declarations'])

    if conf.env.MSVC_COMPILER:
        conf.env.append_unique('CFLAGS', ['/wd4191'])
    else:
        conf.env.append_value('LINKFLAGS', ['-fvisibility=hidden'])
        conf.env.append_value('CFLAGS', ['-fvisibility=hidden'])
        if Options.options.strict:
            conf.env.append_value('CFLAGS', ['-Wunused-parameter',
                                             '-Wno-pedantic'])

    conf.check_cc(lib='m', uselib_store='M', mandatory=False)
    conf.check_cc(lib='dl', uselib_store='DL', mandatory=False)

    # Check for "native" platform dependencies
    conf.env.HAVE_GL = False
    if platform == 'darwin':
        conf.check_cc(framework_name='Cocoa', framework='Cocoa',
                      uselib_store='COCOA')
        if not Options.options.no_gl:
            conf.check_cc(framework_name='OpenGL', uselib_store='GL',
                          mandatory=False)
            conf.env.HAVE_GL = conf.env.FRAMEWORK_GL

    elif platform == 'win32':
        conf.check_cc(lib='gdi32', uselib_store='GDI32')
        conf.check_cc(lib='user32', uselib_store='USER32')
        if not Options.options.no_gl:
            conf.check_cc(lib='opengl32', uselib_store='GL', mandatory=False)
            conf.env.HAVE_GL = conf.env.LIB_GL

    else:
        conf.check_cc(lib='X11', uselib_store='X11')
        if not Options.options.no_gl:
            glx_fragment = """#include <GL/glx.h>
                int main(void) { glXSwapBuffers(0, 0); return 0; }"""

            conf.check_cc(lib='GLX', uselib_store='GLX', mandatory=False)
            conf.check_cc(lib='GL', uselib_store='GL', mandatory=False)
            conf.check_cc(fragment=glx_fragment,
                          lib='GLX' if conf.env.LIB_GLX else 'GL',
                          mandatory=False,
                          msg='Checking for GLX')
            conf.env.HAVE_GL = conf.env.LIB_GL or conf.env.LIB_GLX

    # Check for Cairo via pkg-config
    if not Options.options.no_cairo:
        autowaf.check_pkg(conf, 'cairo',
                          uselib_store    = 'CAIRO',
                          atleast_version = '1.0.0',
                          mandatory       = False)

    if Options.options.log:
        conf.define('PUGL_VERBOSE', 1)

    conf.env.update({
        'BUILD_SHARED': not Options.options.no_shared,
        'BUILD_STATIC': conf.env.BUILD_TESTS or not Options.options.no_static})

    autowaf.set_lib_env(conf, 'pugl', PUGL_VERSION)
    conf.write_config_header('pugl_config.h', remove=False)

    autowaf.display_summary(
        conf,
        {"Build static library":   bool(conf.env.BUILD_STATIC),
         "Build shared library":   bool(conf.env.BUILD_SHARED),
         "OpenGL support":         bool(conf.env.HAVE_GL),
         "Cairo support":          bool(conf.env.HAVE_CAIRO),
         "Verbose console output": conf.is_defined('PUGL_VERBOSE')})


def _build_pc_file(bld, name, desc, target, libname, deps={}, requires=[]):
    "Builds a pkg-config file for a library"
    env = bld.env
    prefix = env.PREFIX
    xprefix = os.path.dirname(env.LIBDIR)

    uselib   = deps.get('uselib', [])
    pkg_deps = [l for l in uselib if 'PKG_' + l.lower() in env]
    lib_deps = [l for l in uselib if 'PKG_' + l.lower() not in env]

    link_flags = [env.LIB_ST % l for l in (deps.get('lib', []) + [libname])]
    for l in lib_deps:
        link_flags += [env.LIB_ST % l for l in env['LIB_' + l]]
    for f in deps.get('framework', []):
        link_flags += ['-framework', f]

    bld(features='subst',
        source='pugl.pc.in',
        target='%s-%s.pc' % (target, PUGL_MAJOR_VERSION),
        install_path=os.path.join(env.LIBDIR, 'pkgconfig'),

        PREFIX=prefix,
        EXEC_PREFIX='${prefix}' if xprefix == prefix else xprefix,
        LIBDIR='${exec_prefix}/' + os.path.basename(env.LIBDIR),
        INCLUDEDIR=env.INCLUDEDIR.replace(prefix, '${prefix}', 1),

        NAME=name,
        DESCRIPTION=desc,
        PUGL_MAJOR_VERSION=PUGL_MAJOR_VERSION,
        REQUIRES=' '.join(requires + [p.lower() for p in pkg_deps]),
        LIBS=' '.join(link_flags))


def build(bld):
    # C Headers
    includedir = '${INCLUDEDIR}/pugl-%s/pugl' % PUGL_MAJOR_VERSION
    bld.install_files(includedir, bld.path.ant_glob('pugl/*.h'))
    bld.install_files(includedir, bld.path.ant_glob('pugl/*.hpp'))
    if bld.env.ALL_HEADERS:
        detaildir = os.path.join(includedir, 'detail')
        bld.install_files(detaildir, bld.path.ant_glob('pugl/detail/*.h'))
        bld.install_files(detaildir, bld.path.ant_glob('pugl/detail/*.c'))

    # Library dependencies of pugl libraries (for buiding tests)
    deps = {}

    def build_pugl_lib(name, **kwargs):
        deps[name] = {}
        for k in ('lib', 'framework', 'uselib'):
            deps[name][k] = kwargs.get(k, [])

        args = kwargs.copy()
        args.update({'includes':        ['.'],
                     'export_includes': ['.'],
                     'install_path':    '${LIBDIR}',
                     'vnum':            PUGL_VERSION})

        if bld.env.BUILD_SHARED:
            bld(features = 'c cshlib',
                name     = name,
                target   = 'pugl_' + name,
                cflags   = ['-DPUGL_INTERNAL', '-DPUGL_SHARED'],
                **args)

        if bld.env.BUILD_STATIC:
            bld(features = 'c cstlib',
                name     = 'pugl_%s_static' % name,
                target   = 'pugl_' + name,
                cflags   = ['-DPUGL_INTERNAL'],
                **args)

    def build_platform(platform, **kwargs):
        build_pugl_lib(platform, **kwargs)
        _build_pc_file(bld, 'Pugl', 'Pugl GUI library core',
                       'pugl', 'pugl_%s' % platform,
                       deps=kwargs)

    def build_backend(platform, backend, **kwargs):
        name  = '%s_%s' % (platform, backend)
        label = 'OpenGL' if backend == 'gl' else backend.title()
        build_pugl_lib(name, **kwargs)
        _build_pc_file(bld, 'Pugl %s' % label,
                       'Pugl GUI library with %s backend' % label,
                       'pugl-%s' % backend, 'pugl_' + name,
                       deps=kwargs,
                       requires=['pugl-%s' % PUGL_MAJOR_VERSION])

    lib_source = ['pugl/detail/implementation.c']
    if bld.env.TARGET_PLATFORM == 'win32':
        platform = 'win'
        build_platform('win',
                       uselib=['GDI32', 'USER32'],
                       source=lib_source + ['pugl/detail/win.c'])

        if bld.env.HAVE_GL:
            build_backend('win', 'gl',
                          uselib=['GDI32', 'USER32', 'GL'],
                          source=['pugl/detail/win_gl.c'])

        if bld.env.HAVE_CAIRO:
            build_backend('win', 'cairo',
                          uselib=['CAIRO', 'GDI32', 'USER32'],
                          source=['pugl/detail/win_cairo.c'])

    elif bld.env.TARGET_PLATFORM == 'darwin':
        platform = 'mac'
        build_platform('mac',
                       framework=['Cocoa'],
                       source=lib_source + ['pugl/detail/mac.m'])

        if bld.env.HAVE_GL:
            build_backend('mac', 'gl',
                          framework=['Cocoa', 'OpenGL'],
                          source=['pugl/detail/mac_gl.m'])

        if bld.env.HAVE_CAIRO:
            build_backend('mac', 'cairo',
                          framework=['Cocoa'],
                          uselib=['CAIRO'],
                          source=['pugl/detail/mac_cairo.m'])
    else:
        platform = 'x11'
        build_platform('x11',
                       uselib=['M', 'X11'],
                       source=lib_source + ['pugl/detail/x11.c'])

        if bld.env.HAVE_GL:
            glx_lib = 'GLX' if bld.env.LIB_GLX else 'GL'
            build_backend('x11', 'gl',
                          uselib=[glx_lib, 'X11'],
                          source=['pugl/detail/x11_gl.c'])

        if bld.env.HAVE_CAIRO:
            build_backend('x11', 'cairo',
                          uselib=['CAIRO', 'X11'],
                          source=['pugl/detail/x11_cairo.c'])

    def build_test(prog, source, platform, backend, **kwargs):
        use = ['pugl_%s_static' % platform,
               'pugl_%s_%s_static' % (platform, backend)]

        target = prog
        if bld.env.TARGET_PLATFORM == 'darwin':
            target = '{0}.app/Contents/MacOS/{0}'.format(prog)

            bld(features     = 'subst',
                source       = 'resources/Info.plist.in',
                target       = '{}.app/Contents/Info.plist'.format(prog),
                install_path = '',
                NAME         = prog)

        backend_lib = platform + '_' + backend
        for k in ('lib', 'framework', 'uselib'):
            kwargs.update({k: (kwargs.get(k, []) +
                               deps.get(platform, {}).get(k, []) +
                               deps.get(backend_lib, {}).get(k, []))})

        bld(features     = 'c cprogram',
            source       = source,
            target       = target,
            use          = use,
            install_path = '',
            **kwargs)

    if bld.env.BUILD_TESTS:
        for s in ('rect.vert', 'rect.frag'):
            # Copy shaders to build directory for test programs
            bld(features = 'subst',
                is_copy  = True,
                source   = 'shaders/%s' % s,
                target   = 'shaders/%s' % s)

        if bld.env.HAVE_GL:
            build_test('pugl_test', ['test/pugl_test.c'],
                       platform, 'gl', uselib=['GL', 'M'])
            build_test('pugl_print_events', ['test/pugl_print_events.c'],
                       platform, 'stub')
            build_test('pugl_gl3_test',
                       ['test/pugl_gl3_test.c', 'test/glad/glad.c'],
                       platform, 'gl', uselib=['DL', 'GL', 'M'])

        if bld.env.HAVE_CAIRO:
            build_test('pugl_cairo_test', ['test/pugl_cairo_test.c'],
                       platform, 'cairo',
                       uselib=['M', 'CAIRO'])

    if bld.env.DOCS:
        bld(features     = 'subst',
            source       = 'doc/reference.doxygen.in',
            target       = 'doc/reference.doxygen',
            install_path = '',
            PUGL_VERSION = PUGL_VERSION,
            PUGL_SRCDIR  = os.path.abspath(bld.path.srcpath()))

        bld(features = 'doxygen',
            doxyfile = 'doc/reference.doxygen')


def test(tst):
    pass


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

    subprocess.call("flake8 wscript --ignore E221,W504,E251,E241",
                    shell=True)

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

    subprocess.call(['clang-tidy'] + files, cwd='build')

    try:
        subprocess.call(['iwyu_tool.py', '-o', 'clang', '-p', 'build'])
    except Exception:
        Logs.warn('Failed to call iwyu_tool.py')

# Alias .m files to be compiled like .c files, gcc will do the right thing.
@TaskGen.extension('.m')
def m_hook(self, node):
    return self.create_compiled_task('c', node)