#!/usr/bin/env python3
""" Run the webapp server. """

import os
import threading
import urllib.request
import time
import glob

import flask_socketio
import click

from asl_rulebook2.webapp import app, globvars

# ---------------------------------------------------------------------

@click.command()
@click.option( "--addr","-a","bind_addr", help="Webapp server address (host:port)." )
@click.option( "--data","-d","data_dir", help="Data directory." )
@click.option( "--force-init-delay", default=0, help="Force the webapp to initialize (#seconds delay)." )
@click.option( "--debug","flask_debug", is_flag=True, default=False, help="Run Flask in debug mode." )
def main( bind_addr, data_dir, force_init_delay, flask_debug ):
    """Run the asl-rulebook2 webapp server."""

    # initialize
    flask_port = None
    if bind_addr:
        words = bind_addr.split( ":" )
        flask_host = words[0]
        if len(words) > 1:
            flask_port = words[1]
    else:
        flask_host = app.config.get( "FLASK_HOST", "localhost" )
    if not flask_port:
        flask_port = app.config.get( "FLASK_PORT_NO" )
    if not flask_debug:
        flask_debug = app.config.get( "FLASK_DEBUG", False )

    # initialize
    if data_dir:
        if not os.path.isdir( data_dir ):
            raise RuntimeError( "Invalid data directory: {}".format( data_dir ) )
        app.config["DATA_DIR"] = data_dir

    # validate the configuration
    if not flask_host:
        raise RuntimeError( "The server host was not set." )
    if not flask_port:
        raise RuntimeError( "The server port was not set." )

    # monitor extra files for changes
    extra_files = []
    fspecs = [ "static/", "templates/", "config/" ]
    if app.config.get( "DATA_DIR" ):
        data_dir = app.config["DATA_DIR"]
        fspecs.append( data_dir )
        fspecs.append( os.path.join( data_dir, "annotations.json" ) )
        paths = [
            os.path.join( data_dir, p )
            for p in os.listdir( data_dir )
        ]
        fspecs.extend( p for p in paths if os.path.isdir(p) )
    for fspec in fspecs:
        fspec = os.path.abspath( os.path.join( os.path.dirname(__file__), fspec ) )
        if os.path.isdir( fspec ):
            files = [ os.path.join(fspec,f) for f in os.listdir(fspec) ]
            files = [
                f for f in files
                if os.path.isfile(f) and os.path.splitext(f)[1] not in [".swp"]
            ]
        else:
            files = glob.glob( fspec )
        extra_files.extend( files )

    # check if we should force webapp initialization
    if force_init_delay > 0:
        def _start_server():
            time.sleep( force_init_delay )
            url = "http://{}:{}".format( flask_host, flask_port )
            with urllib.request.urlopen( url ) as fp:
                _ = fp.read()
        threading.Thread( target=_start_server, daemon=True ).start()

    # run the server
    run_server( flask_host, flask_port, flask_debug, extra_files )

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

def run_server( host, port, debug, extra_files=None ):
    """Run the webapp server."""

    # NOTE: flask-socketio + eventlet handles concurrency differently to the Flask development server,
    # and we need to remain responsive, otherwise pings from the socketio client will timeout, and it will
    # disconnect (and show a big warning in the UI that the server has gone away). To avoid this,
    # we relinquish the CPU regularly, but just in case, we increase the ping timeout (and allow the user
    # to increase it even further, if necessary). This should only be an issue when preparing the data files,
    # since the main program doesn't use socketio.
    # NOTE: Setting the timeout high shouldn't be a problem, since if the server really does go away,
    # the connection will be dropped, and the front-end Javascript will detect that immediately.
    ping_timeout = app.config.get( "SOCKETIO_PING_TIMEOUT", 30 )

    # run the server
    sio = flask_socketio.SocketIO( app,
        async_mode = "eventlet",
        ping_timeout = ping_timeout
    )
    globvars.socketio_server = sio
    args = {
        "debug": debug,
        "log_output": False
    }
    if extra_files:
        args.update( {
            "use_reloader": True,
            "reloader_options": { "extra_files": extra_files },
        } )
    sio.run( app,
        host=host, port=port,
        **args
    )

# ---------------------------------------------------------------------

if __name__ == "__main__":
    main() #pylint: disable=no-value-for-parameter
