#!/usr/bin/env python
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#

import glob
import os
import platform
import sys
from distutils.command.build import build as _build
from distutils.command.clean import clean as _clean
from distutils.command.install_data import install_data as _install_data
from shutil import rmtree, which

from setuptools import Command, find_packages, setup
from setuptools.command.test import test as _test

import msgfmt
from version import get_version

try:
    from sphinx.setup_command import BuildDoc
except ImportError:

    class BuildDoc:
        pass


def windows_check():
    return platform.system() in ('Windows', 'Microsoft')


def osx_check():
    return platform.system() == 'Darwin'


desktop_data = 'deluge/ui/data/share/applications/deluge.desktop'
metainfo_data = 'deluge/ui/data/share/metainfo/deluge.metainfo.xml'

# Variables for setuptools.setup
_package_data = {}
_exclude_package_data = {}
_entry_points = {'console_scripts': [], 'gui_scripts': [], 'deluge.ui': []}
_data_files = []
_version = get_version(prefix='deluge-', suffix='.dev0')


class PyTest(_test):
    def initialize_options(self):
        _test.initialize_options(self)
        self.pytest_args = []

    def finalize_options(self):
        _test.finalize_options(self)
        self.test_args = []
        self.test_suite = True

    def run_tests(self):
        import pytest

        errcode = pytest.main(self.test_args)
        sys.exit(errcode)


class CleanDocs(Command):
    description = 'Clean the documentation build and module rst files'
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        docs_build = 'docs/build'
        print(f'Deleting {docs_build}')
        try:
            rmtree(docs_build)
        except OSError:
            pass

        for module in glob.glob('docs/source/modules/deluge*.rst'):
            os.remove(module)


class BuildWebUI(Command):
    description = 'Minify WebUI files'
    user_options = []

    JS_DIR = os.path.join('deluge', 'ui', 'web', 'js')
    JS_SRC_DIRS = ('deluge-all', os.path.join('extjs', 'ext-extensions'))

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        js_basedir = os.path.join(os.path.dirname(__file__), self.JS_DIR)

        try:
            from minify_web_js import minify_js_dir

            import_error = ''
        except ImportError as err:
            import_error = err

        for js_src_dir in self.JS_SRC_DIRS:
            source_dir = os.path.join(js_basedir, js_src_dir)
            try:
                minify_js_dir(source_dir)
            except NameError:
                js_file = source_dir + '.js'
                if os.path.isfile(js_file):
                    print(
                        'Unable to minify but found existing minified: {}'.format(
                            js_file
                        )
                    )
                else:
                    # Unable to minify and no existing minified file found so exiting.
                    print('Import error: %s' % import_error)
                    sys.exit(1)

        # Create the gettext.js file for translations.
        try:
            from gen_web_gettext import create_gettext_js
        except ImportError:
            pass
        else:
            deluge_all_path = os.path.join(js_basedir, self.JS_SRC_DIRS[0])
            print('Creating WebUI translation file: %s/gettext.js' % deluge_all_path)
            create_gettext_js(deluge_all_path)


class CleanWebUI(Command):
    description = 'Clean the documentation build and rst files'
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        js_basedir = os.path.join(os.path.dirname(__file__), BuildWebUI.JS_DIR)

        # Remove files generated by minify script.
        for js_src_dir in BuildWebUI.JS_SRC_DIRS:
            for file_type in ('.js', '-debug.js'):
                js_file = os.path.join(js_basedir, js_src_dir + file_type)
                print(f'Deleting {js_file}')
                try:
                    os.remove(js_file)
                except OSError:
                    pass

        # Remove generated gettext.js
        js_file = os.path.join(js_basedir, 'gettext.js')
        print(f'Deleting {js_file}')
        try:
            os.remove(js_file)
        except OSError:
            pass


