#!/usr/bin/env python3
# -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
#? from future import *

import sys
import os
import socket
import stat
import logging
import argparse
import traceback

ROLE="proxy"
LOG = logging.getLogger(__name__)

# we want to use our version for now
sGDir = os.path.dirname(os.path.dirname(__file__))
if sGDir not in sys.path:
    sys.path.insert(0, sGDir)
#print('\n'.join(sys.path))
from gridfire import getpass

sDir=os.path.join(sGDir, 'keepassxc_cmd2')
if sDir not in sys.path: sys.path.insert(0, sDir)
try:
    from keepassxc_cmd2.cmd2_ansi import *
except Exception:
    def green(s): return s
    def info(s): print(green(s))
    def red(s):   return s
    def error(s): print(red(s))
    def yellow(s):   return s
    def warn(s): print(yellow(s))
    def blue(s):   return s
    def debug(s): print(blue(s))
    ansi = None
del sGDir

# installed by Makefile install::
sDir=os.path.join('/usr/local/lib/python3.8', 'site-packages')
if sDir not in sys.path: sys.path.insert(0, sDir)
try:
    from pyassuan import common as _common
    from pyassuan import error as _error
except:
    sys.stderr.write('\n'.join(sys.path)+'\n')
    raise
del sDir

LOCALE="en_US.UTF-8"
NAME='pinentry_gridfire'
HOST='127.0.0.1'
PORT=6666
INET_SOCKET=False
RUN='/run/user/{}/'.format(os.geteuid()) +NAME +'.{}'.format(os.getpid())

VALID_OPTIONS = (
#?    'grab',
    'ttyname', # + os.environ['GPG_TTY']
#    'ttytype', # xterm
    'lc-ctype', # " +LOCALE
    'lc-messages', # en_US.UTF-8
#    'allow-external-password-cache',
#    'default-ok', # _OK
#    'default-cancel', # _Cancel
#    'default-yes', # _Yes
#    'default-no', # _No
    'default-prompt', #  'Passphrase:'
#    'default-pwmngr', # _Save in password manager
    'default-cf-visi', # Do you really want to make your passphrase visible on the screen?
    'default-tt-visi', # Make passphrase visible
    'default-tt-hide', # Hide passphrase
)
VALID_COMMANDS = ('GETINFO', 'OPTION', 'BYE', 'QUIT', 'HELP',)
LOG_FILE = '/tmp/pinentry{}.log'.format(os.getpid())

# from pyassuan import server # AssuanServer , AssuanSocketServer
print('from pyassuan.server import AssuanServer , AssuanSocketServer')
from pyassuan.server import AssuanServer , AssuanSocketServer
class GridfireAssuanServer(AssuanServer):
    """A single-threaded Assuan server based on the `development suggestions`_

    Extend by subclassing and adding ``_handle_XXX`` methods for each
    command you want to handle.

    .. _development suggestions:
      http://www.gnupg.org/documentation/manuals/assuan/Server-code.html
    """
    valid_options = VALID_OPTIONS
    valid_commands = VALID_COMMANDS

    def __init__(self, name, logger=LOG, use_sublogger=True,
                 valid_options=VALID_OPTIONS, strict_options=True,
                 single_request=False, listen_to_quit=False,
                 close_on_disconnect=False, args=None):
        self.args = args
        AssuanServer.__init__(self, name, logger, use_sublogger,
                 valid_options, strict_options,
                 single_request, listen_to_quit,
                 close_on_disconnect)

    def reset(self):
        self.stop = False
        self.options.clear()

        if self.args.ttyname:
            self.options['ttyname'] = self.args.ttyname
        elif 'GPG_TTY' in os.environ:
            self.options['ttyname'] = os.environ['GPG_TTY']
        else:
            self.options['ttyname'] = os.ttyname(sys.stdin.fileno())

        if self.args.lc_messages:
            self.options['lc-messages'] = self.args.lc_messages
        elif 'LC_MESSAGES' in os.environ:
            self.options['lc-messages'] = os.environ['LC_MESSAGES']
        elif 'LANG' in os.environ:
            self.options['lc-ctype'] = os.environ['LANG']

        if self.args.lc_ctype:
            self.options['lc-ctype'] = self.args.lc_ctype
        elif 'LC_CTYPE' in os.environ:
            self.options['lc-ctype'] = os.environ['LC_CTYPE']
        elif 'LANG' in os.environ:
            self.options['lc-ctype'] = os.environ['LANG']

        if 'default-prompt' not in self.options:
            self.options['default-prompt'] = 'Passphrase:'

    def run(self):
        self.reset()
        self.logger.info('running')
        self.connect()
        try:
            self.handle_requests()
        finally:
            self.logger.info('stopped running')
            self.disconnect()
            self.logger.info('stopping')

    def init_options(self):
        #? if self.listen_to_quit is None: self.listen_to_quit = True
        if os.path.isfile(LOG_FILE):
            os.chmod(LOG_FILE, stat.S_IRUSR| stat.S_IWUSR)
        if os.path.exists(RUN):
            os.chmod(RUN, stat.S_IRUSR| stat.S_IWUSR)

    def _handle_BYE(self, arg):
        if self.single_request:
            self.stop = True
        yield _common.Response('OK', 'bye connection')

    def _handle_GETINFO(self, args):
        arg = args.split()[0]
        if arg.lower() == 'pid':
            yield _common.Response("D", '{}'.format(os.getpid()).encode('ascii'))
        yield _common.Response('OK', '')

    def _handle_HELP(self, arg):
        s = 'COMMANDS: ' +' '.join(self.valid_commands)
        yield _common.Response("D", s.encode('ascii'))
        s = 'OPTIONS: ' +' '.join(self.valid_options)
        yield _common.Response("D", s.encode('ascii'))
        yield _common.Response('OK', '')

    def _handle_QUIT(self, arg):
        if self.listen_to_quit:
            self.stop = True
            yield _common.Response('OK', 'quitting the server')
        else:
            raise _error.AssuanError(
                code=175, message='Unknown command (reserved)')

    def _handle_GETPIN(self, arg):
        self.init_options()
        main_args = []
        if self.args.debug:
            main_args += ['--verbosity', "3"]
        else:
            main_args += ['--verbosity', "1"]
        main_args += ['--ttyalert', self.args.ttyalert]
        if 'default-tt-hide' in self.options:
            #? and self.options['default-tt-visi']
            main_args += ['-c']

        #? spawn an xterm is DISPLAY is set?
        s = getpass(prompt=self.options['default-prompt'],
                    stream=None,
                    tty=self.options['ttyname'],
                    main_args=main_args,
                    )
        yield _common.Response('D', s.encode('ascii'))
        yield _common.Response('OK', '')

