#!/usr/bin/python
# setup.py - Build system for Ubuntu SSO Client package
#
# Copyright 2014 National University of Defense Technology(NUDT) & Kylin Ltd
# Copyright 2010-2012 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations
# including the two.
# You must obey the GNU General Public License in all respects
# for all of the code used other than OpenSSL.  If you modify
# file(s) with this exception, you may extend this exception to your
# version of the file(s), but you are not obligated to do so.  If you
# do not wish to do so, delete this exception statement from your
# version.  If you delete this exception statement from all source
# files in the program, then also delete it here.
"""Setup.py: build, distribute, clean."""

# pylint: disable=W0404, W0511

import distutils
import os
import sys

try:
    import DistUtilsExtra.auto
    from DistUtilsExtra.command import build_extra
except ImportError:
    print >> sys.stderr, 'To build this program you need '\
                         'https://launchpad.net/python-distutils-extra'
    sys.exit(1)
assert DistUtilsExtra.auto.__version__ >= '2.18', \
    'needs DistUtilsExtra.auto >= 2.18'

from distutils import log

from ubuntu_kylin_sso.utils import get_cert_dir

PROJECT_NAME = 'ubuntu-kylin-sso-client'
VERSION = '99.12'

POT_FILE = 'po/ubuntu-kylin-sso-client.pot'
SERVICE_FILE = 'data/com.ubuntukylin.sso.service'
CONSTANTS = 'ubuntu_kylin_sso/constants.py'

CLEANFILES = [SERVICE_FILE, POT_FILE, CONSTANTS, 'MANIFEST']
QT_UI_DIR = os.path.join('ubuntu_kylin_sso', 'qt', 'ui')


def replace_version(*args, **kwargs):
    """Replace the @VERSION@ in the constants file with the actual version."""
    with open(CONSTANTS + '.in') as in_file:
        content = in_file.read()
        with open(CONSTANTS, 'w') as out_file:
            content = content.replace('@VERSION@', VERSION)
            content = content.replace('@PROJECT_NAME@', PROJECT_NAME)
            out_file.write(content)


def replace_prefix(prefix):
    """Replace every '@prefix@' with prefix within 'filename' content."""
    with open(SERVICE_FILE + '.in') as in_file:
        content = in_file.read()
        with open(SERVICE_FILE, 'w') as out_file:
            out_file.write(content.replace('@prefix@', prefix))


class SSOInstall(DistUtilsExtra.auto.install_auto):
    """Class to install proper files."""

    def run(self):
        """Do the install.

        Read from *.service.in and generate .service files by replacing
        @prefix@ by self.prefix.

        """
        prefix = self.install_data.replace(
            self.root if self.root is not None else '', '')
        replace_prefix(prefix)
        DistUtilsExtra.auto.install_auto.run(self)
        # Replace the CONSTANTS prefix here, so that we can do it directly in
        # the installed copy, rather than the lcoal copy. This allows us to
        # have a semi-generated version for use in tests, and a full version
        # for use in installed systems.
        with open(CONSTANTS) as in_file:
            content = in_file.read()
            with open(os.path.join(self.install_purelib,
                                   PROJECT_NAME,
                                   CONSTANTS), 'w') as out_file:
                out_file.write(content.replace('@prefix@', prefix))