class BuildTranslations(Command):
    description = 'Compile .po files into .mo files & create .desktop file'

    user_options = [
        ('build-lib', None, 'lib build folder'),
        ('develop', 'D', 'Compile translations in develop mode (deluge/i18n)'),
    ]
    boolean_options = ['develop']

    def initialize_options(self):
        self.build_lib = None
        self.develop = False

    def finalize_options(self):
        self.set_undefined_options('build', ('build_lib', 'build_lib'))

    def run(self):
        po_dir = os.path.join(os.path.dirname(__file__), 'deluge', 'i18n')

        if self.develop:
            basedir = po_dir
        else:
            basedir = os.path.join(self.build_lib, 'deluge', 'i18n')

        intltool_merge = 'intltool-merge'
        if not windows_check() and which(intltool_merge):
            intltool_merge_opts = '--utf8 --quiet'
            for data_file in (desktop_data, metainfo_data):
                # creates the translated file from .in file.
                in_file = data_file + '.in'
                if 'xml' in data_file:
                    intltool_merge_opts += ' --xml-style'
                elif 'desktop' in data_file:
                    intltool_merge_opts += ' --desktop-style'

                print('Creating file: %s' % data_file)
                os.system(
                    'C_ALL=C '
                    + '%s '
                    * 5
                    % (intltool_merge, intltool_merge_opts, po_dir, in_file, data_file)
                )

        print('Compiling po files from %s...' % po_dir)
        for path, names, filenames in os.walk(po_dir):
            for f in filenames:
                upto_date = False
                if f.endswith('.po'):
                    lang = f[: len(f) - 3]
                    src = os.path.join(path, f)
                    dest_path = os.path.join(basedir, lang, 'LC_MESSAGES')
                    dest = os.path.join(dest_path, 'deluge.mo')
                    if not os.path.exists(dest_path):
                        os.makedirs(dest_path)
                    if not os.path.exists(dest):
                        sys.stdout.write('%s, ' % lang)
                        sys.stdout.flush()
                        msgfmt.make(src, dest)
                    else:
                        src_mtime = os.stat(src)[8]
                        dest_mtime = os.stat(dest)[8]
                        if src_mtime > dest_mtime:
                            sys.stdout.write('%s, ' % lang)
                            sys.stdout.flush()
                            msgfmt.make(src, dest)
                        else:
                            upto_date = True

        if upto_date:
            sys.stdout.write(' po files already up to date.  ')
        sys.stdout.write('\b\b \nFinished compiling translation files. \n')


class CleanTranslations(Command):
    description = 'Cleans translations files.'
    user_options = [
        ('all', 'a', 'Remove all build output, not just temporary by-products')
    ]
    boolean_options = ['all']

    def initialize_options(self):
        self.all = None

    def finalize_options(self):
        self.set_undefined_options('clean', ('all', 'all'))

    def run(self):
        for path in (desktop_data, metainfo_data):
            if os.path.isfile(path):
                print('Deleting %s' % path)
                os.remove(path)


class BuildPlugins(Command):
    description = 'Build plugins into .eggs'

    user_options = [
        ('install-dir=', None, 'develop install folder'),
        ('develop', 'D', 'Compile plugins in develop mode'),
    ]
    boolean_options = ['develop']

    def initialize_options(self):
        self.install_dir = None
        self.develop = False

    def finalize_options(self):
        pass

    def run(self):
        # Build the plugin eggs
        plugin_path = 'deluge/plugins/*'

        for path in glob.glob(plugin_path):
            if os.path.exists(os.path.join(path, 'setup.py')):
                if self.develop and self.install_dir:
                    os.system(
                        'cd '
                        + path
                        + '&& '
                        + sys.executable
                        + ' setup.py develop --install-dir=%s' % self.install_dir
                    )
                elif self.develop:
                    os.system(
                        'cd ' + path + '&& ' + sys.executable + ' setup.py develop'
                    )
                else:
                    os.system(
                        'cd '
                        + path
                        + '&& '
                        + sys.executable
                        + ' setup.py bdist_egg -d ..'
                    )


class CleanPlugins(Command):
    description = 'Cleans the plugin folders'
    user_options = [
        ('all', 'a', 'Remove all build output, not just temporary by-products')
    ]
    boolean_options = ['all']

    def initialize_options(self):
        self.all = None

    def finalize_options(self):
        self.set_undefined_options('clean', ('all', 'all'))

    def run(self):
        print("Cleaning the plugin's folders...")

        plugin_path = 'deluge/plugins/*'

        for path in glob.glob(plugin_path):
            if os.path.exists(os.path.join(path, 'setup.py')):
                c = 'cd ' + path + ' && ' + sys.executable + ' setup.py clean'
                if self.all:
                    c += ' -a'
                print("Calling '%s'" % c)
                os.system(c)

            # Delete the .eggs
            if path[-4:] == '.egg':
                print('Deleting egg file "%s"' % path)
                os.remove(path)

            # Delete the .egg-link
            if path[-9:] == '.egg-link':
                print('Deleting egg link "%s"' % path)
                os.remove(path)

        egg_info_dir_path = 'deluge/plugins/*/*.egg-info'

        for path in glob.glob(egg_info_dir_path):
            # Delete the .egg-info's directories
            if path[-9:] == '.egg-info':
                print('Deleting %s' % path)
                for fpath in os.listdir(path):
                    os.remove(os.path.join(path, fpath))
                os.removedirs(path)