class GridfireAssuanSocketServer (AssuanSocketServer):
    """A threaded server spawning ``AssuanServer``\s for each connection
    """
    def __init__(self, name, socket, server_klass, kwargs={}, max_threads=10,
                 logger=LOG, use_sublogger=True, args=None):
        self.args = args
        AssuanSocketServer.__init__(self, name, socket, server_klass, kwargs, max_threads,
                 logger=logger, use_sublogger=use_sublogger)

    def run(self):
        self.logger.info('listen on socket')
        self.socket.listen()
        thread_index = 0
        while True:
            self.logger.info('threads: ' +repr(self.threads))

            socket,address = self.socket.accept()
            self.logger.info('connection from {}'.format(address))
            self.cleanup_threads()
            if len(self.threads) > self.max_threads:
                self.drop_connection(socket, address)
            self.spawn_thread(
                'server-thread-{}'.format(thread_index), socket, address)
            thread_index = (thread_index + 1) % self.max_threads

            if 'single_request' in self.kwargs:
                self.logger.info('single_request: ' +repr(self.kwargs['single_request']))
                if self.kwargs['single_request']: break

def make_argparser():
    description='''pinentry_gridfire'''
    if INET_SOCKET:
        description += ' listening on {}:{}'.format(HOST, PORT)
    else:
        description += ' listening at {}'.format(RUN)

    argparser = argparse.ArgumentParser(description=description, add_help=True)
    argparser.add_argument('-d', '--debug', action='store_true',     help='Turn on debugging output')
    # argparser.add_argument('-D', '--display', default=None,      help='Set the X display')
    argparser.add_argument('-T', '--ttyname', type=str,         help='Set the terminal tty')
    # argparser.add_argument('-N', '--ttytype NAME', type=str,         help='Set the tty terminal type')
    argparser.add_argument('-C', '--lc-ctype', type=str,      help='Set the tty LC_CTYPE value')
    argparser.add_argument('-M', '--lc-messages', type=str,   help='Set the tty LC_MESSAGES value')
    # argparser.add_argument('-o', '--timeout', type=int,         help='Timeout in secs. waiting for input after this many seconds')
    # argparser.add_argument('-g', '--no-global-grab', action='store_true',  help='Grab keyboard only while window is focused')
    # argparser.add_argument('-W', '--parent-wid', type=str,            help='Parent window ID (for positioning)')
    # argparser.add_argument('-c', '--colors', type=str,         help='Set custom colors for ncurses')
    argparser.add_argument('-a', '--ttyalert', type=str, default='none', choices=['none', 'beep', 'flash'],
                           help='Set the alert mode (none, beep or flash)')
    return argparser

print( 'iMain '+repr(sys.argv))
def iMain(sys_argv):
    argparser = make_argparser()
    args = argparser.parse_args(sys_argv)

    # Show help and exit if help requested
    if hasattr(args, 'help') and args.help:
        argparser.print_help(sys.stdout)
        sys.exit(0)

    if args.debug:
        LOG.setLevel(logging.INFO)
    else:
        LOG.setLevel(logging.WARN)
    LOG.addHandler(logging.StreamHandler())
    LOG.addHandler(logging.FileHandler(LOG_FILE))
    LOG.handlers[0].setFormatter(logging.Formatter('%(name)s: %(levelname)s: %(message)s'))

    if INET_SOCKET:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind((HOST, PORT))
    else:
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.bind(RUN)

    server_klass = GridfireAssuanServer # (NAME, logger=LOG)
    server_kwargs = dict(valid_options=server_klass.valid_options,
                         single_request=True,
                         close_on_disconnect=True,
                         listen_to_quit=True,
                         strict_options=True,
                         args=args
    )
    i=0
    o = GridfireAssuanSocketServer(NAME, sock, server_klass, logger=LOG, kwargs=server_kwargs, args=args)
    o.run()
    return i

if __name__ == '__main__':
    LOG.info('INFO: Entering iMain sys.argv=' +str(repr(sys.argv)))
    try:
        i = iMain(sys.argv[1:])
        LOG.debug('DEBUG: Leaving iMain exit_code=' + str(i))
    except (KeyboardInterrupt, BrokenPipeError,) as e:
        i=0
        LOG.warn('WARN: Leaving iMain exit_code=0 ' + str(e))
        LOG.debug(traceback.format_exc())
    except (Exception,) as e:
        i=1
        LOG.error('ERROR: Leaving iMain exit_code=1 ' + str(e))
        LOG.debug(traceback.format_exc())
    finally:
        if not INET_SOCKET and os.path.exists(RUN):
            os.unlink(RUN)
    LOG.info('INFO: Leaving iMain exit_code=' +str(i))
    sys.exit(i)
