import json
import os
import sys
import threading
import time
from signal import signal, SIGINT

import numpy as np
from autobahn.twisted.resource import WebSocketResource
from autobahn.twisted.websocket import WebSocketServerFactory, \
    WebSocketServerProtocol
from twisted.internet import reactor
from twisted.python import log
from twisted.web.server import Site
from twisted.web.static import File


class GodswordServerProtocol(WebSocketServerProtocol):
    def __init__(self):
        super().__init__()
        print("Created a server")
        self.frame = 0

        # "connected" is already used by WSSP
        self.sent_environment = False
        self.isConnected = False

        self.pos = [0, 0]
        self.cmd = None

    def onOpen(self):
        print("Opened connection to server")

    def onClose(self, wasClean, code=None, reason=None):
        self.isConnected = False
        print('Connection closed')

    def connectionMade(self):
        super().connectionMade()
        self.factory.clientConnectionMade(self)

    def connectionLost(self, reason):
        super().connectionLost(reason)
        self.factory.clientConnectionLost(self)
        self.sent_environment = False

    # Not used without player interaction
    def onMessage(self, packet, isBinary):
        print("Server packet", packet)
        packet = packet.decode()
        _, packet = packet.split(';')  # Strip headeer
        r, c, cmd = packet.split(' ')  # Split camera coords
        if len(cmd) == 0 or cmd == '\t':
            cmd = None

        self.pos = [int(r), int(c)]
        self.cmd = cmd

        self.isConnected = True

    def onConnect(self, request):
        print("WebSocket connection request: {}".format(request))
        realm = self.factory.realm
        self.realm = realm
        self.frame += 1

    def serverPacket(self):
        data = self.realm.packet
        return data

    def sendUpdate(self, data):
        packet = {}
        packet['resource'] = data['resource']
        packet['player'] = data['player']
        packet['npc'] = data['npc']
        packet['pos'] = data['pos']
        packet['wilderness'] = data['wilderness']

        config = data['config']

        print('Is Connected? : {}'.format(self.isConnected))
        if not self.sent_environment:
            packet['map'] = data['environment']
            packet['border'] = config.TERRAIN_BORDER
            packet['size'] = config.TERRAIN_SIZE

        if 'overlay' in data:
            packet['overlay'] = data['overlay']
            print('SENDING OVERLAY: ', len(packet['overlay']))

        packet = json.dumps(packet).encode('utf8')
        self.sendMessage(packet, False)


class WSServerFactory(WebSocketServerFactory):
    def __init__(self, ip, realm):
        super().__init__(ip)
        self.realm = realm
        self.time = time.time()
        self.clients = []

        self.pos = [0, 0]
        self.cmd = None
        self.tickRate = 0.6
        self.tick = 0

    def update(self, packet):
        self.tick += 1
        uptime = np.round(self.tickRate * self.tick, 1)
        delta = time.time() - self.time
        print('Wall Clock: ', str(delta)[
              :5], 'Uptime: ', uptime, ', Tick: ', self.tick)
        delta = self.tickRate - delta
        if delta > 0:
            time.sleep(delta)
        self.time = time.time()

        for client in self.clients:
            client.sendUpdate(packet)
            if client.pos is not None:
                self.pos = client.pos
                self.cmd = client.cmd

        return self.pos, self.cmd

    def clientConnectionMade(self, client):
        self.clients.append(client)

    def clientConnectionLost(self, client):
        self.clients.remove(client)


class Application:
    def __init__(self, realm):
        signal(SIGINT, self.kill)
        log.startLogging(sys.stdout)

        port = 8080
        self.factory = WSServerFactory(
            u'ws://localhost:{}'.format(port), realm)
        self.factory.protocol = GodswordServerProtocol
        resource = WebSocketResource(self.factory)

        root = File(".")
        root.putChild(b"ws", resource)
        site = Site(root)

        reactor.listenTCP(port, site)

        def run():
            reactor.run(installSignalHandlers=0)

        threading.Thread(target=run).start()

    def update(self, packet):
        return self.factory.update(packet)

    def kill(*args):
        print("Killed by user")
        reactor.stop()
        os._exit(0)
