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

#  Copyright © 2009, 2011-2013  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
import re

import distutils

from distutils.core import setup
import distutils.cmd
from distutils.extension import Extension

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

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

#from Cython.Distutils import build_ext

from tools.po_isempty import po_isempty
from pybiklib import config


class Distribution (distutils.dist.Distribution):
    def __init__(self, attrs=None):
        self.bug_contact = None
        self.qt_ui_files = None
        distutils.dist.Distribution.__init__(self, attrs=attrs)


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'),
            ('use-generated-code', None, 'Use generated Code included in distributed source'
                                         ' to avoid build-dependencies'),
            ('parallel=', None, 'Build in parallel with the given number of processes or "auto"'),
            ('arch-only', None, 'Build only architecture dependent files'),
            ('indep-only', None, 'Build only architecture independent files'),
            ('fast', None, 'Build models without optimization'),
        ]
    boolean_options = distutils.command.build.build.boolean_options + [
            'inplace', 'use-generated-code', 'arch-only', 'indep-only', 'fast',
        ]
        
    def has_pure_modules(self):
        return self.indep_only and not self.inplace 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_ui',      lambda self: self.indep_only),
                    ('build_models',  lambda self: self.indep_only),
                    ('build_i18n',    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.use_generated_code = False
        self.parallel = None
        self.arch_only = False
        self.indep_only = False
        self.fast = False
        
    def finalize_options(self):
        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):
    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):
        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 tools.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)
        
    def run_cython(self, cython_opts, infile, outfile):
        info('cython: %s --> %s' % (infile, outfile))
        self.spawn(['cython3', '-3'] + cython_opts + ['-o', outfile, infile])
        
    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 self.inplace and 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_csrc (_build_csrc):
    description = "create C source code for Python extensions"
    
    def build_extension(self, extension):
        _build_csrc.build_extension(self, extension)
        for c_file in extension.sources:
            inplace_file = os.path.join('csrc', os.path.basename(c_file))
            self.mkpath('csrc')
            self.copy_file(c_file, inplace_file)
            
        
class build_ext (_build_csrc):
    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)
            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_ui(distutils.cmd.Command):
    description = "Compile Qt user interfaces files (.ui) and resources to python modules."
    
    user_options = [
        ('indent=',     'I', 'set indent width to N spaces, tab if N is 0 (default: 4)'),
        ('ui-execute',  'x', 'generate extra code to test and display the class'),
        ('from-imports','F', 'generate imports relative to "."'),
        ('build-lib=',  'b', 'directory for compiled UI modules'),
        ('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)'),
        ('use-generated-code', None, 'Use generated Code included in distributed source'
                                     ' to avoid build-dependencies'),
    ]
    boolean_options = ['from-imports', 'ui-execute', 'inplace', 'force', 'use-generated-code',
                       ]
    
    def initialize_options(self):
        self.qt_ui_files = None
        self.indent = 4
        self.ui_execute = False
        self.from_imports = False
        self.build_lib = None
        self.inplace = None
        self.force = None
        self.use_generated_code = None
        
    def finalize_options(self):
        self.qt_ui_files = self.distribution.qt_ui_files
        self.set_undefined_options('build',
                                       ('build_lib', 'build_lib'),
                                       ('inplace', 'inplace'),
                                       ('force', 'force'),
                                       ('use_generated_code', 'use_generated_code'),
                                   )
                
    def run(self):
        for package_dir, ui_files in self.qt_ui_files:
            if self.use_generated_code:
                if not self.inplace:
                    self.copy_tree(package_dir, os.path.join(self.build_lib, package_dir))
            else:
                if not self.inplace:
                    package_dir = os.path.join(self.build_lib, package_dir)
                self.mkpath(package_dir)
                init_file = os.path.join(package_dir, '__init__.py')
                if self.force and os.path.exists(init_file):
                    os.remove(init_file)
                if not os.path.exists(init_file):
                    self.execute(lambda: open(init_file, 'wt', encoding='utf-8').close(),
                                 [], 'generating ' + init_file)
                for ui_file in ui_files:
                    filebase = os.path.splitext(os.path.basename(ui_file))[0]
                    py_file = os.path.join(package_dir, filebase + '.py')
                    self.make_file(ui_file, py_file, self.compile_ui, [ui_file, py_file])
                    
    def compile_ui(self, ui_file, py_file):
        from tools 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)
            

