#!/usr/bin/env python
"""server-net.py

Network server to handle network connections and pass commands
to projector emulators
"""

__version__ = '0.0.2'
_v = __version__.split(".")
__version_hex__ = int(_v[0]) << 24 | \
                  int(_v[1]) << 16 | \
                  int(_v[2]) << 8

import thread
import threading
import time
import logging
import argparse
import os
import sys
from time import sleep
from socket import *

if os.geteuid() < 1000:
    print "Best to run this as a normal user"
    sys.exit()

# Setup logging to console
FORMAT = '%(asctime)-15s [%(name)s]: %(message)s'
logging.basicConfig(format=FORMAT, datefmt='%Y-%m-%d %H:%M:%S')
log = logging.getLogger()
log.setLevel(logging.WARN)

log.info("Initializing net server")

# Get arguments
use_text = """server-net.py [options]

Network server to emulate a projector for testing programs
that control projectors.

Currently only PJLink is supported.
"""
parser = argparse.ArgumentParser(prog="server-net.py",
                                 usage=use_text)
parser.add_argument('-d', action='count',
    help="Increase debugging for each -d - max 2")
parser.add_argument('--debug', action='store',
    help='Set debugging level: DEBUG INFO WARN ERROR CRITICAL')
parser.add_argument('-f', '--fail', action='store', default=None,
    help='Set random fail check in minutes')
parser.add_argument('--host', action='store',
    help='Set interface to listen to - default all')
parser.add_argument('--lamps', action="store", default=1,
    help='Specify number of lamps installed')
parser.add_argument('-p', '--password', action='store',
    help='Set password for projector authorization. Set to "TESTME" to use test algorithm')
parser.add_argument('--port', action='store',
    help='Set a different port to use other than default')
parser.add_argument('-u', '--user', action='store',
    help="Set username for projector authorization")
parser.add_argument('-t', '--type',
                    default='PJLINK',
                    help='type of projector to emulate - default="PJLINK"')
parser.add_argument('-v', '--version',
                    action='version',
                    version='%(prog)s ' + str( __version__))
args = parser.parse_args()

# Time to process
if args.debug is not None:
    if args.debug.upper() not in "DEBUG INFO WARN ERROR CRITICAL":
        print "--debug: invalid level: %s" % args.debug.upper()
        print "Valid levels are DEBUG INFO WARN ERROR CRITICAL"
    else:
        log.setLevel(logging._levelNames[args.debug.upper()])
elif args.d is not None:
    d = log.getEffectiveLevel() - (args.d * 10)
    if d < logging.DEBUG: d = logging.DEBUG
    log.setLevel(logging._levelNames[d])

print "Log level set to %s" % logging.getLevelName(log.getEffectiveLevel())

log.debug('Parsing arguments')
log.debug("args: %s" % args)

log.debug("Setting projector type")
# Only pass the arguments that your type can use. For example, if your
# projector does not need a username, then don't use the 'user' option.
if args.type.upper() == 'PJLINK':
    log.info("Loading PJLink projector settings")
    from servers.PJLink import *
    projector = Projector(user=args.user,
                          password=args.password,
                          failcheck=args.fail,
                          lamps=args.lamps)

if args.host is None:
    HOST = '' # Listen on all interfaces
    log.info("Listening on all interfaces")
else:
    try:
        HOST = gethostbyname(args.host)
        log.info("Listening on interface %s" % HOST)
    except:
        HOST = '127.0.0.1'
        log.warn("Unkown host: %s" % args.host)
        log.warn("Setting to localhost (127.0.0.1)")

if args.port is not None:
    PORT = int(args.port)
    if PORT < 1024 and os.geteuid() >= 1000:
        print "Cannot use ports below 1024 as non-root user"
        sys.exit(1)

log.info("Setting constants")
CR = chr(0x0D) # \r
LF = chr(0x0A) # \n
# SUFFIX needs to be defined in emulator - normally CR but can be CR+LF
log.info("Setting main variables")
ADDR = (HOST, PORT)
serversock = None       # Holds port binding
main_running = False    # Keep track of main service running

log.info("Creating server functions")
def send(s, d):
    """send(s,d): Sends data d to socket s"""
    log.debug("%s : Sending '%s'" % (repr(addr), d))
    c.send("%s%s" % (d, SUFFIX))

def handler(clientsock, addr):
    """handler(clientsock, addr)

    Handler for new connection. Sends socket/address to Projector() class
    for connection setup.
    """
    log.info("New socket created for %s" % repr(addr))
    log.debug("handler: Setting %s timeout to %s" % (repr(addr), TIMEOUT_MAX))
    clientsock.settimeout(TIMEOUT_MAX)
    log.info("handler: Initializing projector connection")
    v, data = projector.client_connect(clientsock, addr)
    log.debug("handler(): Connection validated: %s - Initial data: '%s'" % \
        (v, data))
    if not v:
        log.error("Authentication failure - closing connection to %s" % \
                repr(addr))
        projector.client_disconnect(addr)
        clientsock.close()
        return
    if data is None:
        log.debug("handler(): Authenticated request with no command")
    else:
        _d = projector.handle_request(data)
        log.debug("handler(): Athenticated connection - sending %s" % _d)
        clientsock.send("%s%s" % (_d, SUFFIX))
    handler_running = True
    while handler_running:
        try:
            data = clientsock.recv(BUFFER).strip()
            if not data:
                break
            elif '\xff\xf4\xff\xfd\x06' in data:
                # ^C pressed from telnet
                log.info("%s: Received ^C - closing connection" % repr(addr))
                break
            else:
                log.debug("handler(): Calling projector.handle_request(%s)" % data)
                _d = projector.handle_request(data)
                log.debug("handler(): Sending '%s'" % _d)
                clientsock.send("%s%s" % (_d, SUFFIX))

        except timeout, e:
            log.info("Timeout - closing connection to %s" % repr(addr))
            break

        except error, e:
            log.info("Socket error - closing connection to %s" % repr(addr))
            break
    projector.client_disconnect(addr)
    log.debug("handler() closing connection to %s" % repr(addr))
    clientsock.close()
    handler_running = False

if __name__ == "__main__":
    log.info("Starting main connection service address: %s" % repr(ADDR))
    serversock = socket(AF_INET, SOCK_STREAM)
    serversock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    serversock.bind(ADDR)
    log.debug("Socket bound to address %s" % repr(serversock.getsockname()))
    serversock.listen(5)
    main_running = True
    projector.start()
    while main_running:
        try:
            log.info("MAIN: Waiting for connection - listening on port %s" % \
                      PORT)
            clientsock, addr = serversock.accept()
            thread.start_new_thread(handler, (clientsock, addr))
        except KeyboardInterrupt:
            log.warn("Keyboard interrupt - stopping")
            projector.stop()
            handler_running = False
            main_running = False
