#!/usr/bin/python3
# -*- coding: utf-8 -*-

#  Copyright © 2009, 2011-2015  B. Clausius <barcc@gmx.de>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

# pylint: disable=W0201


from glob import glob
import os
import sys

use_setuptools = not os.getenv('PYBIK_NO_SETUPTOOLS')
if use_setuptools:
    try:
        import setuptools
    except ImportError:
        use_setuptools = False
if not use_setuptools:
    from distutils.core import setup
    from distutils.cmd import Command
    from distutils.extension import Extension
    from distutils import dist
    from distutils.command.install import install as install_orig
else:
    print('using setuptools')
    from setuptools import setup
    from setuptools import Command
    from setuptools import Extension
    from setuptools import dist
    from setuptools.command.install import install as install_orig

from distutils.dir_util import remove_tree
from distutils.file_util import move_file
from distutils.dep_util import newer, newer_group
from distutils.log import warn, info, error, debug

import distutils.command.build
import distutils.command.build_ext
import distutils.command.build_scripts
import distutils.command.build_py
import distutils.command.install_data
import distutils.command.install_lib
import distutils.command.clean
import distutils.command.sdist

#from Cython.Distutils import build_ext

#print('using', 'setuptools' if use_setuptools else 'distutils')

# the clean command should leave a clean tree
sys.dont_write_bytecode = True
os.environ['PYTHONDONTWRITEBYTECODE'] = '1'

from pybiklib import config


class Distribution (distutils.dist.Distribution):
    def __init__(self, attrs=None):
        self.bug_contact = None
        self.qt_ui_files = None
        self.desktop_templates = None
        #XXX: Mangle alpha/beta/rc versions to PEP440 version scheme for setuptools, pip, pypi ...
        version = attrs['version']
        pep440 = version.replace('~alpha', 'a').replace('~beta', 'b').replace('~rc', 'rc')
        attrs['version'] = pep440
        distutils.dist.Distribution.__init__(self, attrs=attrs)
        if version != pep440:
            self.metadata.version = version


class build(distutils.command.build.build):
    """Adds extra commands to the build target."""
    user_options = distutils.command.build.build.user_options + [
            ('inplace', 'i', 'ignore build-lib and put compiled modules into the source directory'),
            #XXX: In Python 3.5 distutils.command.build.build has a parallel option
            ('parallel=', None, 'number of parallel build jobs or "True"'),
            ('arch-only', None, 'Build only architecture dependent files'),
            ('indep-only', None, 'Build only architecture independent files'),
            ('fast', None, 'Build models without optimization'),
            ('pickle-protocol=', None, 'Pickle protocol for modeldata (default: highest protocol)'),
            ('maxsize=', None, 'Maximum model size (default: 10)'),
            ('reproducible', None, 'Build modeldata reproducible (experimental)'),
        ]
    boolean_options = distutils.command.build.build.boolean_options + [
            'inplace', 'arch-only', 'indep-only', 'fast', 'reproducible',
        ]
        
    def has_pure_modules(self):
        return self.indep_only and distutils.command.build.build.has_pure_modules(self)
        
    _commands_dict = dict(distutils.command.build.build.sub_commands)
    # pylint: disable=W0212
    sub_commands = [('build_py',      has_pure_modules),
                    ('build_ext',     lambda self: self.arch_only and self._commands_dict['build_ext'](self)),
                    ('build_scripts', lambda self: self.indep_only and self._commands_dict['build_scripts'](self)),
                    ('build_models',  lambda self: self.indep_only),
                    ('build_i18n',    lambda self: self.indep_only),
                    ('build_desktop', lambda self: self.indep_only),
                    ('build_man',     lambda self: self.indep_only and not self.inplace),
                ]
    # pylint: enable=W0212
                
    def initialize_options(self):
        distutils.command.build.build.initialize_options(self)
        self.inplace = 0
        self.parallel = None
        self.arch_only = False
        self.indep_only = False
        self.fast = False
        self.pickle_protocol = '-1'
        self.maxsize = None
        self.reproducible = False
        
    def finalize_options(self):
        # In Python 3.5 distutils.command.build.build has a parallel option
        # convert 'true' to True before the base class fails
        if isinstance(self.parallel, str) and self.parallel.lower() == 'true':
            self.parallel = True
        distutils.command.build.build.finalize_options(self)
        if not self.arch_only and not self.indep_only:
            self.arch_only = self.indep_only = True
        
        