class build_models(distutils.cmd.Command):
    description = "Create data for Pybik models and write it to python modules."
    
    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,'Build in parallel with the given number of processes or "auto"'),
        ('fast',        None,'Build models without optimization'),
    ]
    boolean_options = ['inplace', 'force', 'fast']
    
    def initialize_options(self):
        self.build_base = None
        self.inplace = None
        self.force = None
        self.parallel = None
        self.fast = None
        
    def finalize_options(self):
        self.set_undefined_options('build',
                                       ('build_base', 'build_base'),
                                       ('inplace', 'inplace'),
                                       ('force', 'force'),
                                       ('parallel', 'parallel'),
                                       ('fast', 'fast'),
                                   )
        if self.parallel is None:
            self.parallel = 1
        elif self.parallel == 'auto':
            import multiprocessing
            self.parallel = multiprocessing.cpu_count()
        else:
            self.parallel = int(self.parallel)
                
    def run(self):
        from tools 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)
        def create_modeldata(directory, testfunc):
            modeldata.create_modeldata(directory, testfunc, self.parallel, self.fast)
        if self.force:
            testfunc = None
        else:
            testfunc = lambda filename: newer('pybiklib/model.py', filename)
        message = 'generating model data using {} processes'.format(self.parallel)
        self.execute(create_modeldata, [modeldir, testfunc], msg=message)
        data_files = self.distribution.data_files
        for filename in os.listdir(modeldir):
            sourcepath = os.path.join(modeldir, filename)
            data_files.append(('share/pybik/models', (sourcepath,)))
        
        
class build_i18n(distutils.cmd.Command):
    description = "integrate the gettext framework"
    
    user_options = [
        ('desktop-files=',  None, '.desktop.in files that should be merged'),
        ('xml-files=',      None, '.xml.in files that should be merged'),
        ('schemas-files=',  None, '.schemas.in files that should be merged'),
        ('key-files=',      None, '.key.in files that should be merged'),
        ('domain=',         'd',  'gettext domain'),
        ('merge-po',        'm',  'merge po files against template'),
        ('po-dir=',         'p',  'directory that holds the i18n files'),
        ('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 = ['merge-po', 'inplace', 'force']
    
    def initialize_options(self):
        self.build_base = None
        self.desktop_files = []
        self.xml_files = []
        self.key_files = []
        self.schemas_files = []
        self.domain = None
        self.merge_po = False
        self.po_dir = None
        self.inplace = None
        self.force = None
        
    def finalize_options(self):
        self.set_undefined_options('build', ('build_base', 'build_base'),
                                            ('inplace', 'inplace'),
                                            ('force', 'force'),
                                  )
        if self.inplace:
            self.mo_dir = 'data/locale'
        else:
            self.mo_dir = os.path.join(self.build_base, 'mo')
        if self.domain is None:
            self.domain = self.distribution.metadata.name
        if self.po_dir is None:
            self.po_dir = "po"
            
    def do_merge_po(self):
        '''Update po(t) files and print a report'''
        
        from tools.conv_plugin_for_translation import convert
        # We have to change the working dir to the po dir for intltool
        wd = os.getcwd()
        temp_files = []
        try:
            for unused_target, files in self.distribution.data_files:
                for in_file in files:
                    if in_file.endswith('.algorithm'):
                        out_file = in_file + '.py'
                        self.make_file(in_file, out_file, convert, [in_file, out_file])
                        temp_files.append(out_file)
            os.chdir(self.po_dir)
            self.spawn(["intltool-update", "-r", "-g", self.domain])
        finally:
            os.chdir(wd)
            for temp_file in temp_files:
                os.remove(temp_file)
                
    def run(self):
        """Update the language files, generate mo files and add them to the to be installed files"""
        
        os.environ["XGETTEXT_ARGS"] = "--msgid-bugs-address=%s " % self.distribution.bug_contact
            
        # Print a warning if there is a Makefile that would overwrite our values
        if os.path.exists("%s/Makefile" % self.po_dir):
            self.announce(''
                    'WARNING: Intltool will use the values specified from the'
                    '         existing po/Makefile in favor of the vaules'
                    '         from setup.cfg.'
                    '         Remove the Makefile to avoid problems.')
                    
        if self.merge_po:
            #TODO: --merge-po should do this and nothing else
            self.do_merge_po()
            
        data_files = self.distribution.data_files
        for po_file in glob("%s/*.po" % self.po_dir):
            if po_isempty(po_file):
                print('skipping empty po file', po_file)
                continue
            lang = os.path.splitext(os.path.basename(po_file))[0]
            mo_dir =  os.path.join(self.mo_dir, lang, "LC_MESSAGES")
            mo_file = os.path.join(mo_dir, "%s.mo" % self.domain)
            self.mkpath(mo_dir)
            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,)))
            
            
        if not self.merge_po:
            return
        #FIXME: currently *.desktop is treated as source and generated from *.desktop.in with merge-po.sh
        #       better: *.desktop.in is treated as source and *.desktop not committed
        # merge .in with translation
        if not self.desktop_files:
            for targetpath, files in data_files:
                for f in files:
                    if f.endswith('.desktop'):
                        self.desktop_files.append(f)
        for (files, switch) in ((self.xml_files, "-x"),
                                 (self.desktop_files, "-d"),
                                 (self.schemas_files, "-s"),
                                 (self.key_files, "-k"),):
            for f in files:
                if f.endswith(".in"):
                    self.warn("aDon't know, what to do with this file: " + f)
                    continue
                srcfile = f + '.in'
                if not os.path.isfile(srcfile):
                    self.warn("sDon't know, what to do with this file: " + f)
                    continue
                dstfile = f
                def intltool_merge(switch, src, dst):
                    self.spawn(["intltool-merge", switch, self.po_dir, src, dst])
                self.make_file(glob('%s/*.po'%self.po_dir)+[srcfile], dstfile,
                               intltool_merge, [switch, srcfile, dstfile])
                
    
