#!/usr/bin/env python3

import sys
import re

from twisted.internet import defer
from twisted.internet import protocol
from twisted.internet import reactor
from twisted.python import log
from twisted.enterprise import adbapi
import pendulum
from subprocess import check_output

RAW = True

try:
    from config_dev import *
except ModuleNotFoundError:
    from config import *

# from config import *

# Connect to:
# HOST = "twgs"
# PORT = 2002
# Listen on:
# LISTEN_PORT = 2002
# LISTEN_ON = "0.0.0.0"

version = check_output(
    [
        "git",
        "describe",
        "--long",
        "--tags",
        #        "--dirty",
        "--always",
        "--match",
        "v[0-9]\.[0-9]\.[0-9]",
    ],
    universal_newlines=True,
).strip()

cleaner = re.compile(r"\x1b\[[0-9;]*[A-Zmh]")
makeNL = re.compile(r"\x1b\[[0-9;]*[J]")


def treatAsNL(line):
    global makeNL
    return makeNL.sub("\n", line)


def cleanANSI(line):
    global cleaner
    return cleaner.sub("", line)
    # return re.sub(r'\x1b\[([0-9,A-Z]{1,2}(;[0-9]{1,2})?(;[0-9]{3})?)?[m|K]?', '', line)


class PlayerProtocol(protocol.Protocol):
    def __init__(self):
        self.user = None
        self.dbinit = False
        self.db = None
        self.buffer = ""

    def connectionMade(self):
        log.msg("Client: connected to peer")
        self.queue_twgs = self.factory.queue_twgs
        self.queue_twgs.get().addCallback(self.serverDataReceived)

    def serverDataReceived(self, chunk):
        # rlogin looks like this:  \x00 password \x00 username \x00 terminal/speed \x00
        # b'\x00up2lat3\x00bugz\x00ansi-bbs/115200\x00'
        # We're looking for 4 \x00!
        # TODO: Line processing, and line cleaning (remove ANSI color codes)
        if chunk is False:
            self.queue_twgs = None
            log.msg("Client: disconnecting from peer")
            self.factory.continueTrying = False
            self.transport.loseConnection()
        else:
            if self.user is None:
                # Decode the rlogin data
                self.buffer += chunk.decode("utf-8", "ignore")

                # Ok, process this
                # self.buffer += chunk.decode('utf-8')
                # We don't have the username yet
                parts = self.buffer.split("\x00")
                if len(parts) >= 5:
                    # Got it!
                    self.user = parts[1]
                    log.msg("User: {0}".format(self.user))
                    # Reset buffer -- remove everything before last \x00
                    zpos = self.buffer.rindex("\x00")
                    self.buffer = self.buffer[zpos + 1 :]
                    self.buffer = ""
                    # init sqlite db using the username
                    self.factory.getUser(self.user)

            # Pass received data to the server
            self.transport.write(chunk)
            self.queue_twgs.get().addCallback(self.serverDataReceived)

    def dataReceived(self, chunk):
        # log.msg("Client: %d bytes received from peer" % len(chunk))
        # clean, strip ANSI, etc.

        # log.msg("<<", chunk.decode("utf-8", "ignore"))
        # log.msg("<<", repr(chunk))
        self.factory.queue_client.put(chunk)

    def connectionLost(self, why):
        log.msg("connectionLost because: %s" % why)
        # if self.cli_queue:
        #     self.cli_queue = None
        #     log.msg("Client: peer disconnect unexpectedly")
        self.factory.queue_client.put(False)
        self.queue_twgs = None
        self.transport.loseConnection()