class build_csrc (distutils.command.build_ext.build_ext):
    description = 'build C source code with py2pyx and cython'
    user_options = distutils.command.build_ext.build_ext.user_options + [
        ('cython-opts=', None,          'Cython options'),
        ('gles=',        None,          'Build for GLES (experimental)'),
    ]
    
    def initialize_options(self):
        distutils.command.build_ext.build_ext.initialize_options(self)
        self.inplace = None
        self.cython_opts = ''
        self.gles = None
        
    def finalize_options(self):
        # deactivate parallel build in Python 3.5, does not work with this command
        self.parallel = 1
        distutils.command.build_ext.build_ext.finalize_options(self)
        self.set_undefined_options('build',
                                       ('inplace', 'inplace'),
                                    )
        self.cython_opts = self.cython_opts.split()
        
    def compare_file(self, filename1, filename2):
        if self.force or not os.path.exists(filename2):
            return False
        with open(filename1, 'rb') as file1, open(filename2, 'rb') as file2:
            return file1.read() == file2.read()
            
    @staticmethod
    def run_py2pyx(py_file, pyx_file, pxd_file):
        info('py2pyx: %s --> %s, %s' % (py_file, pyx_file, pxd_file))
        from buildlib.py2pyx import create_pyx, Py2pyxParseError
        try:
            create_pyx(py_file, pyx_file, pxd_file)
        except Py2pyxParseError as e:
            error("error: %s", e)
            sys.exit(1)
        
    identifier_translate = bytes((i if bytes([i]).isalnum() else ord('_')) for i in range(256))
    
    @classmethod
    def replace_cwd(cls, filename):
        with open(filename, 'rb') as f:
            text = f.read()
        cwdb_esc = bytes(ord(c) for c in os.getcwd()[:32] if ord(c)<128).translate(cls.identifier_translate)
        text = text.replace(os.getcwdb(), b'/tmp').replace(cwdb_esc, b'_tmp')
        with open(filename, 'wb') as f:
            f.write(text)
            
    def run_cython(self, cython_opts, infile, outfile):
        from Cython.Compiler.CmdLine import parse_command_line
        from Cython.Compiler.Main import compile
        
        info('cython: %s --> %s' % (infile, outfile))
        args = ['-3', '-o', outfile] + cython_opts + [infile]
        options, unused_sources = parse_command_line(args)
        try:
            result = compile([infile], options)
        except Exception as e:
            error("error: %s", e)
            sys.exit(1)
        if result.num_errors > 0:
            sys.exit(1)
        self.replace_cwd(outfile)
        
    def build_pyx(self, py_file, pyx_file, pxd_file):
        pxd_file_tmp = pxd_file + '.tmp'
        self.mkpath(os.path.dirname(pyx_file))
        self.make_file([py_file], pyx_file, self.run_py2pyx, (py_file, pyx_file, pxd_file_tmp))
        if os.path.exists(pxd_file_tmp):
            if self.compare_file(pxd_file_tmp, pxd_file):
                os.remove(pxd_file_tmp)
                info("unchanged file '%s'", pxd_file)
            else:
                if os.path.exists(pxd_file):
                    os.remove(pxd_file)
                move_file(pxd_file_tmp, pxd_file)
        
    def build_extension(self, extension):
        sources = extension.sources
        depends = sources + extension.depends
            
        # generate pxd- and pyx-files from py-files with #px annotations
        pyx_files = []
        pyx_files_dep = []
        for in_file in depends:
            base, ext = os.path.splitext(in_file)
            if ext == '.py':
                base = os.path.join(self.build_temp, os.path.splitext(in_file)[0])
                basename = os.path.basename(base)
                dirname = os.path.dirname(base)
                pyx_file = os.path.join(dirname, '_%s.pyx' % basename)
                pxd_file = os.path.join(dirname, '_%s.pxd' % basename)
                self.build_pyx(in_file, pyx_file, pxd_file)
                if in_file in extension.sources:
                    pyx_files.append(pyx_file)
                pyx_files_dep.append(pxd_file)
                
                if self.gles is not None:
                    info('Changing source for GLES %s', self.gles)
                    with open(pyx_file, 'rt', encoding='utf-8') as f:
                        text = f.read()
                    text = text.replace("DEF SOURCEGLVERSION = 'GL'", "DEF SOURCEGLVERSION = 'GLES%s'" % self.gles)
                    with open(pyx_file, 'wt', encoding='utf-8') as f:
                        f.write(text)
            elif ext in ('.pxi','.pxd'):
                out_file = os.path.join(self.build_temp, in_file)
                self.mkpath(os.path.dirname(out_file))
                self.copy_file(in_file, out_file)
                pyx_files_dep.append(out_file)
            else:
                if in_file in extension.sources:
                    pyx_files.append(in_file)
                else:
                    pyx_files_dep.append(in_file)
                    
        # generate C-files with cython
        if extension.language == 'c++':
            c_file_ext = '.cpp'
            cython_opts = ['--cplus'] + self.cython_opts
        else:
            c_file_ext = '.c'
            cython_opts = self.cython_opts
        c_files = []
        for in_file in pyx_files:
            base, ext = os.path.splitext(in_file)
            if ext == '.pyx':
                out_file = base + c_file_ext
                inplace_file = os.path.join('csrc', os.path.basename(out_file))
                if os.path.exists(inplace_file) and not self.force:
                    self.mkpath(os.path.dirname(out_file))
                    self.copy_file(inplace_file, out_file)
                else:
                    self.make_file(pyx_files+pyx_files_dep, out_file,
                                   self.run_cython, (cython_opts, in_file, out_file))
                c_files.append(out_file)
            else:
                c_files.append(in_file)
                
        # exclude Cython-files from dependencies
        c_files_dep = []
        for in_file in pyx_files_dep:
            base, ext = os.path.splitext(in_file)
            if ext not in ('.pxi', '.pxd'):
                c_files_dep.append(in_file)
        extension.depends = c_files_dep
        extension.sources = c_files
        
        