class SSOBuild(build_extra.build_extra):
    """Build PyQt (.ui) files and resources."""

    description = "build PyQt GUIs (.ui) and resources (.qrc)"

    def compile_ui(self, ui_file, py_file=None):
        """Compile the .ui files to python modules."""
        # Search for pyuic4 in python bin dir, then in the $Path.
        if py_file is None:
            # go from the ui_file in the data folder to the
            # python file in the qt moodule
            py_file = os.path.split(ui_file)[1]
            py_file = os.path.splitext(py_file)[0] + '_ui.py'
            py_file = os.path.join(QT_UI_DIR, py_file)
        # we indeed want to catch Exception, is ugly but we need it
        # pylint: disable=W0703
        try:
            # import the uic compiler from pyqt and generate the .py files
            # something similar could be done with pyside but that is left
            # as an exercise for the reader.
            from PyQt4 import uic
            fp = open(py_file, 'w')
            uic.compileUi(ui_file, fp)
            fp.close()
            log.info('Compiled %s into %s', ui_file, py_file)
        except Exception, e:
            self.warn('Unable to compile user interface %s: %s' % (py_file, e))
            if not os.path.exists(py_file) or not file(py_file).read():
                raise SystemExit(1)
            return
        # pylint: enable=W0703

    def compile_rc(self, qrc_file, py_file=None):
        """Compile the resources that will be included with the project."""
        import PyQt4
        # Search for pyuic4 in python bin dir, then in the $Path.
        if py_file is None:
            py_file = os.path.split(qrc_file)[1]
            py_file = os.path.splitext(py_file)[0] + '_rc.py'
            py_file = os.path.join(QT_UI_DIR, py_file)
        path = os.getenv('PATH')
        os.putenv('PATH', path + os.path.pathsep + os.path.join(
                  os.path.dirname(PyQt4.__file__), 'bin'))
        if os.system('pyrcc4 -no-compress "%s" -o "%s"' %
                     (qrc_file, py_file)) > 0:
            self.warn('Unable to generate python module {py_file}'
                      ' for resource file {qrc_file}'.format(
                          py_file=py_file, qrc_file=qrc_file))
            if not os.path.exists(py_file) or not file(py_file).read():
                raise SystemExit(1)
        else:
            log.info('compiled %s into %s' % (qrc_file, py_file))
        os.putenv('PATH', path)

    def _generate_qrc(self, qrc_file, srcfiles, prefix):
        """Generate the qrc file for the given src files."""
        basedir = os.path.dirname(qrc_file)
        f = open(qrc_file, 'w')
        try:
            f.write('<!DOCTYPE RCC><RCC version="1.0">\n')
            import cgi
            f.write('  <qresource prefix="%s">\n' % cgi.escape(prefix))
            for e in srcfiles:
                relpath = e[len(basedir) + 1:]
                f.write('    <file>%s</file>\n'
                        % cgi.escape(relpath.replace(os.path.sep, '/')))
            f.write('  </qresource>\n')
            f.write('</RCC>\n')
        finally:
            f.close()

    def build_rc(self, py_file, basedir, prefix='/'):
        """Generate compiled resource including any files under basedir"""
        # For details, see http://doc.qt.nokia.com/latest/resources.html
        qrc_file = os.path.join(basedir, '%s.qrc' % os.path.basename(basedir))
        srcfiles = [os.path.join(root, e)
                    for root, _dirs, files in os.walk(basedir) for e in files]
        # NOTE: Here we cannot detect deleted files. In such cases, we need
        # to remove .qrc manually.
        try:
            self._generate_qrc(qrc_file, srcfiles, prefix)
            self.compile_rc(qrc_file, py_file)
        finally:
            os.unlink(qrc_file)

    def run(self):
        """Execute the command."""
        replace_version()
        basepath = os.path.join('data', 'qt')
        # TODO: build the resource files so that we can include them
        # self.build_rc(os.path.join(basepath, 'icons_rc.py'),
        #              os.path.join(os.path.dirname(__file__), 'icons'),
        #              '/icons')
        for dirpath, _, filenames in os.walk(basepath):
            for filename in filenames:
                if filename.endswith('.ui'):
                    self.compile_ui(os.path.join(dirpath, filename))
                elif filename.endswith('.qrc'):
                    self.compile_rc(os.path.join(dirpath, filename))

        build_extra.build_extra.run(self)


class SSOClean(DistUtilsExtra.auto.clean_build_tree):
    """Class to clean up after the build."""

    def run(self):
        """Clean up the built files."""
        for built_file in CLEANFILES:
            if os.path.exists(built_file):
                os.unlink(built_file)

        for dirpath, _, filenames in os.walk(os.path.join(QT_UI_DIR)):
            for current_file in filenames:
                if current_file.endswith('_ui.py') or\
                                current_file.endswith('_rc.py'):
                    os.unlink(os.path.join(dirpath, current_file))

        DistUtilsExtra.auto.clean_build_tree.run(self)


