import errno
import json
import os
import random
import socket
import subprocess
import threading

import jurigged
from sanic import Sanic

from .repr import inject
from .session import Session

here = os.path.dirname(__file__)
assets_path = os.path.join(here, "assets")


def check_port(port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.bind(("0.0.0.0", port))
    except socket.error as e:
        if e.errno == errno.EADDRINUSE:
            return False
        else:
            raise
    s.close()
    return True


def find_port(preferred_port, min_port, max_port):
    """Find a free port in the specified range.

    Use preferred_port if available (does not have to be in the range).
    """
    candidate = preferred_port
    while not check_port(candidate):
        print("Nope to", candidate)
        candidate = random.randint(min_port, max_port)
    return candidate


def status_logger(sess):
    def log(event):
        sess.queue(
            command="status", type="normal", value=str(event),
        )

    return log


class SessionLock:
    def __init__(self):
        self.session = None
        self.lock = threading.Lock()
        self.lock.acquire()

    def set(self, session):
        self.session = session
        self.lock.release()

    def get(self):
        self.lock.acquire()
        self.lock.release()
        return self.session


def _launch(slock, watch_args=None):
    port = find_port(6499, min_port=6500, max_port=6600)

    app = Sanic("snektalk")
    app.static("/", f"{assets_path}/index.html")
    app.static("/lib/", f"{assets_path}/lib/")
    app.static("/scripts/", f"{assets_path}/scripts/")
    app.static("/style/", f"{assets_path}/style/")

    @app.websocket("/sktk")
    async def feed(request, ws):
        sess = Session(ws)
        slock.set(sess)
        if watch_args is not None:
            jurigged.watch(**watch_args, logger=status_logger(sess))
        while True:
            command = json.loads(await ws.recv())
            await sess.recv(**command)

    @app.listener("after_server_start")
    async def launch_func(app, loop):
        subprocess.run(["open", f"http://localhost:{port}/"])

    app.run(host="0.0.0.0", port=port, register_sys_signals=False)


def serve(**kwargs):
    slock = SessionLock()

    def _start_server():
        _launch(slock, **kwargs)

    thread = threading.Thread(target=_start_server, daemon=False)
    thread.start()

    sess = slock.get()
    sess.enter()
    inject()
    return sess