class build_ext (build_csrc):
    description = distutils.command.build_ext.build_ext.description
    
    def build_extension(self, extension):
        if self.gles == '2':
            extension.libraries[:] = ['GLESv2']
        sources = extension.sources
        ext_path = self.get_ext_fullpath(extension.name)
        depends = sources + extension.depends
        if self.force or newer_group(depends, ext_path, 'newer'):
            info('building C-Code for %r extension', extension.name)
        else:
            debug('skipping %r extension (up-to-date)', extension.name)
            return
            
        # build C-code
        build_csrc.build_extension(self, extension)
        # build extension module from C-code
        distutils.command.build_ext.build_ext.build_extension(self, extension)
        #HACK: Due to build_csrc.compare_file the C compiler may not run, even though
        #      the dependencies expect this. Therefore update the timestamp manually.
        ext_path = self.get_ext_fullpath(extension.name)
        try:
            os.utime(ext_path, None)
        except OSError:
            pass
        
        
class build_scripts (distutils.command.build_scripts.build_scripts):
    user_options = distutils.command.build_scripts.build_scripts.user_options + [
            ('inplace',     'i', 'ignore build-lib and put compiled modules into the source directory'),
            ('build-temp=', 't', "temporary build directory"),
        ]
    boolean_options = distutils.command.build.build.boolean_options + ['inplace']
    
    def initialize_options(self):
        distutils.command.build_scripts.build_scripts.initialize_options(self)
        self.inplace = None
        self.build_temp = None
        
    def finalize_options(self):
        distutils.command.build_scripts.build_scripts.finalize_options(self)
        self.set_undefined_options('build',
                    ('inplace', 'inplace'),
                    ('build_temp', 'build_temp'),
                )
                
    def run(self):
        build_dir = self.build_dir
        self.build_dir = self.build_temp
        distutils.command.build_scripts.build_scripts.run(self)
        self.build_dir = build_dir
        for script in self.scripts:
            outfile = os.path.basename(script)
            script = os.path.join(self.build_temp, outfile)
            if not os.path.exists(script):
                continue
            if not self.inplace:
                self.mkpath(self.build_dir)
                outfile = os.path.join(self.build_dir, outfile)
            outfile, ext = os.path.splitext(outfile)
            if ext != '.py':
                outfile += ext
            self.copy_file(script, outfile)
        
        
