#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (C) 2010-2013 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
# Copyright (C) 2010-2013 by Dick Kniep <dick.kniep@lindix.nl>
#
# PyHoca GUI is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# PyHoca GUI 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

modules ={}

import sys
import os
import re
PROG_NAME = os.path.basename(sys.argv[0]).replace('.exe', '')
PROG_PID  = os.getpid()

if hasattr(sys, 'frozen') and str(sys.frozen) in ("windows_exe", "console_exe", "1", ):
    class Win32_Logging(object):

        softspace = 0
        _fname = os.path.join(os.environ['AppData'], PROG_NAME, '%s.log' % PROG_NAME)
        _file = None

        def __init__(self, filemode='a'):
            self._filemode = filemode
            if os.path.isfile(self._fname) and self._filemode == "w+":
                os.remove(self._fname)

        def write(self, text, **kwargs):
            if self._file is None:
                try:
                    try:
                        os.mkdir(os.path.dirname(self._fname))
                    except:
                        pass
                    self._file = open(self._fname, self._filemode)
                except:
                    pass
            else:
                self._file.write(text)
                self._file.flush()

        def flush(self):
            if self._file is not None:
                self._file.flush()

    sys.stdout = Win32_Logging(filemode='w+')
    sys.stderr = Win32_Logging(filemode='a')
    del Win32_Logging

import gevent
import gevent.monkey
gevent.monkey.patch_all()

import subprocess

try:
    import wxversion
    wxversion.select('2.9')
except: pass
try:
    import wxversion
    wxversion.select('2.8')
except: pass

import argparse
import os
import exceptions
import locale
import gettext
import wx

from x2go import X2GOCLIENT_OS as _X2GOCLIENT_OS

if _X2GOCLIENT_OS in ('Linux', 'Mac'):
    import setproctitle
    setproctitle.setproctitle(PROG_NAME)

if sys.argv[0].startswith('./') or sys.argv[0].startswith('python'):
    sys.path.insert(0, os.getcwd())
    os.environ['PYHOCAGUI_DEVELOPMENT'] = '1'
    print '### %s running in development mode ###' % PROG_NAME

from pyhoca.wxgui.basepath import locale_basepath

# Python X2Go modules
from x2go import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER
if _X2GOCLIENT_OS == 'Windows':
    from x2go import X2GoClientXConfig as _X2GoClientXConfig
from x2go import X2GoLogger as _X2GoLogger
from x2go import x2go_cleanup as _x2go_cleanup

# X2Go backends
from x2go.defaults import BACKENDS_CONTROLSESSION, BACKEND_CONTROLSESSION_DEFAULT
from x2go.defaults import BACKENDS_TERMINALSESSION, BACKEND_TERMINALSESSION_DEFAULT
from x2go.defaults import BACKENDS_SERVERSESSIONINFO, BACKEND_SERVERSESSIONINFO_DEFAULT
from x2go.defaults import BACKENDS_SERVERSESSIONLIST, BACKEND_SERVERSESSIONLIST_DEFAULT
from x2go.defaults import BACKENDS_PROXY, BACKEND_PROXY_DEFAULT
from x2go.defaults import BACKENDS_SESSIONPROFILES, BACKEND_SESSIONPROFILES_DEFAULT
from x2go.defaults import BACKENDS_CLIENTSETTINGS, BACKEND_CLIENTSETTINGS_DEFAULT
from x2go.defaults import BACKENDS_CLIENTPRINTING, BACKEND_CLIENTPRINTING_DEFAULT

from pyhoca.wxgui import __VERSION__ as _version
from pyhoca.wxgui import messages
from pyhoca.wxgui import PyHocaGUI

if _X2GOCLIENT_OS == 'Windows':
    from pyhoca.wxgui.basepath import nxproxy_binary
    os.environ.update({'NXPROXY_BINARY': nxproxy_binary, })

__author__ = "Mike Gabriel, Dick Kniep"
__version__ = _version

# version information
VERSION=_version
VERSION_TEXT="""
%s[%s] - an X2Go GUI client written in Python
----------------------------------------------------------------------
developed by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
sponsored by Dick Kniep <dick.kniep@lindix.nl> (2010-2013)

VERSION: %s

""" % (PROG_NAME, PROG_PID, VERSION)