class build_man (distutils.cmd.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
        self.build_dir = None
        
    def finalize_options(self):
        self.set_undefined_options('build', ('build_base', 'build_base'),
                                            ('force', 'force'),
                                  )
        self.set_undefined_options('build_scripts', ('build_dir', 'build_dir'))
        
    def run(self):
        def create_manpage(script_file, section, man_file):
            os.environ['PYTHONPATH'] = '.'
            self.spawn(['help2man', '--name', config.SHORT_DESCRIPTION, '--section', section,
                                    '--output', man_file, '--no-info', script_file])
            
        data_files = self.distribution.data_files
        man_dir = os.path.join(self.build_base, 'man')
        self.mkpath(man_dir)
        if '.' not in sys.path:
            sys.path.insert(0, '.')
        section = '6'
        for script in os.listdir(self.build_dir):
            script_file = os.path.join(self.build_dir, script)
            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])
            data_files.append(('share/man/man'+section, [man_file]))
            
            
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 (distutils.command.install.install):
    user_options = distutils.command.install.install.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):
        distutils.command.install.install.initialize_options(self)
        self.data_dir = None
        self.arch_only = False
        self.indep_only = False
        
    def finalize_options(self):
        distutils.command.install.install.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 distutils.command.install.install.sub_commands
                                if cmd == 'install_lib']
            
    def run(self):
        if not self.skip_build:
            # distutils.command.install.install.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:
            with open(filename, 'rt', encoding='utf-8') as f:
                text = f.read()
            for pattern, repl in [
                        (r'^(data_dir\s*=\s*).*$',      r'\1' + repr(self.data_dir)),
                        (r'^(appdata_dir\s*=\s*).*$',   r'\1' + repr(app_data_dir)),]:
                text = re.sub(pattern, repl, text, count=1, flags=re.MULTILINE)
            with open(filename, 'wt', encoding='utf-8') as f:
                f.write(text)
            
        distutils.command.install.install.run(self)
        if self.record:
            self.warn('The --record option is broken, files from the build_ui command are not recorded.')
        
        
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 ['mo', 'man', 'models']:
                remove_tree_(os.path.join(self.build_base, _dir))
        distutils.command.clean.clean.run(self)
        if self.inplace:
            for dirname in ('pybiklib/', 'pybiklib/ui/', 'data/plugins/', 'pybiktest/'):
                for filename in glob(dirname + '*.pyc'):
                    remove_file(filename)
                remove_tree_(dirname + '__pycache__')
            if self.all:
                dirnames = ['pybiklib/ui', 'data/locale', 'data/models']
                for dirname in dirnames:
                    remove_tree_(dirname)
                for filename in glob('pybiklib/*.so'):
                    remove_file(filename)
                remove_file('pybik')
                
                
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
        
    def run(self):
        from tools import create_docs
        copyright = create_docs.read_utf8('debian/copyright')   # pylint: disable=W0622
        copyright = copyright.replace(
                ' The full text of the GPL is distributed in\n'
                ' /usr/share/common-licenses/GPL-3 on Debian systems.',
                ' The full text of the GPL is distributed in\n'
                ' the original source archive in the file COPYING.')
        create_docs.write_utf8('copyright', copyright)
        create_docs.create_README()
        
        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)
        remove_tree_('csrc')
        self.run_command('build_csrc')
        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)
        remove_tree_('csrc')
        
    def get_file_list(self):
        self.filelist.append('copyright')
        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)
        
        