class build_py(distutils.command.build_py.build_py):
    description = 'build pure Python modules'
    user_options = distutils.command.build_py.build_py.user_options + [
        ('inplace',     'i', 'ignore build-lib and put compiled UI modules into the source '
                             'directory alongside your pure Python modules'),
    ]
    boolean_options = distutils.command.build_py.build_py.boolean_options + [
        'inplace']
    
    def initialize_options(self):
        distutils.command.build_py.build_py.initialize_options(self)
        self.qt_ui_files = None
        self.inplace = None
        
    def finalize_options(self):
        distutils.command.build_py.build_py.finalize_options(self)
        self.qt_ui_files = self.distribution.qt_ui_files
        self.set_undefined_options('build',
                                       ('inplace', 'inplace'),
                                   )
                
    def compile_ui(self, ui_file, py_file):
        from buildlib import translation_from_ui
        windowinfo = translation_from_ui.parse_file(ui_file)
        with open(py_file, 'wt', encoding='utf-8') as file:
            translation_from_ui.write_file(*windowinfo, file=file)
            
    def find_package_modules(self, package, package_dir):
        if package not in self.qt_ui_files:
            return distutils.command.build_py.build_py.find_package_modules(self, package, package_dir)
        modules = []
        for ui_file in self.qt_ui_files[package]:
            module = os.path.splitext(os.path.basename(ui_file))[0]
            modules.append((package, module, ui_file))
        return modules
        
    def build_module(self, module, infile, package):
        if infile and infile.endswith('.py'):
            return distutils.command.build_py.build_py.build_module(self, module, infile, package)
        if self.inplace:
            package_dir = self.get_package_dir(package)
            py_file = os.path.join(package_dir, module) + '.py'
        else:
            package = package.split('.')
            py_file = self.get_module_outfile(self.build_lib, package, module)
            self.mkpath(os.path.dirname(py_file))
        self.make_file(infile, py_file, self.compile_ui, [infile, py_file])

class build_models(Command):
    description = 'build data for Pybik models'
    user_options = [
        ('inplace',     'i', 'ignore build-lib and put compiled UI modules into the source '
                             'directory alongside your pure Python modules'),
        ('force',       'f', 'forcibly build everything (ignore file timestamps)'),
        ('parallel=',   None,'number of parallel build jobs or "True"'),
        ('fast',        None,'Build models without optimization'),
        ('pickle-protocol=', None, 'Pickle protocol for modeldata (default: highest protocol)'),
        ('maxsize=',    None, 'Maximum model size (default: 10)'),
        ('reproducible', None, 'Build modeldata reproducible (experimental)'),
        ('debug=', None, 'Comma-separated list of debug flags'),
    ]
    boolean_options = ['inplace', 'force', 'fast', 'reproducible']
    
    def initialize_options(self):
        self.build_base = None
        self.inplace = None
        self.force = None
        self.parallel = None
        self.fast = None
        self.pickle_protocol = None
        self.maxsize = None
        self.reproducible = None
        self.debug = None
        
    def finalize_options(self):
        self.set_undefined_options('build',
                                       ('build_base', 'build_base'),
                                       ('inplace', 'inplace'),
                                       ('force', 'force'),
                                       ('parallel', 'parallel'),
                                       ('fast', 'fast'),
                                       ('pickle_protocol', 'pickle_protocol'),
                                       ('maxsize', 'maxsize'),
                                       ('reproducible', 'reproducible'),
                                   )
        if isinstance(self.parallel, str):
            if self.parallel.lower() == 'true':
                self.parallel = True
            else:
                self.parallel = int(self.parallel)
        self.pickle_protocol = -1 if self.pickle_protocol is None else int(self.pickle_protocol)
        if self.maxsize is None:
            self.maxsize = 10
        else:
            from ast import literal_eval
            self.maxsize = literal_eval(self.maxsize)
        self.reproducible = False if self.reproducible is None else bool(self.reproducible)
        if self.debug is not None:
            import pybiklib.debug
            known_debug_flags = [d[6:].lower() for d in pybiklib.debug.__all__ if d.startswith('DEBUG_')]
            debug_flags = []
            for d in self.debug.split(','):
                if d not in known_debug_flags:
                    print('unknown debug option:', d)
                elif d:
                    debug_flags.append(d)
            pybiklib.debug.set_flags(debug_flags)
            print('debug flags:', ' '.join(debug_flags))
                
    def run(self):
        from buildlib import modeldata
        modeldir = os.path.join('data' if self.inplace else self.build_base, 'models')
        self.mkpath(modeldir)
        if self.force and not self.dry_run:
            for filename in os.listdir(modeldir):
                filename = os.path.join(modeldir, filename)
                os.remove(filename)
        if self.force:
            testfunc = None
        else:
            testfunc = lambda filename: (any(newer(os.path.join('buildlib', f), filename) for f in
                                                ('modeldata.py', 'modeldef.py', 'geom.py')))
        if testfunc is None or testfunc(modeldata.get_indexfilename(modeldir)):
            message = 'generating model data, this may take some time'
            def create_modeldata():
                modeldata.create_modeldata(modeldir, testfunc, self.parallel, self.fast,
                                        self.pickle_protocol, self.maxsize, self.reproducible)
            self.execute(create_modeldata, [], msg=message)
        data_files = self.distribution.data_files
        if self.dry_run:
            return
        for filename in os.listdir(modeldir):
            sourcepath = os.path.join(modeldir, filename)
            data_files.append(('share/pybik/models', (sourcepath,)))
        
        