def check_running():
    if _X2GOCLIENT_OS  in ('Linux', 'Mac'):
        p = subprocess.Popen(['ps', '-U', _CURRENT_LOCAL_USER, '-u', _CURRENT_LOCAL_USER], stdout=subprocess.PIPE)
        psA_out = p.communicate()
        if psA_out[0].count(PROG_NAME) <= 1:
            return False
        else:
            processes = psA_out[0].strip().strip('\n').strip().split('\n')
            sep = re.compile('[\s]+')
            processes_of_myself = [ sep.split(row) for row in processes if PROG_NAME in row and not str(PROG_PID) in row ]
            print
            print 'FIXME: We found at least one other PyHoca-GUI instance for this user,'
            print 'but cannot (yet) tell if it/they is/are running on the same $DISPLAY'
            print 'or some other $DISPLAY. PyHoca-GUI only allows one instance per $DISPLAY.'
            print
            print 'These are the other instances of PyHoca-GUI found for this user:'
            for line in processes_of_myself:
                print "    ".join(line)
            # FIXME: add $DISPLAY check here, only return False if no PyHoca-GUI instance is running on this $DISPLAY
            # return False
        return True
    elif _X2GOCLIENT_OS == 'Windows':
        import wmi
        w = wmi.WMI()
        _p_names = []
        for process in w.Win32_Process():
            _p_names.append(process.Name)
        return len([ _p_name for _p_name in _p_names if _p_name == PROG_NAME]) > 1


def version():
    # print version text and exit
    sys.stderr.write ("%s\n" % VERSION_TEXT)
    sys.exit(0)


# sometimes we have to fail...
def runtime_error(m, parser=None, exitcode=-1):
    """\
    STILL UNDOCUMENTED
    """
    if parser is not None:
        parser.print_usage()
    sys.stderr.write ("%s: error: %s\n" % (PROG_NAME, m))
    sys.exit(exitcode)


if _X2GOCLIENT_OS == 'Windows':
    _x = _X2GoClientXConfig()
    _known_xservers = _x.known_xservers
    _installed_xservers = _x.installed_xservers

if _X2GOCLIENT_OS == 'Windows':
    _config_backends = ('FILE', 'WINREG')
elif _X2GOCLIENT_OS == 'Linux':
    _config_backends = ('FILE', 'GCONF')
else:
    _config_backends = ('FILE')

for _profiles_backend_default in _config_backends:
    if BACKENDS_SESSIONPROFILES[_profiles_backend_default] == BACKEND_SESSIONPROFILES_DEFAULT:
        break
for _settings_backend_default in _config_backends:
    if BACKENDS_CLIENTSETTINGS[_settings_backend_default] == BACKEND_CLIENTSETTINGS_DEFAULT:
        break
for _printing_backend_default in _config_backends:
    if BACKENDS_CLIENTPRINTING[_printing_backend_default] == BACKEND_CLIENTPRINTING_DEFAULT:
        break