setup(
        # Metadata
        name=config.PACKAGE,
        version=config.VERSION,
        author=config.AUTHOR,
        author_email=config.CONTACT_EMAIL,
        url=config.WEBSITE,
        description=config.SHORT_DESCRIPTION,
        long_description=config.LONG_DESCRIPTION,
        download_url=config.DOWNLOADS,
        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 :: Cython',
                'Programming Language :: OpenGL Shading Language',
                'Topic :: Games/Entertainment :: Puzzle Games',
            ],
        license=config.LICENSE_NAME,
        # Metadata added in overloaded Distribution
        bug_contact=config.CONTACT_FILEBUG,
        
        # Files
        scripts=['pybiklib/pybik.py'],
        data_files=[
                        ('share/applications', ['data/applications/pybik.desktop']),
                        ('share/pixmaps', ['data/pixmaps/pybik.png']),
                        ('share/pybik/ui', glob('data/ui/*.*')),
                        ('share/pybik/ui/images', glob('data/ui/images/*')),
                        ('share/pybik/plugins', glob('data/plugins/*.py')),
                        ('share/pybik/plugins', glob('data/plugins/*.algorithm')),
                        ('share/pybik/shaders', glob('data/shaders/*')),
                        ('share/pybik/tests', glob('data/tests/*')),
                        ('share/pybik/', ['data/GPL-3']),
                    ],
        qt_ui_files=[('pybiklib/ui', glob('data/ui/*.ui'))],
        ext_modules=[
                        Extension('pybiklib/_gldraw',
                                    ['pybiklib/gldraw.py'],
                                    language='c++',
                                    depends=['pybiklib/gl.pxd',
                                             'pybiklib/glm.pxd'],
                                    libraries=['GL'],
                                    define_macros=[('GL_GLEXT_PROTOTYPES', None)]),
                        Extension('pybiklib/_glarea',
                                    ["pybiklib/glarea.py"],
                                    language='c++',
                                    depends=['pybiklib/gl.pxd',
                                             'pybiklib/glm.pxd',
                                             'pybiklib/gldraw.py'],
                                    libraries=['GL'],
                                    define_macros=[('GL_GLEXT_PROTOTYPES', None)]),
                    ],
        packages=['pybiklib', 'pybiktest'],
        package_dir={'pybiklib': 'pybiklib', 'pybiktest': 'pybiktest'},
        package_data={'pybiktest': ['data/*']},
        
        # setup classes
        distclass=Distribution,
        cmdclass={
                    'build': build,
                    'build_csrc': build_csrc,
                    'build_ext': build_ext,
                    'build_scripts': build_scripts,
                    'build_ui': build_ui,
                    'build_models': build_models,
                    'build_i18n': build_i18n,
                    'build_man': build_man,
                    'install_data': distutils.command.install_data.install_data,
                    'install_lib': install_lib,
                    'install': install,
                    'clean': clean,
                    'sdist': sdist,
                },
     )
     