class build_i18n(Command):
    description = 'compile message catalog to binary format'
    
    user_options = [
        ('inplace',         'i',  'ignore build-lib and put compiled UI modules into the source '
                                  'directory alongside your pure Python modules'),
        ('force',           'f',  'forcibly build everything (ignore file timestamps)'),
    ]
        
    boolean_options = ['inplace', 'force']
    
    def initialize_options(self):
        self.build_base = None
        self.inplace = None
        self.force = None
        
    def finalize_options(self):
        self.set_undefined_options('build', ('build_base', 'build_base'),
                                            ('inplace', 'inplace'),
                                            ('force', 'force'),
                                  )
            
    def run(self):
        """Compile message catalog to binary format"""
        from buildlib.utils import po_isempty
        
        if self.inplace:
            mo_dir = 'data/locale'
        else:
            mo_dir = os.path.join(self.build_base, 'mo')
        data_files = self.distribution.data_files
        domain = self.distribution.metadata.name
        
        for po_file in glob('po/*.po'):
            if po_isempty(po_file, verbose=False):
                print('skipping empty po file', po_file)
                continue
            lang = os.path.splitext(os.path.basename(po_file))[0]
            mo_dir_lang =  os.path.join(mo_dir, lang, "LC_MESSAGES")
            mo_file = os.path.join(mo_dir_lang, "%s.mo" % domain)
            self.mkpath(mo_dir_lang)
            def msgfmt(po_file, mo_file):
                self.spawn(["msgfmt", po_file, "-o", mo_file])
            self.make_file([po_file], mo_file, msgfmt, [po_file, mo_file])
            
            targetpath = os.path.join("share/locale", lang, "LC_MESSAGES")
            data_files.append((targetpath, (mo_file,)))
            
            
class build_desktop(Command):
    description = 'build translated desktop files'
    
    user_options = [
        ('inplace',         'i',  'ignore build-lib and put compiled UI modules into the source '
                                  'directory alongside your pure Python modules'),
        ('force',           'f',  'forcibly build everything (ignore file timestamps)'),
    ]
        
    boolean_options = ['inplace', 'force']
    
    def initialize_options(self):
        self.build_base = None
        self.inplace = None
        self.force = None
        
    def finalize_options(self):
        self.set_undefined_options('build', ('build_base', 'build_base'),
                                            ('inplace', 'inplace'),
                                            ('force', 'force'),
                                  )
        self.desktop_dir = os.path.join('data' if self.inplace else self.build_base, 'applications')
            
    def run(self):
        from buildlib import create_docs
        
        if self.distribution.desktop_templates is None:
            return
            
        data_files = self.distribution.data_files
        
        self.mkpath(self.desktop_dir)
        
        # merge .desktop.in with translation
        targetpath, templates = self.distribution.desktop_templates
        targetfiles = []
        for template in templates:
            templatefile = create_docs.get_template_filename(template)
            tmpfile = os.path.join(self.desktop_dir, os.path.basename(templatefile))
            dstfile = os.path.join(self.desktop_dir, os.path.basename(template))
            if not dstfile.endswith('.desktop'):
                self.warn('Skipping %s, seems not to be a desktop file template.' % templatefile)
                continue
            def make_translated_desktopfile(src, tmp, dst):
                create_docs.create_doc(src, tmp, skip='deb')
                self.spawn(["intltool-merge", '-d', 'po', tmp, dst])
                os.remove(tmp)
            self.make_file(glob('po/*.po') + [templatefile], dstfile,
                           make_translated_desktopfile, [template, tmpfile, dstfile])
            targetfiles.append(dstfile)
        data_files.append((targetpath, targetfiles))
        
        
class build_man (Command):
    description = 'build the manpage'
    user_options = [
        ('force',       'f',  'forcibly build everything (ignore file timestamps)'),    ]
    
    def initialize_options(self):
        self.force = None
        self.build_base = None
        
    def finalize_options(self):
        self.set_undefined_options('build', ('build_base', 'build_base'),
                                            ('force', 'force'),                                  )
        
    def run(self):
        def create_manpage(script_file, section, man_file):
            import re
            from buildlib.utils import modify_file
            os.environ['PYTHONPATH'] = '.'
            self.spawn(['help2man', '--name', config.SHORT_DESCRIPTION, '--section', section,
                                    '--output', man_file, '--no-info', script_file])
            modify_file(man_file, [
                    (r'^(?a)(\.TH\s+\S+\s+\S+\s+)".+?"(.*)$', r'\1"{}"\2'.format(re.escape(config.RELEASE_DATE)))])
            
        man_dir = os.path.join(self.build_base, 'man')
        self.mkpath(man_dir)
        if '.' not in sys.path:
            sys.path.insert(0, '.')
        section = '6'
        if self.dry_run:
            return
        for script_file in self.get_finalized_command('build_scripts').scripts:
            script, ext = os.path.splitext(os.path.basename(script_file))
            man_file = os.path.join(man_dir, '.'.join((script, section)))
            self.make_file(['pybiklib/config.py', script_file], man_file,
                           create_manpage, [script_file, section, man_file])
            self.distribution.data_files.append(('share/man/man'+section, [man_file]))
            
            