class EggInfoPlugins(Command):
    description = 'Create .egg-info directories for plugins'

    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        # Build the plugin eggs
        plugin_path = 'deluge/plugins/*'

        for path in glob.glob(plugin_path):
            if os.path.exists(os.path.join(path, 'setup.py')):
                os.system('cd ' + path + '&& ' + sys.executable + ' setup.py egg_info')


class Build(_build):
    sub_commands = [
        ('build_webui', None),
        ('build_trans', None),
        ('build_plugins', None),
    ] + _build.sub_commands

    def run(self):
        # Run all sub-commands (at least those that need to be run).
        _build.run(self)
        try:
            from deluge._libtorrent import LT_VERSION

            print(f'Info: Found libtorrent ({LT_VERSION}) installed.')
        except ImportError as ex:
            print('Warning: libtorrent (libtorrent-rasterbar) not found: %s' % ex)


class InstallData(_install_data):
    """Custom class to fix `setup install` copying data files to incorrect location. (Bug #1389)"""

    def finalize_options(self):
        self.install_dir = None
        self.set_undefined_options(
            'install',
            ('install_data', 'install_dir'),
            ('root', 'root'),
            ('force', 'force'),
        )

    def run(self):
        _install_data.run(self)


class Clean(_clean):
    sub_commands = _clean.sub_commands + [
        ('clean_plugins', None),
        ('clean_trans', None),
        ('clean_webui', None),
    ]

    def run(self):
        # Remove deluge egg-info.
        root_egg_info_dir_path = 'deluge*.egg-info'
        for path in glob.glob(root_egg_info_dir_path):
            print('Deleting %s' % path)
            for fpath in os.listdir(path):
                os.remove(os.path.join(path, fpath))
            os.removedirs(path)

        # Run all sub-commands (at least those that need to be run)
        for cmd_name in self.get_sub_commands():
            self.run_command(cmd_name)
        _clean.run(self)


cmdclass = {
    'build': Build,
    'build_webui': BuildWebUI,
    'build_trans': BuildTranslations,
    'build_plugins': BuildPlugins,
    'build_docs': BuildDoc,
    'spellcheck_docs': BuildDoc,
    'install_data': InstallData,
    'clean_plugins': CleanPlugins,
    'clean_trans': CleanTranslations,
    'clean_docs': CleanDocs,
    'clean_webui': CleanWebUI,
    'clean': Clean,
    'egg_info_plugins': EggInfoPlugins,
    'test': PyTest,
}


if not windows_check() and not osx_check():
    for icon_path in glob.glob('deluge/ui/data/icons/hicolor/*x*'):
        size = os.path.basename(icon_path)
        icons = glob.glob(os.path.join(icon_path, 'apps', 'deluge*.png'))
        _data_files.append((f'share/icons/hicolor/{size}/apps', icons))
    _data_files.extend(
        [
            (
                'share/icons/hicolor/scalable/apps',
                ['deluge/ui/data/icons/hicolor/scalable/apps/deluge.svg'],
            ),
            ('share/pixmaps', ['deluge/ui/data/pixmaps/deluge.png']),
            (
                'share/man/man1',
                [
                    'docs/man/deluge.1',
                    'docs/man/deluged.1',
                    'docs/man/deluge-gtk.1',
                    'docs/man/deluge-web.1',
                    'docs/man/deluge-console.1',
                ],
            ),
        ]
    )
    if os.path.isfile(desktop_data):
        _data_files.append(('share/applications', [desktop_data]))
    if os.path.isfile(metainfo_data):
        _data_files.append(('share/metainfo', [metainfo_data]))