def set_py2exe_paths():
    """Set the path so that py2exe finds the required modules."""
    # Pylint does not understand same spaced imports
    # pylint: disable=F0401
    import win32com
    # pylint: enable=F0401
    try:
        # pylint: disable=F0401
        import py2exe.mf as modulefinder
        # pylint: enable=F0401
    except ImportError:
        import modulefinder

    # py2exe 0.6.4 introduced a replacement modulefinder.
    # This means we have to add package paths there,
    # not to the built-in one.  If this new modulefinder gets
    # integrated into Python, then we might be able to revert
    # this some day. If this doesn't work, try import modulefinder
    for package_path in win32com.__path__[1:]:
        modulefinder.AddPackagePath("win32com", package_path)
    for extra_mod in ["win32com.server", "win32com.client"]:
        __import__(extra_mod)
        module = sys.modules[extra_mod]
        for module_path in module.__path__[1:]:
            modulefinder.AddPackagePath(extra_mod, module_path)


# pylint: disable=C0103

cmdclass = {
    'install': SSOInstall,
    'build': SSOBuild,
    'clean': SSOClean,
}


sso_executables = [
    'bin/ubuntu-kylin-sso-login',
    'bin/ubuntu-kylin-sso-login-qt',
    'bin/ubuntu-kylin-sso-proxy-creds-qt',
    'bin/ubuntu-kylin-sso-ssl-certificate-qt',
]


class dummy_build_i18n(distutils.cmd.Command):

    """Dummy for windows."""

    def initialize_options(self, *args):
        """Dummy."""

    def finalize_options(self, *args):
        """Dummy."""

    def run(self, *args):
        """Dummy."""


if sys.platform == 'win32':
    cmdclass['build_i18n'] = dummy_build_i18n


# data_files = [(get_cert_dir(),
#              ['data/UbuntuOne-Go_Daddy_CA.pem',
#               'data/UbuntuOne-ValiCert_Class_2_VA.pem',
#               'data/UbuntuOne-Go_Daddy_Class_2_CA.pem'])]
data_files = [(get_cert_dir(),
             ['data/UbuntuKylin_CA.pem'])]

if sys.platform == 'win32':
    set_py2exe_paths()
    # for PyQt, see http://www.py2exe.org/index.cgi/Py2exeAndPyQt
    _includes = ['sip', 'email', 'ubuntu_kylin_sso.qt.gui',
                 'ubuntu_kylin_sso.qt.controllers', 'PyQt4.QtNetwork', 'PIL']
    extra = {
        'options': {
            'py2exe': {
                'bundle_files': 1,
                'skip_archive': 0,
                'includes': _includes,
                'optimize': 1,
                'dll_excludes': ["mswsock.dll", "powrprof.dll"],
            },
        },
        # add the console script so that py2exe compiles it
        'console': sso_executables,
        'zipfile': None,
    }
else:
    data_files.extend([
        ('lib/ubuntu-kylin-sso-client', sso_executables),
        ('share/dbus-1/services', ['data/com.ubuntukylin.sso.service']),
        ('share/app-install/icons', ['data/youker-id.png']),
    ])
    extra = {}

DistUtilsExtra.auto.setup(
    name=PROJECT_NAME,
    version=VERSION,
    license='GPL v3',
    author='Ubuntu Kylin Team',
    author_email='ubuntukylin-members@list.launchpad.net',
    description='Ubuntu Kylin Single Sign-On client',
    long_description=('Desktop service to allow applications to sign in'
                      'to Ubuntu Kylin services via SSO'),
    url='https://launchpad.net/ubuntu-kylin-sso-client',
    extra_path=PROJECT_NAME,
    data_files=data_files,
    packages=[
        'ubuntu_kylin_sso',
        'ubuntu_kylin_sso.tests',
        'ubuntu_kylin_sso.keyring',
        'ubuntu_kylin_sso.keyring.tests',
        'ubuntu_kylin_sso.main',
        'ubuntu_kylin_sso.main.tests',
        'ubuntu_kylin_sso.networkstate',
        'ubuntu_kylin_sso.networkstate.tests',
        'ubuntu_kylin_sso.qt',
        'ubuntu_kylin_sso.qt.main',
        'ubuntu_kylin_sso.qt.ui',
        'ubuntu_kylin_sso.utils',
        'ubuntu_kylin_sso.utils.tests',
        'ubuntu_kylin_sso.utils.runner',
        'ubuntu_kylin_sso.utils.runner.tests',
        'ubuntu_kylin_sso.utils.webclient',
        'ubuntu_kylin_sso.utils.webclient.tests',
    ],
    cmdclass=cmdclass,
    **extra)