class build_doc (Command):
    description = "build documentation files"
    user_options = [
        ('inplace',         'i',  'ignore build-lib and put compiled UI modules into the source '
                                  'directory alongside your pure Python modules'),
    ]
    boolean_options = ['inplace']
    
    def initialize_options(self):
        self.inplace = None
        self.build_base = None
        self.doc_files = []
        
    def finalize_options(self):
        self.set_undefined_options('build', ('inplace', 'inplace'), ('build_base', 'build_base'))
        
    def run(self):
        from buildlib import create_docs
        
        info('generating documentation files')
        self.mkpath(os.path.join(self.build_base, 'doc'))
        for basename, skip in [
                    ('copyright', 'deb'),
                    ('README', None),
                    ('INSTALL', None),
                ]:
            filename = None if self.inplace else os.path.join(self.build_base, 'doc', basename)
            info('generating file: %r', filename or basename)
            create_docs.create_doc(basename, filename, skip=skip)
            if filename:
                self.doc_files.append(filename)
        
        
class install_lib (distutils.command.install_lib.install_lib):
    user_options = distutils.command.install_lib.install_lib.user_options + [
            ('arch-only', None, 'Install only architecture dependent files'),
            ('indep-only', None, 'Install only architecture independent files'),
        ]
    boolean_options = ['arch-only', 'indep-only']
    
    def initialize_options(self):
        distutils.command.install_lib.install_lib.initialize_options(self)
        self.data_dir = None
        self.arch_only = None
        self.indep_only = None
        
    def finalize_options(self):
        distutils.command.install_lib.install_lib.finalize_options(self)
        self.set_undefined_options('install',
                                       ('arch_only', 'arch_only'),
                                       ('indep_only', 'indep_only'),
                                   )
        if not self.arch_only and not self.indep_only:
            self.arch_only = self.indep_only = True
            
    def build(self):
        if not self.skip_build:
            if self.distribution.has_pure_modules() and self.indep_only:
                self.run_command('build_py')
            if self.distribution.has_ext_modules() and self.arch_only:
                self.run_command('build_ext')
                
                
class install_desktop(Command):
    description = 'install desktop files'
    user_options = [('ask', None, 'Ask before installation'),]
    boolean_options = ['ask']
    
    def initialize_options(self):
        self.ask = False
        
    def finalize_options(self):
        pass
    
    def run(self):
        if self.ask:
            try:
                answer = input('\nInstall desktop file? [Yn]: ')
            except EOFError:
                print()
                return
            if answer.lower() not in ['y', '']:
                return
                
        self.run_command('build_desktop')
        desktop_dir = self.get_finalized_command('build_desktop').desktop_dir
        target_dir = os.path.join(config.get_data_home(), 'applications')
        
        def keyabspath(line, key, file=None):
            key += '='
            if line.startswith(key):
                if file is None:
                    file = line.split('=', 1)[1].rstrip()
                line = key + os.path.abspath(file) + '\n'
            return line
            
        self.mkpath(target_dir)
        for fnin in glob('%s/*.desktop' % desktop_dir):
            fnout = os.path.join(target_dir, os.path.basename(fnin))
            info('installing desktop file to: %s', fnout)
            with open(fnin, 'rt', encoding='utf-8') as fin, open(fnout, 'wt', encoding='utf-8') as fout:
                for line in fin:
                    line = keyabspath(line, 'TryExec')
                    line = keyabspath(line, 'Exec')
                    line = keyabspath(line, 'Icon', config.APPICON192_FILE)
                    fout.write(line)
                    
                    
