r"""server is a module that enables using VTK through a web-server. This
module implments a Wamp v2 Server Protocol that provides the core RPC-API needed to
place interactive visualization in web-pages. Developers can extent
ServerProtocol to provide additional RPC callbacks for their web-applications.

This module can be used as the entry point to the application. In that case, it
sets up a Twisted web-server that can generate visualizations as well as serve
web-pages are determines by the command line arguments passed in.
Use "--help" to list the supported arguments.

"""

import sys, logging

from vtk.web import testing
from vtk.web import upload
from vtk.web import wamp as vtk_wamp

from autobahn.wamp              import types

from autobahn.twisted.resource  import WebSocketResource
from autobahn.twisted.websocket import listenWS, WebSocketServerFactory
from autobahn.twisted.longpoll  import WampLongPollResource

from twisted.web                import resource
from twisted.web.resource       import Resource
from twisted.internet           import reactor
from twisted.internet.defer     import inlineCallbacks
from twisted.internet.endpoints import serverFromString
from twisted.python             import log

# =============================================================================
# Setup default arguments to be parsed
#   -s, --nosignalhandlers
#   -d, --debug
#   -p, --port     8080
#   -t, --timeout  300 (seconds)
#   -c, --content  '/www'  (No content means WebSocket only)
#   -a, --authKey  vtkweb-secret
# =============================================================================

def add_arguments(parser):
    """
    Add arguments processed know to this module. parser must be
    argparse.ArgumentParser instance.
    """
    import os
    parser.add_argument("-d", "--debug",
        help="log debugging messages to stdout",
        action="store_true")
    parser.add_argument("-s", "--nosignalhandlers",
        help="Prevent Twisted to install the signal handlers so it can be started inside a thread.",
        action="store_true")
    parser.add_argument("-i", "--host", type=str, default='localhost',
        help="the interface for the web-server to listen on (default: localhost)")
    parser.add_argument("-p", "--port", type=int, default=8080,
        help="port number for the web-server to listen on (default: 8080)")
    parser.add_argument("-t", "--timeout", type=int, default=300,
        help="timeout for reaping process on idle in seconds (default: 300s)")
    parser.add_argument("-c", "--content", default='',
        help="root for web-pages to serve (default: none)")
    parser.add_argument("-a", "--authKey", default='vtkweb-secret',
        help="Authentication key for clients to connect to the WebSocket.")
    parser.add_argument("-f", "--force-flush", default=False, help="If provided, this option will force additional padding content to the output.  Useful when application is triggered by a session manager.", dest="forceFlush", action='store_true')
    parser.add_argument("-k", "--sslKey", type=str, default="",
        help="SSL key.  Use this and --sslCert to start the server on https.")
    parser.add_argument("-j", "--sslCert", type=str, default="",
        help="SSL certificate.  Use this and --sslKey to start the server on https.")
    parser.add_argument("-ws", "--ws-endpoint", type=str, default="ws", dest='ws',
        help="Specify WebSocket endpoint. (e.g. foo/bar/ws, Default: ws)")
    parser.add_argument("-lp", "--lp-endpoint", type=str, default="lp", dest='lp',
        help="Specify LongPoll endpoint. (e.g. foo/bar/lp, Default: lp)")
    parser.add_argument("-hp", "--http-endpoint", default='hp', dest='hp',
        help="Specify an HTTP endpoint.  (e.g. foo/bar/hp, Default: hp)")
    parser.add_argument("--no-ws-endpoint", action="store_true", dest='nows',
        help="If provided, disables the websocket endpoint")
    parser.add_argument("--no-bws-endpoint", action="store_true", dest='nobws',
        help="If provided, disables the binary websocket endpoint for pushing images")
    parser.add_argument("--no-lp-endpoint", action="store_true", dest='nolp',
        help="If provided, disables the longpoll endpoint")

    # Hook to extract any testing arguments we need
    testing.add_arguments(parser)

    # Extract any necessary upload server arguments
    upload.add_arguments(parser)

    return parser

# =============================================================================
# Parse arguments and start webserver
# =============================================================================

def start(argv=None,
        protocol=vtk_wamp.ServerProtocol,
        description="VTK/Web web-server based on Twisted."):
    """
    Sets up the web-server using with __name__ == '__main__'. This can also be
    called directly. Pass the opational protocol to override the protocol used.
    Default is ServerProtocol.
    """
    try:
        import argparse
    except ImportError:
        # since  Python 2.6 and earlier don't have argparse, we simply provide
        # the source for the same as _argparse and we use it instead.
        from vtk.util import _argparse as argparse

    parser = argparse.ArgumentParser(description=description)
    add_arguments(parser)
    args = parser.parse_args(argv)
    start_webserver(options=args, protocol=protocol)


# =============================================================================
# Stop webserver
# =============================================================================
def stop_webserver() :
    reactor.callFromThread(reactor.stop)

# =============================================================================
# Convenience method to build a resource sub tree to reflect a desired path.
# =============================================================================
def handle_complex_resource_path(path, root, resource):
    # Handle complex endpoint
    fullpath = path.split('/')
    parent_path_item_resource = root
    for path_item in fullpath:
        if path_item == fullpath[-1]:
            parent_path_item_resource.putChild(path_item, resource)
        else:
            new_resource = Resource()
            parent_path_item_resource.putChild(path_item, new_resource)
            parent_path_item_resource = new_resource


