#-*- coding:utf-8 -*-

#  Copyright © 2009, 2011-2017  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/>.


import sys, os

from . import debug, config

N_ = lambda s: s


class Tee2(object):
    class Tee(object):
        def __init__(self, file1, file2):
            self.file1 = file1
            self.file2 = file2
        def write(self, data):
            self.file1.write(data)
            self.file1.flush()
            self.file2.write(data)
        def flush(self):
            self.file1.flush()
            self.file2.flush()
            
    def __init__(self, name='pybik.log', mode='w'):
        self.file = open(name, mode)
        self.file.write('logfile created\n')
        self.file.flush()
        self.stdout = sys.stdout
        self.stderr = sys.stderr
        sys.stdout = self.Tee(self.file, self.stdout)
        sys.stderr = self.Tee(self.file, self.stderr)
        
    def __del__(self):
        sys.stdout = self.stdout
        sys.stderr = self.stderr
        self.file.close()
        
        
class Options:
    test_arg_names = ['write-y', 'write-yes', 'write-n', 'write-no', 'write-e', 'write-error',
                      'log-widgets', 'notime']
    arg_info = [
        (None, None, '\n'.join(config.LONG_DESCRIPTION)),
        (None, None, 'Pybik has a graphical user interface.'
                     ' The options are intended for testing and debugging.'),
        (None, None, 'Options:'),
        (['-h', '--help'], lambda self, value: self.print_usage(),
                            'Show help message and exit'),
        (['--version'], lambda self, value: self.print_version(),
                            'Show version number and exit'),
        (['--config-file='], lambda self, value: self.__dict__.update(config_file=value),
                            'Specify the configuration filename'),
        (['--defaultconfig'], lambda self, value: self.__dict__.update(defaultconfig=True),
                            'Print default settings to stdout and exit'),
        (['--games-file='], lambda self, value: self.__dict__.update(games_file=value),
                            'Specify the filename for saved games'),
        (['--qml-file='], lambda self, value: self.__dict__.update(qml_file=value),
                            'Alternate user interface'),
        (['--variant='], lambda self, value: self.__dict__.update(variant=value),
                            'Extension module variant'),
        (['--demo='], lambda self, value: self.__dict__.update(demo=value),
                            'Demo module'),
        (['--log'], lambda self, value: self.tee(),
                            'Write logfile'),
        #TODO: test options removed in revision 4196
        (['--debug='], lambda self, value: debug.set_flagstring(value),
                            'D is a comma-separated list of debug flags:\n{0}'
                            .format(' '.join(debug.flag_nicks))),
        (None, None,  'Qt-Options, for a full list refer to the Qt-Reference,'
                ' but not all options make sense for this application:'),
        (['-qwindowgeometry G', '-geometry G'], None,
                            'Specifies window geometry for the main window using the X11-syntax.'
                            ' For example: 100x100+50+50'),
        (['-reverse'], None,
                            'Sets the application layout direction to left-to-right'),
        (['-widgetcount'], None,
                            'Prints debug message at the end about number of widgets left'
                            ' undestroyed and maximum number of widgets existed at the same time'),
        ]
        
    def __init__(self):
        self.config_file = None
        self.defaultconfig = False
        self.games_file = None
        self.qml_file = None
        self.variant = None
        self.demo = None
        self.pure_python = False
        self.test = []
        self.test_args = []
        self.ui_args = []
        
    @staticmethod
    def format_opts(args):
        args = ((a + a.strip('--')[0].upper() if a.endswith('=') else a) for a in args)
        return '  ' + ', '.join(args)
        
    @staticmethod
    def format_help(text, indent=0):
        try:
            width = int(os.environ['COLUMNS']) - 2
        except (KeyError, ValueError):
            width = 78
        width -= indent
        def split(text):
            lines = text.split('\n')
            for line in lines:
                res = None
                words = line.split(' ')
                for word in words:
                    if res is None:
                        res = word
                    elif len(res + word) + 1 <= width:
                        res = ' '.join((res, word))
                    else:
                        yield res
                        res = word
                yield res
        return '\n'.ljust(indent+1).join(split(text))
        
    @classmethod
    def print_usage(cls):
        print('Usage:', sys.argv[0], '[options]')
        for args, unused_func, text in cls.arg_info:
            if args is None:
                print()
                print(cls.format_help(text))
            else:
                opts = cls.format_opts(args)
                if len(opts) > 18:
                    text = '\n' + text
                else:
                    opts = opts.ljust(20)
                text = cls.format_help(text, 20)
                print(opts + text)
        sys.exit(0)
        
    @staticmethod
    def print_version():
        from textwrap import fill
        print(config.APPNAME, config.VERSION)
        print()
        print(config.COPYRIGHT)
        print()
        print('\n\n'.join(fill(text, width=78) for text in config.LICENSE_INFO.split('\n\n')))
        print()
        print(fill(config.LICENSE_NOT_FOUND, width=78))
        sys.exit(0)
        
    def tee(self):
        self._tee = Tee2()
        
    def parse(self, args):
        arg_functs = {o: f for opts, f, h in self.arg_info for o in opts or [] if f is not None}
        for arg in args:
            try:
                index = arg.index('=')
            except ValueError:
                key, value = arg, None
            else:
                key, value = arg[:index+1], arg[index+1:]
            try:
                func = arg_functs[key]
            except KeyError:
                self.ui_args.append(arg)
            else:
                func(self, value)
                
        for a in self.test_args:
            if a not in self.test_arg_names:
                print('unknown test argument:', a)
                sys.exit(1)
                
        