# Entry Points
_entry_points['console_scripts'] = [
    'deluge-console = deluge.ui.console:start',
]

# On Windows use gui_scripts to hide cmd popup (no effect on Linux/MacOS)
_entry_points['gui_scripts'] = [
    'deluge = deluge.ui.ui_entry:start_ui',
    'deluge-gtk = deluge.ui.gtk3:start',
    'deluge-web = deluge.ui.web:start',
    'deluged = deluge.core.daemon_entry:start_daemon',
]

# Provide Windows 'debug' exes for stdin/stdout e.g. logging/errors
if windows_check():
    _entry_points['console_scripts'].extend(
        [
            'deluge-debug = deluge.ui.ui_entry:start_ui',
            'deluge-web-debug = deluge.ui.web:start',
            'deluged-debug = deluge.core.daemon_entry:start_daemon',
        ]
    )

_entry_points['deluge.ui'] = [
    'console = deluge.ui.console:Console',
    'web = deluge.ui.web:Web',
    'gtk = deluge.ui.gtk3:Gtk',
]


_package_data['deluge'] = [
    'ui/data/pixmaps/*.png',
    'ui/data/pixmaps/*.svg',
    'ui/data/pixmaps/*.ico',
    'ui/data/pixmaps/*.gif',
    'ui/data/pixmaps/flags/*.png',
    'plugins/*.egg',
    'i18n/*/LC_MESSAGES/*.mo',
]
_package_data['deluge.ui.web'] = [
    'index.html',
    'css/*.css',
    'icons/*.png',
    'images/*.gif',
    'images/*.png',
    'js/*.js',
    'js/extjs/*.js',
    'render/*.html',
    'themes/css/*.css',
    'themes/images/*/*.gif',
    'themes/images/*/*.png',
    'themes/images/*/*/*.gif',
    'themes/images/*/*/*.png',
]
_package_data['deluge.ui.gtk3'] = ['glade/*.ui']

setup_requires = ['setuptools', 'wheel']
install_requires = [
    'twisted[tls]>=17.1',
    # Add pyasn1 for setuptools workaround:
    #   https://github.com/pypa/setuptools/issues/1510
    'pyasn1',
    'rencode',
    'pyopenssl',
    'pyxdg',
    'mako',
    'setuptools',
    "pywin32; sys_platform == 'win32'",
    "certifi; sys_platform == 'win32'",
    'zope.interface',
]
extras_require = {
    'all': [
        'setproctitle',
        'pillow',
        'chardet',
        'ifaddr',
    ]
}

# Main setup
setup(
    name='deluge',
    version=_version,
    fullname='Deluge BitTorrent Client',
    description='BitTorrent Client',
    author='Deluge Team',
    maintainer='Calum Lind',
    maintainer_email='calumlind+deluge@gmail.com',
    keywords='torrent bittorrent p2p fileshare filesharing',
    long_description=open('README.md').read(),
    long_description_content_type='text/markdown',
    url='https://deluge-torrent.org',
    project_urls={
        'GitHub (mirror)': 'https://github.com/deluge-torrent/deluge',
        'Sourcecode': 'http://git.deluge-torrent.org/deluge',
        'Issues': 'https://dev.deluge-torrent.org/report/1',
        'Discussion': 'https://forum.deluge-torrent.org',
        'Documentation': 'https://deluge.readthedocs.io',
    },
    classifiers=[
        'Development Status :: 4 - Beta',
        'Environment :: Console',
        'Environment :: Web Environment',
        'Environment :: X11 Applications :: GTK',
        'Framework :: Twisted',
        'Intended Audience :: End Users/Desktop',
        ('License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)'),
        'Programming Language :: Python',
        'Operating System :: MacOS :: MacOS X',
        'Operating System :: Microsoft :: Windows',
        'Operating System :: POSIX',
        'Topic :: Internet',
    ],
    python_requires='>=3.9',
    license='GPLv3+',
    cmdclass=cmdclass,
    setup_requires=setup_requires,
    install_requires=install_requires,
    extras_require=extras_require,
    data_files=_data_files,
    package_data=_package_data,
    exclude_package_data=_exclude_package_data,
    packages=find_packages(exclude=['deluge.plugins.*', 'deluge.tests']),
    entry_points=_entry_points,
)