class GlueFactory(protocol.ClientFactory):
    # class GlueFactory(protocol.Factory):
    maxDelay = 10
    protocol = PlayerProtocol

    def __init__(self, queue_client, queue_twgs, twgs):
        self.queue_client = queue_client
        self.queue_twgs = queue_twgs
        self.twgs = twgs

    def closeIt(self):
        log.msg("closeIt")
        self.queue_client.put(False)

    def getUser(self, user):
        log.msg("getUser( %s )" % user)
        self.twgs.logUser(user)

    # This was needed when I replaced ClientFactory with Factory.
    # def clientConnectionLost(self, connector, why):
    #     log.msg("clientconnectionlost: %s" % why)
    #     self.queue_client.put(False)

    def clientConnectionFailed(self, connector, why):
        log.msg("connectionFailed: %s" % why)
        self.queue_client.put(b"Sorry!  I'm Unable to connect to the game server.\r\n")
        # syncterm gets cranky/locks up if we close this here.
        # (Because it is still sending rlogin information?)
        reactor.callLater(2, self.closeIt)


# ProxyServer is created for each connection


class ProxyAction:
    def __init__(self, twgs):
        self.active = False
        self.twgs = twgs
        self.queue_client = twgs.queue_client
        self.queue_twgs = twgs.queue_twgs
        self.buffer = ""
        self.prompt = ""
        self.rptstate = 0

    def isActive(self):
        return self.active

    def keepAlive(self):
        if self.active:
            self.queue_twgs.put(b" ")
            reactor.callLater(30, self.keepAlive)

    def menu(self):
        self.send("\r\n**********\r\nTWGS Proxy ready...\r\n")
        self.send("(T) Display Time\r\n")
        self.send("(P) CIM Port Report\r\n")
        self.send("(Q) Quit\r\n")
        self.send("  --==> ")

    def activate(self, prompt):
        cleaned = cleanANSI(prompt)
        if cleaned.startswith("Command [TL=0"):
            self.active = True
            self.prompt = prompt
            self.menu()
            reactor.callLater(30, self.keepAlive)
        else:
            self.send("\a")

    def sendtwgs(self, text):
        self.queue_twgs.put(text.encode())

    def server(self, line):
        # 2019-11-20 18:37:28-0500 [PlayerProtocol,client] >>> [: ]
        # 2019-11-20 18:37:28-0500 [PlayerProtocol,client] >>> [ 436   2870 100% - 1520 100% - 2820 100% ]
        # 2019-11-20 18:37:28-0500 [PlayerProtocol,client] >>> []
        # 2019-11-20 18:37:42-0500 [PlayerProtocol,client] >>> [: ENDINTERROG]
        # 2019-11-20 18:37:42-0500 [PlayerProtocol,client] >>> []

        if self.rptstate == 1:
            if line.startswith(":"):
                self.sendtwgs("R")
                self.rptstate == 2
        if self.rptstate == 2:
            if line.startswith(":"):
                self.sendtwgs("Q")
                self.rptstate == 3
            else:
                log.msg("[[{0}]]".format(line))
        if self.rptstate == 3:
            if line == ": ENDINTERROG":
                self.rptstate == 3
                self.twgs.passon = True
                self.menu()

    def send(self, text):
        self.queue_client.put((text.encode(),))

    def received(self, chunk):
        # self.buffer += chunk.encode('UTF-8', 'ignore')
        text = chunk.decode("utf-8", "ignore").upper()

        if text == "T":
            now = pendulum.now("America/New_York")
            self.send("\r\nThe time is: {0}.\r\n".format(now.to_rss_string()))

        if text == "P":
            # Port Report
            self.sendtwgs("^")
            self.twgs.passon = False
            self.rptstate = 1

        if text == "Q":
            self.send("\r\nReturning to TWGS.\r\n{0}".format(self.prompt))
            self.active = False
            self.twgs.passon = True