# debug options...
debug_options =  [
                   {'args':['-d','--debug'], 'default': False, 'action': 'store_true', 'help': 'enable application debugging code', },
                   {'args':['--quiet'], 'default': False, 'action': 'store_true', 'help': 'disable any kind of log output', },
                   {'args':['--libdebug'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code of the underlying Python X2Go module', },
                   {'args':['--libdebug-sftpxfer'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code of Python X2Go\'s sFTP server code (very verbose, and even promiscuous)', },
                   {'args':['-V', '--version'], 'default': False, 'action': 'store_true', 'help': 'print version number and exit', },
                 ]
x2go_gui_options = [
                   {'args':['-P','--session-profile'], 'default': None, 'metavar': '<profile-name>', 'help': 'directly connect to a session profile', },
                   {'args':['--remember-username'], 'default': False, 'action': 'store_true', 'help': 'for profiles with interactive authentication, remember the last-used username', },
                   {'args':['--non-interactive'], 'default': False, 'action': 'store_true', 'help': 'run the session manager in non-interactive mode, this option sets the following options to true: --restricted-trayicon, --single_session_profile, --start-on-connect, --resume-all-on-connect, --exit-on-disconnect, --disconnect-on-suspend and --disconnect-on-terminate', },
                   {'args':['--auto-connect'], 'default': False, 'action': 'store_true', 'help': 'connect sessions via SSH pubkey authentication if possible', },
                   {'args':['--show-profile-metatypes'], 'default': False, 'action': 'store_true', 'help': 'show descriptive meta information on session profiles in menus (NOTE: this makes menus appear a bit more sluggish, use it mostly for debugging)', },
                   {'args':['--single-session-profile'], 'default': False, 'action': 'store_true', 'help': 'disable support of handling multiple session profiles', },
                   {'args':['--tray-icon'], 'default': None, 'metavar': '<your-logo>', 'help': 'define an alternative system tray icon file (PNG files only, leave out file extension here, size 22x22 on Linux, 16x16 on Windows)', },
                   {'args':['--tray-icon-connecting'], 'default': None, 'metavar': '<your-logo-while-connecting>', 'help': 'define an alternative system tray icon file while connecting to a server (PNG files only, leave out file extension here, size 22x22 on Linux, 16x16 on Windows)', },
                   {'args':['--restricted-trayicon'], 'default': False, 'action': 'store_true', 'help': 'restricts session manager\'s main icon functionality to information window and application exit; on left-click only a minimal session menu is shown', },
                   {'args':['--add-to-known-hosts'], 'default': False, 'action': 'store_true', 'help': 'automatically add SSH host keys to the known_hosts files of the client-side user', },
                   {'args':['--start-on-connect'], 'default': False, 'action': 'store_true', 'help': 'This is now the hard-coded default. start a session directly after authentication if no session is currently running/suspended', },
                   {'args':['--exit-on-disconnect'], 'default': False, 'action': 'store_true', 'help': 'exit the session manager after a server connection has died', },
                   {'args':['--resume-newest-on-connect', '--resume-on-connect'], 'default': False, 'action': 'store_true', 'help': 'This is now the hard-coded default. On connect auto-resume the newest suspended session', },
                   {'args':['--resume-oldest-on-connect'], 'default': False, 'action': 'store_true', 'help': 'on connect auto-resume the oldest suspended session', },
                   {'args':['--resume-all-on-connect'], 'default': False, 'action': 'store_true', 'help': 'auto-resume all suspended sessions on connect', },
                   {'args':['--disconnect-on-suspend'], 'default': False, 'action': 'store_true', 'help': 'disconnect a server if a session has been suspended', },
                   {'args':['--disconnect-on-terminate'], 'default': False, 'action': 'store_true', 'help': 'disconnect a server if a session has been terminated', },
                   {'args':['--splash-image'], 'default': None, 'metavar': '<your-splash-image>', 'help': 'define an alternative splash image that gets shown on application startup (PNG files only, full path or filename as found in <share>/img)', },
                   {'args':['--about-image'], 'default': None, 'metavar': '<your-about-window-image>', 'help': 'define an alternative image for the application\'s ,,About\'\' window (PNG files only, full path or filename as found in <share>/img)', },
                   {'args':['--disable-splash'], 'default': False, 'action': 'store_true', 'help': 'disable the applications splash screen', },
                   {'args':['--disable-options'], 'default': False, 'action': 'store_true', 'help': 'disable the client options configuration window', },
                   {'args':['--disable-printingprefs'], 'default': False, 'action': 'store_true', 'help': 'disable the client\'s printing preferences window', },
                   {'args':['--disable-profilemanager'], 'default': False, 'action': 'store_true', 'help': 'disable the session profile manager window', },
                   {'args':['--disable-notifications'], 'default': False, 'action': 'store_true', 'help': 'disable all applet notifications', },
                   {'args':['--display'], 'default': None, 'metavar': '<hostname>:<screennumber>', 'help': 'set the DISPLAY environment variable to <hostname>:<screennumber>', },
                   {'args':['--logon-window-position'], 'default': None, 'metavar': '<x-pos>x<y-pos>', 'help': 'give a custom position for the logon window, use negative values to position relative to right/bottom border', },
                   {'args':['--published-applications-no-submenus'], 'default': 10, 'metavar': '<number>', 'help': 'the number of published applications that will be rendered without submenus', },
                 ]
if _X2GOCLIENT_OS == 'Windows':
    x2go_gui_options.append(
                   {'args':['--lang'], 'default': None, 'metavar': 'LANGUAGE', 'help': 'set the GUI language (currently available: en, de, nl, es)', },
        )

backend_options = [
                   {'args':['--backend-controlsession'], 'default': None, 'metavar': '<CONTROLSESSION_BACKEND>', 'choices': BACKENDS_CONTROLSESSION.keys(), 'help': 'force usage of a certain CONTROLSESSION_BACKEND (do not use this unless you know exactly what you are doing)', },
                   {'args':['--backend-terminalsession'], 'default': None, 'metavar': '<TERMINALSESSION_BACKEND>', 'choices': BACKENDS_TERMINALSESSION.keys(), 'help': 'force usage of a certain TERMINALSESSION_BACKEND (do not use this unless you know exactly what you are doing)', },
                   {'args':['--backend-serversessioninfo'], 'default': None, 'metavar': '<SERVERSESSIONINFO_BACKEND>', 'choices': BACKENDS_TERMINALSESSION.keys(), 'help': 'force usage of a certain SERVERSESSIONINFO_BACKEND (do not use this unless you know exactly what you are doing)', },
                   {'args':['--backend-serversessionlist'], 'default': None, 'metavar': '<SERVERSESSIONLIST_BACKEND>', 'choices': BACKENDS_TERMINALSESSION.keys(), 'help': 'force usage of a certain SERVERSESSIONLIST_BACKEND (do not use this unless you know exactly what you are doing)', },
                   {'args':['--backend-proxy'], 'default': None, 'metavar': '<PROXY_BACKEND>', 'choices': BACKENDS_PROXY.keys(), 'help': 'force usage of a certain PROXY_BACKEND (do not use this unless you know exactly what you are doing)', },
                   {'args':['--backend-sessionprofiles'], 'default': None, 'metavar': '<SESSIONPROFILES_BACKEND>', 'choices': _config_backends, 'help': 'use given backend for accessing session profiles, available backends on your system: %s (default: %s)' % (', '.join(_config_backends), _profiles_backend_default), },
                   {'args':['--backend-clientsettings'], 'default': None, 'metavar': '<CLIENTSETTINGS_BACKEND>', 'choices': _config_backends, 'help': 'use given backend for accessing the client settings configuration, available backends on your system: %s (default: %s)' % (', '.join(_config_backends), _settings_backend_default), },
                   {'args':['--backend-clientprinting'], 'default': None, 'metavar': '<CLIENTPRINTING_BACKEND>', 'choices': _config_backends, 'help': 'use given backend for accessing the client printing configuration, available backends on your system: %s (default: %s)' % (', '.join(_config_backends), _printing_backend_default), },
                  ]

if _X2GOCLIENT_OS == 'Windows':
    contrib_options = [
                       {'args':['--start-xserver'], 'default': False, 'action': 'store_true', 'help': 'start the XServer before starting the session manager application, detect best XServer automatically, if more than one XServer is installed on your system', },
                       {'args':['-X', '--preferred-xserver'], 'default': None, 'metavar': '<XSERVER>', 'choices': _known_xservers, 'help': 'start either of the currently supported XServers: %s -- make sure your preferred XServer is installed on your system' % _known_xservers, },
                       {'args':['--start-pulseaudio'], 'default': False, 'action': 'store_true', 'help': 'start the PulseAudio server before starting the session manager application', },
                      ]

portable_options = [
                   {'args':['--client-rootdir'], 'default': None, 'metavar': '</path/to/.x2goclient/dir>', 'help': 'define an alternative location where to find plain text config files (default: <HOME>/.x2goclient). This option will set ,,--backend-profiles FILE\'\', ,,--backend-clientsettings FILE\'\' and ,,--backend-clientprinting FILE\'\'', },
                   {'args':['--sessions-rootdir'], 'default': None, 'metavar': '</path/to/.x2go/dir>', 'help': 'define an alternative location for session runtime files'},
                   {'args':['--ssh-rootdir'], 'default': None, 'metavar': '</path/to/.ssh/dir>', 'help': 'define an alternative location for SSH files', },
                  ]


def parseargs():

    global DEBUG
    global print_action_args

    p = argparse.ArgumentParser(description='Graphical X2Go client implemented in (wx)Python.',\
                                formatter_class=argparse.RawDescriptionHelpFormatter, \
                                add_help=True, argument_default=None)
    p_debugopts = p.add_argument_group('Debug options')
    p_guiopts = p.add_argument_group('%s options' % PROG_NAME)
    p_portableopts = p.add_argument_group('Portable application support')
    p_backendopts = p.add_argument_group('Python X2Go backend options (for experts only)')

    if _X2GOCLIENT_OS == 'Windows':
        p_contribopts = p.add_argument_group('XServer options (MS Windows only)')
        p_portableopts = p.add_argument_group('File locations for portable setups (MS Windows only)')
        _option_groups = ((p_guiopts, x2go_gui_options), (p_debugopts, debug_options), (p_contribopts, contrib_options), (p_portableopts, portable_options), (p_backendopts, backend_options), )
    else:
        _option_groups = ((p_guiopts, x2go_gui_options), (p_debugopts, debug_options),  (p_portableopts, portable_options), (p_backendopts, backend_options), )
    for (p_group, opts) in _option_groups:
        required = False
        for opt in opts:

            args = opt['args']
            del opt['args']
            p_group.add_argument(*args, **opt)

    a = p.parse_args()

    logger = _X2GoLogger(tag=PROG_NAME)
    liblogger = _X2GoLogger()

    if a.debug:
        logger.set_loglevel_debug()

    if a.libdebug:
        liblogger.set_loglevel_debug()

    if a.quiet:
        logger.set_loglevel_quiet()
        liblogger.set_loglevel_quiet()

    if a.libdebug_sftpxfer:
        liblogger.enable_debug_sftpxfer()

    if a.version:
        version()

    if a.single_session_profile and a.session_profile is None:
        runtime_error('The --single-session-profile option requires naming of a specific session profile!', parser=p)

    if a.non_interactive:
        if a.session_profile is None:
            runtime_error('In non-interactive mode you have to use the --session-profile option (or -P) to specify a certain session profile name!', parser=p)
        a.restricted_trayicon = True
        a.auto_connect = True
        a.start_on_connect = True
        a.resume_all_on_connect = True
        a.exit_on_disconnect = True
        a.disconnect_on_suspend = True
        a.disconnect_on_terminate = True
        a.single_session_profile = True

    if a.non_interactive and (a.resume_newest_on_connect or a.resume_oldest_on_connect):
        # allow override...
        a.resume_all_on_connect = False

    if _X2GOCLIENT_OS == 'Windows' and a.preferred_xserver:
        if a.preferred_xserver not in _installed_xservers:
            runtime_error('Xserver ,,%s\'\' is not installed on your Windows system' % a.preferred_xserver, parser=p)
        a.start_xserver = a.preferred_xserver

    if _X2GOCLIENT_OS == 'Windows' and a.start_xserver and a.display:
        runtime_error('You can tell %s to handle XServer startup and then specify a DISPLAY environment variable!' % PROG_NAME, parser=p)

    if a.display:
        os.environ.update({'DISPLAY': a.display})
    else:
        if _X2GOCLIENT_OS == 'Windows' and not a.start_xserver:
            os.environ.update({'DISPLAY': 'localhost:0'})

    if a.client_rootdir:
        a.backend_sessionprofiles='FILE'
        a.backend_clientsettings='FILE'
        a.backend_clientprinting='FILE'

    return a, logger, liblogger

def main():
    args, logger, liblogger = parseargs()
    if _X2GOCLIENT_OS == 'Windows':
        if args.lang:
            lang = gettext.translation('PyHoca-GUI', localedir=locale_basepath, languages=[args.lang], )
        else:
            lang = gettext.translation('PyHoca-GUI', localedir=locale_basepath, languages=['en'], )
        lang.install(unicode=True)
    else:
        gettext.install('PyHoca-GUI', localedir=locale_basepath, unicode=True)

    if check_running(): 
        sys.stderr.write("\n###############################\n### %s: already running for user %s\n###############################\n" % (PROG_NAME, _CURRENT_LOCAL_USER))
        m = messages.PyHoca_MessageWindow_Ok(wx.App(), shortmsg='ALREADY_RUNNING', title=u'%s (%s)...' % (PROG_NAME, VERSION), icon='pyhoca-trayicon')
        m.ShowModal()
        version()

    try:
        thisPyHocaGUI = PyHocaGUI(args, logger, liblogger, appname=PROG_NAME, version=VERSION)
        thisPyHocaGUI.MainLoop()
    except KeyboardInterrupt:
        if thisPyHocaGUI is not None:
            thisPyHocaGUI.WakeUpIdle()
            thisPyHocaGUI.ExitMainLoop()
    except SystemExit:
        if thisPyHocaGUI is not None:
            thisPyHocaGUI.WakeUpIdle()
            thisPyHocaGUI.ExitMainLoop()

if __name__ == '__main__':
    main()