class install (install_orig):
    user_options = install_orig.user_options + [
            ('data-dir=', 't', 'Directory where the application will find the data'),
            ('arch-only', None, 'Install only architecture dependent files'),
            ('indep-only', None, 'Install only architecture independent files'),
        ]
    boolean_options = ['arch-only', 'indep-only']
        
    def initialize_options(self):
        install_orig.initialize_options(self)
        self.data_dir = None
        self.arch_only = False
        self.indep_only = False
        
    def finalize_options(self):
        install_orig.finalize_options(self)
        if self.data_dir is None:
            self.data_dir = os.path.join(self.install_data, 'share')
        if not self.arch_only and not self.indep_only:
            self.arch_only = self.indep_only = True
        if not self.indep_only:
            self.__class__.sub_commands = [(cmd, func)
                            for cmd, func in install_orig.sub_commands
                                if cmd == 'install_lib']
            
    def run(self):
        if not self.skip_build:
            # install_orig.run() will run build, but we need
            # to modify a file between build and install
            build_cmd = self.distribution.get_command_obj('build')
            build_cmd.arch_only = self.arch_only
            build_cmd.indep_only = self.indep_only
            self.run_command('build')
            self.skip_build = True
            
        filename = os.path.join(self.build_lib, 'pybiklib', 'config.py')
        app_data_dir = os.path.join(self.data_dir, 'pybik')
        if self.indep_only:
            from buildlib.utils import modify_file
            modify_file(filename, [
                    (r'^(data_dir\s*=\s*).*$',      r'\1' + repr(self.data_dir)),
                    (r'^(appdata_dir\s*=\s*).*$',   r'\1' + repr(app_data_dir)),])
        install_orig.run(self)
        
        
class clean (distutils.command.clean.clean):
    user_options = distutils.command.clean.clean.user_options + [
            ('inplace', 'i',         'clean up files in the source directory'),
        ]
    boolean_options = distutils.command.clean.clean.boolean_options + [
            'inplace',
        ]
        
    def initialize_options(self):
        distutils.command.clean.clean.initialize_options(self)
        self.inplace = None
        
    def finalize_options(self):
        distutils.command.clean.clean.finalize_options(self)
        self.set_undefined_options('build',
                                       ('inplace', 'inplace'),
                                   )
        
    def run(self):
        def remove_tree_(directory):
            if os.path.exists(directory):
                remove_tree(directory, dry_run=self.dry_run, verbose=self.verbose)
            else:
                warn("%r does not exist -- can't clean it", directory)
        def remove_file(filename):
            if os.path.exists(filename):
                info('removing %r', filename)
                if not self.dry_run:
                    os.remove(filename)
            else:
                warn("%r does not exist -- can't clean it", filename)
                
        if self.all:
            for _dir in ['applications', 'doc', 'man', 'mo', 'models']:
                remove_tree_(os.path.join(self.build_base, _dir))
        distutils.command.clean.clean.run(self)
        remove_file('MANIFEST')
        if self.inplace:
            for dirname in ('buildlib/', 'pybiklib/', 'pybiklib/plugins/', 'pybiklib/ui/', 'pybiktest/', 'tools/'):
                remove_tree_(dirname + '__pycache__')
            if self.all:
                dirnames = ['data/applications', 'data/locale', 'data/models']
                for dirname in dirnames:
                    remove_tree_(dirname)
                for filename in glob('pybiklib/*.so'):
                    remove_file(filename)
                for filename in glob('pybiklib/ui/*.py'):
                    if filename != 'pybiklib/ui/__init__.py':
                        remove_file(filename)
                for filename in ['copyright', 'INSTALL', 'README', 'pybik']:
                    remove_file(filename)
                
                
class sdist(distutils.command.sdist.sdist):
    user_options = distutils.command.sdist.sdist.user_options + [
            ('debian-names', None,  'Create archive files with Debian names'),
        ]
    boolean_options = distutils.command.sdist.sdist.boolean_options + [
            'debian-names'
        ]
        
    def initialize_options(self):
        distutils.command.sdist.sdist.initialize_options(self)
        self.debian_names = False
        self.desktop_dir = None
        
    def finalize_options(self):
        distutils.command.sdist.sdist.finalize_options(self)
        self.set_undefined_options('build_desktop', ('desktop_dir', 'desktop_dir'))
        
    def run(self):
        self.run_command('build_doc')
        self.run_command('build_csrc')
        self.run_command('build_desktop')
        distutils.command.sdist.sdist.run(self)
        
        for archive_file in self.archive_files:
            if self.debian_names:
                debian_file = archive_file.replace('-', '_', 1).replace('.tar', '.orig.tar', 1)
                os.rename(archive_file, debian_file)
        
    def get_file_list(self):
        from buildlib.utils import po_isempty
        for f in glob('po/*.po'):
            if po_isempty(f):
                print('skipping empty po file', f)
            else:
                self.filelist.files.append(f)
        distutils.command.sdist.sdist.get_file_list(self)
        
    def make_release_tree(self, base_dir, files):
        distutils.command.sdist.sdist.make_release_tree(self, base_dir, files)
        # Release date
        from datetime import date
        from buildlib.utils import modify_file
        modify_file(os.path.join(base_dir, 'pybiklib', 'config.py'),[
                (r'^(RELEASE_DATE\s*=\s*).*$',  r'\1' + repr(date.today().isoformat())),
            ])
        # doc files
        for filename in self.get_finalized_command('build_doc').doc_files:
            self.copy_file(filename, base_dir)
        # csrc
        dst_dir = os.path.join(base_dir, 'csrc')
        self.mkpath(dst_dir)
        extensions = self.get_finalized_command('build_csrc').extensions
        for extension in extensions:
            for src in extension.sources:
                self.copy_file(src, dst_dir)
        # desktop files
        dst_dir = os.path.join(base_dir, 'data', 'applications')
        self.mkpath(dst_dir)
        for template in self.distribution.desktop_templates[1]:
            src = os.path.join(self.desktop_dir, template)
            self.copy_file(src, dst_dir)
        
        