class TWGSServer(protocol.Protocol):
    def __init__(self):
        self.buffer = ""
        self.fpRaw = None
        self.fpLines = None
        self.action = None
        self.user = ""
        self.game = ""
        self.passon = True

    def connectionMade(self):
        self.queue_twgs = defer.DeferredQueue()
        self.queue_client = defer.DeferredQueue()
        self.queue_client.get().addCallback(self.clientDataReceived)
        self.action = ProxyAction(self)

        factory = GlueFactory(self.queue_client, self.queue_twgs, self)
        reactor.connectTCP(HOST, PORT, factory, 5)

    def logUser(self, user):
        now = pendulum.now()
        self.user = user
        filename = now.format("YYYY-MM-DD_HHmm") + "-" + user.lower()
        if RAW:
            self.fpRaw = open(filename + ".raw", "ab")
        self.fpLines = open(filename + ".lines", "a")
        # print("Log created:", now.to_rss_string(), "\n", file=self.fpRaw)
        print("Log created:", now.to_rss_string(), "\n", file=self.fpLines)

    def setGame(self, game):
        if self.game != game:
            log.msg("USER {0} ENTERED {1}".format(self.user, self.game))
            self.data = {}
        self.game = game

    def gotLine(self, line):
        # log.msg(">>> [{0}]".format(line.decode("utf-8", "ignore")))
        log.msg(">>> [{0}]".format(line))
        if self.fpLines is not None:
            print(line, file=self.fpLines)
        if "TWGS v2.20b" in line:
            if "www.eisonline.com" in line:
                # Must not be unicode

                # Is there a way to NOT have this logged?
                self.queue_client.put(
                    (
                        b"TWGS Proxy build "
                        + version.encode()
                        + b" is active. \x1b[1;34m~\x1b[0m to activate.\n\r\n\r",
                    )
                )
        if "TradeWars Game Server" in line and "Copyright (C) EIS" in line:
            # We are not in a game
            self.game = ""
        if "Selection (? for menu): " in line:
            game = line[-1]
            if game >= "A" and game < "Q":
                self.setGame(game)
        # If we're not passing it on to the user, we better be looking at it.
        if self.action and not self.passon:
            self.action.server(line)

    def clientDataReceived(self, chunk):
        if chunk is False:
            self.transport.loseConnection()
        else:
            if type(chunk) is tuple:
                self.transport.write(chunk[0])
                self.queue_client.get().addCallback(self.clientDataReceived)
            else:
                if self.fpRaw is not None:
                    self.fpRaw.write(chunk)
                self.buffer += chunk.decode("utf-8", "ignore")

                # Process any backspaces in the buffer

                while "\x08" in self.buffer:
                    part = self.buffer.partition("\x08")
                    self.buffer = part[0][:-1] + part[2]

                # Treat some ANSI codes as a newline (for purposes of Lines)
                self.buffer = treatAsNL(self.buffer)
                # I think I need something else in here.  When something enters or leaves the sector
                # The message isn't shown on it's own line.  I think they are using ANSI codes to
                # clear the line.  (Which certainly would be faster!)

                # Break the buffer into lines

                while "\n" in self.buffer:
                    part = self.buffer.partition("\n")
                    line = part[0].replace("\r", "")
                    # Clean ANSI codes from the line
                    line = cleanANSI(line)
                    self.gotLine(line)
                    self.buffer = part[2]

                # log.msg("Server: writing %d bytes to original client" % len(chunk))
                if self.passon:
                    self.transport.write(chunk)

                self.queue_client.get().addCallback(self.clientDataReceived)

    def dataReceived(self, chunk):
        # log.msg("Server: %d bytes received" % len(chunk))
        if self.action and self.action.isActive():
            # Do something completely different here
            self.action.received(chunk)
        else:
            if chunk == b"~":
                self.action.activate(self.buffer)
            else:
                self.queue_twgs.put(chunk)

    def connectionLost(self, why):
        log.msg("lost connection %s" % why)
        self.queue_twgs.put(False)
        if self.fpRaw is not None:
            self.fpRaw.close()
            self.fpRaw = None
        if self.fpLines is not None:
            if self.buffer != "":
                print(self.buffer, file=self.fpLines)
            self.fpLines.close()
            self.fpLines = None

    def connectionFailed(self, why):
        log.msg("connectionFailed: %s" % why)


if __name__ == "__main__":
    log.startLogging(sys.stdout)
    log.msg("This is version: %s" % version)
    factory = protocol.Factory()
    factory.protocol = TWGSServer
    reactor.listenTCP(LISTEN_PORT, factory, interface=LISTEN_ON)
    reactor.run()