import argparse
import os, sys

import tornado.ioloop
import tornado.web
import tornado.httpserver
import tornado.websocket
from tornado.ioloop import PeriodicCallback
import json

from wlm import WavelengthMeter

# all connected browsers will be here
clients = []

def send_data():
    """Gets wavelengths from the wavemeter and sends it to the client"""
    if len(clients)>0:
        data = wlmeter.wavelengths
        str = json.dumps(data)
        for c in clients:
            c.write_message(str)

class WsHandler(tornado.websocket.WebSocketHandler):
    """Websocket handler"""
    def open(self):
        """Subscribes to the updates by adding itself to the clients list"""
        clients.append(self)

    def on_close(self):
        """Removes itself from clients list"""
        clients.remove(self)
        print('connection closed')

    def check_origin(self, origin):
        """Allows cross origin connection if you want to embed wlm.js library in some page on another domain"""
        return True

class ApiHandler(tornado.web.RequestHandler):
    """Creates simple HTTP API if you don't like websockets"""
    def get(self, channel=None):
        w = wlmeter.wavelengths
        sw = wlmeter.switcher_mode
        if channel is None:
            self.write({ "wavelengths": w, "switcher_mode": sw })
        else:
            ch = int(channel)
            if ch >=0 and ch<len(w):
                self.write("%.8f" % w[ch])
            else:
                self.set_status(400)
                self.write({"error":"Wrong channel"})

class IndexHandler(tornado.web.RequestHandler):
    """Renders index.html page"""
    def get(self):
        self.render("index.html",
            wavelengths=wlmeter.wavelengths,
            **get_config()
        )


default_config_file = os.path.abspath(os.path.join(os.path.dirname(__file__), "config.json"))

def make_app(config):
    """All the routes are defined here"""
    static_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "static"))
    return tornado.web.Application([
            (r"%s/" % config["root"], IndexHandler),
            (r"%s/api/" % config["root"], ApiHandler),
            (r"%s/api/(\d)/" % config["root"], ApiHandler),
            (r"%s/ws/" % config["root"], WsHandler),
            (r"%s/static/(.*)" % config["root"], tornado.web.StaticFileHandler, {'path': static_path}),
    ], debug=True)

class config_action(argparse.Action):
    """Parses config file argument"""
    def __call__(self, parser, namespace, values, option_string=None):
        config_file = values
        if not os.path.isfile(config_file):
            raise argparse.ArgumentTypeError("config:{0} is not a valid file".format(config_file))
        if os.access(config_file, os.R_OK):
            setattr(namespace, self.dest, config_file)
        else:
            raise argparse.ArgumentTypeError("config:{0} is not a readable file".format(config_file))

def get_config():
    """Building configuration dictionary"""

    # command line arguments parsing
    parser = argparse.ArgumentParser(description='Starts a webserver with wavemeter interface.')
    parser.add_argument('--debug', dest='debug', action='store_const',
                        const=True,
                        help='runs the script in debug mode simulating wavelength values')
    parser.add_argument('-c', '--config', action=config_action, default=default_config_file,
                        help='path to config json file, default: config.json in the script folder')
    parser.add_argument('-r', '--root', default=None,
                        help='path where the interface will be, like localhost:8000/root/. Default is "/"')
    parser.add_argument('port', type=int, nargs='?',
                        help='server port, default: 8000')

    args = parser.parse_args()

    # default configuration
    config = {
        "port": 8000, # port
        "root": "/", # path
        "precision": 5, # number of decimals in wavelength display
        "update_rate": 0.1, # how often updates will be sent to the browsers
        "debug": False, # do you want to work with real wavemeter or to test run it?
        "channels": [{"i": i, "label": "Channel %d" % (i+1)} for i in range(8)] # channels to display
    }

    # configuration from the file
    with open(args.config, "r") as f:
        config.update(json.loads(f.read()))

    # configuration from command line
    config["port"] = (args.port or config["port"])
    config["root"] = (args.root or config["root"])
    config["debug"] = (args.debug or config["debug"])

    # add leading slash
    if len(config["root"]) > 0 and config["root"][0] != "/":
        config["root"] = "/"+config["root"]

    # remove trailing slash
    if config["root"][-1] == "/":
        config["root"] = config["root"][:-1]

    return config

if __name__ == "__main__":

    config = get_config()

    wlmeter = WavelengthMeter(debug=config["debug"])

    app = make_app(config)

    if "ssl" in config:
        # https and wss server
        server = tornado.httpserver.HTTPServer(app, xheaders=True, ssl_options=config["ssl"])
    else:
        # http and ws server
        server = tornado.httpserver.HTTPServer(app)

    server.listen(config["port"])
    print("Server started at http://localhost:%d%s/" % (config["port"], config["root"]))

    # periodic callback takes update rate in ms
    PeriodicCallback(send_data, config["update_rate"]*1000).start()

    tornado.ioloop.IOLoop.instance().start()