setup(
        # Metadata
        name=config.PACKAGE,
        version=config.VERSION,
        author=config.AUTHOR,
        author_email=config.CONTACT_EMAIL,
        url=config.WEBSITE,
        download_url=config.DOWNLOADS,
        
        description=config.SHORT_DESCRIPTION,
        long_description='\n'.join(config.LONG_DESCRIPTION),
        classifiers=[
                'Development Status :: 5 - Production/Stable',
                'Environment :: X11 Applications :: Qt',
                'Intended Audience :: End Users/Desktop',
                'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
                'Operating System :: POSIX',
                'Operating System :: POSIX :: Linux',
                'Programming Language :: Python :: 3',
                'Programming Language :: Python :: 3.4',
                'Programming Language :: Cython',
                #TODO: 'Programming Language :: OpenGL Shading Language',
                'Topic :: Games/Entertainment :: Puzzle Games',
            ],
        keywords='rubik cube puzzle game',
        license=config.LICENSE_NAME,
        # Metadata added in overloaded Distribution
        bug_contact=config.CONTACT_FILEBUG,
        
        # Files
        scripts=['pybiklib/pybik.py'],
        data_files=[
                        ('share/pixmaps', ['data/icons/pybik.png']),
                        ('share/pybik/icons', ['data/icons/pybik-64.png']),
                        ('share/pybik/icons', ['data/icons/pybik-192.png']),
                        ('share/pybik/ui/cursors', glob('data/ui/cursors/*')),
                        ('share/pybik/ui/images', glob('data/ui/images/*')),
                        ('share/pybik/ui/qt', glob('data/ui/qt/*')),
                        ('share/pybik/plugins', glob('data/plugins/*')),
                        ('share/pybik/shaders', glob('data/shaders/*')),
                        ('share/pybik/tests', glob('data/tests/*')),
                        ('share/pybik/', ['data/GPL-3']),
                    ],
        desktop_templates=('share/applications', ['pybik.desktop']),
        ext_modules=[
                        Extension('pybiklib/_gldraw',
                                    ['pybiklib/gldraw.py'],
                                    depends=['pybiklib/gl.pxd'],
                                    libraries=['GL'],
                                    define_macros=[('GL_GLEXT_PROTOTYPES', None)]),
                        Extension('pybiklib/_glarea',
                                    ["pybiklib/glarea.py"],
                                    depends=['pybiklib/gl.pxd',
                                             'pybiklib/gldraw.py'],
                                    libraries=['GL'],
                                    define_macros=[('GL_GLEXT_PROTOTYPES', None)]),
                    ],
        packages=['pybiklib', 'pybiklib.plugins', 'pybiklib.ui', 'pybiktest'],
        qt_ui_files={'pybiklib.ui': glob('data/ui/qt/*.ui')+['pybiklib/ui/__init__.py']},
        
        # setup classes
        distclass=Distribution,
        cmdclass={
                    'build': build,
                    'build_csrc': build_csrc,
                    'build_ext': build_ext,
                    'build_scripts': build_scripts,
                    'build_py': build_py,
                    'build_models': build_models,
                    'build_i18n': build_i18n,
                    'build_desktop': build_desktop,
                    'build_man': build_man,
                    'build_doc': build_doc,
                    'install_data': distutils.command.install_data.install_data,
                    'install_lib': install_lib,
                    'install_desktop': install_desktop,
                    'install': install,
                    'clean': clean,
                    'sdist': sdist,
                },
     )
     