# =============================================================================
# Start webserver
# =============================================================================

def start_webserver(options, protocol=vtk_wamp.ServerProtocol, disableLogging=False):
    """
    Starts the web-server with the given protocol. Options must be an object
    with the following members:
        options.host : the interface for the web-server to listen on
        options.port : port number for the web-server to listen on
        options.timeout : timeout for reaping process on idle in seconds
        options.content : root for web-pages to serve.
    """
    from twisted.internet import reactor
    from twisted.web.server import Site
    from twisted.web.static import File
    import sys

    if not disableLogging:
        log.startLogging(sys.stdout)

    contextFactory = None

    use_SSL = False
    if options.sslKey and options.sslCert:
      use_SSL = True
      wsProtocol = "wss"
      from twisted.internet import ssl
      contextFactory = ssl.DefaultOpenSSLContextFactory(options.sslKey, options.sslCert)
    else:
      wsProtocol = "ws"

    # Create WAMP router factory
    from autobahn.twisted.wamp import RouterFactory
    router_factory = RouterFactory()

    # create a user DB
    authdb = vtk_wamp.AuthDb()

    # create a WAMP router session factory
    from autobahn.twisted.wamp import RouterSessionFactory
    session_factory = RouterSessionFactory(router_factory)
    session_factory.session = vtk_wamp.CustomWampCraRouterSession
    session_factory.authdb = authdb

    # Create ApplicationSession and register protocols
    appSession = protocol(types.ComponentConfig(realm = "vtkweb"))
    appSession.setAuthDB(authdb)
    session_factory.add(appSession)

    # create a WAMP-over-WebSocket transport server factory
    transport_factory = vtk_wamp.TimeoutWampWebSocketServerFactory(session_factory, \
           url        = "%s://%s:%d" % (wsProtocol, options.host, options.port),    \
           debug      = options.debug,                                              \
           debug_wamp = options.debug,                                              \
           timeout    = options.timeout )

    root = Resource()

    # Do we serve static content or just websocket ?
    if len(options.content) > 0:
        # Static HTTP + WebSocket
        root = File(options.content)

    # Handle possibly complex ws endpoint
    if not options.nows:
        wsResource = WebSocketResource(transport_factory)
        handle_complex_resource_path(options.ws, root, wsResource)

    # Handle binary push WebSocket for images
    if not options.nobws:
        wsbFactory = WebSocketServerFactory( \
            url   = "%s://%s:%d" % (wsProtocol, options.host, options.port), \
            debug = options.debug)
        wsbFactory.protocol = vtk_wamp.ImagePushBinaryWebSocketServerProtocol
        wsbResource = WebSocketResource(wsbFactory)
        handle_complex_resource_path('wsb', root, wsbResource)

    # Handle possibly complex lp endpoint
    if not options.nolp:
        lpResource = WampLongPollResource(session_factory,
                                          timeout=options.timeout,
                                          debug=options.debug)
                                          #killAfter = 30000,
                                          #queueLimitBytes = 1024 * 1024,
                                          #queueLimitMessages = 1000,
                                          #debug=True,
                                          #reactor=reactor)
        handle_complex_resource_path(options.lp, root, lpResource)

    if options.uploadPath != None :
        from upload import UploadPage
        uploadResource = UploadPage(options.uploadPath)
        root.putChild("upload", uploadResource)

    site = Site(root)

    if use_SSL:
      reactor.listenSSL(options.port, site, contextFactory)
    else:
      reactor.listenTCP(options.port, site)

    # flush ready line
    sys.stdout.flush()

    # Work around to force the output buffer to be flushed
    # This allow the process launcher to parse the output and
    # wait for "Start factory" to know that the WebServer
    # is running.
    if options.forceFlush :
        for i in range(200):
            log.msg("+"*80, logLevel=logging.CRITICAL)

    # Initialize testing: checks if we're doing a test and sets it up
    testing.initialize(options, reactor, stop_webserver)

    # Start the reactor
    if options.nosignalhandlers:
        reactor.run(installSignalHandlers=0)
    else:
        reactor.run()

    # Give the testing module a chance to finalize, if necessary
    testing.finalize()


# =============================================================================
# Start httpserver
# =============================================================================

def start_httpserver(options, protocol=vtk_wamp.ServerProtocol, disableLogging=False):
    """
    Starts an http-only server with the given protocol.  The options argument should
    contain 'host', 'port', 'urlRegex', and optionally 'content'.
    """
    from twisted.web import server, http
    from twisted.internet import reactor
    from twisted.web.static import File

    host = options.host
    port = options.port
    contentDir = options.content
    rootPath = options.hp

    # Initialize web resource
    web_resource = File(contentDir) if contentDir else resource.Resource()

    # Add the rpc method server
    handle_complex_resource_path(rootPath, web_resource, vtk_wamp.HttpRpcResource(protocol(None), rootPath))

    site = server.Site(web_resource)
    reactor.listenTCP(port, site, interface=host)

    reactor.run()

if __name__ == "__main__":
    start()