class Main:
    def __init__(self, root_dir=None):
        self.root_dir = root_dir
        self.opts = Options()
        
        self.opts.parse(sys.argv)
        
        from .debug import DEBUG, DEBUG_QSGINFO, DEBUG_BASICRENDERER, DEBUG_PUREPYTHON, DEBUG_PHONE, DEBUG_CLOCALE
        if DEBUG:
            print('Qt args:', self.opts.ui_args)
            if DEBUG_QSGINFO:
                print('QSG_INFO=1')
                os.environ['QSG_INFO'] = '1'
            if DEBUG_BASICRENDERER:
                print('QSG_RENDER_LOOP=basic')
                os.environ['QSG_RENDER_LOOP'] = 'basic'
            if DEBUG_CLOCALE:
                os.environ['LANGUAGE'] = ''
                os.environ['LC_ALL'] = 'C.UTF-8'
                print('LC_ALL='+os.environ['LC_ALL'])
        
        if self.opts.defaultconfig:
            from .settings import settings
            settings.load('')
            settings.dump(sys.stdout, all=True)
            sys.exit(0)
            
        package = config.PACKAGE_FULL if DEBUG_PHONE else config.PACKAGE
        if self.opts.config_file is None:
            self.opts.config_file = config.get_config_home(package, 'settings.conf')
        elif not self.opts.config_file:
            self.opts.config_file = None
        if self.opts.games_file is None:
            self.opts.games_file = config.get_data_home(package, 'games')
        elif not self.opts.games_file:
            self.opts.games_file = None
        if self.opts.demo:
            if self.opts.games_file is not None:
                print('saved games not used in demo mode')
                self.opts.games_file = None
            if self.opts.config_file is not None:
                print('saved config not used in demo mode')
                self.opts.config_file = None
        if self.opts.qml_file is None:
            self.opts.qml_file = os.path.join(config.UI_DIR, 'qt', 'main.qml')
            
        #TODO: purepython mode currently not implemented
        if DEBUG_PUREPYTHON and False:
            from .ext import qtexec
        else:
            from .ext import _qtexec_ as qtexec
        self.qtexec = qtexec
            
        res = qtexec.exec_application(self)
        if res:
            sys.exit(res)
            
    def resolve_variant(self):
        from .debug import DEBUG, DEBUG_IMPORT
        if self.opts.variant is None:
            if DEBUG_IMPORT: print('no variant specified, using default')
            self.opts.variant = self.qtexec.aliases['']
        elif self.opts.variant not in self.qtexec.depends:
            if self.opts.variant in self.qtexec.aliases:
                if DEBUG_IMPORT: print('alias variant specified:', repr(self.opts.variant))
                self.opts.variant = self.qtexec.aliases[self.opts.variant]
            else:
                if DEBUG_IMPORT: print('unknown variant: ', repr(self.opts.variant), ', using default instead', sep='')
                self.opts.variant = self.qtexec.aliases['']
        if '???' in self.opts.variant:
            if DEBUG_IMPORT: print('autodetect GL-variant')
            self.opts.variant = self.opts.variant.replace('???', self.qtexec.get_gl_variant())
        if DEBUG: print('variant:', self.opts.variant)
        assert self.opts.variant in self.qtexec.depends
        
    def load_gllib(self):
        from .debug import DEBUG_IMPORT
        #XXX: Workaround for lp:941826, "dlopen(libGL.so) resolves to mesa rather than nvidia"
        #     Was: "PyQt cannot compile shaders with Ubuntu's Nvidia drivers"
        #     https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
        #XXX: Also needed in compiled mode. Without this, on the phone the OpenGL entry points
        #     in the first module are NULL and the app will crash
        # The Python doc says:
        #   it may be better to determine the shared library name at development time,
        #   and hardcode that into the wrapper module instead of using find_library()
        #   to locate the library at runtime.
        # find_library() also may fail as it relies on external tools, so hardcode the names
        import ctypes
        # self.opts.variant must be a full variant name: (qtw|qtq)(ogl|es2)[os][d]
        gllib = {'ogl': 'libGL.so.1', 'es2': 'libGLESv2.so.2'}[self.opts.variant[3:6]]
        if DEBUG_IMPORT:
            import ctypes.util
            glname = gllib.split('.')[0][3:]
            print('OpenGL library:', gllib, glname, ctypes.util.find_library(glname))
        try:
            ctypes.CDLL(gllib, ctypes.RTLD_GLOBAL)
        except OSError as e:
            import ctypes.util
            print('OpenGL library not found:', gllib, glname)
            print('   ', e)
            glname = gllib.split('.')[0][3:]
            gllib = ctypes.util.find_library(glname)
            if gllib is None:
                print('    not found by name {!r}, continue without preloading'.format(glname))
            else:
                print('    retry with:', gllib)
                try:
                    ctypes.CDLL(gllib, ctypes.RTLD_GLOBAL)
                except OSError as e:
                    print('    not found, continue without preloading:')
                    print('   ', e)
        
    def cb_app_post_create(self, language):
        self.resolve_variant()
        self.load_gllib()
        
        from .debug import DEBUG_IMPORT
        import pybiklib.ext
        for extname in self.qtexec.depends[self.opts.variant]:
            name = extname.split('_')[1]
            if DEBUG_IMPORT:
                print('importing extension', extname, 'as', name)
            __import__('pybiklib.ext', fromlist=(extname,))
            module = getattr(pybiklib.ext, extname)
            sys.modules['pybiklib.ext.'+name] = module
            setattr(pybiklib.ext, name, module)
            if debug is not None:
                module.set_debug_flags(debug)
            
        import gettext
        if self.root_dir == sys.prefix:
            # normal installation
            LOCALEDIR = None
        else:
            # different root, e.g. /usr/local, source directory
            LOCALEDIR = config.LOCALE_DIR
        t = gettext.translation(config.PACKAGE, LOCALEDIR, languages=[language], fallback=True)
        t.install(names=['ngettext'])
        
        from pybiklib.ext import qt
        qt.app_post_create(_)
        
    def cb_run_app(self):
        from .application import MainWindow
        if self.opts is None:
            return None
        elif self.opts.test:
            #TODO: removed in revision 4196
            return None
        else:
            window = MainWindow(self.opts)
            self.opts = None
            return window
            
    

